Using hibernate #Formula to fetch a collection - java

I have a similar db structure. The only difference that I have more tables on the path from A to C entity:
And I have following mapping for this structure:
#Entity
#Table(name = "a")
class A {
#Id
private int id;
private String title;
#ElementCollection(targetClass=String.class)
#Formula("(select (c.useful_information) from A a " +
"join B b on a.id = b.a_id " +
"join C c on b.id = c.b_id " +
"where a.id = id)")
private List<String> usefulStuff;
}
My aim is to get a list of all useful things from table C in entity A.
But I get syntax errors.
Could you say what's wrong in my example? And maybe you know better way for this purpose?

The problem in your #Formula annotation is the "from A a". The value passed to this annotation is actually SQL, not JPQL.
Thus, if you want to alias the table to reference it elsewhere, you need to rather write FROM A AS a.

Related

Is it possible to query with list of objects using JPA like foreach tag?

Let's say I have a query like this:
Query object
public class myObject {
private String code;
private String name;
}
query method
#Query(nativeQuery = true,value = "...")
findByAcodeAndBname(List<MyObject> queryObject);
Pseudocode of the query
select a.code, b.name from tableA a
left join table b on a.column = b.column
where a.code = ${} b.name = ${}
Since it could cause performance issues just simply using foreach code in Java to do the query, how do I map every pair of a.code and b.name using native JPA query like mybatis foreach tag? Or is it posssible?
I think you don't need native query here, because the costs of querying data from the database and transfering it over network are higher than any related Java operations.
Instead of native query you can use JPQL query, e.g.
#Query("select new com.tsypanov.domain.HasIdAndNameDto(e.id, e.name)" +
" from ManyFieldsEntity e " +
"where e.name = :name")
List<HasIdAndName> findAllByNameUsingDto(#Param("name") String name);
Here we call constructor of DTO from JPQL query passing the fields from our entity. The DTO class must have the specified constructor:
#RequiredArgsConstructor
public class HasIdAndNameDto {
private final Long id;
private final String name;
}

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

JPA: Query an embeddable List inside an entity

I am trying to "extract" Embeddable classes based on some criterias from a list in an Entity. Either with the help of JPQL or Criteria API. I am not a pro at this, so please help me out. Been googling for 4 hours solid for an answer without any results.
These are the classes:
#Entity
public class PostOffice {
#Id
private Long id;
#ElementCollection(fetch=FetchType.LAZY)
#CollectionTable(joinColumns=#JoinColumn(name = "CARRIERID"))
private List<PostalCarrier> carriers;
}
#Embeddable
public class PostalCarrier {
#JoinColumn(name = "area")
private Area area;
}
#Entity
public class Area {
#Id
private int code;
}
So, basically what I'm trying to achieve is something like this.
TypedQuery<PostalCarrier> query = entityManager.createQuery("SELECT p.carriers FROM PostOffice p
WHERE p.id = ?1 AND p.carriers.area.code = ?2", PostalCarrier.class);
query.setParameter(1, postOfficeId);
query.setParameter(2, areaCode);
I only want to get a list of the PostalCarriers at specific area code from a specific PostOffice.
Any help is much appreciated! :)
I think I'm almost there, but keep getting the following error:
Error compiling the query [SELECT h FROM PostOffice p INNER JOIN p.carriers h
WHERE p.id = ?1 AND h.area.code = ?2], line 1, column 71: unknown state or
association field [area] of class [com.test.PostalCarrier].
You must make a join to the PostalCarrier. You can't access a property from a collection. Thus, postalcarrier.area isn't correct.
select postalcarrier from PostOffice p
inner join p.carriers postalcarrier
where p.id = :postOfficeId
and postalcarrier.area.code = :areaCode

problem with "select new Object ... join ... where"

I'm having a problem with an HQL query
Three classes
ClassOne is my BusinessObject
public class ClassOne {
private int id;
private int status;
private Set<ClassTwo> classTwos;
+ other fields/getters/setters/constructor etc
}
ClassTwo is referenced in a set of ClassOne and is kind of the history of an object of ClassOne
public class ClassTwo {
private int id;
private int oldStatus;
private int newStatus;
private String message;
//+ getters/setters/constructor etc
}
ClassThree is my DTO/VO with just one classTwo (not the whole history)
public class ClassThree {
private int id;
private int status;
private ClassTwo classTwo;
public ClassThree(int pId, int pStatus, ClassTwo pClassTwo) {
id=pId;
status=pStatus;
classTwo=pClassTwo;
}
//+ getters/setters etc
}
Now I'd like to create an HQL query like this:
I'd like to get all objects of ClassThree with a certain status and if it exists the newest ClassTwo with a certain newStatus.
For example:
I'd like to get all the DTOs (ClassThree) of ClassOne whose status is now 1, but earlier in their history it has been 2 and I'd like to have the latest ClassTwo object which has 2 as newStatus.
SELECT new ClassThree(c1.id, c1.status, c2)
FROM ClassOne c1
LEFT JOIN c1.classtwos c2 (...)
and (...) is where I don't know what to do, I'm not even sure if it's a join / join fetch
Looked around and tried quite a lot already, but no clue. Especially with the join fetch I get some Hibernate errors like org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list.
Fetching the BusinessObject like that is no problem
SELECT distinct(c1)
FROM ClassOne c1
LEFT OUTER JOIN FETCH c1.classtwos c2
and I get the ClassTwos as my field.
Thanks in advance,
Jacob
P.S.: One thing might be important, ClassTwo has no reference to ClassOne!!
P.P.S : The simple SQL query which resolves my problem looks more or less like that:
select * from classone as c1 left join (select * from classtwo where newstatus = 2) c2 on c1.id=c2.id_classone whete c1.status = 1
This query works and gets all the information needed on my PostGreSQL DB, but I'd really like to have an HQL to continue to work with, especially for maintenance reasons and so on...
Update with workaround solution:
Getting the ids of all the ClassOnes with a status 1
Collection<Integer> ids = null;
ids = (Collection<Integer>) getHibernateTemplate().execute(
new HibernateCallback() {
public Object doInHibernate(Session pSession) throws HibernateException, SQLException {
return getDocumentIds(pSession, pStatus);
}
}
);
Now I get all the DTOs which have been in status 2 (thanks to Ivan) with:
Named query Document.dto.with.transfer
SELECT new DocumentDTO(d.id, d.status, histo)
FROM Document d
LEFT JOIN d.histories histo
WHERE
d.id in (:ids)
AND
(histo.id =
SELECT MAX(innerhisto.id)
FROM Document innerd
JOIN innerd.histories innerhisto
WHERE d.id = innerd.id AND innerhisto.newStatus = 21)
(in my code I use some named queries)
List<DocumentDTO> lRes = new ArrayList<DocumentDTO>();
Query lQuery = getSession(false).getNamedQuery("Document.dto.with.transfer");
lQuery.setParameterList("ids", ids);
lResultList.addAll(lQuery.list());
afterwards I remove all the IDs already found from my list ids
for (DocumentDTO dto : lResultList) {
ids.remove(dto .getId());
}
I do a third query using a second constructor for the DTO, initializing my history with a dummy-object.
Named query Document.dto.simple
SELECT new DocumentDTO(d.id, d.status)
FROM Document d
WHERE
d.id in (:ids)
(another named query)
lQuery = getSession(false).getNamedQuery("Document.dto.simple");
lQuery.setParameterList("ids", ids);
lResultList.addAll(lQuery.list());
and it's done.
To include Documents with no history we should use LEFT JOIN and test for empty collection, then we use subquery (SELECT COUNT(...)) to detect all documents that have never been in status 2. The last OR-clause is for fetching the last history with the specified status.
Here is the HQL query:
SELECT new DocumentDto(doc.id, doc.status, hist)
FROM Document doc
LEFT JOIN doc.histories hist
WHERE doc.status = :docStatus
AND (size(doc.histories) = 0
OR (SELECT COUNT(innerhist)
FROM Document innerdoc JOIN innerdoc.histories innerhist
WHERE innerdoc.id=doc.id AND innerhist.newStatus = :historyStatus) = 0
OR (hist.newStatus = :historyStatus AND hist.id =
(SELECT max(innerhist.id)
FROM Document innerdoc
JOIN innerdoc.histories innerhist
WHERE innerdoc.status = :docStatus AND innerhist.newStatus = :historyStatus))
Then call setParameter("historyStatus", 2) and setParameter("docStatus", 1) on your query to get the correct result.
That's it!
Please note, I've made an assumption, that we can use a value of id attribute of History as an indicator of the order in which objects were put in your database.

Categories

Resources