JPQL joining tables with where clause - java

I hve a table with a forign key like so:
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="BASE_ID", updatable = false)
private Base base;
Which connects to a Base table using its "id" .. I'm trying to query this with a where clause but i'm running into errors, my query looks like:
return getEntityManager()
.createQuery("SELECT a FROM BaseEventLog a join fetch a.base p WHERE p.eventStatus = 'ERROR'",
BaseEventLog.class)
.getResultList();
In sql it would look like this:
SELECT *
FROM BASE_EVENT_LOG
JOIN BASE
ON BASE.ID=BASE_EVENT_LOG.BASE_ID
WHERE EVENT_STATUS = 'ERROR'
Any suggestions on how to create such a query with jpsql ?

SELECT a FROM BaseEventLog a
join a.base p
WHERE p.eventStatus = 'ERROR'
No need for the fetch; or atleast I have never used it with JPQL. If you want to use the fetch keyword take a look out how to use it here:
http://docs.oracle.com/cd/E17904_01/apirefs.1111/e13946/ejb3_langref.html#ejb3_langref_fetch_joins

Related

Why does subselect make 2 queries instead of one?

I have a simple Restaurant Entity that has a List of Votes ( some fields and methods are omitted for brevity ).
public class Restaurant extends AbstractNamedEntity {
#OneToMany(mappedBy = "restaurant", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
#Fetch(FetchMode.SUBSELECT)
private List<Vote> votes;
}
Here is another side of the relationship
public class Vote extends AbstractBaseEntity {
#ManyToOne
#JsonBackReference
private Restaurant restaurant;
}
When I try to fetch data using Spring data JPA findAll() method and then convert it to DTOs
public static RestaurantResponseDTO toRestaurantDto(Restaurant restaurant) {
return new RestaurantResponseDTO(restaurant.getId(), restaurant.getName(),
restaurant.getAddress(), getRestaurantVoteCount(restaurant));
}
public static long getRestaurantVoteCount(Restaurant restaurant) {
var votes = restaurant.getVotes();
if (votes == null) return 0;
return votes.stream().filter(vote -> vote.getVoteDate().equals(LocalDate.now())).count();
}
these are the SQLs I have:
Hibernate:
select
restaurant0_.id as id1_1_,
restaurant0_.name as name2_1_,
restaurant0_.address as address3_1_
from
restaurant restaurant0_
Hibernate:
select
votes0_.restaurant_id as restaura3_4_1_,
votes0_.id as id1_4_1_,
votes0_.id as id1_4_0_,
votes0_.restaurant_id as restaura3_4_0_,
votes0_.user_id as user_id4_4_0_,
votes0_.vote_date as vote_dat2_4_0_
from
vote votes0_
where
votes0_.restaurant_id in (
select
restaurant0_.id
from
restaurant restaurant0_
)
I thought that Subselect needs only 1, why do I have 2?
Actually, what you see is expected hibernate behavior. See this section of the hibernate documentation.
Hibernate is going to avoid the N+1 query issue by generating a single SQL statement to initialize all votes collections for all Restaurant entities that were previously fetched. Instead of using passing all entity identifiers, Hibernate simply reruns the previous query that fetched the Restaurant entities.
P.S. As for the FetchMode.JOIN option, please note that as it's mentioned here:
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.
So, I guess the most flexible way for your case would be to write own JPQL query and use JOIN FETCH directive where it's needed.

List of entity inside a hibernate entity with #NamedNativeQueries

I have a situation where i get List of entity in hiberate, But inside the main entity i have another entity list. The entities that i use:
EmpListBean.java
#NamedNativeQueries({
#NamedNativeQuery(
name = "EmpList",
//Actual query involves lot of joins
query = " SELECT ID , NAME FROM EMPLOYEE WHERE EMP_ID=:EMPID"
,resultClass = EmpListBean.class
)
})
#Entity
public class EmpListBean {
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String empName;
// This is the list i need to retreive
#ManyToOne
#Column(name="workList")
private List<WorkListBean> workList;
//Getters & Setters
}
WorkListBean.java
#NamedNativeQueries({
#NamedNativeQuery(
name = "WorkListBeanList",
query = " SELECT ID , NAME FROM Work_List WHERE EMP_ID=:EMPID"
,resultClass = WorkListBean.class
)
})
#Entity
public class WorkListBean {
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String workName;
//Getters & Setters
}
The DAO Layer
Query query = session.getNamedQuery("EmpList");
query.setParameter("EMPID", myObj.getEmpId());
List<EmpListBean> oEmpListBean = query.list();
When executing below DAO layer code I get the "workList" Object as empty , I know this can be achieved by iterating the EmpListBean separately and calling named query for WorkListBean separately , but since the data is huge it takes too much time when doing that way, So wanted to know if there is any way that we could fetch WorkListBean inside EmpList Bean. The two entities used here are only for reference , the actual query i use is complex and could not reveal in this forum and it involves lot of table joins, So kindly let me know how this can be possible in hibernate.
I know this can be achieved by iterating the EmpListBean separately and calling named query for WorkListBean separately , but since the data is huge it takes too much time when doing that way
I understand you want to merge the two queries to include the data for both entities, then?
Once the association between EmpListBean and WorkListBean is properly defined, i.e. you have:
class EmpListBean {
...
#OneToMany
#JoinColumn(name = "EMP_ID")
private List<WorkListBean> workList;
}
you should be able to use the following approach:
session.createNativeQuery(
"SELECT employee.* FROM EMPLOYEE employee JOIN Work_List wl JOIN ... WHERE wl.EMP_ID=emp.id AND employee.EMP_ID=:EMPID AND ..." )
.addEntity("employee", EmpListBean.class )
.addJoin( "wl", "employee.workList")
.setResultTransformer( Criteria.ROOT_ENTITY )
.list();
Not sure if it works for named native queries, though, you'll need to check.
As commented by #Javalerner you should use #OneTomany and add Eager Loading with fetch="FetchType.EAGER"
and remove the Column annotation
#OneToMany(fetch = FetchType.EAGER)
private List<WorkListBean> workList;
I like Abd's response if you're looking for a simple #OneToMany.
Rather than forcing the bean to always eager load, you may want to just incorporate this concept into your NamedNative query using the FETCH JOIN style. Using this approach you can craft queries that are EAGER on an object whose association is otherwise Lazy. In that case, Hibernate will only eagerly fetch via your query and generally be lazy, which is I think what most people want.
Feel free to google around. Here is a well written article that may start you off.
http://www.basilv.com/psd/blog/2008/improving-performance-via-eager-fetching-in-hibernate
Best of luck!
to get whatever FetchType.LAZY is, you have to use JOIN FETCH in the sentence. When you use JOIN you get all whatever FetchType.EAGER is but not whatever FetchType.LAZY is
EmpListBean.java
#OneToMany(fetch = FetchType.LAZY, mappedBy="empListBean")
private List<WorkListBean> workList;
WorkListBean.java
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="EMP_ID")
private EmpListBean empListBean;
DAO
String hql = "SELECT empListBean "
+ "FROM EmpListBean empListBean "
+ "JOIN FETCH empListBean.workList workList "
+ "WHERE empListBean.id = :EMPID";
Query q = sessionFactory.getCurrentSession().createQuery(hql);
q.setParameter("EMPID", myObj.getEmpId());
List<EmpListBean> empListBean = query.list();

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

Hibernate Issuing Individual queries instead of joining

I have an application where i am using Hibernate and JPA. I have 2 objects (order and product). They join together and i can get the information i want but its actually issuing 1 query to get all my orders, i get a bunch of queries in my logs to find the product information. I have tried all sorts of annotations but cannot get it to work properly. Any help with this would be great.
Here is my code:
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import javax.persistence.*;
#Entity
public class ProductFeedback implements ProductFeedbackInterface {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(columnDefinition="INT")
private Long id;
private String decision;
private String reportId;
...
#ManyToOne
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "sku", referencedColumnName = "sku", insertable = false, updatable = false)
public Product product;
When i try to query for this table i get somethign like the following queries:
Hibernate: select top 50 productfee0_.id as id1_1_... from product_feedback productfee0_ where 1=1 and productfee0_.date_reported_date>=? and productfee0_.date_reported_date<=? and (productfee0_.decision=? or productfee0_.decision is null) and (productfee0_.dtype=? or productfee0_.dtype=?) and (productfee0_.severity in (0) or productfee0_.dtype<>?) order by productfee0_.date_reported_date desc
Hibernate: select ... from product product0_ where product0_.sku=?
Hibernate: select ... from product product0_ where product0_.sku=?
EDIT::: I have tried using #Fetch(FetchMode.JOIN) also with no luck.
Any ideas?
EDIT:
We are using specifications to build the query:
public List<ProductFeedback> findAll(ProductFeedbackFilters productFeedbackFilters) {
Page recordsPage = jpaRecordRepository.findAll(buildSpecifications(productFeedbackFilters), sortPageable(productFeedbackFilters.getLimit()));
return recordsPage.getContent();
}
Here is a sample...
private Specifications<ProductFeedback> defaultSpecifications() {
return where((queryRoot, query, criteriaBuilder) -> {
return criteriaBuilder.and(new Predicate[]{}); // Always true
});
}
private Specification<ProductFeedback> buildSpecifications(ProductFeedbackFilters filters) {
return new RecordSpecificationsBuilder(filters)
.onOrAfterStartDate()
.onOrBeforeEndDate()
.status()
.type()
.userQuery()
.severity()
.keyword()
.build();
}
public ProductFeedback findByReturnIdentifier(Integer catalogNumber, String commentType) {
return jpaRecordRepository.findOne((queryRoot, query, criteriaBuilder) ->
criteriaBuilder.and(criteriaBuilder.equal(queryRoot.get("catalogNumber"), catalogNumber), criteriaBuilder.equal(queryRoot.get("commentType"), commentType)));
}
You can tell the persistence provider to fetch the ProductFeedback.product relation with your queried Product entites, so the simple query for ProductFeedback objects
SELECT pf FROM ProductFeedback pf
would become
SELECT pf FROM ProductFeedback pf LEFT JOIN FETCH pf.product
With criteria query, assuming you already have a Root<ProductFeedback> object, you can tell to eagerly fetch the associated ProductFeedback.product object with the following:
productFeedbackRoot.fetch("product", JoinType.LEFT);
or if you have your JPA static metamodel classes generated:
productFeedbackRoot.fetch(ProductFeedback_.product, JoinType.LEFT);
The fetching strategy is determined by the default behavior of hibernate which is explained in its documentation like this ..
*The fetch strategy defined in the mapping document affects:
retrieval via get() or load()
retrieval that happens implicitly when an association is navigated
Criteria queries
HQL queries if subselect fetching is used
Irrespective of the fetching strategy you use, the defined non-lazy graph is guaranteed to be loaded into memory.
This might, however, result in several immediate selects being used to execute a particular HQL query.
Usually, the mapping document is not used to customize fetching. Instead, we keep the default behavior, and override it for a particular transaction, using left join fetch in HQL. This tells Hibernate to fetch the association eagerly in the first select, using an outer join. In the Criteria query API, you would use setFetchMode(FetchMode.JOIN).
If you want to change the fetching strategy used by get() or load(), you can use a Criteria query. For example:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
This is Hibernate's equivalent of what some ORM solutions call a "fetch plan".*
The key is that hibernate expects the fetch mode to be set at transaction level.

Join tables in Hibernate

I have two tables in my PostgreSQL database:
CREATE TABLE tableOne (id int, name varchar(10), address varchar(20))
CREATE TABLE tableTwo (id int, info text, addresses varchar(20)[])
now I want to create a join as follows:
SELECT * FROM tableOne JOIN tableTwo ON address = ANY(addresses)
I tried to achieve this using Hibernate - class TableOne:
#Entity
#Table(name = "tableOne")
class TableOne {
private int id;
private TableTwo tableTwo;
private String address;
#Id
#Column(name = "id")
public getId() { return id; }
#ManyToOne
#JoinFormula(value = "address = any(tableTwo.addresses)",
referencedColumnName = "addresses")
public TableTwo getTableTwo(){
return tableTwo;
}
// Setters follow here
}
But Hibernate keeps generating queries with non-sense JOIN clauses, like:
... JOIN tableTwo ON _this.address = any(tableTwo.addresses) = tableTwo.addresses
How do I tell Hibernate using annotations to format my join query correctly? Unfortunately, our project must be restricted only to the Criteria API.
EDIT:
After suggestion from ashokhein in the comments below, I annotated the method getTableTwo() with just #ManyToOne - and now I would like to do the join using Criteria API, presumably with createAlias(associationPath,alias,joinType,withClause) method where withClause would be my ON clause in the join.
But Im not sure what to put as associationPath and alias parameters.
Any hints?
To support PostgreSQL array you need a custom Hibernate Type. Having a dedicated user type will allow you to run native SQL queries to make use of the type:
String[] values = ...
Type arrayType = new CustomType(new ArrayUserType());
query.setParameter("value", values, arrayType);
HQL supports ANY/SOME syntax but only for sub-queries. In your case you'll need a native query to use the PostgreSQL specific ANY clause against array values.
You can try Named Query.
#NamedQuery(name="JOINQUERY", query="SELECT one FROM tableOne one JOIN tableTwo two ON one.address = :address" )
#Entity
class TableOne{......
Retrieving part is:
TypedQuery<TableOne> q = em.createNamedQuery("query", TableOne.class);
q.setParameter("address", "Mumbai");
for (TableOne t : q.getResultList())
System.out.println(t.address);
You might need to do some permutations on the query
So after a lot of time searching for the right answer, the only real solution that works for us is creating a view:
CREATE VIEW TableA_view AS SELECT TableOne.*,TableTwo.id FROM TableA JOIN TableTwo ON TableOne.address = ANY(TableTwo.addresses)
and mapping it to an entity TableOne instead of the original table.
This was the only solution for us besides, of course, using a named query, which was a no-go as we needed to stick to the Criteria API.
As #ericbn has mentioned in the comments this is really an example where ORM gets really annoying. I would never expect that custom join clause like this is not possible to do in Hibernate.
#JoinFormula should contain SQL instead of HQL.
https://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/annotations/JoinFormula.html

Categories

Resources