Search code examples
javaspringhibernatejpathymeleaf

How to fix ' org.hibernate.TransientPropertyValueException'?


I'm setting up client side shopping cart in my web application. All was ok before adding Shopping Cart class and his service. Now when I try to start the Spring application this error is shown:

Caused by: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.myBookstoreProject.domain.security.UserRole.role -> com.myBookstoreProject.domain.security.Role

I searched for a solution but what I found is a problem with entities of my application. A solution was to add (cascade=CascadeType.ALL) to the entities that are causing the error. But my classes already have it and all was good before Shopping Cart class.

  • User class:

    @Entity
    public class User implements UserDetails {
    
        @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;
    
        @Column(name = "email", nullable = false, updatable = false)
        private String email;
        private String phone;
        private boolean enabled = true;
    
        @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
        @JsonIgnore 
        private Set<UserRole> userRoles = new HashSet<>();
    
        @OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
        private List<UserShipping> userShippingList;
    
        @OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
        private List<UserPayment> userPaymentList;
    
        @OneToOne(cascade = CascadeType.ALL, mappedBy = "user")
        private ShoppingCart shoppingCart;
    
        // getters and setters..
    }
    
  • Role

    @Entity 
    public class Role {
    
        @Id
        private int roleId;
        private String name;
    
        @OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
        private Set<UserRole> userRoles = new HashSet<UserRole>();
    
        // getters and setters..
    }
    
  • UserRole class:

    @Entity
    @Table(name = "user_role")
    public class UserRole {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long userRoleId;
    
        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "user_id")
        private User user;
    
        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "role_id")
        private Role role;
    
        // getters and setters..
    }
    
  • Shopping Cart:

    @Entity
    public class ShoppingCart {
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private BigDecimal GrandTotal;
    
        @OneToMany(mappedBy="shoppingCart", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
        @JsonIgnore
        private List<CartItem> cartItemList;
    
        @OneToOne(cascade=CascadeType.ALL)
        private User user;
        // getters and setters...
    }
    
  • Shopping Cart Service implementation:

    @Service
    public class ShoppingCartServiceImpl implements ShoppingCartService {
    
        @Autowired
        private CartItemService cartItemService;
    
        @Autowired
        private ShoppingCartRepository shoppingCartRepository;
    
        @Override
        public ShoppingCart updateShoppingCart(ShoppingCart shoppingCart) {
            BigDecimal cartTotal = new BigDecimal(0);
    
            List<CartItem> cartItemList = cartItemService.findByShoppingCart(shoppingCart);
    
            for (CartItem cartItem : cartItemList) {
                if (cartItem.getBook().getInStockNumber() > 0) {
                    cartItemService.updateCartItem(cartItem);
                    cartTotal = cartTotal.add(cartItem.getSubtotal());
                }
            }
    
            shoppingCart.setGrandTotal(cartTotal);
    
            shoppingCartRepository.save(shoppingCart);
    
            return shoppingCart;
        }
    
    }
    
  • User Service implementation:

In this class method I added "@Transactional" and 5 lines of Shopping Cart and then the error

@Override
@Transactional
    public User createUser(User user, Set<UserRole> userRoles) throws Exception {
        User localUser = userRepository.findByUsername(user.getUsername());

        if (localUser != null) {
            LOG.info("user {} already exists. Nothing will be done.", user.getUsername());
        } else {
            for (UserRole ur : userRoles) {
                roleRepository.save(ur.getRole());
            }

            user.getUserRoles().addAll(userRoles);

            ShoppingCart shoppingCart = new ShoppingCart(); // 1
            shoppingCart.setUser(user); // 2
            user.setShoppingCart(shoppingCart); // 3

            user.setUserShippingList(new ArrayList<UserShipping>()); //4
            user.setUserPaymentList(new ArrayList<UserPayment>()); // 5

            localUser = userRepository.save(user);
        }
        return localUser;
    }

This error terminates Spring application and only creates tables in MySql without adding rows.

Edit 1: The problem occurs when I try to add a new user to my application. This is my boot main class:

@SpringBootApplication
public class BookstoreProjectApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(BookstoreProjectApplication.class, args);
    }

    @Autowired
    private UserService userService;

    @Override
    public void run(String... args) throws Exception {
        User user1 = new User();
        user1.setFirstName("New");
        user1.setLastName("User");
        user1.setUsername("j");
        user1.setPassword(SecurityUtility.passwordEncoder().encode("p"));
        user1.setEmail("[email protected]");
        Set<UserRole> userRoles = new HashSet<>();
        Role role1 = new Role();
        role1.setRoleId(1);
        role1.setName("ROLE_USER");
        userRoles.add(new UserRole(user1, role1));

        userService.createUser(user1, userRoles);
    }
}

If I comment method body(run), server runs very well until a new user should be created, then the error appears.


Solution

  • You are persisting the roles from your userRole and then assigning them to the user, but you don't assign the persisted entities to the roles after saving them, thus the roles in userRole are not the same as the persisted ones anymore and also do not have the generated id. When you save an Entity and then add it or a parent to another Entity as a value and not have full cascading, you are adding a different Object. This means, use the return Object from save and reassign it to the object you saved and then it should be fine, or use cascading everywhere and only save 1 object.

    TLDR; userRoles' role is not the same as the Role Entities in your Database.

    EDIT 1:

    Change Set<UserRole> userRoles to List<UserRole> userRoles (otherwise you have to convert it like 100 times since you cannot replace the value of a Set while traversing it afaik) and then replace

    for (UserRole ur : userRoles) {
      roleRepository.save(ur.getRole());
    }
    

    with

    for (int i = 0; i < userRoles.size(); i++) {
      userRoles.get(i).setRole(roleRepository.save(userRoles.get(i).getRole())
    }