JPA criteria API order by NULL last - java

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

Related

How do you specify a column by it's native name in an SQL Selection?

I am writing an application with Spring 4 and Hibernate 5.1. I need to be able to interoperate with a legacy system that saves SQL queries using the native table and column names. I need to be able to add a Selection object to my Tuple query which uses the original column name rather then the entity field name.
I tried doing this using Hibernate with Projections.sqlProjection(column_name... and that sort of worked, but other issues are preventing me from continuing in this direction.
#Transactional(propagation = Propagation.MANDATORY)
public List<Object[]> testQuery() {
Class<?> rootClass = getEntityClassByTable("pm_project");
List<String> columnList = new ArrayList<>();
columnList.add("pm_project.pm_project_id");
columnList.add("pm_project.pm_project_name");
columnList.add("pm_project.pm_project_title");
columnList.add("pm_project.parent_id");
columnList.add("pm_project.pm_project_from_date");
CriteriaQuery<Tuple> query = criteriaBuilder.createTupleQuery();
Root<?> root = query.from(rootClass);
List<Selection> selectionList = new ArrayList<>();
for (String column : columnList) {
Selection s = nativeSelection(column);
selectionList.add(s);
}
query.multiselect(selectionList.toArray());
List<Tuple> resultList = em.createQuery(query).getResultList();
return resultList;
}
I need 'NativeSelection', something that produces a Selection for a native column name, not an entity attribute name. It would be really good if it took any sql that could be a field, because some JSON_VALUE fields might pop up.
The values of the native sql are dynamic, and I am not receiving a full SQL, only column and table names, with possible JSON_VALUE calls. I would very much like not to have to generate native sql, as I want to leverage other JPA features in my query.
If you are using native query then no need to specify entity attribute names. All you have to do is to set the value of the nativeQuery attribute to true and define the native SQL query in the value attribute of the annotation
#Query(
value = "SELECT * FROM pm_project p WHERE p.pm_project_id= 1",
nativeQuery = true)
Collection<Project> findProjectById();
You can also do it without using native query by simple JPA
List<Project> findAllByProjectId(Integer id);

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

HQL and joins - a faster way?

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.

Hibernate criteria Using GROUP BY and RETURN ENTITY LIST

I'm trying to use GROUP BY in my criteria. I need to do this:
SELECT b FROM Book b GROUP BY volumeCode;
I have following code:
Criteria c = s.createCriteria(Book.class);
c.setProjection(Projections.projectionList().add(Projections.groupProperty("volumeCode")));
List<Book> result = c.list();
But this criteria returns only volumeCodes (a list of Strings). I need to get a list of Books. So I tried to use Transformers:
Criteria c = s.createCriteria(Book.class);
c.setProjection(Projections.projectionList().add(Projections.groupProperty("volumeCode")));
c.setResultTransformer(Transformers.aliasToBean(Book.class));
List<Book> result = c.list();
This code returns list of null values. Is it possible to do that with criteria?
First of all, the projecton filters the amount of data retrieved, if you want more data, you should add those properties to the projection too.
Example:
c.setProjection( Projections.projectionList()
.add( Projections.property("id").as("id") )
.add( Projections.property("descripction").as("description") )
.add( Projections.groupProperty("volumeCode").as("volumeCode") ));
Now, the transformer does what it says "Alias to Bean", it does an alias match with the properties of your java bean "Book.java".
Edit:
Without the transformer, if the projection has more than one property, the result comes out like this:
for(Object[] item:criteria.list()){
System.out.println( (String)item[0] ); //ID
System.out.println( (String)item[1] ); //Description
System.out.println( (String)item[2] ); //Volume code
}
Thats why you were getting the cast exception, about the transformer, try to match every alias with the property name of your java bean.
cz_Nesh.
sorry about my first answer.
i read Hibernate api and read some Hibernate source code i find that.
if you use this code
session.createCriteria(EmpUserImpl.class).list();
it will return List EmpUserImpl.
if you use this code
criteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("company").as("company"))
.add(Projections.property("name").as("name"))
.add(Projections.property("company").as("company")));
List list = criteria.list();
it will return List ,is not List EmpUserImpl why?
i see the criterion's parent class CriteriaSpecification i find that .
public interface CriteriaSpecification {
/**
* The alias that refers to the "root" entity of the criteria query.
*/
public static final String ROOT_ALIAS = "this";
/**
* Each row of results is a <tt>Map</tt> from alias to entity instance
*/
public static final ResultTransformer ALIAS_TO_ENTITY_MAP = AliasToEntityMapResultTransformer.INSTANCE;
/**
* Each row of results is an instance of the root entity
*/
public static final ResultTransformer ROOT_ENTITY = RootEntityResultTransformer.INSTANCE;
/**
* Each row of results is a distinct instance of the root entity
*/
public static final ResultTransformer DISTINCT_ROOT_ENTITY = DistinctRootEntityResultTransformer.INSTANCE;
/**
* This result transformer is selected implicitly by calling <tt>setProjection()</tt>
*/
public static final ResultTransformer PROJECTION = PassThroughResultTransformer.INSTANCE;
/**
* Specifies joining to an entity based on an inner join.
*
* #deprecated use {#link org.hibernate.sql.JoinType#INNER_JOIN}
*/
#Deprecated
public static final int INNER_JOIN = JoinType.INNER_JOIN.getJoinTypeValue();
/**
* Specifies joining to an entity based on a full join.
*
* #deprecated use {#link org.hibernate.sql.JoinType#FULL_JOIN}
*/
#Deprecated
public static final int FULL_JOIN = JoinType.FULL_JOIN.getJoinTypeValue();
/**
* Specifies joining to an entity based on a left outer join.
*
* #deprecated use {#link org.hibernate.sql.JoinType#LEFT_OUTER_JOIN}
*/
#Deprecated
public static final int LEFT_JOIN = JoinType.LEFT_OUTER_JOIN.getJoinTypeValue();
}
can you see the public static final ResultTransformer PROJECTION ? it say that This result transformer is selected implicitly by calling setProjection()
is mean when you use criteria.setProjection,the result will not List EmpUserImpl,because ResultTransformer is change to "PROJECTION" from "ROOT_ENTITY".it will packaging by Projection(like select name,oid .. ).
so, if you want to return List EmpUserImpl you need set Projections.property("name").as("name").,(if you need name just set name).
this is my code .
Criteria criteria = session.createCriteria(EmpUserImpl.class);
criteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("company").as("company"))
.add(Projections.property("name").as("name"))
.add(Projections.property("company").as("company")));
criteria.setResultTransformer(Transformers.aliasToBean(EmpUserImpl.class));
List<EmpUserImpl> list = criteria.list();
for (EmpUserImpl empUserImpl : list) {
System.out.println(empUserImpl.getName());
}
it can work . i hope it can help you.
I think you can use : criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

CriteriaBuilder join / subquery with resulting Integer

I'm trying to create a query using CriteriaBuilder to select all Product with a stock greater than zero. Stock is sum(DeliveryRow.amount) - sum(DispatchRow.amount). Both ofcourse only containing the right Product.
I have tried creating Subquery for both DeliveryRow and DispatchRow though I feel like this should be done using a join().
Classes
Product {
(...)
}
DeliveryRow {
#ManyToOne
private Product product;
private int amount;
}
DispatchRow {
#ManyToOne
private Product product;
private int amount;
}
Query
In this query I'm not sure how to handle the xxx. I've tried making to subqueries but that didn't work out.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Product> query = cb.createQuery(Product.class);
Root product = query.from(Product.class);
query.select(product);
// sum of DeliveryRow.amount where DeliveryRow.product = Product
// minus
// sum of DispatchRow.amount where DispatchRow.product = Product
Expression stock = xxx;
query.where(cb.gt(stock, Integer.parseInt(0)));
return em.createQuery(query).getResultList();
Any suggestions on how to solve this?
I have recently been doing research on JPA/JPQL, studying the three different approaches for retrieving entities: NamedQueries, em.CreateQuery and CriteriaBuilder. The CriteriaBuilder in my opinion is the most awkward of the three to use. I would recommend creating a NamedQuery to handle this situation, it will be a lot easier to implement and read.
Using this JPQL expression you could retrieve all of the products with a stock greater than zero:
SELECT p.name, SUM(delRow.amount) - SUM(disRow.amount)
FROM Product p join p.deliveryRows delRow join p.dispatchRows disRow
HAVING SUM(delRow.amount) - SUM(disRow.amount) > 0
/* This assumes product has a Collection<DispatchRow> named dispatchRows
and a Collection<DeliveryRow> named deliveryRows.
*/
Make this a named query in the `Product' entity
//This should be concatenated or on one line
#NamedQuery(name="Product.hasStock"
query="SELECT p.name, SUM(delRow.amount) - SUM(disRow.amount)
FROM Product p join p.deliveryRows delRow join p.dispatchRows disRow
HAVING SUM(delRow.amount) - SUM(disRow.amount) > 0");
Then execute this query with an EntityManager
#PersistenceContext
EntityManager em;
public void execute(){
List<Object[]> products =
em.createNamedQuery("Product.hasStock").getResultList();
/* Projections return a List<Object[]> where position 1 in the object array
corresponds with the first field in the select statement, position two
corresponds with the second field and so on... These can also be strongly typed
if an object is created and the constructor is specified in JPQL statement
*/
}
I know this is a different approach than using the Criteria API, but in my opinion JPQL queries are vastly superior to the Criteria API. Compared to the JPQL syntax, which is very similar to SQL the API felt less concise and intuitive. If you decide to take this route, I have created a video tutorial that demonstrates #NamedQueries and shows how to strongly type the results of queries containing projections. It can be found here.

Categories

Resources