Is there way to fetch queries with Hibernate session.get()? - java

I am working with Hibernate 5 Criteria Builder Queries fetching with Criteria Queries. But when calling session.get() SQL creating multiple queries for related Hibernate entities when calling them. Is there way to fetch them with one query as Hibernate Criteria Query Fetching.
CriteriaQuery<AdvanceRecieved> advanceCriteria = builder.createQuery(AdvanceRecieved.class);
Root<AdvanceRecieved> advanceRoot = advanceCriteria.from(AdvanceRecieved.class);
advanceRoot.fetch(AdvanceRecieved_.department,JoinType.LEFT);
I fetched these entities with fetch(), But I haven't find an example for fetch below code example.
ItemsABS selectedItem = jpaSess.get(ItemsABS.class, dealer.id);
Set<Tax> itemtaxes = selectedItem.getTaxEligibility();

You are seeing multiple queries because you probably have a One-To-Many relation between ItemsABS and TAX entities. So when you request ItemsABS data, it by default fetches the attached references (i.e. TAX data), and hence multiple queries are fired for that.
If you just need ItemsABS data, then you probably would have to use LAZY LOADING while fetching data for ItemsABS.
This can be defined at entity level using #OneToMany(fetch = FetchType.LAZY)

Related

JPA Criteria query eager fetch associated entities using a SINGLE query with joins instead of multiple queries

We are moving from Hibernate native criteria to JPA criteria queries in scope of upgrading a hibernate from 4.3.11 to 5.2.12 and found out different behavior. Previously hibernate criteria use a single query with joins to eager fetch one-to-many associated entities, but JPA use separate queries to fetch the associated entities for each root entity.
I know I can explicitly set fetch mode like entityRoot.fetch("attributes", JoinType.INNER); but we need to do it in some AbstractDao implementation that should work for any eager one-to-many association so can't explicitly set this.
So can I somehow tell JPA criteria to eager fetch associated entities in a single query using joins by default instead of separate queries for each root entity?
The code example:
CriteriaBuilder builder = createCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = builder.createQuery(getEntityClass());
Root<T> entityRoot = criteriaQuery.from(getEntityClass());
criteriaQuery.select(entityRoot);
criteriaQuery.where(builder.equal(entityRoot.get("param1"), "value"));
return getEntityManager().createQuery(criteriaQuery).getResultList();
Short answer
You can't configure it in such a way, but you may implement the necessary behavior.
Long answer
As you may read in Hibernate 5.2 User Guide, there are several ways to apply a fetching strategy:
#Fetch annotation
JPQL/HQL query - fetch join
JPA Criteria query - FetchParent::fetch
JPA entity graph - attributeNodes
Hibernate profile - fetchOverrides
#Fetch annotation is a static way to apply fetching strategy, and the FetchMode.JOIN works exactly as you've described:
Inherently an EAGER style of fetching. The data to be fetched is
obtained through the use of an SQL outer join.
The problem is, even if you would mark your attributes collection with the #Fetch(FetchMode.JOIN) annotation, it would be overridden:
The reason why we are not using a JPQL query to fetch multiple
Department entities is because the FetchMode.JOIN strategy would be
overridden by the query fetching directive.
To fetch multiple relationships with a JPQL query, the JOIN FETCH
directive must be used instead.
Therefore, FetchMode.JOIN is useful for when entities are fetched
directly, via their identifier or natural-id.
JPA Criteria query without FetchParent::fetch would do the same.
Since you need a universal solution for an abstract DAO, the possible way is to process all eager one-to-many associations with reflection:
Arrays.stream(getEntityClass().getDeclaredFields())
.filter(field ->
field.isAnnotationPresent(OneToMany.class))
.filter(field ->
FetchType.EAGER == field.getAnnotation(OneToMany.class).fetch())
.forEach(field ->
entityRoot.fetch(field.getName(), JoinType.INNER));
Of course, calling reflection for every query would be inefficient. You may obtain all loaded #Entity classes from Metamodel, process them, and store results for further use:
Metamodel metamodel = getEntityManager().getMetamodel();
List<Class> entityClasses = metamodel.getEntities().stream()
.map(Type::getJavaType)
.collect(Collectors.toList());
Map<Class, List<String>> fetchingAssociations = entityClasses.stream()
.collect(Collectors.toMap(
Function.identity(),
aClass -> Arrays.stream(aClass.getDeclaredFields())
.filter(field ->
field.isAnnotationPresent(OneToMany.class))
.filter(field ->
FetchType.EAGER == field.getAnnotation(OneToMany.class).fetch())
.map(Field::getName)
.collect(Collectors.toList())
));

What is the best way to bulk delete rows in JPA while also cascading the delete to child records

I'm trying to do bulk delete in my entities, and the best solution will be to go with CriteriaDelete. But CriteriaDelete does not cascade (at least not for me).
So, it seems like the only solution which I have is to do select first and delete each element separately. Which does not seems wrong to me.
Is anyone have a better idea of how to do bulk delete? Is it actually a better way?
If it helps I'm using EclipseLink 2.5.2.
The options are:
use the cascade.Remove setting on the mapping, loading entities
and calling em.remove on each
Use bulk delete on the main entity and have the "ON DELETE
CASCADE" database option set so that the database will cascade the
delete for you. EclipseLink has a #CascadeOnDelete annotation that
lets it know the "ON DELETE CASCADE" is set on a relationship, or to
create it if using JPA for DDL generation: http://eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_cascadeondelete.htm
Use multiple bulk deletes to remove children that might be referenced before removing the main entity. For example: "Delete FROM Child c where c.parent = (select p from Parent P where [delete-conditions])" and "Delete FROM Parent p where [delete-conditions]" See section 10.2.4 of http://docs.oracle.com/middleware/1212/toplink/OTLCG/queries.htm#OTLCG94370 for details.
How does the JPA CriteriaDelete work
A JPA CriteriaDelete statement generates a JPQL bulk delete statement, that's parsed to an SQL bulk delete statement.
So, the following JPA CriteriaDelete statement:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaDelete<PostComment> delete = builder.createCriteriaDelete(PostComment.class);
Root<T> root = delete.from(PostComment.class);
int daysValidityThreshold = 3;
delete.where(
builder.and(
builder.equal(
root.get("status"),
PostStatus.SPAM
),
builder.lessThanOrEqualTo(
root.get("updatedOn"),
Timestamp.valueOf(
LocalDateTime
.now()
.minusDays(daysValidityThreshold)
)
)
)
);
int deleteCount = entityManager.createQuery(delete).executeUpdate();
generates this SQL delete query:
DELETE FROM
post_comment
WHERE
status = 2 AND
updated_on <= '2020-08-06 10:50:43.115'
So, there is no entity-level cascade since the delete is done using the SQL statement, not via the EntityManager.
Bulk Delete Cascading
To enable cascading when executing bulk delete, you need to use DDL-level cascade when declaring the FK constraints.
ALTER TABLE post_comment
ADD CONSTRAINT FK_POST_COMMENT_POST_ID
FOREIGN KEY (post_id) REFERENCES post
ON DELETE CASCADE
Now, when executing the following bulk delete statement:
DELETE FROM
post
WHERE
status = 2 AND
updated_on <= '2020-08-02 10:50:43.109'
The DB will delete the post_comment records referencing the post rows that got deleted.
The best way to execute DDL is via an automatic schema migration tool, like Flyway, so the Foreign Key definition should reside in a migration script.
If you are generating the migration scripts using the HBM2DLL tool, then, in the PostComment class, you can use the following mapping to generate the aforementioned DDL statement:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(foreignKey = #ForeignKey(name = "FK_POST_COMMENT_POST_ID"))
#OnDelete(action = OnDeleteAction.CASCADE)
private Post post;
If you really care about the time it takes to perform this bulk delete, I suggest you use JPQL to delete your entities. When you issue a DELETE JPQL query, it will directly issue a delete on those entities without retrieving them in the first place.
int deletedCount = entityManager.createQuery("DELETE FROM Country").executeUpdate();
You can even do conditional deletes based on some parameters on those entities using Query API like below
Query query = entityManager.createQuery("DELETE FROM Country c
WHERE c.population < :p");
int deletedCount = query.setParameter(p, 100000).executeUpdate();
executeUpdate will return the number of deleted rows once the operation is complete.
If you've proper cascading type in place in your entities like CascadeType.ALL (or) CascadeType.REMOVE, then the above query will do the trick for you.
#Entity
class Employee {
#OneToOne(cascade=CascadeType.REMOVE)
private Address address;
}
For more details, have a look at this and this.
JPQL BULK DELETE (whether using string-based JPQL or using Criteria JPQL) is not intended to cascade (i.e follow the cascade type settings for fields). If you want cascading then you either set up the datastore to use real FOREIGN KEYs, or you pull back the objects to delete and call EntityManager.remove().

NamedEntityGraph - JPA / Hibernate throwing org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags

We have a project where we need to lazily load collections of an entity, but in some cases we need them loaded eagerly. We have added a #NamedEntityGraph annotation to our entity. In our repository methods we add a "javax.persistence.loadgraph" hint to eagerly load 4 of attributes defined in said annotation. When we invoke that query, Hibernate throws org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags.
Funnily, when I redefine all of those collection as eagerly fetched Hibernate does fetch them eagerly with no MultipleBagFetchException.
Here is the distilled code.
Entity:
#Entity
#NamedEntityGraph(name = "Post.Full", attributeNodes = {
#NamedAttributeNode("comments"),
#NamedAttributeNode("plusoners"),
#NamedAttributeNode("sharedWith")
}
)
public class Post {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "postId")
private List<Comment> comments;
#ElementCollection
#CollectionTable(name="post_plusoners")
private List<PostRelatedPerson> plusoners;
#ElementCollection
#CollectionTable(name="post_shared_with")
private List<PostRelatedPerson> sharedWith;
}
Query method (all cramped together to make it postable):
#Override
public Page<Post> findFullPosts(Specification<Post> spec, Pageable pageable) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Post> query = builder.createQuery(Post.class);
Root<Post> post = query.from(Post.class);
Predicate postsPredicate = spec.toPredicate(post, query, builder);
query.where(postsPredicate);
EntityGraph<?> entityGraph = entityManager.createEntityGraph("PlusPost.Full");
TypedQuery<GooglePlusFullPost> typedQuery = entityManager.createQuery(query);
typedQuery.setHint("javax.persistence.loadgraph", entityGraph);
query.setFirstResult(pageable.getOffset());
query.setMaxResults(pageable.getPageSize());
Long total = QueryUtils.executeCountQuery(getPostCountQuery(specification));
List<P> resultList = total > pageable.getOffset() ? query.getResultList() : Collections.<P>emptyList();
return new PageImpl<P>(resultList, pageable, total);
}
Any hints on why is this working with eager fetches on entity level, but not with dynamic entity graphs?
I'm betting the eager fetches you think were working, were actually working incorrectly.
When you eager fetch more than one "bag" (an unorder collection allowing duplicates), the sql used to perform the eager fetch (left outer join) will return multiple results for the joined associations as explained by this SO answer. So while hibernate does not throw the org.hibernate.loader.MultipleBagFetchException when you have more than one List eagerly fetched it would not return accurate results for the reason given above.
However, when you give the query the entity graph hint, hibernate will (rightly) complain. Hibernate developer, Emmanuel Bernard, addresses the reasons for this exception to be thrown:
eager fetching is not the problem per se, using multiple joins in one SQL query is. It's not limited to the static fetching strategy; it has never been supported (property), because it's conceptually not possible.
Emmanuel goes on to say in a different JIRA comment that,
most uses of "non-indexed" List or raw Collection are erroneous and should semantically be Sets.
So bottom line, in order to get the multiple eager fetching to work as you desire:
use a Set rather than a List
persist the List index using JPA 2's #OrderColumn annotation,
if all else fails, fallback to Hibernate specific fetch annotations (FetchMode.SELECT or FetchMode.SUBSELECT)
EDIT
related:
https://stackoverflow.com/a/17567590/225217
https://stackoverflow.com/a/24676806/225217

OpenJPA Eager Fetching

I use OpenJPA 2.3 bundled with WebSphere 8.5 and I have to read a lot of data from a table. I also have to fetch a lot of relations with the root entity.
Atm I am using the criteria API to create the search query and select the entities. I annotated all collections with EAGER. When I check the logfile it creates 5 Queries to fetch all children. That is the way I want it.
The catch is that I have to filter a lot in java after the select and stop after 1000 matching entities. So I thought i specify the fetch size and stop reading entities from the db as soon I have my 1k results.
If I introduce the FetchBatchSize setting, OpenJPA creates single queries for each entity to load the children. (n+1 problem)
I also tried to use the fetch join syntax directly in my query, but without any success. So what am I doing wrong?
I tried:
1)
query.setHint("openjpa.FetchPlan.FetchBatchSize", 1000);
query.setHint("openjpa.FetchPlan.ResultSetType", "SCROLL_INSENSITIVE");
2)
OpenJPAQuery<?> kq = OpenJPAPersistence.cast(query);
JDBCFetchPlan fetch = (JDBCFetchPlan) kq.getFetchPlan();
fetch.setFetchBatchSize(1000);
fetch.setResultSetType(ResultSetType.FORWARD_ONLY);
fetch.setFetchDirection(FetchDirection.FORWARD);
fetch.setLRSSizeAlgorithm(LRSSizeAlgorithm.UNKNOWN);
The entity:
#Entity
#Table(name = "CONTRACT")
public class Contract {
// omitted the other properties. The other relationships are annotated the same way
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "contract")
private List<Vehicle> vehicles= new ArrayList<Vehicle>();
The query:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Contract> crit = cb.createQuery(Contract.class);
crit.distinct(true);
Root<Contract> r = crit.from(Contract.class);
// omited the where clause. In worst case I have a full table scan without any where clause. (the reason I need the batch size)
Fetch<Contract, Vehicle> fetchVehicles = r.fetch("vehicles", JoinType.LEFT); // I tried to work with a fetch join as well
TypedQuery<Contract> query = em.createQuery(crit);
// query.setHint("openjpa.FetchPlan.FetchBatchSize", FETCH_SIZE);
// query.setHint("openjpa.FetchPlan.ResultSetType", "SCROLL_INSENSITIVE");
OpenJPAQuery<?> kq = OpenJPAPersistence.cast(query);
JDBCFetchPlan fetch = (JDBCFetchPlan) kq.getFetchPlan();
fetch.setFetchBatchSize(FETCH_SIZE);
fetch.setResultSetType(ResultSetType.FORWARD_ONLY);
fetch.setFetchDirection(FetchDirection.FORWARD);
fetch.setLRSSizeAlgorithm(LRSSizeAlgorithm.UNKNOWN);
fetch.setEagerFetchMode(FetchMode.PARALLEL);
List<TPV> queryResult = query.getResultList();
// here begins the filtering and I stop as soon I have 1000 results
Thanks for the help!
Have a look at how to deal with large result sets and you will see that EAGER is the opposite of what you should do.
As I stated in comments, EAGER means that JPA loads all results at once, so it is not recommended for large result sets. Setting the fetchBatchSize causes JPA to lazy load every x (in your case 1000) results. So it would be practically the same as if you would use #OneToMany(fetch = FetchType.LAZY, ...) (also worth a try)
Setting the fetchBatch size to a much lower number (e.g. 50) will also lower the objects that are kept in memory.
Also try
query.setHint("openjpa.FetchPlan.ResultSetType", "SCROLL_SENSITIVE");
It seems that there are some Bugs filed which apply in my scenario. I found a workaround which scales well.
First I select only the ids (Criteria API can select skalar values) and I apply the batching there. So I have no n+1 problem due to the wrong fetching strategy anymore.
After this I select my entities with an IN() statement in batches of 1000 without limiting with fetch batch size or max results. So I do not run into this bug and OpenJPA generates one query for each relation.
So I have around 6 querys for the entity with all its dependencies.
Thanks again thobens for your help!

JPA eager fetch does not join

What exactly does JPA's fetch strategy control? I can't detect any difference between eager and lazy. In both cases JPA/Hibernate does not automatically join many-to-one relationships.
Example: Person has a single address. An address can belong to many people. The JPA annotated entity classes look like:
#Entity
public class Person {
#Id
public Integer id;
public String name;
#ManyToOne(fetch=FetchType.LAZY or EAGER)
public Address address;
}
#Entity
public class Address {
#Id
public Integer id;
public String name;
}
If I use the JPA query:
select p from Person p where ...
JPA/Hibernate generates one SQL query to select from Person table, and then a distinct address query for each person:
select ... from Person where ...
select ... from Address where id=1
select ... from Address where id=2
select ... from Address where id=3
This is very bad for large result sets. If there are 1000 people it generates 1001 queries (1 from Person and 1000 distinct from Address). I know this because I'm looking at MySQL's query log. It was my understanding that setting address's fetch type to eager will cause JPA/Hibernate to automatically query with a join. However, regardless of the fetch type, it still generates distinct queries for relationships.
Only when I explicitly tell it to join does it actually join:
select p, a from Person p left join p.address a where ...
Am I missing something here? I now have to hand code every query so that it left joins the many-to-one relationships. I'm using Hibernate's JPA implementation with MySQL.
Edit: It appears (see Hibernate FAQ here and here) that FetchType does not impact JPA queries. So in my case I have explicitly tell it to join.
JPA doesn't provide any specification on mapping annotations to select fetch strategy. In general, related entities can be fetched in any one of the ways given below
SELECT => one query for root entities + one query for related mapped entity/collection of each root entity = (n+1) queries
SUBSELECT => one query for root entities + second query for related mapped entity/collection of all root entities retrieved in first query = 2 queries
JOIN => one query to fetch both root entities and all of their mapped entity/collection = 1 query
So SELECT and JOIN are two extremes and SUBSELECT falls in between. One can choose suitable strategy based on her/his domain model.
By default SELECT is used by both JPA/EclipseLink and Hibernate. This can be overridden by using:
#Fetch(FetchMode.JOIN)
#Fetch(FetchMode.SUBSELECT)
in Hibernate. It also allows to set SELECT mode explicitly using #Fetch(FetchMode.SELECT) which can be tuned by using batch size e.g. #BatchSize(size=10).
Corresponding annotations in EclipseLink are:
#JoinFetch
#BatchFetch
"mxc" is right. fetchType just specifies when the relation should be resolved.
To optimize eager loading by using an outer join you have to add
#Fetch(FetchMode.JOIN)
to your field. This is a hibernate specific annotation.
The fetchType attribute controls whether the annotated field is fetched immediately when the primary entity is fetched. It does not necessarily dictate how the fetch statement is constructed, the actual sql implementation depends on the provider you are using toplink/hibernate etc.
If you set fetchType=EAGER This means that the annotated field is populated with its values at the same time as the other fields in the entity. So if you open an entitymanager retrieve your person objects and then close the entitymanager, subsequently doing a person.address will not result in a lazy load exception being thrown.
If you set fetchType=LAZY the field is only populated when it is accessed. If you have closed the entitymanager by then a lazy load exception will be thrown if you do a person.address. To load the field you need to put the entity back into an entitymangers context with em.merge(), then do the field access and then close the entitymanager.
You might want lazy loading when constructing a customer class with a collection for customer orders. If you retrieved every order for a customer when you wanted to get a customer list this may be a expensive database operation when you only looking for customer name and contact details. Best to leave the db access till later.
For the second part of the question - how to get hibernate to generate optimised SQL?
Hibernate should allow you to provide hints as to how to construct the most efficient query but I suspect there is something wrong with your table construction. Is the relationship established in the tables? Hibernate may have decided that a simple query will be quicker than a join especially if indexes etc are missing.
Try with:
select p from Person p left join FETCH p.address a where...
It works for me in a similar with JPA2/EclipseLink, but it seems this feature is present in JPA1 too:
If you use EclipseLink instead of Hibernate you can optimize your queries by "query hints". See this article from the Eclipse Wiki: EclipseLink/Examples/JPA/QueryOptimization.
There is a chapter about "Joined Reading".
to join you can do multiple things (using eclipselink)
in jpql you can do left join fetch
in named query you can specify query hint
in TypedQuery you can say something like
query.setHint("eclipselink.join-fetch", "e.projects.milestones");
there is also batch fetch hint
query.setHint("eclipselink.batch", "e.address");
see
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html
I had exactly this problem with the exception that the Person class had a embedded key class.
My own solution was to join them in the query AND remove
#Fetch(FetchMode.JOIN)
My embedded id class:
#Embeddable
public class MessageRecipientId implements Serializable {
#ManyToOne(targetEntity = Message.class, fetch = FetchType.LAZY)
#JoinColumn(name="messageId")
private Message message;
private String governmentId;
public MessageRecipientId() {
}
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
public String getGovernmentId() {
return governmentId;
}
public void setGovernmentId(String governmentId) {
this.governmentId = governmentId;
}
public MessageRecipientId(Message message, GovernmentId governmentId) {
this.message = message;
this.governmentId = governmentId.getValue();
}
}
Two things occur to me.
First, are you sure you mean ManyToOne for address? That means multiple people will have the same address. If it's edited for one of them, it'll be edited for all of them. Is that your intent? 99% of the time addresses are "private" (in the sense that they belong to only one person).
Secondly, do you have any other eager relationships on the Person entity? If I recall correctly, Hibernate can only handle one eager relationship on an entity but that is possibly outdated information.
I say that because your understanding of how this should work is essentially correct from where I'm sitting.

Categories

Resources