HQL and joins - a faster way? - java

This part of my model is as follows:
IQCEntity has many Documents
DocumentCategory has many Documents
I am using Hibernate for my ORM.
Now, please consider the following method:
/**
* Get all documents in the supplied IQCEntity which are in the
* specified DocumentCategory.
* #param entity the {#link IQCEntity} which holds the Documents
* #param category the {#link DocumentCategory} which the Documents belong to
* #return Collection<{#link Document}>
*/
#SuppressWarnings("unchecked")
public Collection<Document> getDocuments(IQCEntity entity, DocumentCategory category) {
String q = "from Document d where d.documentCategory.documentCategoryId = :c and d.entity.entityId = :e";
Query query = session.createQuery(q);
query.setParameter("c", category.getDocumentCategoryId());
query.setParameter("e", entity.getEntityId());
List<Document> documents = (List<Document>)query.list();
Collections.sort(documents);
return documents;
}
This method works, and brings back the correct results, however it seems to be pretty slow.
If I look at the table structure in the database, the Document table has parent ids (of course it does - else how could it join!), documentCategory_documentCategoryId and entity_entityId.
We all know that in SQL the correct results can be achieved without any joins at all. How can the same be done in HQL?
I have tried this: (Note the _ instead of .)
String q = "from Document d where d.documentCategory_documentCategoryId = :c and d.entity_entityId = :e";
but the property is not found.
org.hibernate.QueryException: could not resolve property: documentCategory_documentCategoryId of: com.foo.bar.entities.Document
Is there some way to reference the join fields instead of object references?

To avoid the joins use the identifier property .id:
String q = "from Document d where d.documentCategory.id = :c and d.entity.id = :e";
But since you also have the referenced objects you can even write a shorter version, using entity and category as parameters:
String q = "from Document d where d.documentCategory = :c and d.entity = :e";
Query query = session.createQuery(q);
query.setParameter("c", category);
query.setParameter("e", entity);
In both versions Hibernate is able to figure out that it actually does not need to join.

Related

Spring Data and native query with like statement

I have the following repository definition:
public interface InstructionRepository extends JpaRepositoryWithSpecification<Instruction> {
String FIND_INSTRUCTIONS_BY_TRACKING_ID_QUERY =
"SELECT * FROM instruction i WHERE i.invoice_documents_trackings like '%:trackingId%'";
#Query(value = FIND_INSTRUCTIONS_BY_TRACKING_ID_QUERY, nativeQuery = true)
List<Instruction> findByFileTrackingsContaining(#Param("trackingId") String trackingId);
}
The reason why I use native query here is because invoice_documents_trackings column represents a map serialized to json string. So basically I want to find all instructions that have particular trackingId stored in the invoice_documents_trackings map.
When I execute the method I always get 0 results despite the fact that If I execute the same query manually I get expected results.
I also tried to change the query so that it looks like:
String FIND_INSTRUCTIONS_BY_TRACKING_ID_QUERY =
"SELECT * FROM instruction i WHERE i.invoice_documents_trackings like %:trackingId%"
And this does not work either.
Would really appreciate any help, than
I think the issue is the way you're using %
String FIND_INSTRUCTIONS_BY_TRACKING_ID_QUERY =
"SELECT * FROM instruction i WHERE i.invoice_documents_trackings like CONCAT('%', :trackingId, '%')"

Problems mapping Hibernate entities - native query containing left join with condition

This should be straight-forward though can't get my Hibernate entities to play nice for the following scenario with a simple two table structure:
I'm attempting to get all config names and matching config values for a given currency code (and null's where not matching).. so have written a native query to retrieve the following like so:
SELECT * FROM CONFIG_NAME LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID =
CONFIG_VALUE.CONFIG_ID AND CONFIG_VALUE.CURRENCY_CODE = '<CURRENCY_CODE>'
ORDER BY CONFIG_NAME.ID
This query doesn't seem to play nice with my Hibernate mapping as it appears to be essentially ignoring the CURRENCY_CODE clause in the join.
Essentially, for the following subset of data:
CONFIG_NAME:
CONFIG_VALUE:
There is no value defined for 'FREE_SHIPPING_ENABLED' for 'USD' so running the query above for both currency code returns as expected:
QUERY RESULTS FOR 'CAD':
QUERY RESULTS FOR 'USD':
I'm running the above query as a native query in a JpaRepository for the ConfigName entity. But what I appear to be getting is that it seems to ignore the currency_code clause in the JOIN condition. As the list of config values defined has both values for USD and CAD where they're populated. Is there an Hibernate annotation to factor this in that I'm unaware of?
It's worth bearing in mind there will only ever be ONE value defined for each config for a given currency - there's a unique constraint across CONFIG_VALUE.CONFIG_ID/CONFIG_VALUE.CURRENCY_CODE so potentially ConfigValue on the ConfigName entity would not need to be a map.
Mappings as are follows:
ConfigName - Entity
#OneToMany(mappedBy = "config")
private Set<ConfigValue> configValue;
ConfigValue - Entity
#ManyToOne(optional = false)
#JoinColumn(name="CONFIG_ID")
#Property(policy=PojomaticPolicy.NONE)
private ConfigName config;
Doesn't need to be strictly unidirectional either.. as I'm only concerned with the values from the ConfigName entity either being populated or null.
Think I'm missing something simple, so hope someone can help.
EDIT: Am querying using JpaRepository:
Am using JpaRepository to query:
#Repository
public interface ConfigNameRepository extends JpaRepository<ConfigName, Long>
{
static final String SQL_QUERY = "SELECT * FROM CONFIG_NAME "
+ "LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID = CONFIG_VALUE.CONFIG_ID "
+ "AND CONFIG_VALUE.CURRENCY_CODE = ?1 ORDER BY CONFIG_NAME.ID";
#Query(value = SQL_QUERY, nativeQuery = true)
List<ConfigName> findConfigValuesByCurrencyCode(final String currencyCode);
}
As mentioned by #Ouney, your JPA relations are not taken in account if you use a native query.
You declared a SELECT * and List<ConfigName> (the real sql result contains ConfigName+ConfigValue). So with this query, Hibernate fetchs all the ConfigName. Then, when you try to access to the set of configValue, it fetchs all the related ConfigValue.
I think this should be better/easier to use a JPQL query instead (but you need Hibernate 5.1+) :
SELECT n, v
FROM ConfigName n
LEFT JOIN ConfigValue v
ON v.config = n AND v.currencyCode = :currencyCode
ORDER BY n.id
With this method signature :
List<Object[]> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
Where the result will be :
o[0] // ConfigName
o[1] // ConfigValue (nullable)
You may want to do this prettier with a wrapper :
SELECT new my.package.MyWrapper(n, v)
...
MyWrapper constructor :
public MyWrapper(ConfigName configName, ConfigValue configValue) {
...
}
Method signature with the wrapper :
List<MyWrapper> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
(update)
I think in this case, your query can be :
SELECT n, v // or new my.package.MyWrapper(n, v)
FROM ConfigName n
LEFT JOIN n.configValue v
WITH v.currencyCode = :currencyCode
ORDER BY n.id

JPA criteria API order by NULL last

I use JPA criteria API to fetch records from the datebase.
I have entity Record with field dateTime which can be null. I would code:
public List<Record> find(RecordFilter recordFilter, int page, int pageSize) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Record> criteriaQuery = criteriaBuilder.createQuery(Record.class);
Root<Record> recordRoot = criteriaQuery.from(Record.class);
/*
* JOINS. Left Joins are used for optional fields, or fields inside of the optional fields.
*/
Join<Record, Agency> recordAgencyJoin = recordRoot.join(RecordTable.FIELD_AGENCY);
//Some other joins
//This is where I had the problem.
applyOrderBy(criteriaQuery, criteriaBuilder, recordRoot);
/*
* Specify which columns to select and their order.
* criteriaQuery.multiselect(....);
*/
applyMultiSelect(recordRoot, recordAgencyJoin, /*other joins*/ criteriaQuery);
/*
* criteriaQuery.where(somePredicate);
*/
applyFilter(recordFilter, criteriaQuery, criteriaBuilder,
recordRoot, recordAgencyJoin /*, other joins*/);
TypedQuery<Record> query = entityManager.<Record>createQuery(criteriaQuery);
RepositoryUtils.applyPagination(query, page, pageSize);
return query.getResultList();
}
private void applyOrderBy(CriteriaBuilder criteriaBuilder, Root<Record> recordRoot, CriteriaQuery<Record> criteriaQuery) {
//Other fields to be added to the final sort.
Order dateTimeDescOrder = criteriaBuilder.desc(recordRoot.get(RecordTable.FIELD_DATE_TIME));
criteriaQuery.orderBy(dateTimeDescOrder /*, other orders by*/);
}
It turns out, records with NULL dateTimeField are shown first.
I use Postrgres database.
I will answer this question because I found a solution.
Here is a similar post.
JPA Criteria Query API and order by null last
Here I put an answer to this task.
First, Postgres by default returns nulls first.
SELECT * FROM record ORDER BY date_time_field DESC;
https://stackoverflow.com/a/7621232/4587961
SELECT * FROM record ORDER BY date_time_field DESC NULLS LAST;
Second, I had to change applyOrderBy method
private void applyOrderBy(CriteriaBuilder criteriaBuilder, Root<Record> recordRoot, CriteriaQuery<Record> criteriaQuery) {
//In the class code
//private static final Date MIN_DATE = new Date(0L);
final Date MIN_DATE = new Date(0L);
//We treat records will NULL dateTimeField as if it was MIN_DATE.
Order dateTimeDescOrder = criteriaBuilder.desc(
//NULL values - last - WORKAROUND.
criteriaBuilder.coalesce(recordRoot.get(RecordTable.FIELD_DATE_TIME), MIN_DATE));
criteriaQuery.orderBy(dateTimeDescOrder);
}
Note, CriteriaBuilder from hibernate-jpa-2.1.
/**
* Create an expression that returns null if all its arguments
* evaluate to null, and the value of the first non-null argument
* otherwise.
*
* #param x expression
* #param y value
*
* #return coalesce expression
*/
<Y> Expression<Y> coalesce(Expression<? extends Y> x, Y y);
There is nothing in the JPA spec to control how NULLS are handled (and all RDBMS have their preference for the default). This is not available either in JPQL (string based querying) or Criteria API. With JPQL, all major JPA providers allow use of
NULLS [FIRST|LAST]
in the ordering clause. For Criteria you are constrained by the Criteria API so nothing is possible. I know that DataNucleus JPA provide a custom version of the JPA Criteria API that allows specification of
Order order = criteriaBuilder.asc(myExpression).nullsFirst();
but clearly that is specific to that JPA provider.
There is nothing in JPA or Eclipselink, that you can use case in CriteriaBuilder.
Example:
Order order1 = cBuilder.desc(cBuilder.selectCase().when(cBuilder.isNotNull(from.get('ColumnName1')), from.get('ColumnName1')).otherwise(from.get('ColumnName1')));
CQuery.orderBy(order1);
It will give you best answer
I use JPA with eclipselink with Postgres, and here is namedquery which works as expected.
This is an excerpt :
,#NamedQuery(name = "ServerPasswords.forTheServer",
query = "SELECT ss FROM ServerPasswords ss WHERE ss.fkIds = :IDServer AND ss.private = FALSE "
+ "ORDER BY ss.servce, ss.active DESC, ss.datefrom DESC NULLS LAST, ss.dateto NULLS LAST")

Entity Manager Hibernate / Spring Roo Error - Cannot create TypedQuery

Using Spring Roo (but manually creating these methods so that may be of no relevance) I am trying to display two tables in one list. I have the below method to get all people with their usernames in the class people.java. I believe this method works correctly.
public static TypedQuery<Person> findAllPeople() {
String queryStr = "SELECT o.name, b.username FROM Person o INNER JOIN o.users b";
TypedQuery<Person> query = entityManager().createQuery(queryStr, Person.class);
return query;
}
Here is the code for the controller
#RequestMapping(produces = "text/html")
public String list(#RequestParam(value = "page", required = false) Model uiModel) {
uiModel.addAttribute("people", Person.findAllPeople());
return "people/list";
}
When I try to run this through a list.jspx I get the error "Cannot create TypedQuery for query with more than one return using requested result type [com.test.peopletest.Person]; "
Does anyone know how I can return this query with all the results output properly?
You are not querying for a Person object, you're query appears to return two strings
SELECT o.name, b.username...
In order to return a Person, you should have
SELECT o FROM Person o
Just from looking at your query, it looks like you can drill down to the users/usernames once you have your person object.
It seems like you can use something like ...
SELECT o FROM Person o FETCH JOIN o.users b
when you walk the person.users all the users are loaded from memory (no DB round trip) will be done.
Or, theoretically (read untested!!), you could
SELECT o.name, b.username FROM Person o FETCH JOIN o.users b
TypedQuery<String[]> query = entityManager().createQuery(queryStr, String[]);

Cast Hibernate result to a list of objects

I have a hibernate call in my DAO that looks like this
List<Associate> associate = (List<Associate>)session.createSQLQuery("SELECT * FROM associates WHERE fk_id = :id AND fk_associate_id = (SELECT id FROM users WHERE fk_user_type = 2)").setParameter("id", id).list();
I am getting an error saying that I cannot cast the resulting list to the model type Associate. I don't understand why this is happening. I am returning only the fields that are in the associates table.
You need to specify the class of entity the result should be converted to using addEntity(), because you are executing SQL query that doesn't know anything about entities:
List<Associate> associate = (List<Associate>) session.createSQLQuery(
"SELECT * FROM associates WHERE fk_id = :id AND fk_associate_id = (SELECT id FROM users WHERE fk_user_type = 2)")
.addEntity(Associate.class)
.setParameter("id", id).list();
See also:
18.1.2. Entity queries
It is possible to apply a ResultTransformer to native SQL queries, allowing it to return non-managed entities.
sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.setResultTransformer(Transformers.aliasToBean(CatDTO.class))
The above query will return a list of CatDTO which has been instantiated and injected the values of NAME and BIRTHNAME into its corresponding properties or fields.
source : Link

Categories

Resources