Reputation: 701
I am writing application using Spring framework. I want to create dto model for my entity object and using it in spring controller. It is not difficult, but I have a relation between my table in database and I must have set in my entity object.
User entity
@Entity
@Table(name = "users")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = "id")
private Integer id;
@NotBlank
@NotNull
@Size(min = 3, max = 40)
@Column(name = "username")
private String username;
@NotBlank
@NotNull
@Size(min = 3, max = 40)
@Column(name = "password")
private String password;
@NotBlank
@NotNull
@Size(min = 3, max = 40)
@Column(name = "firstName")
private String firstName;
@NotBlank
@NotNull
@Size(min = 3, max = 40)
@Column(name = "lastName")
private String lastName;
@Size(min = 11, max = 11)
@Column(name = "personalId")
private String personalId;
@Size(max = 40)
@Column(name = "city")
private String city;
@Size(max = 40)
@Column(name = "address")
private String address;
@NotBlank
@NotNull
@Email
@Size(max = 40)
@Column(name = "email")
private String email;
@Size(min = 9, max = 9)
@Column(name = "phone")
private String phone;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<UserRole> userRoleSet;
user_roles entity
@Entity
@Table(name = "user_roles")
public class UserRole implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = "id")
private Integer id;
@NotNull
@Size(max = 40)
@Column(name = "name")
private String name;
@JoinColumn(name = "userId")
@ManyToOne(targetEntity = User.class)
private User user;
Table user have a relation one to many to user_role table. I create a DTO object for this entities:
userDto
public class UserDto {
private Integer id;
@NotBlank
@NotNull
@Size(min = 3, max = 40)
private String username;
@NotBlank
@NotNull
@Size(min = 3, max = 40)
private String password;
@NotBlank
@NotNull
@Size(min = 3, max = 40)
private String firstName;
@NotBlank
@NotNull
@Size(min = 3, max = 40)
private String lastName;
@Size(min = 11, max = 11)
private String personalId;
@Size(min = 3, max = 40)
private String city;
@Size(min = 3, max = 40)
private String address;
@NotBlank
@NotNull
@Email
@Size(max = 40)
private String email;
@Size(min = 9, max = 9)
private String phone;
private Set<UserRoleDto> userRoleSetDto;
userRoleDto
public class UserRoleDto {
private Integer id;
@NotNull
@Size(min = 3, max = 40)
private String name;
private UserDto userDto;
My controller which redirects to a page with a list of users
@SuppressWarnings("unchecked")
@RequestMapping(value = "/admin/adminlist", method = RequestMethod.GET)
public ModelAndView goAdminList() {
ModelAndView mav = new ModelAndView("admin/adminlist");
List<UserDto> admins = prepareUserListDto(userService.getAdminList());
mav.addObject("admins", admins);
return mav;
}
In this controller I prepare user list to userdto list, using method prepareUserListDto:
private List<UserDto> prepareUserListDto(List<User> users) {
List<UserDto> userDtoList = null;
if (users != null && !users.isEmpty()) {
userDtoList = new ArrayList<UserDto>();
UserDto userDto = null;
for (User user : users) {
userDto = new UserDto();
userDto.setId(user.getId());
userDto.setUsername(user.getUsername());
userDto.setPassword(user.getPassword());
userDto.setFirstName(user.getFirstName());
userDto.setLastName(user.getLastName());
userDto.setEmail(user.getEmail());
userDto.setUserRoleSetDto(prepareUserRoleDtoSet(user
.getUserRoleSet()));
userDtoList.add(userDto);
}
}
return userDtoList;
}
this method invoke to method to prepare userRole to userRoleDto
private Set<UserRoleDto> prepareUserRoleDtoSet(Set<UserRole> userRoles) {
Set<UserRoleDto> userRoleDtoSet = null;
if (userRoles != null && !userRoles.isEmpty()) {
userRoleDtoSet = new HashSet<UserRoleDto>();
UserRoleDto userRoleDto = null;
for (UserRole userRole : userRoles) {
userRoleDto = new UserRoleDto();
userRoleDto.setId(userRole.getId());
userRoleDto.setName(userRole.getName());
userRoleDto.setUserDto(prepareUserDto(userRole.getUser()));
userRoleDtoSet.add(userRoleDto);
}
}
return userRoleDtoSet;
}
but this method again invoke to method:
private UserDto prepareUserDto(User user) {
UserDto userDto = new UserDto();
userDto.setId(user.getId());
userDto.setUsername(user.getUsername());
userDto.setPassword(user.getPassword());
userDto.setFirstName(user.getFirstName());
userDto.setLastName(user.getLastName());
userDto.setEmail(user.getEmail());
userDto.setUserRoleSetDto(prepareUserRoleDtoSet(user.getUserRoleSet()));
return userDto;
}
In conclusion when i try show the admin list I have recursive call and i get this error:
java.lang.StackOverflowError org.hibernate.collection.internal.PersistentSet.isEmpty(PersistentSet.java:166) pl.piotr.ibank.controller.AdminController.prepareUserRoleDtoSet(AdminController.java:170) pl.piotr.ibank.controller.AdminController.prepareUserDto(AdminController.java:139) pl.piotr.ibank.controller.AdminController.prepareUserRoleDtoSet(AdminController.java:177) pl.piotr.ibank.controller.AdminController.prepareUserDto(AdminController.java:139) pl.piotr.ibank.controller.AdminController.prepareUserRoleDtoSet(AdminController.java:177) pl.piotr.ibank.controller.AdminController.prepareUserDto(AdminController.java:139) pl.piotr.ibank.controller.AdminController.prepareUserRoleDtoSet(AdminController.java:177) pl.piotr.ibank.controller.AdminController.prepareUserDto(AdminController.java:139) pl.piotr.ibank.controller.AdminController.prepareUserRoleDtoSet(AdminController.java:177) pl.piotr.ibank.controller.AdminController.prepareUserDto(AdminController.java:139)
Line 139 is userDto.setUserRoleSetDto(prepareUserRoleDtoSet(user.getUserRoleSet()));
in private UserDto prepareUserDto(User user)
method and Line 177 is private UserDto prepareUserDto(User user)
in private Set<UserRoleDto> prepareUserRoleDtoSet(Set<UserRole> userRoles)
method.
How to write dto for this entity? If it would not have a constraint this is easy, but my entity have a constraint between user and user_role and I don't know how to good solution.
Upvotes: 1
Views: 12291
Reputation: 148890
You declare the attribute fetch = FetchType.EAGER
on the collection of roles. So when hibernate gives you a User
the collection of its UserRole
is already initialized. And even if it was not, it would be initialized on first call to getUserRoleSet()
. You can simply write :
private List<UserDto> prepareUserListDto(List<User> users) {
List<UserDto> userDtoList = null;
Set<UserRoleDto> roleDtoSet = null;
if (users != null && !users.isEmpty()) {
userDtoList = new ArrayList<UserDto>();
UserDto userDto = null;
for (User user : users) {
userDto = new UserDto();
userDto.setId(user.getId());
userDto.setUsername(user.getUsername());
userDto.setPassword(user.getPassword());
userDto.setFirstName(user.getFirstName());
userDto.setLastName(user.getLastName());
userDto.setEmail(user.getEmail());
roleDtoSet = new HashSet<UserRoleDto>();
for (UserRole role: user.getUserRoleSet) {
UserRoleDto roleDto = new UserRoleDto();
roleDto.setId(role.getId());
roleDto.setUserDto(userDto);
roleDto.setName(user.getName);
roleDtoSet.add(roleDto);
}
userDto.setUserRoleSetDto(roleDtoSet);
userDtoList.add(userDto);
}
}
return userDtoList;
}
If you prefere small methods (easier to write and test), you can split it, almost the same you wrote but passing userDto
that you already have :
private List<UserDto> prepareUserListDto(List<User> users) {
List<UserDto> userDtoList = null;
if (users != null && !users.isEmpty()) {
userDtoList = new ArrayList<UserDto>();
UserDto userDto = null;
for (User user : users) {
userDto = new UserDto();
userDto.setId(user.getId());
userDto.setUsername(user.getUsername());
userDto.setPassword(user.getPassword());
userDto.setFirstName(user.getFirstName());
userDto.setLastName(user.getLastName());
userDto.setEmail(user.getEmail());
userDto.setUserRoleSetDto(prepareUserRoleDtoSet(userDto, user
.getUserRoleSet()));
userDtoList.add(userDto);
}
}
return userDtoList;
}
private Set<UserRoleDto> prepareUserRoleDtoSet(UserDto userDto,
Set<UserRole> userRoles) {
Set<UserRoleDto> userRoleDtoSet = null;
if (userRoles != null && !userRoles.isEmpty()) {
userRoleDtoSet = new HashSet<UserRoleDto>();
UserRoleDto userRoleDto = null;
for (UserRole userRole : userRoles) {
userRoleDto = new UserRoleDto();
userRoleDto.setId(userRole.getId());
userRoleDto.setName(userRole.getName());
userRoleDto.setUserDto(userDto);
userRoleDtoSet.add(userRoleDto);
}
}
return userRoleDtoSet;
}
This avoids all cyclic dependancy.
Upvotes: 2
Reputation: 2773
You have a bi-directionnal relation between UserDto and UserRoleDto.
If you really need to keep the bi-directional relation, I would modify your code as follow :
private UserDto prepareUserDto(User user) {
UserDto userDto = new UserDto();
userDto.setId(user.getId());
userDto.setUsername(user.getUsername());
userDto.setPassword(user.getPassword());
userDto.setFirstName(user.getFirstName());
userDto.setLastName(user.getLastName());
userDto.setEmail(user.getEmail());
userDto.setUserRoleSetDto(prepareUserRoleDtoSet(user.getUserRoleSet(), userDto));
return userDto;
}
This pass the userDto being created to prepareUserRoleDtoSet.
private Set<UserRoleDto> prepareUserRoleDtoSet(Set<UserRole> userRoles, UserDto userDto) {
Set<UserRoleDto> userRoleDtoSet = null;
if (userRoles != null && !userRoles.isEmpty()) {
userRoleDtoSet = new HashSet<UserRoleDto>();
UserRoleDto userRoleDto = null;
for (UserRole userRole : userRoles) {
userRoleDto = new UserRoleDto();
userRoleDto.setId(userRole.getId());
userRoleDto.setName(userRole.getName());
userRoleDto.setUserDto(userDto);
userRoleDtoSet.add(userRoleDto);
}
}
return userRoleDtoSet;
}
Upvotes: 3
Reputation: 1957
A very simple solution would be to remove completely the userDto
property from the UserRoleDto class. In contrast to the entity classes where you might need to go from one entity to another and include bi-directional relations, the client will usually only need to get the roles of a user, and never users from a role. Therefore you do not need bi-directional relations between those two dtos
Upvotes: 1