JPA+HIBERNATE: cartesian product in Many-to-Many relationship - jpa

I try to explain the problem. I have an entity with ManyToMany relationship
#Entity
#Table(name="TABLE1")
public class Table1 implements Serializable {
...
//bi-directional many-to-many association to Table1
#ManyToMany
#JoinTable(
name="TABLE2"
, joinColumns={
#JoinColumn(name="ID_ELEMENTS1")
}
, inverseJoinColumns={
#JoinColumn(name="ID_ELEMENTS2")
}
)
private List<Table1> elements;
//bi-directional many-to-many association to Table1
#ManyToMany(mappedBy="elements")
private List<Table1> elementOf;
...
}
Db table are:
TABLE1
ID ...
55499 ...
55498 ...
55497 ...
TABLE2
ID_ELEMENTS1 ID_ELEMENTS2
55499 55498
55499 55497
When I try to execute following jpql query
SELECT
t
FROM
Table1 t
LEFT JOIN FETCH t.elementOf
WHERE
t.id = 55499
result is an arraylist with two elements (with id 55499) and every element has an arraylist of two elements (one with id 55498 and one with id 55497). The result I would like to obtain is one element (with id 55499) with arraylist of two elements (one with id 55498 and one with id 55497).
I hope I was clear. Can you help me to optimize the java object result (I vaguely remember QueryHints.BATCH in eclipselink)?

You have specified the join in the query, that's what gets executed. JPA provider will not remove duplicates automatically.
You can just add distinct in the query to remove any duplicates:
SELECT
DISTINCT t
FROM
Table1 t
LEFT JOIN FETCH t.elementOf
WHERE
t.id = 55499

Related

How to join an entity and a custom select using CriteriaBuilder

I need to convert a certain query to JPA using CriteriaBuilder. The query looks like this:
SELECT *
FROM ENTITY1 a
INNER JOIN (
SELECT *
FROM ENTITY2 b
where b.STARTDATE = '2019-10-31T00:00:00.000+0100'
) ON ENTITY2.ID = ENTITY1.ENTITY2_ID
Before you ask: for the sake of performance, I wish to avoid converting the query to:
SELECT *
FROM ENTITY1 a
INNER JOIN ENTITY2 b ON ENTITY2.ID = ENTITY1.ENTITY2_ID
where b.STARTDATE = '2019-10-31T00:00:00.000+0100'
Indeed, both ENTITY1 and ENTITY2 contain such a huge lot of rows, even with the right indexes, executing the latter version of the query takes an unacceptable amount of time.
Now, I'm at a loss how to implement the former version with JPA. Any hint would be appreciated!

JPA criteria query + self join on rowid

I'm trying to write JPA criteria query.
Select * from classA t1
inner join
(SELECT rowid
FROM classA
where conditions...
ORDER BY clause
)t2
on t1.rowid = t2.rowid
ORDER BY clause
where rownum <= 500
I'm having problems in joining the main criteria query with inner criteria query(with predicates)? .Is there a possibilty to do join on criteria queries(not on roots)?
Any help is much appreciated.
note:domain class already having composite PK- annoted with embeddedId.
CriteriaQuery joins can only be defined on explicitly defined relationships between entities. E.g. in your example for ClassA to join to itself there would need an explicit field such as this:
#ManyToOne
#JoinColumn(name = "linked_class_a")
private ClassA linkedClassA
Is there a possibilty to do join on criteria queries(not on roots)?
The simple answer is no - as you've alluded to, it's possible for a CriteriaQuery to define multiple roots but these end up as cartesian products (CROSS JOINs), which can be very inefficient.

JPA Query over a join table

I have 3 tables like:
A AB B
------------- ------------ ---------------
a1 a1,b1 b1
AB is a transition table between A and B
With this, my classes have no composition within these two classes to each other. But I want to know that , with a JPQL Query, if any records exist for my element from A table in AB table. Just number or a boolean value is what I need.
Because AB is a transition table, there is no model object for it and I want to know if I can do this with a #Query in my Repository object.
the AB table must be modeled in an entity to be queried in JPQL. So you must model this as
an own entity class or an association in your A and or your B entity.
I suggest to use Native query method intead of JPQL (JPA supports Native query too). Let us assume table A is Customer and table B is a Product and AB is a Sale. Here is the query for getting list of products which are ordered by a customer.
entityManager.createNativeQuery("SELECT PRODUCT_ID FROM
SALE WHERE CUSTOMER_ID = 'C_123'");
Actually, the answer to this situation is simpler than you might think. It's a simple matter of using the right tool for the right job. JPA was not designed for implementing complicated SQL queries, that's what SQL is for! So you need a way to get JPA to access a production-level SQL query;
em.createNativeQuery
So in your case what you want to do is access the AB table looking only for the id field. Once you have retrieved your query, take your id field and look up the Java object using the id field. It's a second search true, but trivial by SQL standards.
Let's assume you are looking for an A object based on the number of times a B object references it. Say you are wanting a semi-complicated (but typical) SQL query to group type A objects based on the number of B objects and in descending order. This would be a typical popularity query that you might want to implement as per project requirements.
Your native SQL query would be as such:
select a_id as id from AB group by a_id order by count(*) desc;
Now what you want to do is tell JPA to expect the id list to comeback in a form that that JPA can accept. You need to put together an extra JPA entity. One that will never be used in the normal fashion of JPA. But JPA needs a way to get the queried objects back to you. You would put together an entity for this search query as such;
#Entity
public class IdSearch {
#Id
#Column
Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Now you implement a little bit of code to bring the two technologies together;
#SuppressWarnings("unchecked")
public List<IdSearch> findMostPopularA() {
return em.createNativeQuery("select a_id as id from AB group by a_id
order by count(*) desc", IdSearch.class).getResultList();
}
There, that's all you have to do to get JPA to get your query completed successfully. To get at your A objects you would simply cross reference into your the A list using the traditional JPA approach, as such;
List<IdSearch> list = producer.getMostPopularA();
Iterator<IdSearch> it = list.iterator();
while ( it.hasNext() ) {
IdSearch a = it.next();
A object = em.find(A.class,a.getId());
// your in business!
Still, a little more refinement of the above can simplify things a bit further actually given the many many capabilities of the SQL design structure. A slightly more complicated SQL query will an even more direct JPA interface to your actual data;
#SuppressWarnings("unchecked")
public List<A> findMostPopularA() {
return em.createNativeQuery("select * from A, AB
where A.id = AB.a_id
group by a_id
order by count(*) desc", A.class).getResultList();
}
This removes the need for an interm IdSearch table!
List<A> list = producer.getMostPopularA();
Iterator<A> it = list.iterator();
while ( it.hasNext() ) {
A a = it.next();
// your in business!
What may not be clear tot the naked eye is the wonderfully simplified way JPA allows you to make use of complicated SQL structures inside the JPA interface. Imagine if you an SQL as follows;
SELECT array_agg(players), player_teams
FROM (
SELECT DISTINCT t1.t1player AS players, t1.player_teams
FROM (
SELECT
p.playerid AS t1id,
concat(p.playerid,':', p.playername, ' ') AS t1player,
array_agg(pl.teamid ORDER BY pl.teamid) AS player_teams
FROM player p
LEFT JOIN plays pl ON p.playerid = pl.playerid
GROUP BY p.playerid, p.playername
) t1
INNER JOIN (
SELECT
p.playerid AS t2id,
array_agg(pl.teamid ORDER BY pl.teamid) AS player_teams
FROM player p
LEFT JOIN plays pl ON p.playerid = pl.playerid
GROUP BY p.playerid, p.playername
) t2 ON t1.player_teams=t2.player_teams AND t1.t1id <> t2.t2id
) innerQuery
GROUP BY player_teams
The point is that with createNativeQuery interface, you can still retrieve precisely the data you are looking for and straight into the desired object for easy access by Java.
#SuppressWarnings("unchecked")
public List<A> findMostPopularA() {
return em.createNativeQuery("SELECT array_agg(players), player_teams
FROM (
SELECT DISTINCT t1.t1player AS players, t1.player_teams
FROM (
SELECT
p.playerid AS t1id,
concat(p.playerid,':', p.playername, ' ') AS t1player,
array_agg(pl.teamid ORDER BY pl.teamid) AS player_teams
FROM player p
LEFT JOIN plays pl ON p.playerid = pl.playerid
GROUP BY p.playerid, p.playername
) t1
INNER JOIN (
SELECT
p.playerid AS t2id,
array_agg(pl.teamid ORDER BY pl.teamid) AS player_teams
FROM player p
LEFT JOIN plays pl ON p.playerid = pl.playerid
GROUP BY p.playerid, p.playername
) t2 ON t1.player_teams=t2.player_teams AND t1.t1id <> t2.t2id
) innerQuery
GROUP BY player_teams
", A.class).getResultList();
}

Why does JPQL's "OR" operator narrow result set?

I have three tables: "User", "Employee" and "Worker". "User" table has one-to-zero-or-one relationship with "Worker" and the same one-to-zero-or-one with "Employee". User entity bean has following mapping attributes:
#OneToOne(cascade = CascadeType.ALL, mappedBy = "user")
private Worker worker;
#JoinColumn(name = "id_employee", referencedColumnName = "id")
#OneToOne
private Employee idEmployee;
My aim is to get all "User" records which have one of this attributes filled (not null). I try to use the query:
SELECT u FROM User u WHERE u.idEmployee IS NOT NULL OR u.worker IS NOT NULL
ORDER BY u.login
I suppose to get 15 records, but I get only 6. I divided this query into two separate:
SELECT u FROM User u WHERE u.idEmployee IS NOT NULL ORDER BY u.login;
SELECT u FROM User u WHERE u.worker IS NOT NULL ORDER BY u.login;
I get 9 and 6 records, respectively. Put together - required 15 records.
It looks like "OR" narrows the result set to only those records, which have worker field not null. Why does it work in such way? Thanks in advance.
The worker association is mapped by a foreign key in the worker table. This means that using u.worker makes an inner join to the worker table, and the is not null is always true. The resulting SQL should look like this:
select u.* from user u, worker w where u.id = w.user_id and w.user_id is not null.
You need to use a left join to accept users having no worker:
select u from User u
left join u.worker w
left join u.employee e
where w is not null or e is not null
i actually had similar problem recently, and it turns out i was using old version of eclipse link, where statements is null and is not null wasn't correctly executed if they were part of and/or statement.
If i remember fix for that was in eclipse link 2.2.0. if you are not using eclipse link, ignore my post.

JPA OUTER JOIN without relation

I need make OUTER JOIN of two entities in JPA (saying master, detail), but the problem that at the entity level there are no relations (and I don't want add it).
#Entity
class Master
{
#Column(name="altKey")
Integer altKey;
}
#Entity
class Detail
{
#Column(name="altKeyRef")
#Basic (optional = true)
Integer altKeyRef;
}
SELECT m, d FROM Master m OUTER JOIN ????? d.altKeyRef = m.altKey
My understanding of the spec (see 4.14 BNF) is that a [ LEFT [OUTER] | INNER ] JOIN mush be done along a path expression (either a single valued association field or a collection valued association field).