Spring JPA-I get error that object is not persisted even though i have persisted it

Tags: , ,



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);
         
    }
    
    

}

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 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



Source: stackoverflow