I have CandidateEntity, each CandiadateEntity has multiple job tags. I want to query all CandidateEntity have any job tag in given job tags list. How can I do it with CollectionTable as following code:
query = "SELECT c FROM CandidateEntity c JOIN c.jobFlags f WHERE f in :job_flags"
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "job_flags")
#Column(name = "job_flag")
private List<String> jobFlags;
Thanks.
try this:
List<CandidateEntity> candidates= em.createQuery("SELECT c " +
"FROM CandidateEntity c " +
"WHERE :ids IN c.jobFlags")
.setParameter("ids",Arrays.asList(yourList)))
.getResultList();
Related
My JPQL Query returns returns same results 5 more times than I expected.
I think there's 'N+1' problems maybe, so I used fetch join at the very first join of my query but it doesn't work.
code from domain Lv2
#ManyToOne
#JoinColumn(name = "level_3_id")
private Lv3 lv3;
code from domain Lv3
#ManyToOne
#JoinColumn(name = "level_2_id")
private Lv2 lv2;
code from domain Title
#ManyToOne
#JoinColumn(name = "title_info_id")
private TitleInfo titleInfo;
jpql query
#Repository
public class Lv4Repository {
#PersistenceContext
private EntityManager em;
public List<Lv4> findLv4ByLv3Id(String level_2_Title, String level_3_Title) {
return em.createQuery("select l4 from Lv4 l4 join Lv3 as l3 on l4.lv3 = " +
"(select l3 from Lv3 l3 join Lv2 as l2 on l3.lv2 = l2 " +
"join TitleInfo as ti on l2.metaInfo = ti.metaInfo " +
"join Title as t on ti.id = t.titleInfo.id " +
"where t.titleText like concat('%',:level_2_Title,'%'))" +
"join TitleInfo as ti on l3.metaInfo = ti.metaInfo " +
"join Title as t on ti.id = t.titleInfo.id " +
"where t.titleText =: level_3_Title", Lv4.class)
.setParameter("level_2_Title", level_2_Title)
.setParameter("level_3_Title", level_3_Title)
.getResultList();
}
}
Result
My Expectation
[[94], [ITKC_MO_1237A_0010_010_0010, ITKC_MO_1237A_0010_010_0020,...,ITKC_MO_1237A_0010_010_0940]]
My Result
[[470], [ITKC_MO_1237A_0010_010_0010,...,ITKC_MO_1237A_0010_010_0940,ITKC_MO_1237A_0010_010_0010,...,ITKC_MO_1237A_0010_010_0940,ITKC_MO_1237A_0010_010_0010,...,ITKC_MO_1237A_0010_010_0940,ITKC_MO_1237A_0010_010_0010,...,ITKC_MO_1237A_0010_010_0940,
ITKC_MO_1237A_0010_010_0010,...,ITKC_MO_1237A_0010_010_0940]]
I'm using Quarkus and Hibernate / Panache.
For this example, I have 3 tables (table_a, table_b, table_c) that I am joining together using a native query. In the project I'm working on, it's around 5 JOIN tables to retrieve the information I'm looking for.
table_b is purely a mapping / join table for table_a and table_c:
SELECT
a.id,
a.name,
c.login_date
FROM
table_a a
JOIN table_b b ON b.a_id = a.id
JOIN table_c c ON b.c_id = c.id
WHERE
c.login_date > '01-MAY-21'
I'm porting the above to HQL. I've mapped all my #Entity classes with their respective #Table, along with their #Column names. We're good in that department.
SELECT
a.id,
a.name,
c.loginDate
FROM
TableA a
JOIN TableA b ON b.aId = a.id
JOIN TableB c ON b.cId = c.id
WHERE
c.loginDate > '01-MAY-21'
I'm only looking for name and login_date. There is a bunch of other information stored in table_a and table_c that I don't want for this specific query. So I created an entity for this call:
#Entity
#IdClass(LoginDetailsPk.class)
#NamedQuery(
name = "LoginDetails.findFromDate",
query = "FROM TableA a " +
"JOIN TableA b ON b.aId = a.id " +
"JOIN TableB c ON b.cId = c.id " +
"WHERE c.loginDate > '01-MAY-21'"
)
public class LoginDetails extends PanacheEntityBase {
#Id
private int id;
#Id
private String name;
#Id
private String loginDate;
public static List<LoginDetails> findFromDate(String fromDate) {
// Eventually pass fromDate into find()
return find("#LoginDetails.findFromDate").list();
}
}
I'm having a hard time trying to understand why the return even works. When I invoke LoginDetails.findFromDate(...) and store it in a List<LoginDetails>, it works fine. However, when I try to access the list, I get a ClassCastException error.
List<LoginDetails> details = LoginDetails.findFromDate(null);
for(LoginDetails detail : details) { // <------ Throws a class cast exception
//...
}
After debugging, I'm noticing that generic type stored in my List isn't even my LoginDetails class; rather, it's an array of objects (List<Object[]>) with all my #Entities and the irrelevant information I'm not looking for.
I'm lost. Would it make more sense to move back to a native query?
Your HQL is creating a Object[] for every row in the result, because you are not specifying any SELECT, and by default all the objects in the FROM clause are included in that Object array. If you want to return a LoginDetails object you need to create a constructor with all the attributes:
public LoginDetails(int id, String name, String loginDate) {
this.id = id;
this.name = name;
this.loginDate = loginDate;
}
And then change the query to:
query = "SELECT new LoginDetails(a.id, a.name, c.loginDate) "
"FROM TableA a " +
"JOIN TableA b ON b.aId = a.id " +
"JOIN TableB c ON b.cId = c.id " +
"WHERE c.loginDate > '01-MAY-21'"
See https://docs.jboss.org/hibernate/core/3.5/reference/en/html/queryhql.html#queryhql-select
In my Spring boot application I have a query which should return a distinct List of Focus' (works perfectly in MySQL)
#Query(value = "SELECT DISTINCT * FROM Focus F " +
"JOIN FrameworkFoci FF on FF.focusID = F.focusID " +
"JOIN FocusGroups FG on FF.frameworkID = FG.frameworkID " +
"JOIN GroupMembers GM on FG.groupID = GM.groupID " +
"JOIN Users U on GM.userID = U.userID " +
"WHERE U.userID = :userID", nativeQuery = true)
List<Focus> findByUserID(#Param("userID") Long userID);
However this does not return distinct values, duplicates are contained in the resulting list. Another issue is that I can't return a whole entity using #Query annotation - changing my query to SELECT DISTINCT(F) FROM Focus F gives the error java.sql.SQLSyntaxErrorException: Unknown column 'F' in 'field list'.
Furthermore, I tried changing adjusting the query to the following
#Query(value = "SELECT DISTINCT * FROM FrameworkFoci FF " +
"JOIN FocusGroups FG on FF.frameworkID = FG.frameworkID " +
"JOIN GroupMembers GM on FG.groupID = GM.groupID " +
"JOIN Users U on GM.userID = U.userID " +
"WHERE U.userID = :userID", nativeQuery = true)
however this produced the error java.sql.SQLException: Column 'focusCategory' not found.
Why is the query not returning distinct values? And why can't I return a whole entity nor use the second query?
Focus Entity:
#Entity
public class Focus {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long focusID;
#Column(name = "focusCategory")
private String focusCategory;
private String focusName;
private String focusExplanation;
#OneToMany(mappedBy = "focus")
private Set<Rating> ratings;
#ManyToMany
#JoinTable(name = "FrameworkFoci",
joinColumns = #JoinColumn(
name = "focusID"),
inverseJoinColumns = #JoinColumn(
name = "frameworkID"))
private Set<Framework> frameworks;
//image
protected Focus(){}
public Focus(String focusName, String focusCategory, String focusExplanation) {
this.focusCategory = focusCategory;
this.focusName = focusName;
this.focusExplanation = focusExplanation;
}
public Focus(String focusCategory, String focusName, String focusExplanation, Set<Rating> ratings){
this.focusCategory = focusCategory;
this.focusName = focusName;
this.focusExplanation = focusExplanation;
this.ratings = ratings;
}
public Long getFocusId() {
return focusID;
}
public void setFocusId(Long focusID) {
this.focusID = focusID;
}
public String getFocusCategory() {
return focusCategory;
}
public void setFocusCategory(String focusCategory) {
this.focusCategory = focusCategory;
}
EDIT:
I've switched from SQL to JPQL with the following query:
#Query(value = "SELECT DISTINCT focus FROM Focus focus " +
"WHERE focus.frameworks.groups.groupMembers.user.userID =:userID ")
I now get an error org.hibernate.QueryException: illegal attempt to dereference collection [focus0_.focusID.frameworks] with element property reference [groups]
Framework entity:
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "frameworkID")
public class Framework {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long frameworkID;
private String frameworkName;
#ManyToMany
#JoinTable(name = "FrameworkFoci",
joinColumns = #JoinColumn(
name = "frameworkID"),
inverseJoinColumns = #JoinColumn(
name = "focusID"))
private Set<Focus> frameworkFoci = new HashSet<>();
#OneToMany(mappedBy = "framework", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
private Set<Group> groups;
public Framework(){}
The following query solves the issue
#Query(value = "SELECT DISTINCT focus FROM Focus focus " +
"JOIN focus.frameworks frameworks " +
"JOIN frameworks.groups groups " +
"JOIN groups.groupMembers groupMembers "+
"WHERE groupMembers.userID =:userID ")
List<Focus> findByUserID(#Param("userID") Long userID);
Frameworks and GroupMembers are collections and hence needed to be joined, otherwise illegal attempt to dereference collection [focus0_.focusID.frameworks] with element property reference [groups] was produced
you should write your query like this:
'
SELECT DISTINCT f FROM Focus F '
The problem stems from you using SQL by specifying nativeQuery = true. SQL doesn't know about entities, just tables.
Since you presumably have many FrameworkFoci rows (and rows in all the other tables) for each Focus row, each Focus row gets repeated for each matching row in FrameworkFoci. This kind of duplicates the Focus row but the resulting rows are still distinct, because they differ in the columns from the other tables.
And then each row gets turned into a Focus entity, probably with a single element in the framework set.
So therefore query doesn't so much return duplicate results as results split into multiple entities.
Fortunately the solution should be fairly simple: Use JPQL which should be perfectly possible, since you're using only simple joins.
The following should give you a start:
#Query(value = "SELECT DISTINCT * FROM Focus F " +
"WHERE F.framework.groupMembers.user.id=:userID")
List<Focus> findByUserID(#Param("userID") Long userID);
I have a HQL query with a JOIN but the where clause (instrPrice.date BETWEEN :dateFrom AND :dateTo ) on the joined entity doesn't work. The query always returns all the records of instrumentPrice instead of limiting the result by the dates.
NamedQuery
#NamedQuery(name = "findAllPrices",
query = "SELECT DISTINCT taPat FROM TaPatternInstrument taPat "
+ "LEFT JOIN FETCH taPat.instrument instr "
+ "LEFT JOIN instr.instrumentPriceList instrPrice "
+ "WHERE taPat.id = :taPatternInstrumentId "
+ "AND instrPrice.date BETWEEN :dateFrom AND :dateTo ")
Service which calls the Query
public TaPatternInstrument findAllPrices(int taPatternInstrumentId, LocalDate dateFrom, LocalDate dateTo) {
TypedQuery<TaPatternInstrument> typedQuery = createNamedQuery("findAllPrices",
TaPatternInstrument.class);
typedQuery.setParameter("taPatternInstrumentId", taPatternInstrumentId);
typedQuery.setParameter("dateFrom", dateFrom);
typedQuery.setParameter("dateTo", dateTo);
return typedQuery.getSingleResult();
}
Entities
public abstract class BaseEntity implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy =
GenerationType.IDENTITY)
protected int id; ...
}
public class TaPatternInstrument extends BaseEntity {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "instrument_id", nullable = false, foreignKey = #ForeignKey(name =
"tapatterninstrument_instrument_fk"))
private Instrument instrument;
}
public class Instrument extends BaseEntity {
#OneToMany(mappedBy = "instrument", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<InstrumentPrice> instrumentPriceList;
}
Generated SQL
SELECT DISTINCT tapatterni0_.id AS id1_34_0_,
...
FROM tapatterninstrument tapatterni0_
LEFT OUTER JOIN instrument instrument1_
ON tapatterni0_.instrument_id = instrument1_.id
LEFT OUTER JOIN instrumentprice instrument2_
ON instrument1_.id = instrument2_.instrument_id
WHERE tapatterni0_.id = ?
AND ( instrument2_.date BETWEEN ? AND ? )
The solution is to add a FETCH on instrumentPriceList : LEFT JOIN FETCH instr.instrumentPriceList instrPrice
#NamedQuery(name = "findAllPrices",
query = "SELECT DISTINCT taPat FROM TaPatternInstrument taPat "
+ "LEFT JOIN FETCH taPat.instrument instr "
+ "LEFT JOIN FETCH instr.instrumentPriceList instrPrice "
+ "LEFT JOIN taPat.taPatternInstrumentPriceList taPatpr "
+ "WHERE taPat.id = :taPatternInstrumentId "
+ "AND instrPrice.date BETWEEN :dateFrom AND :dateTo ")
The FETCH forces Hibernate to retrieve the Entity (InstrumentPrice) immediately at the first DB request. And thus the where clause is taken into account.
Without FETCH, the Entity InstrumentPrice is only retrieved from the DB when the method getInstrumentPriceList of the Entity Instrument is called (an additional call to the DB is performed). And with this additional call to the DB, the where clause is not taken into account anymore, thus retrieving all records from Entity instrumentPrice.
I have an #Entity class Company with several attributes, referencing a companies Table in my db. One of them represents a Map companyProperties where the companies table is extended by a company_properties table, and the properties are saved in key-value format.
#Entity
#Table(name = "companies")
public class Company extends AbstractEntity {
private static final String TABLE_NAME = "companies";
#Id
#GeneratedValue(generator = TABLE_NAME + SEQUENCE_SUFFIX)
#SequenceGenerator(name = TABLE_NAME + SEQUENCE_SUFFIX, sequenceName = TABLE_NAME + SEQUENCE_SUFFIX, allocationSize = SEQUENCE_ALLOCATION_SIZE)
private Long id;
//some attributes
#ElementCollection
#CollectionTable(name = "company_properties", joinColumns = #JoinColumn(name = "companyid"))
#MapKeyColumn(name = "propname")
#Column(name = "propvalue")
private Map<String, String> companyProperties;
//getters and setters
}
The entity manager is able to perform properly find clauses
Company company = entityManager.find(Company.class, companyId);
However, I am not able to perform JPQL Queries in this entity and retrieve the Map accordingly. Since the object is big, I just need to select some of the attributes in my entity class. I also do not want to filter by companyProperties but to retrieve all of them coming with the proper assigned companyid Foreign Key. What I have tried to do is the following:
TypedQuery<Company> query = entityManager.createQuery("SELECT c.id, c.name, c.companyProperties " +
"FROM Company as c where c.id = :id", Company.class);
query.setParameter("id", companyId);
Company result = query.getSingleResult();
The error I get is:
java.lang.IllegalArgumentException: An exception occurred while creating a query in EntityManager:
Exception Description: Problem compiling [SELECT c.id, c.name, c.companyProperties FROM Company as c where c.id = :id]. [21, 40] The state field path 'c.companyProperties' cannot be resolved to a collection type.
org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1616)
org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1636)
com.sun.enterprise.container.common.impl.EntityManagerWrapper.createQuery(EntityManagerWrapper.java:476)
Trying to do it with joins (the furthest point I got was with
Query query = entityManager.createQuery("SELECT c.id, c.name, p " +
"FROM Company c LEFT JOIN c.companyProperties p where c.id = :id");
does not give me either the correct results (it only returns the value of the property and not a list of them with key-value).
How can I define the right query to do this?
Your JPA syntax looks off to me. In your first query you were selecting individual fields in the Company entity. But this isn't how JPA works; when you query you get back the entire object, with which you can access any field you want. I propose the following code instead:
TypedQuery<Company> query = entityManager.createQuery("from Company as c where c.id = :id", Company.class);
query.setParameter("id", companyId);
Company result = query.getSingleResult();
Similarly, for the second join query I suggest the following code:
Query query = entityManager.createQuery("SELECT c" +
"FROM Company c LEFT JOIN c.companyProperties p WHERE c.id = :id");
query.setParameter("id", companyId);
List<Company> companies = query.getResultList();
The reason why only select a Company and not a property entity is that properties would appear as a collection inside the Company class. Assuming a one to many exists between companies and properties, you could access the propeties from each Company entity.
You are expecting to get a complete Company object when doing select only on particular fields, which is not possible. If you really want to save some memory (which in most cases would not be that much of a success) and select only some field, then you should expect a List<Object[]>:
List<Object[]> results = entityManager.createQuery("SELECT c.id, c.name, p " +
"FROM Company c LEFT JOIN c.companyProperties p where c.id = :id")
.setParameter("id", companyId)
.getResultList();
Here the results will contain a single array of the selected fields. You can use getSingleResult, but be aware that it will throw an exception if no results were found.