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);
}
}
Related
I have basicly user and role entities in my project.
#Entity
#Table(name="`User`")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String username;
private String password;
#ManyToMany(fetch = EAGER)
private Collection<Role> roles = new ArrayList<>();
}
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private List<User> users= new ArrayList<User>();
}
This is my controller class
#RestController
#RequestMapping("/api")
#RequiredArgsConstructor
public class UserResource {
private final UserService userService;
#GetMapping("/users")
public ResponseEntity<List<User>> getUsers(){
return ResponseEntity.ok().body(userService.getUsers());
}
And this is my UserManager class
#Service #RequiredArgsConstructor #Transactional #Slf4j
public class UserManager implements UserService {
private final UserRepo userRepo;
private final RoleRepo roleRepo;
#Override
public List<User> getUsers() {
log.info("Fetching all users {}" );
return userRepo.findAll() ;
}
}
When I request http://localhost:8080/api/users, I get wrong data like this
[{"id":5,"name":"Emirhan Ay","username":"emrhn1888","password":"1234","roles":[{"id":1,"name":"ROLE_USER","users":[{"id":5,"name":"Emirhan Ay","username":"emrhn1888","password":"1234","roles":[{"id":1,"name":"ROLE_USER","users":[{"id":5,"name":"Emirhan Ay","username":"emrhn1888","password":"1234","roles":[{"id":1,"name":"ROLE_USER","users":[{"id":5,"name":"Emirhan Ay","username":"emrhn1888","password":"1234","roles":[{"id"}]}]}]}]
But the data saved in the db is like this
Where is my mistake? Thanks in advance for your answers.
You have 'circular dependency'. User has roles, the role has users, etc. You should probably first map entities to DTOS and then maybe add #JsonManagedReference or #JsonBackReference.
Or you can simply put #JsonIgnore on private List<User> users= new ArrayList<User>();
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.
I'm trying to utilize the ModelMapper in my convertion process. What I need to do is to convert the Sample entity to SampleDTO object.
I have the Sample entity like the following:
#Entity
#Table(name = "sample", schema = "sample_schema")
#Data
#NoArgsConstructor
public class Sample {
private static final String SEQUENCE = "SAMPLE_SEQUENCE";
#Id
#SequenceGenerator(sequenceName = SEQUENCE, name = SEQUENCE, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
private Long id;
#Column(name = "name")
private String name;
#Column
private String surname;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "id_deetails")
private Details details;
}
Which holds the Details one:
#Entity
#Table(name = "details", schema = "sample_schema")
#Data
#NoArgsConstructor
public class Details {
private static final String SEQUENCE = "DETAILS_SEQUENCE";
#Id
#SequenceGenerator(sequenceName = SEQUENCE, name = SEQUENCE, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
private Long id;
#Column(name = "street_name")
private String streetName;
#Column
private String city;
}
I'd like the DTO to be this format:
#NoArgsConstructor
#AllArgsConstructor
#Data
public class SampleDTO {
private Long id;
private String name;
private String surname;
private String streetName;
private String city;
}
I also made a ModelMapper bean like:
#Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
And I made a converter component:
#Component
public class EntityDtoConverter {
private final ModelMapper modelMapper;
#Autowired
public EntityDtoConverter(ModelMapper modelMapper) {
this.modelMapper = modelMapper;
}
public SampleDTO sampleToDto(Sample entity) {
return modelMapper.map(entity, SampleDTO.class);
}
}
The problem is
when I try to use this mapper converter in my service
#Service
public class SampleService {
private final SampleRepository sampleRepository;
private final EntityDtoConverter entityDtoConverter;
#Autowired
public SampleService(SampleRepository sampleRepository, EntityDtoConverter entityDtoConverter) {
this.sampleRepository = sampleRepository;
this.entityDtoConverter = entityDtoConverter;
}
public List<SampleDTO> getSamples() {
List<SampleDTO> samples = sampleRepository.findAll()
.map(entityDtoConverter::sampleToDto);
return new List<SampleDTO>(samplesPage);
}
}
I get nulls in places of Details fields.
I have followed Baeldung's tutorial about model-to-dto conversion with ModelMapper and the documentation of it as well but the least wasn't much of help. There is something I'm missing and I have no idea what it is.
I'm working on:
Java 11
Spring Boot 2.3.0
ModelMapper 2.3.8
Try:
modelMapper.getConfiguration().setPropertyCondition(Conditions.isNotNull());
Also check: Modelmapper: How to apply custom mapping when source object is null?
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);
}