Java JPA Inner JOIN with WHERE statement - java

I want to annotate following structure:
I have this query:
SELECT A.*, BES.*, BES_2.*
INNER JOIN BES ON A.a = BES.a AND A.b = BES.b
INNER JOIN BES AS BES_2 ON A.a = BES_2.a AND A.b = BES_2.b
WHERE (BES.c = N'foo') AND (BES_2.c = N'bar')
I have the entities Job (representing A) and JobEndPoint (representing BES). The Job object should contain two JobEndPoint which map like a one-to-one relation. I need two JOIN the table two times checking for the same values only differed by the column "c" which I check in the WHERE statement.
#OneToOne
private JobEndPoint from;
#OneToOne
private JobEndPoint to;
My problem is now that the database columns and the object fields differ a lot and I don't know how to add the WHERE statement.

Create a JPA repository, and type a custom #Query.
I assume you already linked the parent and JobEndPoint classes over a and b fields. (To do that, define a multiple-column id on JobEndPoint and specify joinColumns in the parent class.)
#Query("SELECT u FROM parent
LEFT JOIN u.from bes
LEFT JOIN u.to bes2
WHERE bes.c = 'foo'
AND bes2.c = 'bar'")
Set<Parent> findMatchingParents()

Related

problems with OneToMany including a filter clause in spring jpa

I currently get unexpected results in my MYSQL8/H2 test-case when using on a #OneToMany relationship in spring jpa. I want to filter in a list of TKBColumn-tables inside my TKBData table using JPQL. I expect to get one TKBData-table with the filtered TKBColumn but I always get the TKBData-table with ALL TKBColumn (unfiltered). When I using a SQL command it works!
I got no Idea whats the problem here, why it always give me the TKBData-table with always ALL TKBColumn-tables inside.
Native Query (This works):
SELECT d.id,c.name FROM TKBDATA d LEFT JOIN TKBDATA_TKBCOLUMN dc ON d.ID = dc.TKBDATA_ID LEFT JOIN TKBCOLUMN c ON c.ID = dc.COLUMNS_ID WHERE c.name = 'column1';
Output
ID NAME
7b6ec910-3e53-40a3-9221-ee60e75c8d67 column1
JPQL Query (Not works):
select d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name
Output:
id: e892bc28-c35f-4fc8-9b09-387f97a758d8, name:column1
id: 069cc76b-3487-4ad8-a4ae-6568694e2287, name:column2
Table 'TKBData'
public class TKBData {
#Id
#Builder.Default
private String id = UUID.randomUUID().toString();
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Builder.Default
private Set<TKBColumn> columns = Sets.newHashSet();
...
}
Table 'TKBColumn'
public class TKBColumn {
#Id
#Builder.Default
private String id = UUID.randomUUID().toString();
...
}
Spring Data Repository
#Service
public interface KBDataRepository extends CrudRepository<TKBData, String>, KBDataCustomRepository {
#Query("select d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name")
public TKBData filterByColumn(#Param("name") String name);
}
Spring JPA Generated H2 Tables (relevant)
CREATE CACHED TABLE "PUBLIC"."TKBCOLUMN"(
"ID" VARCHAR(255) NOT NULL,
"NAME" VARCHAR(255),
...
)
CREATE CACHED TABLE "PUBLIC"."TKBDATA_TKBCOLUMN"(
"TKBDATA_ID" VARCHAR(255) NOT NULL,
"COLUMNS_ID" VARCHAR(255) NOT NULL
)
CREATE CACHED TABLE "PUBLIC"."TKBDATA"(
"ID" VARCHAR(255) NOT NULL,
...
)
Relevant Content of tables which are generated at the start of the test class
Table: TKBDATA
ID
726004cf-5cab-4b1d-bb3f-466ba22622e9
Table: TKBDATA_TKBCOLUMN
TKBDATA_ID COLUMNS_ID
726004cf-5cab-4b1d-bb3f-466ba22622e9 7b4e4ea8-4ff9-4668-8882-67ff93b595ca
726004cf-5cab-4b1d-bb3f-466ba22622e9 d670e813-0466-48a8-be54-ee992cf28462
Table: TKBCOLUMN
ID DATAORDER NAME OWNERID
d670e813-0466-48a8-be54-ee992cf28462 0 column1 16e01046-9a84-4651-98d8-4e3e358212eb
7b4e4ea8-4ff9-4668-8882-67ff93b595ca 1 column2 16e01046-9a84-4651-98d8-4e3e358212eb
For more informations you can find the github repository here: https://github.com/fo0/ScrumTool
Test class: https://github.com/fo0/ScrumTool/blob/master/ScrumTool/src/test/java/com/fo0/vaadin/scrumtool/test/data/TKBDataColumnFilterTest.java
Edit:
The solution for this was to use a native query, because of the design of JPA and how it works with objects, thats why my use-case has exactly this problem.
Meaning of select d from TKBData d JOIN d.columns c WHERE c.name = column1 is
Find a TKBData object where it has an associated column object for which name is column1
Once its decided which TKBData has at least one column object for which name is column1, then it will return all its associated column objects which you don't have control over in JPA. ( see My answer to another question ). Alternative is to write native sql and return custom non entity objects
For example, you have TKBDATA_1 with column1 and column2 associated, you also have TKBDATA_2 with column3 associated.
When you run your query, it will ignore TKBDATA_2 and decides to return TKBDATA_1 as it has atleast one column object with name= column2. But after that you don't have control over which associated column objects to return for TKBDATA_1 and JPA will return all associated column objects
If you are not sure of the reason, read about hibernate session.How it provides unique presentation of any associated entry in memory. It is the foundation for its dirty checking and repeatable read
Update your #OneToMany as follows
#OneToMany(fetch = FetchType.EAGER,
cascade = CascadeType.ALL, orphanRemoval = true)
#Builder.Default
#JoinTable(name = "TKBDATA_TKBCOLUMN",
joinColumns = #JoinColumn(name = "TKBDATA_ID"),
inverseJoinColumns = #JoinColumn(name = "COLUMNS_ID"))
private Set<TKBColumn> columns = Sets.newHashSet();
When it comes to JPA query language, I would like to think in terms of query a collection of in-memory objects.
So now try to describe the meaning of the following two queries in terms of objects.
select d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name
vs
select d from TKBData d JOIN d.columns c WHERE c.name = :name
Don't forget unlike in sql where you are select any columns here you have said you want to select TKBData objects and restricting which TKBData objects to return.
So to achieve the same result as of your native sql, use the second JPA query
Note:
Even though you used a left join in your sql query, it is effectively an inner join sql query because you also applied a where condition to the most right table on that join.
Use the DISTINCT JPQL keyword
#Query("select distinct d from TKBData d LEFT JOIN d.columns c WHERE c.name = :name")
public TKBData filterByColumn(#Param("name") String name);
Or use JPA method naming query
public TKBData findByColumnsName(String name);

Update Entity Relationship via UPDATE .. SET Query

I have two entities:
class A {
#OneToMany(mappedBy = "a")
List<B> bs;
// getter/ setter
}
class B {
#ManyToOne
A a;
// getter/ setter
}
To delete one b, I first need to invalidate that relationship.
"Traditionally" I would do something like that:
A a = em.getReference(A.class, entityIdA)
B b = em.getReference(B.class, entityIdB);
a.getBs().remove(b);
b.setA(null);
em.remove(b);
This is not very performant, if the List of a's is getting large (a few hundreds in my case).
I know I can also use JPQL to create an update query.
something like this:
Query q = em.createQuery("UPDATE B b SET b.a = NULL");
q.executeUpdate();
Question: What would be the corresponding JPQL query to remove one b from a's list of bs?
In short:
How to translate
a.getBs().remove(b);
into a JPQL query?
EDIT: the mentioned update query translates to
UPDATE B SET A_ID = ? WHERE (ID = ?)
bind => [null, 2]
Tables look like this:
A
ID
B
ID A_ID
From the comments and from this question, changing the owning side of the relationship is sufficient.
Therefore, to do
a.getBs().remove(b);
as an jpql query, one can do
"UPDATE B b SET b.a = NULL"
This will release the bidirectional relationship between a and b.
Note that you might need to clear the L2 cache or close the EntitiyManagerFactory for this to take effect.
factory.getCache().evictAll();

hibernate order by nested entity field with possible null values

I've stumbled upon a problem with Hibernate. I've 2 entities - let's say A and B like so (Entity/Table annotations ommited):
class A {
#ManyToOne
#JoinColumn(name = "b_id")
private B b;
}
class B {
#Column(name = "name")
private String name;
}
Now, I'm trying to query all A entities and ordering them by name field of B's entity like so:
SELECT q FROM A AS q ORDER BY q.b.name asc nulls last
The problem is, there are rows in A's table having null foreign-key (b is null) - in result the aforementioned query returns only rows that don't contain null in b field, and I'd like to have them all.
I guess hibernate joins the table without using LEFT JOIN (OUTER JOIN?) resulting in null values being skipped.
Is there any way to change this behaviour? It would be great, if I could solve it by using annotations in entity classes, because the query-generating mechanism is pretty locked up.
You can use CriteriaBuilder and set alias on entityRoot
Root<A> entityRoot = criteriaQuery.from(A);
entityRoot.join("b", JoinType.LEFT).alias("b");
criteriaQuery.select(entityRoot)
.orderBy(criteriaBuilder.asc(entityRoot.get("b").get("name"))
;
you can use criteria query for this but you will have to create session while using that, it is simpler to access database using criteria:
Criteria criteria = session.createCriteria(A.class)
//create alias of your other class to provide ordering according to foriegn key
criteria.createAlias("foreignkey","keyin table A(eg..b)");
criteria.addOrder(Order.asc(b.name));
List list = criteria.getlist();
hope this helps

JPA parent child relationship

I have a scenario where I need to load all the child values in one case and some specific child values in other . I am using a single bean for both the cases and writing the queries using named query.
#namedqueries{
#namedQuery(name="query1") = "select parent from Parent parent",
#namedQuery(name="query2") = "select parent from Parent parent",
}
Class Parent {
#manytomany
#join mentioned my join condition here //
List<Child> child ;
}
Class Child
{
String A;
String B;
String C;
#manytomany(mappedby = "child")
List<parent> parent ;
}
Now in my query 2 I need to load only String A not String B and String C .
I tried using
"select parent.child .A from Parent parent" as Query 2
but getting the below error
"Attempting to navigate to relation field via multi-valued association and
jpql doesnt allow traversal through multi valued relationship. Try join instead"
So any suggestions on how to proceed on this ..
1) Should I have to create a new bean for each Query
2) Or Can we control the child object parameters in specific named queries
You are accessing a collection when you say select parent.child and you can't say select parent.child.A this is wrong according HQL standards. You need to do this using join as the error message is suggesting:
select c.A from parent as p join p.child as c
Then no, you don't have to create a new bean for each query.

Hibernate Query Criteria for mappings involving inheritance

Lets say that class 'X' is mapped to table 'X' class 'A' is mapped to Table 'A' and Class 'B is mapped to table 'B'.
Table X Structure:(X_ID, some other columns
Table A Structure:(A_Id,X_Id, some other columns)
Table B Structure:(A_Id, some other columns)...Table B also has A_Id
Class 'B' extends class 'A'. We have the mapping files for both of them as:
Class 'A' Parent Mapping file:
#Entity
#Table(name = 'A')
#Inheritance(stratergy=InheritanceType.Joined)
public abstract class A {
#Id #Clumns(name = "A_Id)
#GeneratedValue
protected Long aId;
-- some more A specific fields
}
Class 'B' Mapping file:
#Entity
#Table(name= 'B')
Public class B extends A{
---- B specific fields
}
Now, I have a SQL Query as below that I need to write using hibernate criteria API.
select * from X
INNER JOIN A
ON X.id = A.id
INNER JOIN B
ON A.id = B.id
where B.name = 'XYZ'
and B.Sex = 'M'
I have come up with:
Criteria c = session.createCriteria(x.class, "x");
.createAlias("x.a", "a")
.createAlias("a.b", "b")
.add(Restrictions.eq("b.sex", "M"))
.add(Restrictions.eq("b.name", "XYZ"));
But, if we check the mapping file, there is no direct reference of B in A. Hence hibernate throws out "B not related to A" entity.
Is there any way this inheritance can be mapped in query crteria
You shouldn't need to reference A at all in your criteria, or use any aliases.
Criteria c = session.createCriteria(B.class);
.add(Restrictions.eq("sex", "M"))
.add(Restrictions.eq("name", "XYZ"));
will give you the result you need.
Because of the InheritanceType.Joined, this will probably produce SQL that includes a join to the the A table (something close to the sql you show), but it isn't necessary to specify that join in the criteria.
The things that look like columns in the criteria are actually (reflective) references to fields in your Java objects. Hibernate figures out the columns to put in the sql from your annotations, and should the join to the A table if it's needed based on the inheritance annotation.
To be sure of this in your context, and to understand all this a bit better, I'd advise trying it and turning on logging of the generated sql as described in this answer to another SO hibernate question.
Try this way:
Criteria rootCrit = session.createCriteria(A.class);
rootCrit.createAlias("B", "B");
rootCrit.add(Restrictions.eq("B.sex", "M"));
rootCrit.add(Restrictions.eq("B.name", "XYZ"));

Categories

Resources