We are using Spring Data repositories with Hibernate 5.x
We have a entity graph with a deep hierarchy.
The mapping looks like this:
#Entity
public class FooBar {
#OneToMany(mappedBy = "fooBar", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Foo> chassis = new HashSet<>(0);
...
}
#Entity
public class Foo {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "foobar_id")
private FooBar fooBar;
#OneToMany(mappedBy = "foo", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Bar> chassis = new HashSet<>(0);
...
}
#Entity
public class Bar {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "foo_id")
private FooBar foo;
...
}
As you can see the FooBar entity has a set of Foo entities. Each Foo entity contains more Bar entities and so on.
We use the Fetchgraph feature to load the FooBar entity with the relations we need during runtime to avoid n+1 query issue when fetching lazy associations.
After the service call to load the entity graph the transaction has ended and the entity is detached.
When calling save on the FooBar entity at a later time, this causes multiple select statements. Each fetching one of the child entities.
I know that this comes from the entitymanager merge() call which fetches the object graph from the db before copying state changes from the detached objects.
I have two questions:
Why is hibernate not able to join these statements to one big select like what happens when using the fetchgraph?
When i remove all cascade options from the relations it still causes multiple selects but only attributes of the top, FooBar entity, will be updated. Why is hibernate still fetching all loaded child entites during merge even with no cascade merge?
Thanks
You can use session.update instead of merge to overcome this issue.
Session session = entityManager.unwrap(Session.class);
for (Post post: posts) {
session.update(post);
}
I have similar issue with your case, and the reason is the setting of cascading CascadeType.ALL on the #OneToMany association. Updating and merging the parent entity cause a lot of select on the child association.
#Entity
public class FooBar {
#OneToMany(mappedBy = "fooBar", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Foo> chassis = new HashSet<>(0);
...
}
I fix my case by reducing the scope of cascading, only PERSIST and REMOVE is sufficient
#OneToMany(mappedBy = "fooBar", cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true)
private Set<Foo> chassis = new HashSet<>(0);
Related
In the context of a Spring Boot project using Spring Data JPA, I have defined the following entities:
Ent1 contains a list of Ent2 elements
Ent2 contains a list of Ent3 elements
When fetching a top-level Ent1 object through a repository, I'm seeing that every Ent2 which has more than one child appears multiple times in the Ent1.ent2 list. For example, an Ent2 with two childs will appear twice.
So instead of getting this:
I'm getting this:
Notes:
There are no duplicates in the database
If I delete ent3b in the database, the duplicated ent2 disappears
Here's a simplified version of the code:
```java
#Entity
public class Ent1 {
#OneToMany(mappedBy="parent", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Ent2> ent2 = new ArrayList<Ent2>();
}
#Entity
public class Ent2 {
#ManyToOne
#JoinColumn(name = "PARENT_ID", nullable = false)
protected Ent1 parent;
#OneToMany(mappedBy="parent", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Ent3> ent3 = new ArrayList<Ent3>();
}
#Entity
public class Ent3 {
#ManyToOne
#JoinColumn(name = "PARENT_ID", nullable = false)
protected Ent2 parent;
}
```
Solution was to convert Lists into Sets. Lists in JPA require additional data (i.e. an ordering column) to extract a total ordering of elements from the relationship. It can be done but typically the average user only needs Set and it's a reflection of the relationship that most people model.
OP also commented that the previous provider didn't have this requirement so if you were previously using EclipseLink and switching ORM providers this may be a problem for you too.
I have entities "ZakladProdukcyjny" and "MiejsceProwadzeniaDzialnosci".
There is an unidirectional relation #OneToMany with a join table.
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "ZAKLAD_PRODUKCYJNY_MIEJSCE_PROWADZENIA_DZIALALNOSCI",
joinColumns = {
#JoinColumn(name = "zakladProdukcyjny_ID")},
inverseJoinColumns = {
#JoinColumn(name = "miejsceProwadzeniaDzialalnosci_ID")})
private List<MiejsceProwadzeniaDzialalnosci> miejscaProwadzeniaDzialalnosci = new ArrayList<>();
I am using Spring JPARepositories
public interface ZakladProdukcyjnyRepository extends JpaRepository<ZakladProdukcyjny, Long>,
Everytime i am saving the parent entity with zakladProdukcyjnyRepository.save(zakladProdukcyjny), children entities are being persised into DB so everytime save is executed on the JPARepository i am having duplicated entries.
The child entity uses a lombok for generating equals and hashcode.
#EqualsAndHashCode(callSuper=false)
public class MiejsceProwadzeniaDzialalnosci extends BaseEntity {
I have no idea what may be wrong here.
This should have beed fixed long time ago:
https://hibernate.atlassian.net/browse/HHH-5855
https://hibernate.atlassian.net/browse/HHH-6776
Try changing the List to a Set or remove CascadeType.ALL and leave just CascadeType.MERGE.
I have solved the problem. The issue was an equals functionality. Somewhere in the code i had:
for (MiejsceProwadzeniaDzialalnosci mpd : uaktualnioneMiejscaProwadzeniaDzialalnosciZBDO) {
if (!(zaklad.getMiejscaProwadzeniaDzialalnosci().contains(mpd))) {
zaklad.getMiejscaProwadzeniaDzialalnosci().add(mpd);
}
}
after ovveriding the equals method there is no duplicates.
Being new to Hibernate, I'm encountering a "detached entity passed to persist" exception when trying to remove an item from an entity's List.
I tried adding orphanRemoval=true as well as changing the cascade type to MERGE and/or DELETE in various combinations, but this hasn't helped. My entities are:
#Entity
public class User
{
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) // adding "orphanRemoval = true" doesn't help
#JsonManagedReference
private List<Application> applications;
}
#Entity
public class Application
{
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) // adding "orphanRemoval = true" doesn't help
#JsonBackReference
private User user;
}
#Entity
public class ServerApplication extends Application
{
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) // adding "orphanRemoval = true" doesn't help
#JsonManagedReference
private List<Instance> instances;
}
#Entity
public class Instance
{
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) // adding "orphanRemoval = true" doesn't help
#JsonBackReference
private ServerApplication server;
}
Inside a transaction, I try do effectively do the following:
// For some User's ServerApplication object:
serverAplication.getInstances().remove(0); // Attempt to remove an item from DB.
entityManager.persist(user);
This results in:
org.hibernate.PersistentObjectException: detached entity passed to persist: my.project.User
How can I correctly annotate the properties to be able to perform persistent removal of items, namely call remove on List<Instance>?
Note: adding new Instance objects and updating the list works normally; it's just removal that is causing problems.
I think you should persist serverAplication after instance removal, not the user:
// For some User's ServerApplication object:
serverAplication.getInstances().remove(0); // Attempt to remove an item from DB.
entityManager.persist(serverAplication);
What is difference between codes?
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(mappedBy = "siteesTypeSite", cascade = CascadeType.ALL,fetch = FetchType.LAZY)
public Set<Sites> getSitees() {
return sitees;
}
and
#OneToMany(mappedBy = "siteesTypeSite", cascade = CascadeType.ALL,fetch = FetchType.EAGER)
public Set<Sites> getSitees() {
return sitees;
}
As for me both of them makes similar result, but second case is more cleaner. If i mistook correct me please.
The main difference between the annotations is that #OneToMany is a pure JPA annotation. Whereas #LazyCollection is Hibernate specific.
So if you want your code to be portable across various JPA providers you should use JPA annotations.
Update
To explain between those two annotation, consider the OneToMany relationship between Department -> Employee
Case 1:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "DEPARTMENT_ID")
private List<Employee> employees = new ArrayList<>();
if you fetch a Department object from the db using :
entityManager.find(Department.class, 1L);
following query gets fired to fetch the data
SELECT department0_.DEPARTMENT_ID AS DEPARTMENT_ID1_0_0_,
department0_.DEPARTMENT_NAME AS DEPARTMENT_NAME2_0_0_,
department0_.LOCATION AS LOCATION3_0_0_,
employees1_.DEPARTMENT_ID AS DEPARTMENT_ID3_1_1_,
employees1_.EMPLOYEE_ID AS EMPLOYEE_ID1_1_1_,
employees1_.EMPLOYEE_ID AS EMPLOYEE_ID1_1_2_,
employees1_.DEPARTMENT_ID AS DEPARTMENT_ID3_1_2_,
employees1_.EMPLOYEE_NAME AS EMPLOYEE_NAME2_1_2_
FROM DEPARTMENT department0_
LEFT OUTER JOIN EMPLOYEE employees1_
ON department0_.DEPARTMENT_ID =employees1_.DEPARTMENT_ID
WHERE department0_.DEPARTMENT_ID=?
so it means it will fetch all the data in a single query at once.
Case 2:
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY, orphanRemoval = true)
#JoinColumn(name = "DEPARTMENT_ID")
private List<Employee> employees = new ArrayList<>();
similary if you fetch a Department object from the db using :
entityManager.find(Department.class, 1L);
following queries gets fired to fetch the data :
SELECT department0_.DEPARTMENT_ID AS DEPARTMENT_ID1_0_0_,
department0_.DEPARTMENT_NAME AS DEPARTMENT_NAME2_0_0_,
department0_.LOCATION AS LOCATION3_0_0_
FROM DEPARTMENT department0_
WHERE department0_.DEPARTMENT_ID=?
SELECT employees0_.DEPARTMENT_ID AS DEPARTMENT_ID3_1_0_,
employees0_.EMPLOYEE_ID AS EMPLOYEE_ID1_1_0_,
employees0_.EMPLOYEE_ID AS EMPLOYEE_ID1_1_1_,
employees0_.DEPARTMENT_ID AS DEPARTMENT_ID3_1_1_,
employees0_.EMPLOYEE_NAME AS EMPLOYEE_NAME2_1_1_
FROM EMPLOYEE employees0_
WHERE employees0_.DEPARTMENT_ID=?
So to summarize, in first case since the FetchType is EAGER Employees are fetched eagerly along with Department in a single JOIN query.
And,
In second case, Employees are fetched with Department but since the FetchType is LAZY a seperate query will be fired to fetch Employees. And if you remove #LazyCollection(LazyCollectionOption.FALSE) Employees wont be fetched at all until you access Employees on Department instance.
I'm having an issue where a Validation instance is added to a Collection on a Step instance.
Declaration is as follows:
Step class:
#Entity
#Table
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Step extends AbstractEntity implements ValidatableStep {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "step_id", nullable = false)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Validation> validations = new HashSet<>();
#Override
public void addValidation(Validation validation) {
// do some stuff
...
// add validation instance to collection
getValidations().add(validation);
}
}
Validation class:
#Entity
#Table
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Validation extends AbstractEntity {
//some properties
}
Both classes are Cacheable with a READ_WRITE strategy applied. The unidirectional Collection of Validations are also cached with same strategy.
One would expect when a read-write transaction that invokes addValidation(new Validation('userName')); commits, the new Validation would be visible in a subsequent read-only transaction. The weird thing is that sometimes it does work and sometimes it doesn't work...
The first transaction always succeeds; we see the new validation being persisted in database and Step's version property (for optimistic locking puposes) getting incremented. But sometimes, the 2nd read transaction contains a Step instance with an empty Validation Collection...
Our Hibernate caching config is as follows:
hibernate.cache.use_second_level_cache = true
hibernate.cache.use_query_cache = true
hibernate.cache.region.factory_class = org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
hibernate.cache.provider_configuration_file_resource_path = classpath:ehcache.xml
net.sf.ehcache.hibernate.cache_lock_timeout = 10000
Any idea what's causing this weird (and random) behavior?
The Hibernate Collection Cache always invalidates existing entries and both the Entity and the Collection caches are sharing the same AbstractReadWriteEhcacheAccessStrategy, so a soft-lock is acquired when updating data.
Because you are using a unidirectional one-to-many association, you will end up with a Validation table and a Step_validation link table too. Whenever you add/remove a Validation you have to hit two tables and that's less efficient.
I suggest you adding the #ManyToOne side in the Validation entity and turn the #OneToMany side into a mapped-by collection:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "step")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Validation> validations = new HashSet<>();