I have a problem with Rest response when I use ManyToMany annotation.
Problem is this answer:
Problem accessing /json2/1. Reason:
Server Error
Caused by:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.Tomek.entity.User.roles, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.Tomek.entity.Role["users"]->org.hibernate.collection.internal.PersistentBag[0]->com.Tomek.entity.User["roles"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.Tomek.entity.User.roles, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.Tomek.entity.Role["users"]->org.hibernate.collection.internal.PersistentBag[0]->com.Tomek.entity.User["roles"])
Without ManyToMany annotation(like in the Model class Role) I response JSON format
[{"id":1,"name":"ROLE_USER"},{"id":2,"name":"ROLE_ADMIN"}]
RestController
#Controller
public class RestController {
#Autowired
private UserService userService;
#Autowired
private BlogService blogService;
#Autowired
private RoleService roleService;
#RequestMapping("/json")
public String JsonLink(Model model){
model.addAttribute("result", blogService.findAll());
return "json";
}
#RequestMapping(value = "/json2/{id}", method = RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody List<Role> ShowJson(#PathVariable int id) {
Hibernate.initialize(roleService.findAll());
List<Role> role = roleService.findAll();
System.out.println(role.toString());
return role;
}
Model Role (comment #ManyToMany)
#Entity
#JsonAutoDetect
public class Role {
#Id
#GeneratedValue
private Integer id;
private String name;
/*#ManyToMany(fetch = FetchType.EAGER,mappedBy = "roles")
private List<User> users;
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}*/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Role(String name) {
this.name = name;
}
public Role() {
}
#Override
public String toString() {
return "Role [id=" + id + ", name=" + name + "]";
}
}
Model User
#Entity
#JsonAutoDetect
public class User {
#Id
#GeneratedValue
private Integer id;
private String name;
private String email;
private String password;
private boolean enabled;
#ManyToMany
#JoinTable
private List<Role> roles;
#OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
private List<Blog> blogs;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public List<Blog> getBlogs() {
return blogs;
}
public void setBlogs(List<Blog> blogs) {
this.blogs = blogs;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Service
#Service
public class RoleService {
#Autowired
private RoleRepository roleRepository;
public List<Role> findAll(){
return roleRepository.findAll();
}
JSP
<c:forEach items="${result}" var="item">
json
</c:forEach>
The problem is with serializing property roles of User entity. When you load your entity in RoleService and then return result to controller the hibernate session is ended. You are not able to load it outside hibernate session.
You cannot load these property eagerly too. Then there would be a chance to load large tree of objects.
In my opinion to solve your proplem you have to create 3 REST services and 3 normal service method which would load flat data structures:
/roles/{id} - it loads single role (without users property)
/roles/{id}/users - it loads all users that have role with given id (without roles property)
/users/{id}/roles - it loads roles for user with given id
Additionaly you have to annotate your collection properties (roles, users) with annotation #JsonIgnore to ignore them during serialization to json.
Related
I have two entities: UserEntity and LoginEntity and crudrepositories for each. The entities have a relationship of OneToOne where one user will have one login account. I also created a controller and I can get all the data from the database i.e when call getalluser I get all users with their relationship to login. and when I call getAllLogins I get all logins accounts. I also managed to insert the user and the login using API each individually and it's working fine but this will omit the foreign-key user_id.
Now since am knew am stack on how to insert the user and login each respectively with their relationships through one json body
#Entity#Table(name="user_table")public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long uid;
private String fname;
private String lname;
#OneToOne( cascade = CascadeType.ALL, mappedBy = "userEntityFk")
private LoginEntity logins;
public UserEntity() {
super();
}
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public String getFname() {
return fname;
}
public void setFname(String fname) {
this.fname = fname;
}
public String getLname() {
return lname;
}
public void setLname(String lname) {
this.lname = lname;
}
public LoginEntity getLogins() {
return logins;
}
public void setLogins(LoginEntity logins) {
this.logins = logins;
}
public UserEntity(Long uid, String fname, String lname, LoginEntity logins) {
super();
this.uid = uid;
this.fname = fname;
this.lname = lname;
this.logins = logins;
}
#Entity #Table(name="login_table") public class LoginEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long lid;
private String username;
private String password;
#OneToOne(cascade = CascadeType.ALL )
private UserEntity userEntityFk;
public LoginEntity() {
super();
}
public LoginEntity(Long lid, String username, String password, UserEntity userEntityFk) {
super();
this.lid = lid;
this.username = username;
this.password = password;
this.userEntityFk = userEntityFk;
}
public Long getLid() {
return lid;
}
public void setLid(Long lid) {
this.lid = lid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public UserEntity getUserEntityFk() {
return userEntityFk;
}
public void setUserEntityFk(UserEntity userEntityFk) {
this.userEntityFk = userEntityFk;
}
}
#Repository
public interface LoginRepo extends CrudRepository<LoginEntity, Integer> {
}
#Repository
public interface UserRepo extends CrudRepository<UserEntity, Integer> {
}
#RestController
#RequestMapping(path="/api")
public class MainController {
#Autowired
private UserRepo userRepository;
#Autowired
private LoginRepo loginRepository;
//===this works fine i can get all the users after insert
#GetMapping(path="/user_acc")
public #ResponseBody Iterable<UserEntity> getAllUsers() {
// This returns a JSON or XML with the users
return userRepository.findAll();
}
//================this too works fine after insert
#GetMapping(path="/login_acc")
public #ResponseBody Iterable<LoginEntity> getAlllogins() {
// This returns a JSON or XML with the users
return loginRepository.findAll();
}
//===adding single user works fine. and it returns user_id
#PostMapping("/user_acc")
public Long addNewUser(#RequestBody UserEntity userz){
UserEntity ue = userRepository.save(userz);
return ue.getUid();
}
//===this works but the foreign key not inserted and thats where my problem is
#PostMapping("/login_acc")
public LoginEntity addNewLogin(#RequestBody LoginEntity loginz){
return loginRepository.save(loginz);
}
}
class UserLogin{
UserEntity myuser;
LoginEntity mylogin;
public UserEntity getMyuser() {
return myuser;
}
public void setMyuser(UserEntity myuser) {
this.myuser = myuser;
}
public LoginEntity getMylogin() {
return mylogin;
}
public void setMylogin(LoginEntity mylogin) {
this.mylogin = mylogin;
}
}
result on post http://localhost:8080/api/login_acc. account created but no foreign key
result on post http://localhost:8080/api/login_acc. account created but no foreign key
{
"lid": 1,
"username": "admin1",
"password": "11111",
"userEntityFk": null
}
result on geting all users on get method http://localhost:8080/api/user_acc
{
"uid": 1,
"fname": "hassan",
"lname": "zahor",
"logins": null
}
what i want to post is this body below to multiple tables
{
"fname":"hassan",
"lname":"zahor",
"username": "admin5",
"password": "55555"
}
This one should works better :
{
"fname":"hassan",
"lname":"zahor",
"userEntityFk" : {
"username": "admin5",
"password": "55555"
}
}
However, you will get an error if your endpoint returns an object that contains a loop inclusion : spring will try to map it into json indefinitely.
You can correct this issue to map your result into another object before returning it from your controller, or just remove the property "logins" from UserEntity.
I am using Spring boot-I have 3 classes User,Role and UserRole.I have pesisted both role object and user object but i get error that role object is not persisted.The mappings- between User and UserRole is OneToMany ,between Role and UserRole OneToMany.In the UserServiceImpl class i have persisted Role object roleRepository.save(userRole.getRole());
Error is-
Caused by: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.bookstore.domain.security.UserRole.role -> com.bookstore.domain.security.Role
#Entity
public class User implements UserDetails,Serializable {
private static final long serialVersionUID=157954L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Id",nullable = false,updatable = false)
private Long id;
private String userName;
private String password;
private String firstName;
private String lastName;
private String email;
private String phone;
private boolean enabled;
#OneToMany(mappedBy = "user",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JsonIgnore
private Set<UserRole> userRoles=new HashSet<UserRole>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities=new HashSet<GrantedAuthority>();
userRoles.forEach(userRole->{
authorities.add(new Authority(userRole.getRole().getRoleName()));
});
return authorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
#Override
public String getUsername() {
return userName;
}
}
#Entity
public class Role implements Serializable{
private static final long serialVersionUID=68678L;
#Id
private Long roleId;
private String roleName;
#OneToMany(mappedBy = "role",cascade = CascadeType.ALL,fetch = FetchType.LAZY)
#JsonIgnore
private Set<UserRole> userRoles=new HashSet<UserRole>();
public Role() {
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
}
#Entity
public class UserRole implements Serializable {
private static final long serialVersionUID=456874L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userRoleId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "roleId")
private Role role;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "userId")
private User user;
public UserRole(User user,Role role) {
this.role = role;
this.user = user;
}
public UserRole() {
super();
}
public Long getUserRoleId() {
return userRoleId;
}
public void setUserRoleId(Long userRoleId) {
this.userRoleId = userRoleId;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
#Service
public class UserServiceImpl implements UserService {
private static final Logger LOG=LoggerFactory.getLogger(UserServiceImpl.class);
#Autowired
UserRepository userRepository;
#Autowired
RoleRepository roleRepository;
#Transactional
#Override
public User CreateUser(User user, Set<UserRole> userRoles) {
User localUser=userRepository.findByUserName(user.getUserName());
if(localUser!=null) {
LOG.warn("Username {} already exists",user.getUserName());
}
else {
for(UserRole userRole:userRoles) {
roleRepository.save(userRole.getRole());
LOG.error("inside for {}",userRole.getRole().getRoleName());
}
user.getUserRoles().addAll(userRoles);
localUser=userRepository.save(user);
}
return localUser;
}
}
#SpringBootApplication
public class BookStoreApplication implements CommandLineRunner {
#Autowired
UserService userService;
public static void main(String[] args) {
SpringApplication.run(BookStoreApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
User user1=new User();
user1.setUserName("test");
user1.setPassword(SecurityUtility.passwordEncoder().encode("test"));
user1.setEmail("test#test.com");
user1.setEnabled(true);
user1.setFirstName("testFirstName");
user1.setLastName("testLastName");
user1.setPhone("123456789");
Role role1=new Role();
role1.setRoleId((long)1);
role1.setRoleName("ROLE_USER");
UserRole userRole1=new
UserRole(user1,role1);
Set<UserRole> userRoles1=new HashSet<UserRole>();
userRoles1.add(userRole1);
userService.CreateUser(user1, userRoles1);
User user2=new User();
user2.setUserName("admin");
user2.setPassword(SecurityUtility.passwordEncoder().encode("admin"));
user2.setEmail("admin#admin.com");
user2.setEnabled(true);
user2.setFirstName("adminFirstName");
user2.setLastName("adminLastName");
user2.setPhone("223456789");
Role role2=new Role();
role2.setRoleId((long) 2);
role2.setRoleName("ROLE_ADMIN");
UserRole userRole2=new UserRole(user2,role2);
Set<UserRole> userRoles2=new HashSet<UserRole>();
userRoles2.add(userRole2);
userService.CreateUser(user2, userRoles2);
}
}
Couple of issues here.
The first (and the question) issue and the reason why you are getting a "Transient state error" is because you are trying to save an entity with entities attached to it that are NOT yet managed by hibernate.
Have a read of: Entity Lifecycle Model in Hibernate
Caused by: org.hibernate.TransientPropertyValueException:
object references an unsaved transient instance -
save the transient instance before flushing :
com.bookstore.domain.security.UserRole.role
->
com.bookstore.domain.security.Role
So you are somewhere trying to save a UserRole with a Role that is not yet managed.
When you call new on an entity, it is in a Transient state. Hibernate doesn't know how to handle it. It doesn't have a database ID and is not part of the context for hibernate to manage (make the relevent queries etc).
To make an entity managed you need to save it via the repo.
I.e. roleRepo.save(role)
You will then notice it then has an Id and is now managed by hibernate.
#Service
public class UserServiceImpl implements UserService {
#Transactional
#Override
public User CreateUser(User user, Set<UserRole> userRoles) {
User localUser = userRepository.findByUserName(user.getUserName());
if (localUser != null) {
LOG.warn("Username {} already exists", user.getUserName());
} else {
// For each transient UserRole passed in, save the Role.
// Role is now managed.
for (UserRole userRole : userRoles) {
roleRepository.save(userRole.getRole());
LOG.error("inside for {}", userRole.getRole().getRoleName());
}
user.getUserRoles().addAll(userRoles);
localUser = userRepository.save(user);
}
return localUser;
}
}
This service above doesn't maybe do what you expect.
You are getting the Roles and saving them.
You then don't replace the Role in UserRole with the managed one back from the repo.
Maybe this would work?
for(UserRole userRole:userRoles) {
//The role is now managed.
Role managedRole = roleRepository.save(userRole.getRole());
//Replace the transient role in the UserRole with a managed Role.
userRole.setRole(managedRole);
}
So when it goes on to save the User:
user.getUserRoles().addAll(userRoles);
localUser = userRepository.save(user);
The UserRoles (which are still transient) have a managed Role at least.
The Cascade.ALL should do what you expect (I am unsure mind!) and save the transient UserRoles because Cascade.ALL will save the children UserRoles.
https://www.baeldung.com/jpa-cascade-types
=============================
The second issue, not causing the problem in question, but you may want to go have a think about:
At the moment you have:
User 1 : M UserRole
UserRole M : 1 Role
1 User has many UserRoles.
1 Role has many UserRoles.
The modelling here just smells off.
Usually you'd have some Role entities/database entries that can be related to a user via ManyToMany relationship.
User signs up, is given the "USER" role in their Set<Role> userRoles rather than creating a new Role for each user with "USER" as a field.
So a user has a relationship to a role via a "join table" UserRole.
Spring can already create a join table for you. You do not really need the UserRole entity in your code as it stands as it just holds a FK_User and FK_Role.
Basically, you want:
User M:M Role
I.e. a user can have many roles.
Simply use the #ManyToMany annotation for a Many:Many relationship between user and roles.
To add a role you search the database for
Role findByRoleName
And add that managed entity to the user's roles and then persist the user.
ManyToMany Baeldung
I'm trying to add a functionality to my webapp with user registration. Webapp is based on spring boot, hibernate and mysql database, frontend is in angular. Generally, the user creation procedure is working correctly, user data is correctly send from frontend to backend via json and saved to the database in shop_user table (with all the user data, such as name, surname, address etc.), but it DOESN'T have role column.
I also have table 'role', which should be:
id name
1 USER
2 ADMIN
and joined table user_role, which consists of user_id from table shop_user and role id from table role, so it should look like this:
id_user id_role
1 2
2 1
3 1
When user is being created on the website, it is hard-coded to set the role by default to USER. This seems to work quite well as it adds a new row in shop_user, and it adds a row to user_role, but... it also creates a new row in 'role' table.
so in the end 'role' table looks like this:
id name
1 ADMIN
2 USER
3 USER
4 USER
5 USER
99 USER
`
while this is not a blocking bug that stops application from working, it is not 'as it should work' unfortunately... as the table should only consist of two role rows (and possibly additional ones, in the future), but not multiplicated for each user!
here's the flawed code of user:
User
#Entity
#Table(name = "shop_user")
public class User extends AbstractEntity {
#Column
private String firstName;
#Column
private String lastName;
#Column
private String addressLine;
#Column
private String city;
#Column
private String country;
#Column
private String zipCode;
#Column
private String phoneNumber;
#Column
private String email;
#Column
private String password;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_role",
joinColumns = #JoinColumn(name = "id_user", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "id_role", referencedColumnName = "id"),
uniqueConstraints = {#UniqueConstraint(columnNames = {"id_user", "id_role"})})
private List<Role> roles;
public User() {
}
public User(User user) {
setId(user.getId());
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
this.addressLine = user.getAddressLine();
this.city = user.getCity();
this.country = user.getCountry();
this.zipCode = user.getZipCode();
this.phoneNumber = user.getPhoneNumber();
this.email = user.getEmail();
this.password = user.getPassword();
this.roles= user.getRoles();
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getAddressLine() {
return addressLine;
}
public void setAddressLine(String addressLine) {
this.addressLine = addressLine;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Role implementation:
Role
#Entity
#Table(name = "role")
public class Role extends AbstractEntity {
#Column
private String name;
#ManyToMany(mappedBy = "roles", cascade = CascadeType.PERSIST)
private List<User> users;
public Role(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
Abstract entity:
AbstractEntity
#MappedSuperclass
public abstract class AbstractEntity implements Persistable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public void setId(Long id) {
this.id = id;
}
#Override
public Long getId() {
return id;
}
#Override
public boolean isNew() {
return id == null;
}
}
User service:
UserServiceImpl
#Service
public class UserServiceImpl extends AbstractServiceImpl<User, UserDTO> implements UserService {
private final UserRepository userRepository;
private final UserConverter userConverter;
public UserServiceImpl(UserRepository userRepository, UserConverter
userConverter) {
this.userRepository = userRepository;
this.userConverter = userConverter;
}
#Override
protected JpaRepository<User, Long> getRepository() {
return userRepository;
}
#Override
protected Converter<User, UserDTO> getConverter() {
return userConverter;
}
#Override
#Transactional
public User registerUser(User user) {
List<Role> roles = new LinkedList<>();
roles.add(new Role("USER"));
user.setRoles(roles);
return userRepository.save(user);
}}
I am nearly sure that this comes to the relations mapping in Hibernate and object creation, but can't quite figure it out...
Any help will be appreciated, thank you!
The issue is here:
#Override
#Transactional
public User registerUser(User user) {
List<Role> roles = new LinkedList<>();
roles.add(new Role("USER"));
user.setRoles(roles);
return userRepository.save(user);
}}
Since the relationship User -> Role is cascade persist, the (new) role new Role("USER") is also persisted and you ended up with a new Role for each user instead of reusing the existing one.
The solution is to check the existence of a Role with name = USER. If doesn't exist, insert it. Otherwise add the existent one to the roles collection.
#Entity
#NamedQuery(name="User.findAll", query="SELECT u FROM User u")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
private String email;
private String password;
private String username;
#Transient
private String passwordConfirm;
//bi-directional many-to-one association to Role
#ManyToOne(fetch=FetchType.LAZY ,cascade = {CascadeType.PERSIST})
private Role role;
public User() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public Role getRole() {
return this.role;
}
public void setRole(Role role) {
this.role = role;
}
#Transient
public String getPasswordConfirm() {
return passwordConfirm;
}
public void setPasswordConfirm(String passwordConfirm) {
this.passwordConfirm = passwordConfirm;
}
}
#Entity
#NamedQuery(name="Role.findAll", query="SELECT r FROM Role r")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
private String name;
//bi-directional many-to-one association to User
#OneToMany(fetch=FetchType.LAZY, mappedBy="role", cascade = {CascadeType.PERSIST})
private List<User> users;
public Role() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public List<User> getUsers() {
return this.users;
}
public void setUsers(List<User> users) {
this.users = users;
}
public User addUser(User user) {
getUsers().add(user);
user.setRole(this);
return user;
}
public User removeUser(User user) {
getUsers().remove(user);
user.setRole(null);
return user;
}
}
public interface UserRepository extends CrudRepository <User, Long> {
public UserDto getUserDetailsById(Long userId) throws commonException {
//ArrayList<UserDto> arr = new ArrayList<>();
User user = userRepository.`findOne`(userId);
UserDto userDto = new UserDto();
userDto.setId(user.getId());
userDto.setUsername(user.getUsername());
userDto.setEmail(user.getEmail());
userDto.setPassword(user.getPassword());
userDto.setRoleId(user.getRole().getId());
userDto.setRoleName(user.getRole().getName());
// arr.add(userDto);
return userDto;
}
find by Id is not getting role details by using user object.lazy initialization is not happening.When I use to get user ID from user object, I can not get role details.
userDto.setRoleId(user.getRole().getId()); is having null value
In your User entity, you have configured the Role collection to load lazily. Therefore when you first call user.getRole(), you get a proxy object. If you want to call methods on the proxy object that need to fetch data, you should initialize the proxy. There is a technique to initialize it. Try the following:
Before the following line of code
userDto.setRoleId(user.getRole().getId());
add the following;
user.getRole().size();
For situations where you know that you need a particular association, its generally recommended that you specify that the association be join-fetched.
If the association is optional, you'd use something like:
FROM User u LEFT JOIN FETCH u.role WHERE u.id = :userId
If the association is not-optional, you could improve the above by specifying an inner join like:
FROM User u JOIN FETCH u.role WHERE u.id = :userId
With Spring data, you can use the #Query annotation on methods and specify the JPQL/HQL above to suit your needs.
I have started testing with spring boot to create a Restful webservice that has
simple crud functions.i have two entity classes
Company.java
#Entity
#Table(name="Company_new")
public class Company {
#Id
#NotNull
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
public void setId(int id) {
this.id = id;
}
#NotNull
#Column(name="name")
private String name;
#OneToMany(mappedBy="company",cascade=CascadeType.ALL)
private Set<User> users;
public Company(String name){
this.name = name;
}
public Company(){
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public int getId(){
return id;
}
}
and User.java
#Entity
#Table(name="user_new")
public class User {
#Id
#Column
#GeneratedValue(strategy = GenerationType.AUTO)
private int idUser;
#NotNull
#Column
private String name;
#NotNull
#Column
private String userName;
#NotNull
#Column
private String authLevel;
#Column
private String password;
#ManyToOne
#JoinColumn(name="idCompany")
private Company company;
// Public methods
public Company getCompany(){
return company;
}
public void setCompany(Company company){
this.company = company;
}
public void setName(String name){
this.name =name;
}
public void setUserName(String userName){
this.userName = userName;
}
public String getName(){
return this.name;
}
public String getUsername(){
return this.userName;
}
public String getPassword(){
return this.password;
}
public String getAuthLevel() {
return authLevel;
}
public void setAuthLevel(String authLevel) {
this.authLevel = authLevel;
}
public String getUserName() {
return userName;
}
public void setId(int idUser) {
this.idUser = idUser;
}
public void setPassword(String password) {
this.password = password;
}
public int getId(){
return this.idUser;
}
}
i want to have a relationship with a Company having many users.
I have tried presisting a user record like this
#Autowired
CompanyDao companyDao;
#RequestMapping(value = "/user/create", method = RequestMethod.POST)
public #ResponseBody ResponseEntity createUser(#RequestBody User user) {
try {
Company c = companyDao.findOne(user.getCompany().getId());
user.setCompany(c);
userDao.save(user);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(user, HttpStatus.OK);
}
my data is presisting in the database the way i want
but when i try to access a company record it loads like this
obviously it loads relationships in a cycle and eventually gives stack overflow error. how to solve this ?
Depending on your desired outcome you can:
use #JsonIgnore on public Set<User> getUsers() to prevent serializing the users collection or #JsonIgnore on public Company getCompany() to prevent company serialization
use #JsonManagedReference on public Set<User> getUsers() and #JsonBackReference on public Company getCompany() to let Jackson know that it's a bidirectional relation.
PS. If the API you're exposing will be consumed by code you do not control consider not exposing entities directly but mapping them to DTOs