I am making a small Rest API using Spring boot and mysql as database. I am getting "No serializer found" error with fetch type lazy. My application is working fine with fetch type eager but I want fetch type to lazy so how can I resolve this.
User Model:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(nullable = false,length = 50)
private String firstName;
#Column(nullable = false,length = 50)
private String lastName;
#Column(nullable = false,unique = true)
private String email;
#OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name = "address_id",referencedColumnName = "id")
#JsonIgnoreProperties("user")
private Address address;
}
Address Model:
#Entity
#Table(name = "addresses")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(nullable = false,length = 255)
private String street;
#Column(nullable = false)
private int postalCode;
#Column(nullable = false,length = 100)
private String city;
#OneToOne(mappedBy = "address",fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JsonIgnoreProperties("address")
private User user;
}
User Service:
#Service
public class UserService {
private final UserRepository userRepository;
#Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User save(User user){
return userRepository.save(user);
}
public List<User> find(){
return userRepository.findAll();
}
public User find(Integer id){
return userRepository.findById(id).get();
}
public void delete(Integer id){
userRepository.deleteById(id);
}
}
User Controller:
#RestController
#RequestMapping(path = "users")
public class UserController {
private final UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
#GetMapping
public List<User> get(){
return userService.find();
}
#GetMapping(path = "{id}")
public User get(#PathVariable Integer id){
return userService.find(id);
}
#PostMapping
public User post(#RequestBody User user){
return userService.save(user);
}
#DeleteMapping(path = "{id}")
public boolean delete(#PathVariable Integer id){
userService.delete(id);
return true;
}
}
Stack Trace:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.ArrayList[0]->com.kumasroh.usersapp.models.User["address"]->com.kumasroh.usersapp.models.Address$HibernateProxy$VM2Pif4w["hibernateLazyInitializer"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.13.1.jar:2.13.1]
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1300) ~[jackson-databind-2.13.1.jar:2.13.1]
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400) ~[jackson-databind-2.13.1.jar:2.13.1]
I think fetchType Lazy is used in OneToMany relationship on the otherhand fetchType Eager is used in OneToOne Relationship.
Related
I'm new to the Spring boot JPA and struggling to find out the relationships between multiple entities.
I have a User Entity, a Product Entity, and a Review Entity.
A user has many reviews.
A product has many reviews.
A review has a product and a user.
At the moment, I'm using one-to-many relationships for user&reivew, product&review. However, the error occurred when deleting a review: ERROR: update or delete on table "users" violates foreign key constraint "fkcgy7qjc1r99dp117y9en6lxye" on table "reviews".
My question:
How can I delete a Review Entity without deleting the Product entity and User entity?
Which cascade type should I use?
User Entity:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "user_name")
private String userName;
#Column(name = "email")
private String email;
#Column(name = "password")
private String password;
#JsonManagedReference("reviews")
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "user")
private List<Review> reviews = new ArrayList<>();
//constructor + getter+ setter
Product Entity:
#Entity
#Table(name = "products")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Float price;
#Transient
private Float rate;
private String category;
private String brand;
#JsonManagedReference("reviews")
#JsonIgnore
#OneToMany(mappedBy = "product")
List<Review> reviews = new ArrayList<>();
//constructor + getter+ setter
Review Entity:
#Entity
#Table(name = "reviews")
public class Review {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Float rate;
private String comment;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id",referencedColumnName = "id")
#JsonBackReference("user")
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JsonBackReference("product")
#JoinColumn(name = "product_id",referencedColumnName = "id")
private Product product;
//constructor + getter+ setter
User Controller:
#CrossOrigin(origins = "http://localhost:3000")
#RestController
#RequestMapping(path="users/")
public class UserController {
private final UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
...
#DeleteMapping("{userid}")
public User deleteUser(#PathVariable("userid") Long userid){
return userService.deleteById(userid);
}
}
User service:
#Service
public class UserService {
private final UserRepository userRepository;
private final ReviewRepository reviewRepository;
//dependency injection
#Autowired
public UserService(UserRepository userRepository, ReviewRepository reviewRepository) {
this.userRepository = userRepository;
this.reviewRepository =reviewRepository;
}
...
public User getUserById(Long id){
return userRepository.findById(id).orElseThrow(()->
new UserNotFoundException(id));
}
public User deleteById(Long id){
User user = getUserById(id);
userRepository.delete(user);
return user;
}
}
Simple run:
#SpringBootApplication
public class GroceryShoppingAppApplication {
public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext =
SpringApplication.run(GroceryShoppingAppApplication.class, args);
UserRepository userRepository = configurableApplicationContext.getBean(UserRepository.class);
ProductRepository productRepository =configurableApplicationContext.getBean(ProductRepository.class);
ReviewRepository reviewRepository = configurableApplicationContext.getBean(ReviewRepository.class);
User debbi= new User("Debbi","debbi#gamil.com","password");
Product apple = new Product("Apple",(float)3.40,"Fruit","Gala");
Product milk = new Product("Milk",(float)5.22,"Dairy","Anchor");
Review review1 = new Review(debbi,(float)4.5,"Good taste",apple);
Review review2 = new Review(debbi,(float)5.0,"Good milk",milk);
productRepository.save(apple);
productRepository.save(milk);
userRepository.save(debbi);
reviewRepository.save(review1);
reviewRepository.save(review2);
I think I should not use casacadeType.All because when deleting a user, I shouldn't delete the product in the review. I tried other types, the error still remains. Thus, currently I didn't use any casacadeType and need to save each entity one by one.
Please help me with this.
You are getting an error because the user in the review model does not have a referenced Column value.
Try this code:
#JoinColumn(name = "user_id",referencedColumnName = "id")
I have two Entities. One is UserEntity and other is TaskEntity.
#Entity
#Table(name="user")
public class UserEntity {
#Id
private String userEmail;
private String password;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="user_email")
private List<TaskEntity> tasks;
//getter setter for variables
}
#Entity
#Table(name="task")
public class TaskEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String description;
private String statusDate;
private String status;
//getter setter for variables
}
Now I want to create a new task based on userEmail, so this I am doing as follow in DAO class:
#PersistenceContext
EntityManager em;
public Integer addNewTaskByUserEmail(Task task, String userEmail) {
UserEntity userEntity = em.find(UserEntity.class, userEmail);
TaskEntity taskEntity = new TaskEntity();
taskEntity.setName(task.getName());
taskEntity.setDescription(task.getDescription());
taskEntity.setStatus(task.getStatus());
taskEntity.setStatusDate(task.getDate());
userEntity.getTasks().add(taskEntity);
return taskEntity.getId();
}
But in the return statement of I am getting null in service class. How can I return the auto-generated taskId?
A possible issue is you are not saving a task associated with the user. Save the task and then you might be able to get the taskId.
public Integer addNewTaskByUserEmail(Task task, String userEmail) {
UserEntity userEntity = em.find(UserEntity.class, userEmail);
TaskEntity taskEntity = new TaskEntity();
taskEntity.setName(task.getName());
taskEntity.setDescription(task.getDescription());
taskEntity.setStatus(task.getStatus());
taskEntity.setStatusDate(task.getDate());
em.getTransaction().begin();
em.persist(taskEntity);
em.getTransaction().commit();
userEntity.getTasks().add(taskEntity);
return taskEntity.getId();
}
OR
#Autowired TaskRepository taskRepository
public Integer addNewTaskByUserEmail(Task task, String userEmail) {
UserEntity userEntity = em.find(UserEntity.class, userEmail);
TaskEntity taskEntity = new TaskEntity();
taskEntity.setName(task.getName());
taskEntity.setDescription(task.getDescription());
taskEntity.setStatus(task.getStatus());
taskEntity.setStatusDate(task.getDate());
taskEntity = taskRepository.save(taskEntity)
userEntity.getTasks().add(taskEntity);
return taskEntity.getId();
}
Where TaskRepository will be
#Repository
public interface TaskRepository extends JpaRepository<TaskEntity, Integer>
{
}
So I am tidying up my small Spring project and I noticed for some reason the #OneToOne annotation is not doing its job for me which in turn causes issues in another model.
github link : https://github.com/eamonmckelvey/sports-app
Basically, I have a User model class, a team model class and a player model class.
I want only one user to be able to create one team, and one team to have many players.
However, I am able to add as many teams to my user as I want which is wrong.
All the answers provided require me to add a no arg constructor and a constructor for my users class, but when I do this I get an error in my registration from class.
Please help.
1. User Model
#Entity
#Data
#NoArgsConstructor(access= AccessLevel.PRIVATE, force=true)
#RequiredArgsConstructor
public class User implements UserDetails {
#OneToOne(cascade = CascadeType.ALL,mappedBy = "user")
private Team team;
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
private final String username;
private final String password;
//private final String fullname;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
2. Team Model
#Data
#Entity
#Table(name="User_Team")
public class Team implements Serializable {
#OneToOne(fetch= FetchType.LAZY)
private User user;
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
//#NotBlank(message="Team Name is required")
private String teamName;
//#NotBlank(message="Location is required")
private String location;
//#NotBlank(message="Nickname required")
private String nickName;
private String yearEstablished;
public Sport sport;
private Divison divison;
3. Team Controller
#Slf4j
#Controller
#SessionAttributes("Team")
public class TeamController {
private TeamRepository teamRepository;
public TeamController(TeamRepository teamRepository) {
this.teamRepository = teamRepository;
}
#Autowired
TeamRepository service;
#GetMapping("/team")
public String displayTeam(Model model) {
model.addAttribute("team", service.findAll());
return "/team";
}
#GetMapping("/addTeam")
public String showSignUpForm(User user) {
return "addTeam";
}
#PostMapping("/addTeam")
public String processOrder(#Valid Team team, BindingResult result, SessionStatus
sessionStatus,
#AuthenticationPrincipal User user, Model model) {
if (result.hasErrors()) {
return "addTeam";
}
team.setUser(user);
service.save(team);
model.addAttribute("team", service.findAll());
return "team";
}
4. Registeration Form
#Data
public class RegistrationForm {
private String username;
private String password;
//private String fullname;
public User toUser(PasswordEncoder passwordEncoder) {
return new User(
username, passwordEncoder.encode(password));
}
}
5. Registration Controller
#Controller
#RequestMapping("/register")
public class RegistrationController {
private UserRepository userRepo;
private PasswordEncoder passwordEncoder;
public RegistrationController( UserRepository userRepo,
PasswordEncoder passwordEncoder){
this.userRepo = userRepo;
this.passwordEncoder = passwordEncoder;
}
#GetMapping
public String registerForm(){
return "registration";
}
#PostMapping
public String processRegistration(RegistrationForm form){
userRepo.save(form.toUser(passwordEncoder));
return "redirect:/login";
}
6. user details class
#Service
public class UserRepositoryUserDetailsService implements
UserDetailsService {
private UserRepository userRepo;
#Autowired
public UserRepositoryUserDetailsService(UserRepository userRepo) {
this.userRepo = userRepo;
}
#Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
User user = userRepo.findByUsername(username);
if (user != null) {
return user;
}
throw new UsernameNotFoundException(
"User '" + username + "' not found");
}
1) One user can have one team. That means OneToOne relation between user and team. You need not to annotate both user and team with #OneToOne.Remove #OneToOne annotation from team model.
Changes required are:
User model:
#Entity
class User{
#Id
private String id;
#OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name = "team_id")
private Team team;
//other fields
}
Team Model:
#Entity
class Team{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String teamName;
//other field
}
2) For one team to have many players requires #OneToMany
So, I copied your code and did some changes. After the following changes your code works fine.
1) Drop final keyword from below fields in user class(initalizing them doesn't seems to be a great idea).
private final String username;
private final String password;
2) User and Team should not have same serialization version.
private static final long serialVersionUID = 1L;
3) After doing above corrections. Your code will give you the actual error "nested exception is javax.persistence.PersistenceException"
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1628)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
..............
..........
Caused by: org.postgresql.util.PSQLException: ERROR: syntax error at or near "user"
To avoid it do the following changes in your model :
Put #Table(name="users") in user model.
Following are the models:
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
private String username;
#OneToOne(mappedBy = "user")
private Team team;
public User() {
}
}
Team Model
#Table(name="teams")
public class Team {
#Id
private Long id;
#OneToOne
#MapsId
// or you can use
// #OneToOne
// #JoinColumn(name="user_id")
private User user;
private String teamName;
public Team() {
}
}
Follow the above code. It works fine for me. Test Controller to check:
#RequestMapping(value = "/test", method = RequestMethod.GET)
public ResponseEntity<?> test() {
User user = userRepository.findById(2l);
Team team = user.getTeam();
return new ResponseEntity(team, HttpStatus.OK);
}
}
I hope this will help you out.
There are several issues with your code:
#JoinColumn on the child side is missing. It's not even on the parent side. In the User entity you declare #OneToOne(cascade = CascadeType.ALL,mappedBy = "user"), but it is not mapped in the child class.
FetchType.LAZY does not give you much in terms of performance in one-to-one, since hibernate needs to check the database for existence of an object to know whether return a proxy or null.
You're saving a child entity in your TeamController: service.save(team);, but there is no cascading from Team to User.
Try the following mapping:
public class User implements UserDetails {
#OneToOne(cascade = CascadeType.ALL, mappedBy = "user")
private Team team;
// other fields
}
public class Team implements Serializable {
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
// other fields
}
And keeping both sides synchronized. Instead of:
team.setUser(user);
service.save(team);
Try the following code in your TeamController (you will have to autowire UserRepository):
team = service.save(team);
team.setUser(user);
user.setTeam(team);
userRepository.save(user);
Hey so i found a fix here for my code.
1. Team Controller
#GetMapping("/addTeam")
public String showSignUpForm(SessionStatus sessionStatus,
#AuthenticationPrincipal User user, Model model)
{
//if the user has already the team we should not let them add another
// one
//this is due to having one to one relationship
long userHasTeamCount = service.countAllByUser(user);
if (userHasTeamCount > 0) {
return "redirect:team";
}
return "addTeam";
}
2. Team model
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
3. User Model
Removed the OneToOne here as its not needed
4. Team repo
#Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
Team findAllById(Long id);
long countAllByUser(final User user);
}
I want to create an integration test for a repository method which will find a trainings of trainer between two dates to check in the next step if the proposed date of training will not overlapping with trainer time-table.
The main assumption in the application is that the User is the main entity and from it the account, training etc. are created accordingly. For this reason, the User is an active side in relation to the account and training.
The method looks like below:
#Query(
value =
"SELECT t FROM Training t INNER JOIN t.trainingParticipants p "
+ "WHERE p.trainer.id = :trainer_id "
+ "AND t.trainingDate.startDate <= :trainingStartDate "
+ "AND t.trainingDate.endDate <= :trainingEndDate ")
List<Training> findTrainingOfTrainer(
#Param("trainer_id") Long trainerId,
#Param("trainingStartDate") LocalDateTime trainingStartDate,
#Param("trainingEndDate") LocalDateTime trainingEndDate);
I am using this method in TrainingService like below:
#Service
#Slf4j
#Transactional
class TrainingService {
private final TrainingRepository trainingRepository;
private final UserRepository userRepository;
public TrainingService(TrainingRepository trainingRepository, UserRepository userRepository) {
this.trainingRepository = trainingRepository;
this.userRepository = userRepository;
}
public List<Training> findTrainingBetweenTwoDatesQuery(
Long trainerId, LocalDateTime startDate, LocalDateTime endDate) {
return trainingRepository.findTrainingOfTrainer(trainerId, startDate, endDate);
}
}
My User.java class looks like:
#Entity
#Table(name = "users")
#SuperBuilder
#AllArgsConstructor
#Getter
#Setter
public class User extends AbstractBaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "user_account_id", unique = true)
private Account account;
#Enumerated(EnumType.STRING)
private UserType userType;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private Set<Training> training;
#OneToOne(mappedBy = "trainer")
private TrainingParticipants trainingParticipants;
public User() {}
public User(Account account) {
this.account = account;
}
public Long getId() {
return id;
}
public Account getAccount() {
return account;
}
public UserType getUserType() {
return userType;
}
}
Training.java class looks like:
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Entity
#SuperBuilder
public class Training extends AbstractBaseEntity {
#Id
#GeneratedValue
private Long id;
#Column(name = "training_start_date")
LocalDateTime trainingStartDate;
#Column(name = "training_end_date")
LocalDateTime trainingEndDate;
#OneToMany(mappedBy = "training", cascade = CascadeType.ALL)
List<Exercise> exercises = new ArrayList<>();
#Column(name = "difficulty_level")
#Enumerated(EnumType.STRING)
private DifficultyLevel difficultyLevel;
#Column(name = "completed")
boolean completed;
#OneToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "training_participants_id", unique = true)
private TrainingParticipants trainingParticipants;
#Embedded private TrainingDate trainingDate;
#ManyToOne(cascade = CascadeType.PERSIST)
private User user;
}
My test class looks like:
#SpringBootTest
#ExtendWith(SpringExtension.class)
class TrainingServiceTest {
#Autowired private TrainingRepository trainingRepository;
#Autowired private UserRepository userRepository;
#Autowired private AccountRepository accountRepository;
#Autowired private TrainingService trainingService;
#BeforeEach
void setUp() {
trainingService = new TrainingService(trainingRepository, userRepository);
}
#Test
void shouldReturnTrainerTrainingBetweenTwoDates() {
LocalDateTime now = LocalDateTime.now();
userRepository.saveAll(userWithTrainingDataStub());
List<Training> trainingOfTrainer =
trainingRepository.findTrainingOfTrainer(1L, now.minusDays(7), now.plusDays(7));
assertEquals(4, userRepository.findAll().size());
assertEquals(4, trainingRepository.findAll().size());
assertEquals(2, trainingOfTrainer.size());
}
List<User> userWithTrainingDataStub() {
User user4 = userRepository.saveAndFlush(
User.builder()
.account(Account.builder().build())
.userType(UserType.TRAINER)
.build());
return List.of(
User.builder()
.account(Account.builder().build())
.userType(UserType.TRAINEE)
.training(
newHashSet(
Training.builder()
.trainingParticipants(
TrainingParticipants.builder()
.trainer(user4)
.build())
.trainingDate(
TrainingDate.builder()
.startDate(LocalDateTime.now().minusDays(3))
.endDate(LocalDateTime.now().minusDays(2))
.build())
.build()))
.build(),
User.builder()
.account(Account.builder().build())
.userType(UserType.TRAINEE)
.training(
newHashSet(
Training.builder()
.trainingParticipants(
TrainingParticipants.builder()
.trainer(user4)
.build())
.trainingDate(
TrainingDate.builder()
.startDate(LocalDateTime.now().minusDays(5))
.endDate(LocalDateTime.now().minusDays(3))
.build())
.build()))
.build(),
User.builder()
.account(Account.builder().build())
.userType(UserType.TRAINEE)
.training(
newHashSet(
Training.builder()
.trainingParticipants(
TrainingParticipants.builder()
.trainer(user4)
.build())
.trainingDate(
TrainingDate.builder()
.startDate(LocalDateTime.now().minusDays(5))
.endDate(LocalDateTime.now().minusDays(3))
.build())
.build()))
.build());
}
}
Problem is that first assertion passes and I am able to save 4 users but second assertion:
assertEquals(4, trainingRepository.findAll().size());
fails and my trainings are not saved while user creation. All I want to do is to be able to save my training while creating a user. I am pretty sure that there is a problem with mapping User <-> Training but honestly, I don't see any other potential fix to reach a desirable goal. I will be grateful for suggestions about how to fix my mapping and get a possibility to persist in training while user creation.
I started building my first REST webservice in Java using Spring and JPA.
Now I'm trying to create sign-up service. I have no problem with sending a request containing all Entity fields what looks:
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
#Entity
#Table(name = "users")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Enumerated(EnumType.STRING)
private Gender gender;
#Column(name = "email")
private String email;
#Column(name = "login")
private String login;
#Column(name = "password")
private String password;
#Column(name = "registration_date")
#CreatedDate
private LocalDateTime registrationDate;
#OneToMany(mappedBy = "bookOwner", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Book> bookList = new ArrayList<>();
}
But what to do in situation I want my registration form having only login, password and email fields and filling the rest user details would be optional - after confirmation of registration?
I consider using ModelMapper and creating separate classes for every form, but is there any better approach?
I solved problem by my own using mentioned ModelMapper. I paste my code. Can be useful if someone's interested. Didn't make tests, but my DB looks fine and no exceptions are thrown.
public class DTOMapper {
private static final ModelMapper MAPPER = new ModelMapper();
private DTOMapper(){}
public static <S, T> T map(S source, Class<T> targetClass){
return MAPPER.map(source, targetClass);
}
}
#Service
#Transactional
public class SignUpService {
private final UserRepository userRepository;
#Autowired
public SignUpService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User registerUser(SignUpForm form){
if(userRepository.findByLogin(form.getLogin())!=null){
throw new LoginAlreadyUsedException(form.getLogin());
}
if(userRepository.findByEmail(form.getEmail())!=null){
throw new EmailAlreadyUsedException(form.getEmail());
}
User user = DTOMapper.map(form, User.class);
User saved = userRepository.save(user);
return DTOMapper.map(saved, User.class);
}
}
#AllArgsConstructor
#NoArgsConstructor
#Data
#Builder
public class SignUpForm implements Serializable {
private static final long serialVersionUID = 1L;
#NotEmpty
#Size(min = 5)
private String login;
#NotEmpty
#Size(min = 7)
private String password;
//todo email validation
#NotEmpty
private String email;
}
#RestController
public class SignUpController {
private static final Logger log = LoggerFactory.getLogger(SignUpController.class);
#Autowired
private SignUpService signUpService;
#PostMapping(value = "/signup")
public ResponseEntity<?> addUser(#RequestBody #Valid SignUpForm form, BindingResult errors){
if(errors.hasErrors()){
throw new InvalidRequestException(errors);
}
signUpService.registerUser(form);
return new ResponseEntity<>(form, HttpStatus.CREATED);
}
}