Can't delete parent without a child in Hibernate - java

I have two objects User and Workorder. One user can have multiple work orders. The problem is when I delete user it also deletes assigned work orders to that user. I have tried to set my work orders foreign keys to NULL before deleting the user but it still deletes all the associated work orders with that user. I'd like to delete user without deleting the work order assigned to user. What am I missing or doing wrong?
Here's is my User class:
#OneToMany(fetch = FetchType.LAZY, mappedBy="user", orphanRemoval=true)
private Set<WorkOrder> workOrder;
WorkOrder class:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id", nullable = true)
private User user;
UserDAOImpl class:
#Override
public void deleteUser(int theId) {
// get the current hibernate session
Session currentSession = sessionFactory.getCurrentSession();
// delete object with primary key
User user = currentSession.get(User.class, theId);
Set workorders = user.getWorkOrder();
Iterator<WorkOrder> work = workorders.iterator();
while (work.hasNext()){
WorkOrder workorder = work.next();
workorder.setUser(null);
}
currentSession.remove(user);
}

Remove that 'orphanRemoval=true' and check there's no 'cascade' on Workorder.user (if the relation is bidirectional)

Related

Bidirectional Entities - Overwriting an object in database and hibernate is doing insert instead of update

I have an issue when trying to update the contents of a cart with new values added one by one. I am using Spring boot with Hibernate, JPA Repositories, MySQL Database and a front-end built with vanilla JS.I will describe my issue in the following lines.
I am having an user entity that looks like this:
#Entity
#Table(name = "users")
#Getter
#Setter
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
#Column(name = "cartprod_id")
#OneToMany(cascade = {CascadeType.ALL})
private List<CartItem> cartProduct;
This entity has a List<CartItem> field that looks like this:
#Entity
#Getter
#Setter
public class CartItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "product_id")
private int productId;
#JsonIgnore
#ManyToOne
private User user;
}
And the relationship between them in the Database looks like in this image:
The idea of the above code is to have a way to keep the state of the products Cart of an user.
The flow is as follows :
I am doing a request from the front-end each time a user adds a product in the cart, when this happens, I am saving the contents of the cart(that consist of only the products ID's) in the database using the CartItem entity.
The problem
With this approach, instead of saving only the last product added to the cart(or replace the cart completely with the new values similar to an update), it is inserting the same object over and over again but with all the new appended values instead of overwriting the old object(table) in the database. An example of this would be in this first image . As you can see I have added a product to the cart with id 327 first.
Next I am adding a product with id 328 but it also adds 327 a second time, this goes on and on, the more products I add. This last code snippet contains my controller code .
#PostMapping("/savecart")
public ResponseEntity<String> saveCartToDb(#RequestBody List<CartItem> cartItemList, Principal principal){
System.out.println(cartItemList);
User logedInUser = userService.findUserByUsername(principal.getName()).get();
List<CartItem> cartItem = logedInUser.getCartProduct();
if(cartItem.isEmpty()){
logedInUser.setCartProduct(cartItemList);
userService.saveNewUser(logedInUser);
}else {
cartItem = cartItemList;
logedInUser.setCartProduct(cartItem);
userService.saveNewUser(logedInUser);
}
// userService.saveNewUser(logedInUser);
return ResponseEntity.ok().body("ASD");
}
How can I overwrite the contents of the List<CartItems> for the user so that I will not append new and old values again and again ? Would a SET help as it won't allow duplicates ?
I have also tried this How do I update an entity using spring-data-jpa? but I a not sure that I need to create a #Query for this issue.
I managed to make it work. The solution is the following.
This is a bidirectional one-to-many / many-to-one issue. In order to be able to remove child elements from the parent, we need to decouple(detach) them from the parent. Since parent and child are bound together by a foreign key the detachment has to be done on both ends. If one has a reference to the other this will not work so BOTH REFERENCES have to be removed. 2 methods are needed and both need to be called when doing decoupling. This is what I used.
private Set<CartProduct> cartProduct;
This one to be added in the parent class (User).
public void removeChild(CartProduct child) {
this.cartProduct.remove(child);
}
This one to be added in the Child class
public void removeParent() {
this.user.removeChild(this);
this.user = null;
}
Methods also have to be called like this
for(CartProduct cartItem : cartItemList){
cartItem.removeParent();
logedInUser.removeChild(cartItem);
}
L.E
It may be that with the above implementation you will get a
java.util.ConcurrentModificationException: null
it happen to me in one of the cases too. In order to fix this I used an Iterator like below.
for (Iterator<CartProduct> iterator = cartItemList.iterator(); iterator.hasNext();) {
CartProduct cartItem = iterator.next();
if (cartItem != null) {
iterator.remove();
}
}

How to delete parent without deleting children in one to many relationship

I am trying to delete a Patient entity without deleting the associated OutboundMessage entities as i want to keep them in the database for reporting/historical purposes. This is the relevant code :
Patient entity
#OneToMany (mappedBy="patient", fetch = FetchType.EAGER,orphanRemoval = false)
public Set<OutboundMessage> getOutboundMessages()
{
return outboundMessages;
}
OutboundMessage entity
#ManyToOne (fetch=FetchType.EAGER)
#JoinColumn(name = "id_patient")
public Patient getPatient()
{
return patient;
}
When i set a cascade type on the Patient side the records are deleted which is not what i want. When trying as shown in the code above (without the cascade type) I get the following exception :
The DELETE statement conflicted with the REFERENCE constraint "FKqjpga9w6wp3qk26ox9pg252d9". The conflict occurred in database "MDHIS", table "dbo.tblOutboundMessage", column 'id_patient'.
What settings does the owning entity need to allow deletion without cascading to children entities and without cleaning orphaned records?
Thanks!
You need to allow null values in your foreign key column (#JoinColumn) as follows:
#ManyToOne (fetch=FetchType.EAGER)
#JoinColumn(name = "id_patient", nullable = true)
public Patient getPatient() {
return patient;
}
Then in your DAO you need to set null values for all the OutboundMessage related to the Patient you're about to delete and only then delete it, as follows:
public void deletePatient(int patientId) {
Session currentSession = sessionFactory.getCurrentSession();
// get patient with primary key
Patient patient = currentSession.get(Patient.class, patientId);
Set<OutboundMessage> messages = patient.getOutboundMessages();
//set patient id null
for(OutboundMessage message : messages) {
message.setPatient(null);
}
//delete the patient
currentSession.remove(patient);
}

Can Hibernate orphanRemoval work with unique constraints?

I have 2 entities: Role and Privilege. One role has many privileges. The entities look like this:
#Entity
public class Role {
private Integer id;
private String code;
#OneToMany(mappedBy = "role", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Privilege> privileges;
}
#Entity
public class Privilege {
private Integer id;
private String code;
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
}
The privilege table has the unique constraint U__ROLE_ID__CODE__PRIVILEGE on the role_id and code columns.
I have a REST endpoint which updates roles. The update includes also changing privileges assigned to a role:
private static void setPrivileges(Set<Privilege> existing, Set<Privilege> privileges) {
existing.clear();
existing.addAll(privileges);
}
By some reason, when I do the update of a role, Hibernate first inserts new privileges into the privilege table, and only then it removes the orphaned privileges. As a result, the update fails with the U__ROLE_ID__CODE__PRIVILEGE constraint violation in case when the new list of privileges contains at least one privilege from the old list.
Without the constraint everything works fine. However, removing the constraint does not look like a perfect solution.
Is it possible to change the order the Hibernate handles the role-privilege relationship update so that first the orphaned privileges are removed and only then the new ones are inserted?
The reproduction project is available here.
You may want to revisit the CascadeType. If you are looking to propagate deletes upon removal of the Role you will want to use CasadeType.REMOVE
Because you have it set to CascadeType.ALL you'll notice that each update/persistence attempt on Role will propagate to the Privilege. Thus conflicting with the the unique constraint on these tables.
// You can flush:
private static void setPrivileges(Set<Privilege> existing, Set<Privilege> privileges) {
existing.clear();
repository.flush(); // queues delete statements before subsequent operations
existing.addAll(privileges);
}

How to perform a LEFT JOIN on 2 related tables

So I have 2 tables. A users table and a finished_exams table. The finished_exams table has a user_id as foreign key and is related as OneToOne with the users table.
I made the relation in Users model like this:
#OneToOne(fetch = FetchType.EAGER, mappedBy = "user")
private FinishedExam finishedExam;
And in the FinishedExams model like this:
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
When I call all finished exams like this in the controller:
#GetMapping("/all")
public Iterable<FinishedExam> getAllFinishedExams()
{
return finishedExamRepository.findAll();
}
I get all Finishedexams linked with the users which is good.
My question is, is it possible to get ALL users with their finishedExam if they have it and without if they don't (So basically a LEFT JOIN)
Is this possible in Hibernate?
EDIT:
I just tried this but it only returns all users without the exams.
#GetMapping("/allUsersWithExams")
#Fetch(FetchMode.SELECT)
public Iterable<User> getAllUsersWithTheirExams()
{
return userRepository.findAll();
}
First of all sure is possible, you just have to write the query yourself:
"Select u from Users u LEFT JOIN u.finishedExam"
simple enough.
But what's stopping you to just select all user and get all their finished exam?
public Iterable<Users> getAllUsers()
{
return UsersRepository.findAll();
}
It will give you a list of all Users independently from having or not any finishedExam

Hibernate Constraint Violation Exception

I'm using Spring with Hibernate and I'm getting this exception.
Here is what I'm trying to get:
I have User and UserSettings classes, they're bounded with OneToMany and ManyToOne annotations like this:
public class UserImpl implements User, Serializable {
...some fields
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
private Set<UserSettingsImpl> settings;
}
public class UserSettingsImpl implements UserSettings, Serializable {
...some fields
#ManyToOne(cascade = CascadeType.ALL)
private UserImpl user;
}
Now, I want to add settings for user, that's how I'm doing it:
public long addSettings(final UserSettings settings) {
userSettingsDAO.persist((UserSettingsImpl) settings);
UserSettingsImpl settingsImpl = (UserSettingsImpl) settings;
User user = settings.getUser();
Set<UserSettingsImpl> settingsSet = (Set<UserSettingsImpl>) user.getSettingsSet();
settingsSet.add(settingsImpl);
userManager.updateUser(user); //it's just entityManager.merge(user);
return ((UserSettingsImpl) settings).getId();
}
And here comes the problem:
User from UserSettings holds set with old settings and the new one (the settings, that I've just created and want to add), but old settings holds sets with user, that don't have new settings in it. That's why I'm getting the exception, I suppose, but I don't know how to fix it. (I think I'm working with Hibernate in the wrong way)
You will get old Usersettings in the user object with the current setup.
It is because, the way the merge method works.
The way it works is it will create a copy of the object and then persist and refresh the object and return it back. So, in essense, the user object which you do pass in to the userManager will not be updated, but you have to get the returning value for the updated user
I would try something like below, the userImpl is referenced by the relationship from UserSettingsImpl, if you persist UserSettingsImpl, the operation is applied to the UserImpl because of the cascade, and if UserImpl is new it will be persisted otherwise nothing happens.
public long saveSettings(final UserSettings settings) {
userSettingsDAO.persist((UserSettingsImpl) settings);
return ((UserSettingsImpl) settings).getId();
}
and manage the bidirectional relationship in the class UserImpl:
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
private Set<UserSettingsImpl> settings = new HashSet<UserSettingsImpl>();
public void addUserSettings(UserSettings userSettings) {
this.settings.add(userSettings);
userSettings.setUserImpl(this);
}

Categories

Resources