Spring, JPA - bidirectional #OneToMany: replace child list with another - spring

I read a lot of topics on this and made hundreds of experiments, but no success so far. I have following classes:
class Parent {
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL/*, orphanRemoval=true*/)
private List<Child> children = new ArrayList<>();
class Child {
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "parentId", nullable = false)
#JsonIgnore
Parent parent;
}
What I do is try to replace children list with the one provided in PATCH request:
Hibernate.initialize(fromDb.getChildren());
entityManager.detach(fromDb); // detach from JPA. I need this
List<Child> newChildren = fromClient.getChildren();
fromDb.getChildren().clear(); // get rid of all old elements
for (Child child : newChildren) { // add the new ones
child.setParent(fromDb);
fromDb.getChildren().add(child);
}
ParentRepository.save(merged);
Behavior is following:
when I run it as it is, it adds the new ones, but leaves the old
ones! So I have growing number of unwanted children (sorry..)
when I
uncomment orphanRemoval=true part... the parent is removed!
Can you explain why it behaves this way and what can I do here?

Solution found.
I should have orphanRemoval=true:
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval=true)
Now it deleted the parent because of the other cascade in #ManyToOne. I changed it to following:
#ManyToOne(cascade = {CascadeType.MERGE})
Now it works.

Related

Spring JPA delete entity not working when iterating

I am trying to delete an entity using its remove method of its repository from another service class, but it is not getting deleted. Below code works when I hard code the Id:
long id = 1234;
Optional<Employee> employeeOptional = employeeRepository.findById(id);
Employee employee = employeeOptional.get();
employeeRepository.delete(employee);
Above code is working fine, but if I try with below code, deletion is not happening.
for (Employee employee : department.getEmployees()) {
if (employee.getRole().equals("Manager")) {
employeeRepository.delete(employee);
}
}
I am trying the code from DepartmentServiceImpl class, but above is not working, but same when id is hardcoded it works.
Inside Department I have relationship like below,
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "deal")
private Set<Employee> employees= new HashSet<>();
And inside Employee I have like below,
#ManyToOne
#JoinColumn(name = "department_id", referencedColumnName = "department_id")
private Department department;
How can I fix this issue?
You are attempting to delete Employees, but your entities still have references to each other.
A better way to delete an employee is to use orphan removal and remove the employee from the collection which will trigger a delete.
Also mappedBy = "deal" should be the name of the attribute on the owning side of the relationship so this should be mappedBy = "department"
#OneToMany(
cascade = CascadeType.ALL,
mappedBy = "department",
orphanRemoval = true
)
private Set<Employee> employees= new HashSet<>();
add a method to Department to remove the Employee and keep the bidirectional relationship in sync by also removing Department from Employee
public void removeEmployee(Employee employee) {
employees.removeEmployee(employee);
employee.setDepartment(null);
}
you can then remove the 'Managers' from your Employees collection which should trigger the delete statements
List<Employee> managers = department.getEmployees.stream()
.filter(e -> e.getRole().equals("Manager"))
.collect(Collectors.toList());
for (Employee manager : managers) {
department.removeEmployee(manager);
}
Not tested but should work fine:
Try tweaking your code a little like this:
Set<Employee>employees= new HashSet<>();
for (Employee employee : department.getEmployees()) {
if (employee.getRole().equals("Manager")) {
employees.add(employee);
}
}
department.setEmployees(employees);//I assume you have a setter
departmentRepository.save(department); //I hope you understand what departmentRepository means
Here you are reassigning the valid employees list.
You could follow another method, instead of deleting each entity separately, you could call a bulk-save using saveAll(...) method on the valid list.

ManyToMany how to remove child from parent list

How to remove object from list without removing that parent object in many to many relationship?
I have ticketEntity with set of offenceEntity:
public class TicketEntity {
#Id
private String id;
#ManyToMany
#JoinTable(
name = "offences_in_tickets",
joinColumns = #JoinColumn(name = "ticket_id"),
inverseJoinColumns = #JoinColumn(name = "offence_id"))
private Set<OffenceEntity> offences;
}
and OffenceEntity with set of TicketEntity:
public class OffenceEntity {
#Id
#Column(name="id")
private String id;
private String name;
#ManyToMany(mappedBy="offences", cascade = CascadeType.ALL)
private Set<TicketEntity> tickets;
}
Should I change cascade type? Now when I remove one of OffenceEntity from database I am removing TicketEntity too, but I would like to delete only Offence.
I don't know what "parent" or "child" is, or what you mean by "removing from list", as there is no list in your examples. If you want an answer to your questions, please invest more time in writing up a proper question and try to re-read the question with the mindset of someone who doesn't work on this problem. Maybe ask a colleague if the question is immediately understandable before posting this.
Having said that, I can only assume that "parent" refers to TicketEntity and "child" refers to OffenceEntity.
So if you want to remove the many-to-many relationship between two such objects, you simply have to load the TicketEntity and remove the respective OffenceEntity from the offences set e.g. like this:
entityManager.find(TicketEntity.class, ticketId)
.getOffences()
.remove(
entityManager.find(OffenceEntity.class, offenceId)
);

Spring/JPA twice uniderectional not working, because of #JoinColumn(nullable = false)

I have problem with unidirectional mapping and need help.
I have 2 Entities with the same unidirectional mapping.
The first one:
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "massnahme_id", nullable = false)
private Set<VerortungDAO> verortungen = new LinkedHashSet<>();
The second one:
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "massnahmen_verbund_id", nullable = false)
private Set<VerortungDAO> verortungen = new LinkedHashSet<>();
If I try to save one Entity, hibernate throws an Exception because of the second Entity definition (not null).
org.hibernate.PropertyValueException: not-null property references a
null or transient value
If I change the JoinColumn to nullable = true, then the unidirectional mapping not working and the list is not saved in DB.
What can I do to make it work?
Make the associations bidirectional and map the to-one association in VerortungDAO, or if you don't want that, at least map the FK-columns. If you map it bidirectional, use #OneToMany(mappedBy = "..."). Either way, you will have to initialize the two to-one associations or FK-columns on the VerortungDAO objects.
PS: An entity isn't a DAO (data access object), so the naming xxDAO is quite confusing for an entity.

"could not initialize proxy - no Session" For Multiple ManyToMany relationships in the parent

I have a Parent User Class that has multiple ManyToMany Relationships.
#Table(name = "user")
public class User {
..
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.DETACH})
#JoinTable(
name = "user_address",
joinColumns = { #JoinColumn(name = "user_id")},
inverseJoinColumns = { #JoinColumn(name = "address_id")}
)
#JsonIgnore
private final List<Address> addresses = new ArrayList<Address>();
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.DETACH})
#JoinTable(
name = "reports",
joinColumns = { #JoinColumn(name = "user_id")},
inverseJoinColumns = { #JoinColumn(name = "reports_id")}
)
#JsonIgnore
private final List<Reports> reports = new ArrayList<Reports>();
}
When I access the FIRST ManyToMany property, everything works fine. However, immediately after
accessing the first, when I try to access the SECOND ManyToMany Property I get the "could not initialize proxy - no Session" exception:
#Component
public class Combiner {
public void combineData() {
...
List<Address> addresses = user.getAddress(); // This works
List<Reports> reports = user.getReports(); // Get the error here
..
}
}
The Address and Reports classes have the inverse relationship as many ManyToMany back to the User Entity Above.
public class Address {
#ManyToMany(mappedBy = "addresses", fetch = FetchType.LAZY)
private final List<User> users = new ArrayList<User>();
}
public class Reports {
#ManyToMany(mappedBy = "reports", fetch = FetchType.LAZY)
private final List<User> users = new ArrayList<User>();
}
I tried searching SO for the same error where there are MULTIPLE relationships like mine and the first passes but second fails, but could'nt find a post (or google couldn't understand the search terms, if anyone knows a pre-existing one - please let me know).
Could someone assess what else Im missing?
I've tried these so far to no avail:
Added #Transactional to the parent Service class that calls Combiner above
Made the second failing relationship EAGER. (as i understand it you cant make BOTH EAGER since i get a multiple bags error probably because of Cartesian join)
AM Using SpringBoot (2.2.4) with Hibernate Core {5.4.10.Final}
Approach one:
Make #ManyToMany uni-directional. The exception clearly says it can not initialize the collection of role you have in User class.
As you asked in the comment section Why can't this use case be Bi Directional - You can make this bi-directional as well.
Approach two: make collection of role EAGER or use Hibernate.initialize() to initialize the collection.
Bonus: you can make both collection EAGER by using Set not List.

Hibernate 2 records being inserted for Single JAVA Object

I am using Hibernate Implementation of JPA with Spring.
Class Country{
#OneToMany(mappedBy="Country", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
List<State> stateList;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "Current_State_ID")
State currnetState;
}
Class State{
#ManyToOne
#JoinColumn(name="Country_ID")
private Country country;
}
State stateObj = new State();
country.getStateList().add(stateObj);
country.setCurrnetState(stateObj);
countryRepository.saveAndFlush(country);
countryRepository is a JPA Repository Implemenntation.
This creates 2 entries for in State Table, which messes up my logic. Can someone please point me what I am doing worng.
I am not sure why but changing my code to following works for me.
List<State> stateList = new ArrayList<State>();
stateList.add(state);
country.setStateList(stateList);
Creating a New List instance and setting it to country.

Resources