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<>();
Related
I am implementing soft delete with Hibernate. Each entity that supports soft-deleting has an attribute for it.
#Column(name = "DELETED")
private boolean deleted;
I have created #FilterDef in package-info.java for package with domain objects.
#FilterDef(name = "deletedFilter",
parameters = #ParamDef(name = "includeDeleted", type = Boolean.class),
defaultCondition = ":includeDeleted = true OR DELETED = false"
)
applied it to all DeleteAware entities
#Filter(name = "deletedFilter")
public class CustomerGroup
and enabled in when using in queries
Session session = em.unwrap(Session.class);
session.enableFilter("deletedFilter")
.setParameter("includeDeleted", fp.isDeleted());
Filter is applied and works correctly for primary entity (for example when I query customers I can see that additional where condition is always applied as needed).
Problem is with filter of association. Let's say Customer entity has collection of CustomerGroup.
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinTable(name = "CUSTOMER_CUSTOMER_GROUP",
joinColumns = #JoinColumn(name = "CUSTOMER_ID"),
inverseJoinColumns = #JoinColumn(name = "CUSTOMER_GROUP_ID"))
private Set<CustomerGroup> groups;
However when I query for Customer, groups collection contains deleted entities. I have turned on sql logging and I can see that condition is not applied for lazy query. However if I change
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
to
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
it works.
Both entities are annotated with #Filter. I have also tried applying #Filter annotation to collection itself success without. For initial testing I have also ensured that filters are not disabled and includeDeleted parameter is always false.
#Where annotation on entities works like a charm, but cannot be disabled (99% percent of queries we want to filter out deleted objects but there is that pesky 1% where we need deleted ones).
We are using Hibernate 6.1.13 provided in WildFly 27 application server. Looks like filters are not applied when relation is lazy loaded. Am I missing something?
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.
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);
happy new year:)
I have a Spring MVC project using Hibernate and DataJPA.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "restaurant_id", nullable = false)
#NotNull
private Restaurant restaurant;
As you can see, here is two fields with eager fetch. I want to make both a lazy. I need to user #NamedEntityGraph annotation asI made here:
#NamedQueries({
#NamedQuery(name = Restaurant.GET_BY_ID, query = "SELECT r FROM Restaurant r WHERE r.id=?1"),
})
#Entity
#NamedEntityGraph(name = Restaurant.GRAPH_WITH_MENU_HISTORY, attributeNodes = {#NamedAttributeNode("menuHistory")})
#Table(name = "restaurants")
public class Restaurant extends NamedEntity {
#OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "restaurant")
#OrderBy(value = "date DESC")
private List<Dish> menuHistory;
public static final String GRAPH_WITH_MENU_HISTORY = "Restaurant.withMenuHistory";
I want to know, if I'll write
#NamedEntityGraph(name = "G_NAME", attributeNodes = {#NamedAttributeNode("user", "restaurant")})
and if I'll request one of them, will the second load anyway or it will load only by request to him? May be, I need to user two graphs?
According to JPA 2.1 Spec 3.7.4:
The persistence provider is permitted to fetch additional entity state
beyond that specified by a fetch graph or load graph. It is required,
however, that the persistence provider fetch all state specified by
the fetch or load graph.
So actually the #NamedEntityGraph just guarantees what fields should be eagerly loaded, but not what fields should not be loaded.
So, if you make #NamedEntityGraph with user, your persistence provider (Hibernate for example) can load only user field or both user and restaurant fields eagerly. This is dependent on implementation and not guaranteed.
See this hibernate's issue.
But as far as I know, the Hibernate loads only simple fields in addition to specified in #NamedEntityGraph, and not loads lazy associations.
So if you use hibernate, it should work.
But of course you need two separate #NamedEntityGraphs for user and restaurant fields.
Or you can use ad-hoc spring-data-jpa's feature:
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
With this code you don't need explicitly declare #NamedEntityGraph anymore. You can just specify fields inline.
I use ehcache and hibernate 3.6.7 Final. This a pseudo code sample that reveals problem with caching.
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class A{
long id;
#OneToMany(mappedBy = "aId", targetEntity = B.class, fetch = FetchType.LAZY)
#Fetch(value = FetchMode.JOIN)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
protected Set<B> fieldB;
}
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class B {
long id;
long bId;
}
1) First time when I load entity A from hibernate it does not read fieldB. And this is ok - cause FetchType.LAZY is set.
2) Second time when I load entity A I see sql queries retrieving entity A JOIN entity B.
3)If remove #Fetch(value = FetchMode.JOIN) point 2 will not be performed.
So the question is this bug or feature? And how can I avoid such latent things.
You have two conflicting fetches, you definitely do not want to specify the fetch on the column and the #Fetch annotation as it will provide unpredicatable behavior.