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); } }
Advertisement
Answer
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 Role
s 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 UserRole
s.
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.