I have two simple entities
//Address @Entity
@OneToOne(fetch = FetchType.EAGER, orphanRemoval = true)
@JoinColumn(name = "user_id", nullable = false)
private User user;
and
//User @Entity
@OneToOne(mappedBy = "user", orphanRemoval = true)
Address address;
But I am unable to update the user with a new address.
First approach:
Address address = new Address();
User user = userRepository.findByUsername("someemail");
address.setUser(user);
addressRepository.save(address);
the code runs fine but an insert statement is done and now I have two address records in the database which causes future exceptions.
Second approach
Address address = new Address();
User user = userRepository.findByUsername("someemail");
address.setUser(user);
user.setAddress(address); //added this line
addressRepository.save(address);
Now it throws exception
org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
Third approach
Address address = new Address();
User user = userRepository.findByUsername("someemail");
address.setUser(user);
user.setAddress(address);
userRepository.save(user); //added this
addressRepository.save(address);
New exception:
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing
Temp solution
This solution works, but it's the most ugly code I've written and also I have no idea why it works. If someone has a better answer I will mark as solution.
First add cascade to the entity
@OneToOne(mappedBy = "user", orphanRemoval = true, cascade = CascadeType.ALL)
Address address;
And then update using this:
User user = userRepository.findByUsername("someemail");
if (user.getAddress() != null) {
addressRepository.delete(user.getAddress());
user.setAddress(address);
address.setUser(user);
userRepository.save(user);
} else {
address.setUser(user);
addressRepository.save(address);
}
CodePudding user response:
//Address @Entity
@OneToOne(fetch = FetchType.EAGER, orphanRemoval = true) @JoinColumn(name = "user_id", nullable = false) private User user;
and
//User @Entity @OneToOne(mappedBy = "user", orphanRemoval = true) Address address;
According to the above code, you have declared that the entity Address
will be the one that has the mapping for the relevant User
. So your Address
table in database has an extra field user_id
that maps this address with the relevant user.
Check your 1st approach
Address address = new Address(); <--- new address entity created
User user = userRepository.findByUsername("someemail"); <--- user retrieved from DB
address.setUser(user); <----New address record in address table is set up to have the relevant user
addressRepository.save(address);
You should do it the other way around
Address address = new Address();
User user = userRepository.findByUsername("someemail");
user.setAddress(address);
userRepository.save(user);
For this to work you have to modify in your User
entity the
@OneToOne(mappedBy = "user", orphanRemoval = true, cascade = CascadeType.ALL)
Address address;
Now when you execute the above code, the previous address entity will be updated to have a null
reference for a user
and this way the orphanRemoval
will do it's job and remove it from database while the new address will be persisted and linked with the relative user.
CodePudding user response:
You and Thomas Edison have a lot in common ...
User user = userRepository.findById(1L).get();
user.getAddress().setSomething("YYY");
addressRepository.save(user.getAddress());
Really though, you should not make problems for yourself. If you do, print the SQL to see what's going on.
User user = userRepository.findById(1L).get();
addressRepository.delete(user.getAddress());
user = userRepository.save(user);
addressRepository.save(Address.builder().something("YYY").user(user).build());
I would do this differently.
// Address
@OneToOne(mappedBy = "address")
private User user;
// User
@OneToOne
private Address address;
and
User user = userRepository.findById(2L).get();
user.setAddress(addressRepository.save(Address.builder().something("YYY").user(user).build()));
userRepository.save(user);