Hibernate: Criteria query taking a long time with three-level relationship - java

I'm having trouble using Criteria to find all the objects that belong to a certain entity. My model is like the following (just showing the relevant code):
#Entity...
Class A {
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "ide_b", referencedColumnName = "ide_b", nullable = false)
private B b;
}
#Entity...
class B {
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "ide_c", referencedColumnName = "ide_c", nullable = false)
private C c;
}
#Entity...
class C {
...
}
My Criteria query is as simple as this (actually, there would be some filters, but they're not being used):
Criteria criteria = getSession().createCriteria(A.class);
criteria.list(); // MY SYSTEM STAYS HERE FOREVER WHEN RUNNING AGAINST A REAL DATABASE
Would anybody have a clue on this issue? The system just stays forever on the line "criteria.list()" and never returns.
I've already tested the SQL that it generates directly on the database and it works just fine.
I've already tested this query both with code involving only class A having a reference to B and class A having a reference to C (directly). They both work. This third level in the association seems to be causing problems... Note: my Hibernate version is an old one, like 3.0.0

You need to remove fetch type join. Eager is fine for your data layout, but do your really need that much data at once?
With default fetch, hibernate will query table A only. Then for each B's foreign key, queries B only once per key. Same done for C.
i.e. once b_id=1 is fetched from B, it wont be fetched again even it used with million rows of A. Hibernate's 2 nd level cache handles it.
With join type fetch, for each row from A, you'll get 1 single row containing columns of all 3 tables.
If your relation was OneToMany, then you'll get A x B x C rows returned. But since ManyToOne, there is no such issue.
Your problem is even this query returns 1 big row for each items at A, there is too much B and C replication. So DB response is huge so processing is hard as well for both DB and your app.

Well, actually I think the problem is something else. I've changed both relationships (A -> B and B -> C) to lazy (and removed the #Fetch). Then I queried for a specific A object. So far so good. I was able to get the object and I was able to call a.getB() successfully.
Nonetheless, when I call b.getC() Hibernate does not return the C object to me (I mean, Hibernate gets stuck at this line).
The query Hibernate creates to fetch the C object when I call b.getC() is the following:
select
myCtable0_.id as id1_85_0_,
myCtable0_.name as nam2_85_0_
from
MyTableC myCtable0_
where
myCtable0_.id in (?, ?)
The C table has an id field (the primary key) and a name (varchar).

Another option is to fetch all data separately. Then hibernate cache will have each entity and then, I think, criteria query will be faster.

Related

Unique Constraint Violation on Hibernate #ManyToMany Relationship

I have 3 entities in my database, let's call them A, B, and C.
A and B share a many-to-many relationship with one another. A has a SortedSet of Bs, but B does not reference A (no collection or w/e configured). So we have the following.
// Inside class A
#ManyToMany
#JoinTable(name = "a_b",
joinColumns = {#JoinColumn(name = "a_id")},
inverseJoinColumns = {#JoinColumn(name = "b_id")})
#LazyCollection(LazyCollectionOption.FALSE)
#SortNatural
private SortedSet<B> bSet = new TreeSet<B>();
B and C have a one-to-many relationship with one another (1 B to many Cs). C has a B in it's entity, but B does not have a reference to it's many C entities. So we have the following
// Inside class C
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "b_id", nullable = false, updatable = true)
protected B b;
We have a sync process that runs a nightly job to update the A entities and their associations to B entities (which doesn't change very often). We end up having something similar to the following (really, it's a lot more complicated with DAOs, services, etc).
// Get the A value to be updated
A aToUpdate = entityManager.find(A.class, idForA);
// Out of scope of the question, but we need to figure out B via a string field on C
C cValue = myDao.getByProperty("fieldName", fieldValue);
// Determine the B values to set on aToUpdate
B bToSetOnA = cValue.getB();
TreeSet<B> bSet = new TreeSet<>();
bSet.add(bToSetOnA);
// Update aToUpdate
aToUpdate.setBSet(bSet);
aToUpdate = entityManager.merge(aToUpdate);
entityManager.flush();
When this happens, the following error occurs.
ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (Sync Thread)
Duplicate entry 'myAId-almostMyBId' for key 'uk_a_b'
One interesting thing is that the almostMyBId is 1 character short of the actual B ID. But only the full ID appears in the a_b table.
When I look through the code base, there is a uk_a_b constraint on an index on the a_b table. This is from liquibase.
<createIndex indexName="uk_a_b" tableName="a_b" unique="true">
<column name="a_id"/>
<column name="b_id"/>
</createIndex>
If I remove the aToUpdate.setBSet(bSet); line, the error disappears.
I added logging and confirmed that the IDs of the new bSet are the same as the old ones on aToUpdate.
Somehow, Hibernate seems to be trying to re-add the association, even though we're doing a merge and the associations haven't really changed.
I've tried changing a few CascadeType and FetchType things here and there, but the error doesn't seem to go away. Anyone have an idea of what's going on?
This is very specific to my situation, but figured I'd post the answer anyways in case anyone read my question and has been working on it.
Another developer on my team was tasked with speeding up a report based on the a and a_b tables. To avoid needing to do a JOIN and a WHERE clause, the developer copied the data over from a_b into a new table (using the WHERE clause) and added triggers so that whenever things were updated, it would insert into the new table. This new table had a constraint named the same as the a_b table (namely, uk_a_b). The duplicate case was not properly handled, so an error was being thrown. Due to the name similarities, it seemed to be the a_b table causing the issue when it was really the new table. Fun times.

JPA/Hibernate: Map many-to-many relationship when join table has own primary key

I have three tables with simple structure:
pub [id, name]
days [id, name]
pub_days [id, pub_id, days_id]
For some unholy reason, somebody thought that compound identity for pub_days table (that would be pub_id + days_id) is not enough and added own primary key. I can't change it now, other and larger system depends on that. #sigh
I am trying to map this to Hibernate with standard #ManyToMany JPA annotation like so (I omitted getters, setters, #Entitiy annotations and other clutter):
class Pub {
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "pub_days",
joinColumns = {#JoinColumn(name = "pub_id")},
inverseJoinColumns = {#JoinColumn(name = "days_id")})
#OrderBy("id")
private List<Day> pubOpeningDays;
}
class Day {
#Id Long id;
String name.
}
when I execute following code:
Day day = repository.find(Day.class, id);
pub.getPubOpeningDays().add(day);
repository.persist(pub);
I get this error:
ERROR: ORA-01400: cannot insert NULL into ("PUB"."pub_days"."id")
Sadly, that makes perfect sense, because I haven't mapped that ID anywhere. The thing is, I don't even want to. I want it to be generated, but not sure how do I overcome this issue with #ManyToMany mapping. Any ideas?
What you can do is like I mentioned in my comments you can create a separate entity CD which will in turn connect with two classes A and B, Now relationship would be many to many between A and B, and hence A (many to many) CD (many to many) B. Now as per your requirement whenever you need to fetch the instance of A or B, what you can do is simply fire a query in the DB with proper parameters i.e id of a or id of b this will help you get your required result.
I only see two choices, either you change your mapping to a list of PubDay as samwise-gamgee told you in the comments or you add a trigger on insert on table pub_days which set a value for the column id if it is null (it could be using a sequence). But this approach depends on the features supported by your DB.

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!

Eclipselink inserting entity with 0 ID

I just discovered a rather strange problem with EclipseLink (both 2.3.0 and 2.4.1) and just wanted to ask if anyone can confirm that this is a bug and not just that I am missing something obvious ...
Basically, I have two entities involved, let us simply call them A and B. A had an eager (if that matters), non-cascading many-to-one unidirectional reference to B modelled in the database with a join column. The DB table A contains the columns ID (PK), B_ID (FK to B.ID), and more, the table B contains the columns ID and a few more.
Now, I had a list of As, which should be updated with references to new B instances. In pseudo-code something like:
for(A a : list) {
// using some criteria, check if perhaps a matching B
// entity is already found in the database
B b = perhapsGetExistingBFromDatabase();
if(b == null) {
// no existing B was found, create a new
b = new B();
// set values in B
b.setFoo(4711),
b.setBar(42);
// save B
em.merge(b);
}
// set reference from A to B ...
a.setB(b);
// ... and update A
em.merge(a);
}
Since the reference was non-cascading, it was necessary to merge both b and a. Everything worked as expected.
Then, someone (I) changed the cascade type of the relationship from none to merge/persist, since that was required somewhere else in the code. I expected the old code to work, merging b is not really required, shouldn't IMHO however hurt? A brief test confirmed that it still worked, the new B entity was inserted and A updated accordingly.
BUT, it only works if there is only one A entity in the list. Running through the loop a second time causes EclipseLink to auto-flush the session since perhapsGetExistingBFromDatabase does a "SELECT .. FROM B", there is a merged B entity cached and it wants the database table to be up to date. Using FINEST logging level and breakpoints in the code, I can verify that EclipseLink determines that it is required to generate an id for the new B entity, it invokes the sequence generator and also sets the id in the correct field of the entity. Still, EclipseLink invokes SQL statements similar to these:
INSERT INTO B (ID, ...) VALUES(0, ...);
UPDATE A SET B_ID = 0 WHERE ID = ...;
The generated id is lost somewhere and EclipseLink tries to create a new entity with id=0. The data is indeed invalid and later, EclipseLink also throws a PersistenceException: Null or zero primary key encountered in unit of work clone.
Bug or my mistake?
Edit: James asked for the mapping of B.ID:
#Id
#GeneratedValue(generator = "sq_idgen", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "sq_idgen", sequenceName = "...", allocationSize = 100)
#Column(name = "id")
protected long id;
Note, that removing the unneccesary em.merge(b); solves my problem. It is just not obvious to me why the invocation of merge causes EclipseLink to fail completely, trying to insert a B instance without a populated id.
That is odd, how is the Id of B mapped?
Seems like the merged might somehow be getting two separate instances of B (as there is nothing to identify them as being the same as they have no Id). Not that merge() is normally not required, it is only required for detached objects, such as when using serialization (try using persist instead).
To avoid the flush you can set the flushMode in the EntityManager or persistence unit to COMMIT instead of AUTO.

How do I left join tables in unidirectional many-to-one in Hibernate?

I'm piggy-backing off of How to join tables in unidirectional many-to-one condition?.
If you have two classes:
class A {
#Id
public Long id;
}
class B {
#Id
public Long id;
#ManyToOne
#JoinColumn(name = "parent_id", referencedColumnName = "id")
public A parent;
}
B -> A is a many to one relationship. I understand that I could add a Collection of Bs to A however I do not want that association.
So my actual question is, Is there an HQL or Criteria way of creating the SQL query:
select * from A left join B on (b.parent_id = a.id)
This will retrieve all A records with a Cartesian product of each B record that references A and will include A records that have no B referencing them.
If you use:
from A a, B b where b.a = a
then it is an inner join and you do not receive the A records that do not have a B referencing them.
I have not found a good way of doing this without two queries so anything less than that would be great.
Thanks.
I've made an example with what you posted and I think this may work:
select a,b from B as b left outer join b.parent as a in HQL.
I have to find a "criteria" way of doing that though.
You may do so by specifying the fetch attribute.
(10) fetch (optional) Choose between outer-join fetching and fetching by sequential select.
You find it at: Chapter 6. Collection Mapping, scroll down to: 6.2. Mapping a Collection
EDIT
I read in your question's comment that you wanted a way to perform a raw SQL query? Here a reference that might possibly be of interest:
Chapter 13 - Native SQL Queries
and if you want a way to make it possible through HQL:
Chapter 11. HQL: The Hibernate Query Language
In chapter 11, you want to scroll down to 11.3. Associations and joins.
IQuery q = session.CreateQuery(#"from A as ClassA left join B as ClassB");
I guess however that ClassB needs to be a member of ClassA. Further reasdings shall help.
Another thing that might proove to be useful to you are named queries:
<query name="PeopleByName">
from Person p
where p.Name like :name
</query>
And calling this query from within code like so:
using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction()) {
session.GetNamedQuery("PeopleByName")
.SetParameter("name", "ayende")
.List();
tx.Commit();
}
Please take a look at the referenced link by Ayende who explains it more in depth.

Categories

Resources