JPA OUTER JOIN without relation - jpa

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).

Related

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

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

Querying a root entity in entity inheritance in JPA

I am experimenting the joined table inheritance in JPA (EclipseLink 2.6.0 having JPA 2.1).
If I execute the following JPQL statement on the root entity Vehicle,
List<Vehicle> vehicleList = entityManager.createQuery("SELECT v FROM Vehicle v", Vehicle.class).getResultList();
The EclipseLink provider generates the following SQL statements.
SELECT DISTINCT vehicle_type FROM vehicle
SELECT t0.vehicle_id, t0.vehicle_type, t0.manufacturer, t1.vehicle_id, t1.no_of_passengers, t1.saddle_height FROM vehicle t0, BIKE t1 WHERE ((t1.vehicle_id = t0.vehicle_id) AND (t0.vehicle_type = ?))
bind => [Bike]
SELECT t0.vehicle_id, t0.vehicle_type, t0.manufacturer, t1.vehicle_id, t1.no_of_doors, t1.no_of_passengers FROM vehicle t0, CAR t1 WHERE ((t1.vehicle_id = t0.vehicle_id) AND (t0.vehicle_type = ?))
bind => [Car]
SELECT t0.vehicle_id, t0.vehicle_type, t0.manufacturer, t1.vehicle_id, t1.load_capacity, t1.no_of_containers FROM vehicle t0, TRUCK t1 WHERE ((t1.vehicle_id = t0.vehicle_id) AND (t0.vehicle_type = ?))
bind => [Truck]
If an ORDER BY clause is added to the given JPQL statement like the following,
List<Vehicle> vehicleList = entityManager.createQuery("SELECT v FROM Vehicle v ORDER BY v.vehicleId DESC", Vehicle.class).getResultList();
the EclipseLink provider however, generates the following single SQL statement with outer joins.
SELECT t0.vehicle_id,
t0.vehicle_type,
t0.manufacturer,
t1.vehicle_id,
t1.load_capacity,
t1.no_of_containers,
t2.vehicle_id,
t2.no_of_passengers,
t2.saddle_height,
t3.vehicle_id,
t3.no_of_doors,
t3.no_of_passengers
FROM vehicle t0
LEFT OUTER JOIN truck t1
ON ( t1.vehicle_id = t0.vehicle_id )
LEFT OUTER JOIN bike t2
ON ( t2.vehicle_id = t0.vehicle_id )
LEFT OUTER JOIN car t3
ON ( t3.vehicle_id = t0.vehicle_id )
ORDER BY t0.vehicle_id DESC
This is mentioned in the JPA Wiki Book.
The poorest performing queries will be those to the root or branch
classes. Avoiding queries and relationships to the root and branch
classes will help to alleviate this burden. If you must query the
root or branch classes there are two methods that JPA providers use,
one is to outer join all of the subclass tables, the second is to
first query the root table, then query only the required subclass
table directly. The first method has the advantage of only requiring
one query, the second has the advantage of avoiding outer joins which
typically have poor performance in databases.
When a particular JPA provider determines which method to apply to generate/produce SQL statement(s), when a root entity is queried -- why does the ORDER BY clause in this particular case make a difference?
I have not yet given it a try on other JPA providers.
I presumed the entity classes are unrelated but let me know, if you need the inheritance hierarchy of classes used in these examples.

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();
}

SqlResultSetMapping with self join table

I have a query with a self join that looks like this,
select t1., t2. from table t1
left outer join table t2 on t2.LFT < t1.LFT
and t2.RGT > t1.RGT
AND t2.REG_CODE_PAR = 'ALL'
AND t1.STATUS_CODE = 'A'
AND t2.STATUS_CODE = 'A'
I'm using #NamedNativeQuery with a result set mapping to get the result.
#NamedNativeQuery(
name="findTree",
query="..... the query above",
resultSetMapping = "regionT")
With the following result set mapping
#SqlResultSetMapping(name = "regionT" , entities ={
#EntityResult(
entityClass = Tree.class
fields = {
#FieldResult(name = "regCode", column = "REG_CODE")
#FieldResult(name = "rgt", column = "RGT"),
#FieldResult(name = "lft", column = "LFT"),
#FieldResult(name = "name", column = "NAME"),
#FieldResult(name = "regCodePar", column = "REG_CODE_PAR"),
#FieldResult(name = "statusCode", column = "STATUS_CODE")
}
),
#EntityResult(
entityClass = TreeSelf.class
fields = {
#FieldResult(name = "regCode1", column = "REG_CODE")
#FieldResult(name = "rgt1", column = "RGT"),
#FieldResult(name = "lft1", column = "LFT"),
#FieldResult(name = "name1", column = "NAME"),
#FieldResult(name = "regCodePar1", column = "REG_CODE_PAR"),
#FieldResult(name = "statusCode1", column = "STATUS_CODE")
}
)
})
The entity class contains looks like this.
#NamedNativeQuery(...)
#SqlResultSetMapping(...)
#Entity
#Table(name = "table")
public class Tree implements Serializable {
#Id
#Column(name = "REG_CODE")
private String regCode; ... ..getters and setters...}
When I run the query using em.createQuery("findTree"), I get the exact same object in both
the 1st and 2nd elements of the returned object array.
Even if I create a class called TreeSelf that is identical to Tree and use it as the 2nd
EntityResult instead of having 2 EntityResults using the same entityClass, I get the same
result.
Can someone point out what's wrong with the configuration?
Let's see if I understand your question. You're expecting to capture two Tree entities from each native query result row. The first entity should be formed from t1's columns. The second entity should be formed from t2's columns. Contrary to expectation, you actually receive two instances formed from t1. No instances from t2 appear. You made a doppelganger Entity for Tree called TreeSelf while debugging, but TreeSelf is ultimately unnecessary and you want to get rid of it. Stop me if any of that was wrong.
I think the problem is due to ambiguous column names. Each column name in the native query appears twice, once from t1 and once from t2. The result mapper seems to be arbitrarily picking the first occurrence of each ambiguous column name for both Tree entities. I'm surprised that works at all. I would have expected an SQLException complaining about column reference ambiguity.
Also, are you sure you want a left outer join? What if no match is found for a t1 row? It will be paired with all NULL in t2's columns. Then you have a null-valued Tree entity. I think. I don't even know what the result mapper would do in that case. Perhaps you want an inner join?
Consider translating this native query into a JPQL query. (JPA Criteria API is just as well, but I find it more cumbersome for examples.) Here's a JPQL version of the native query:
SELECT t1, t2
FROM Tree t1, Tree t2
WHERE t2.lft < t1.lft AND t2.rgt > t1.rgt AND t2.regCodePar = 'ALL' AND
t1.statusCode = 'A' AND t2.statusCode = 'A'
N.B.: This changes the join semantics to inner instead of left outer.
Here's a sketch of code that could run this query:
EntityManager em = ... // EntityManager by injection, EntityManagerFactory, etc.
String jpql = ... // Like example above
TypedQuery<Object[]> q = em.createQuery(jpql, Object[].class);
for (Object[] oa : q.getResultList()) {
Tree t1 = (Tree)oa[0];
Tree t2 = (Tree)oa[1];
}
In case you are stuck with the native query for whatever reason, here's how you can work around the column name ambiguity. Instead of starting the native query like select t1.*, t2.*, alias each column with AS. The SELECT clause would resemble this:
SELECT t1.REG_CODE AS t1_REG_CODE, t1.RGT AS t1_RGT, (... rest of t1 cols ...),
t2.REG_CODE AS t2_REG_CODE, t2.RGT AS t2_RGT, (... rest of t2 cols ...)
The column attribute in each FieldResult must change accordingly. So the column attributes under the first EntityResult should all start with t1_ and the second's should all start with t2_.
I'd humbly recommend deleting the native query and sql result mapper and using JPA Query Language or Criteria API, if you can find a way.
Update: As confirmed in your comments, a useful answer to your question must preserve left (outer) join semantics. Unfortunately, JPQL and the Criteria API don't support complex left join conditions. There is no way to qualify a JPQL left join with an explicit ON condition.
To my knowledege, the only way to do a left outer join under the spec is by traversing an entity relationship. The JPA implementation then generates an ON condition that tests identity equality. The relevant spec bits are 4.4.5 "Joins" and 4.4.5.2 "Left Outer Joins".
To satisfy this constraint, each Tree you want to left-join to its ultimate parent must have an additional column storing the ultimate parent's id. You might be able to cheat around this constraint in a variety of ways (views?). But the path of least resistance seems to be modifying the native query to use aliased arguments, deleting TreeSelf, and updating the result mapper accordingly. Cleverer solutions welcome, though...