How to fix ' org.hibernate.TransientPropertyValueException'? - java

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("newUser#gmail.com");
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.

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

Related

Hibernate insert null values ​in composite foreign key

I have two simple domain objects as follows:
USER:
#Entity
#Table(name="USER")
#IdClass(UserPK.class)
public class User implements Serializable {
//...
#Id
#Column(name = "FISCALCODE")
private String fiscalCode;
#Id
#Column(name = "USERNUMBER")
private String userNumber;
#OneToMany(mappedBy="user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Items> items;
// getters and setters
}
UserPK:
public class UserPK implements Serializable {
#Column(name = "FISCALCODE")
private String fiscalCode;
#Column(name = "USERNUMBER")
private String userNumber;
// getter and setter
}
ITEMS:
#Entity
#Table(name="ITEMS")
public class Items implements Serializable {
//...
#Id
#Column(name = "ID_ITEM")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_item_generator")
#SequenceGenerator( name = "id_item_generator", sequenceName = "ITEM_SEQ", allocationSize = 1)
private Integer id;
#ManyToOne
#JoinColumns({
#JoinColumn(name="FISCALCODE"),
#JoinColumn(name="USERNUMBER")
})
private User user;
// getters and setters
}
DB Table:
user { fiscalcode, usernumber, other columns... } // fiscalcode+usernumber = PK
items { id, fiscalcode, usernumber, other columns... } // fiscalcode,usernumber is a foreign key
CONTROLLER:
#RequestMapping(value="/user", method = RequestMethod.POST, produces = "application/json")
public Object postUser(#RequestBody(required = false) User user){
//connection etc..
session.save(user);
//...
return new ResponseEntity<>(HttpStatus.OK);
}
Why when I run the command session.save(user) Hibernate insert value null in the columns FISCALCODE and USERNUMBER of the ITEMS table?
I tried to set the ManyToOne and the JoinColumns on the getter, but the result is the same.
EDIT: i have added my method for POST operation
As it's stated in the documentation for #JoinColumns:
When the JoinColumns annotation is used, both the name and the referencedColumnName elements must be specified in each such JoinColumn annotation.
So, you should correct your mapping like below:
#ManyToOne
#JoinColumns({
#JoinColumn(name="FISCALCODE", referencedColumnName = "FISCALCODE"),
#JoinColumn(name="USERNUMBER", referencedColumnName = "USERNUMBER")
})
private User user;
Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times.
So, you should have in your User entity the addItem() and removeItem() utility methods that synchronize both ends whenever a child element is added or removed like below.
#Entity
#Table(name="USER")
#IdClass(UserPK.class)
public class User implements Serializable {
//...
#OneToMany(mappedBy="user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Items> items;
// getters and setters
public void addItem(Items item) {
this.items.add(item);
item.setUser(this);
}
public void removeItem(Items item) {
this.items.remove(item);
item.setUser(null);
}
}
Example of saving:
User user = new User();
user.setFiscalCode("Code1");
user.setUserNumber("User1");
// ...
Items item1 = new Items();
Items item2 = new Items();
// ...
user.addItem(item1);
user.addItem(item2);
session.save(user);

Hibernate One to Many relation- Child table not updating

Here I am adding a list of new orders to a specific user. While adding orders to a specific user, it returns success status, but still my database is empty. Why the data is not getting added...?
Users
#Entity
Class Users{
#Id
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private List<Orders> orders;
}
Orders
#Entity
Class Orders{
#Id
private int id;
private Date orderDate;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
}
Repository
public ResponseEntity<String> addOrder(int userId,List<Orders> orders) throws UserNotFound{
User user =userRepository.findById(userId).orElse(null);
if(user==null) throw new UserNotFound("User Not Found");
for(Orders o:orders){
o.setOrderDate(new Date("....."));
}
user.getOrders().addAll(orders);
userRepository.save(user);
}
Try to add cascade persist to your relation specification:
#Entity
Class Users{
#Id
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade={CascadeType.PERSIST})
private List<Orders> orders;
}

How to map 2 classes using model mapper, Entity to Dto

I want to convert two objects with same attributes using model mapper. How ever I cant because of many to many relationship.
private User fromEntity(UserEntity userEntity) {
LOGGER.info("Converting userEntity to user model with Id" +
userEntity.getUserId());
User user = modelMapper.map(userEntity,User.class);
LOGGER.info("Converted userEntity to user model with Id" +
userEntity.getUserId());
return user;
}
I have User and UserEntity classes. They are mapped with Role and RoleEntity classess:
Here is my User class:
public class User {
private Long userId;
private String userUsername;
private String userName;
private String userSurname;
private String password;
private String addres;
private String eMail;
private boolean active = false;
private String key;
//#JsonBackReference
private Set<Role> role ;
public User(){
role = new HashSet<>();
}
Role Class:
public class Role {
private Long roleId;
private String role;
private Set<User> user;
public Role(){
user = new HashSet<>();
}
//#JsonManagedReference
public Set<User> getUser() {
return user;
}
}
UserEntity Class:
#Entity
#Table( name="users" )
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userId")
private Long userId;
private String userUsername;
private String userName;
private String userSurname;
private String password;
private String addres;
private String eMail;
private boolean active;
private String key;
#JsonManagedReference
#ManyToMany( cascade = CascadeType.ALL, fetch = FetchType.EAGER )
#JoinTable(
name = "users_roles",
joinColumns = {#JoinColumn(name="userId")},
inverseJoinColumns = {#JoinColumn(name="roleId")}
)
private Set<RoleEntity> roleEntities;
public UserEntity(){
active=false;
roleEntities = new HashSet<>();
}
and RoleEntity class:
#Entity
#Table(name="roles")
public class RoleEntity {
#Id
#GeneratedValue
private Long roleId;
private String role;
#ManyToMany //( mappedBy = "roleEntities") //Bunu kaldırdım
private Set<UserEntity> userEntities ;
public RoleEntity(){
userEntities = new HashSet<>();
}
It gives me error when I login correctly:
ModelMapper mapping errors: 1) Converter org.modelmapper.internal.converter.CollectionConverter#735060fc failed to convert java.util.Set to java.util.Set. 1 error
I have changed my RoleEntity class attribute from:
#ManyToMany //( mappedBy = "roleEntities")
private Set<UserEntity> userEntities ;
to this:
#ManyToMany (mappedBy = "roleEntities", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<UserEntity> userEntities ;
That is how I resolved the problem.

Hibernate "object references an unsaved transient instance"

My Users are in Organisations in a ManyToOne relationship, when a user is created with an existing Organisation I am trying to assign it to it without creating a new one.
In my service, here is how I create a user:
#Override
public UserInfo createUser(UserInfo newUser) {
// Check if organisation exists
OrganisationEntity orga = organisationDao.findByName(newUser.getOrganisation());
if (orga != null) {
// Organisation exists, we save it with the correct ID
return mapper.map(userDao.save(mapper.map(newUser, orga.getId())));
} else {
// Organisation does NOT exists, we save it and create a new one
return mapper.map(userDao.save(mapper.map(newUser, (long) -1)));
}
}
With my Mapper (helping me to convert a model to an entity) being:
public UserEntity map(UserInfo userInfo, Long orgaId) {
UserEntity user = new UserEntity();
user.setEmail(userInfo.getEmail());
user.setFirstName(userInfo.getFirstName());
user.setLastName(userInfo.getLastName());
user.setPassword(userInfo.getPassword());
OrganisationEntity orga = new OrganisationEntity();
orga.setName(userInfo.getOrganisation());
// We set the organisation's ID
if (orgaId != -1)
orga.setId(orgaId);
user.setOrganisation(orga);
return user;
}
And here is my UserDao:
#Transactional
public interface UserDao extends CrudRepository<UserEntity, Long> {
UserEntity save(UserEntity user);
}
And finally the relation in my UserEntity:
#ManyToOne(targetEntity = OrganisationEntity.class, cascade = CascadeType.ALL)
#JoinColumn(name = "orga_id")
private OrganisationEntity organisation;
Creating a user with a new Organisation work but when I input an existing one, I get the following:
detached entity passed to persist
From my understanding it is a bidirectional consistency problem, but the answers did not help me so far.
Finally here are my Entity classes:
#Entity
#Table(name = "\"user\"")
public class UserEntity {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
private String email;
#NotNull
private String firstName;
#NotNull
private String lastName;
#NotNull
private String password;
#ManyToOne(targetEntity = OrganisationEntity.class, cascade = CascadeType.ALL)
#JoinColumn(name = "orga_id")
private OrganisationEntity organisation;
// Getters & Setters
}
and
#Entity
#Table(name = "organisation")
public class OrganisationEntity {
#Id
#Column(name = "orga_id", unique = true)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Column(unique = true)
private String name;
// Getters & Setters
}
I have solved my problem,
As you can see in the mapper above, I am creating a new instance of OrganisatonEntity no matter what, even if it already exists !
So a small change in my code solved it:
public UserEntity map(UserInfo userInfo, OrganisationEntity organisationEntity);
instead of
public UserEntity map(UserInfo userInfo, Long orgaId);
When the organisation already exists, I then assign it to my UserEntity like such:
user.setOrganisation(organisationEntity);
instead of instantiating a new object.
Problem solved !

Why OneToOne relation doesn't works as expected?

I have 2 entitie. User:
#Table(name = "USERS")
#Entity
public class User {
#Column(name = "user_id")
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String email;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
private Authentication authentication;
}
and Authentication:
#Table(name = "AUTHENTICATIONS")
#Entity
public class Authentication {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "login_id")
private String loginId;//openId login
#JsonIgnore
private String password;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
}
And I have service which provides registration for new users:
#Override
#Transactional
public User createUser(UserRegistrationForm form) {
Authentication authentication = new Authentication();
authentication.setPassword(form.getPassword());
User user = new User();
user.setAuthentication(authentication);
user.setEmail(form.getEmail());
user.setName(form.getLogin());
authentication.setUser(user);
return userRepository.save(user);
}
And my problem is method userRepository.save() returns infinitely nested objects:
{"id":1,"name":"myName","email":"myemail#gmail.com","authentication":{"id":1,"loginId":null,"user":{"id":1,"name":"myName","email":"myemail#gmail.com","authentication":{"id":1,"loginId":null,"user":{"id":1,"name":"myName","email":"myemail#gmail.com","authentication":{"id":1,"loginId":null,"user":{"id":1,"name":"myName","email":"myemail#gmail.com","authentication":{"id":1,"loginId":null,"user":{"id":1,"name":"myName","email":"myemail#gmail.com","authentication":{"id":1,"loginId":null,"user":{"id":1,"name":"
What am I doing wrong? Help to understand how it should work.
it's json which returns nested objects ... not your repository !
you are using jackson ?
add #JsonManagedReference on one side and #JsonBackReference on the other
Your problem is:
user.setAuthentication(authentication);
...
authentication.setUser(user);
You have a nested reference between user and authentication

Categories