I am trying a sum function in spring specification as below:
#Override
public Predicate toPredicate(Root<Stock> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Path expression = root.get("qty");
query.select(builder.sum(expression));
return null;
}
The query I want to execute is:
SELECT SUM(o.qty) FROM Stock o;
But Spring is not creating a sum function and is executing this:
SELECT o.qty FROM Stock o
I checked so many Stack Overflow question but there is no answer in Specification way, most of the people use JPQL for #Query annotation for this. But my further query and design is very complex so I have to use Specification only. Because I need fully dynamic query.
There is similar question as well, and this as well.
I think this is not possible out of the box.
Here is the reason why.
I'm referring to Spring JPA 1.7.3 (source code can be found here: http://grepcode.com/snapshot/repo1.maven.org/maven2/org.springframework.data/spring-data-jpa/1.7.3.RELEASE/)
A JPA specification is used via JPA repository, e.g.:
myRepo.findAll(mySpec);
The source code for this function is:
/*
* (non-Javadoc)
* #see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(org.springframework.data.jpa.domain.Specification)
*/
public List<T> findAll(Specification<T> spec) {
return getQuery(spec, (Sort) null).getResultList();
}
Here is the first issue here: the return type and query type are bound to entity type T. In your case it means that return result is going to be a list of Stock entities.
Second issue is with the way select list is composed in getQuery() function:
/**
* Creates a {#link TypedQuery} for the given {#link Specification} and {#link Sort}.
*
* #param spec can be {#literal null}.
* #param sort can be {#literal null}.
* #return
*/
protected TypedQuery<T> getQuery(Specification<T> spec, Sort sort) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<T> query = builder.createQuery(getDomainClass());
Root<T> root = applySpecificationToCriteria(spec, query);
query.select(root);
if (sort != null) {
query.orderBy(toOrders(sort, root, builder));
}
return applyRepositoryMethodMetadata(em.createQuery(query));
}
As you can see this type of call explicitly selects root (which is of type T or Stock in your case) and since it's executed after applying specification to criteria query, it overrides whatever you do in specification.
There is a workaround, though.
You can extend an existing JPA repository class:
public class MyRepositoryImpl<T>
extends SimpleJpaRepository<T, Integer> implements MyRepository<T>
{
and add special methods with appropriate signatures to perform what you need:
public <P> P calcAggregate(EntitySpecification<T> spec, SingularAttribute<?,P> column)
{
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<P> query = criteriaBuilder.createQuery(column.getJavaType());
if (spec != null)
{
Root<T> root = query.from(getDomainClass());
query.where(spec.toPredicate(root, query, criteriaBuilder));
query.select(criteriaBuilder.sum(root.get(column.getName())));
}
TypedQuery<P> typedQuery = em.createQuery(query);
P result = typedQuery.getSingleResult();
return result;
}
Then use it as:
myRepo.calcAggregate(mySpec, Stock_.qty);
where Stock_ is a metamodel class for Stock entity.
This is a very simple example with just one field and without ability to select an aggregate operation. It can be extended to suit one's needs.
Related
I have an interface that enforces implementing classes to define and implement a function that returns an org.apache.lucene.search.Query. These classes create various queries like TermQuery, PhraseQuery, etc. Is it possible to take the org.apache.lucene.search.Query that gets returned and iterate over all of the queries and terms it's comprised of?
public interface BaseQuery {
public Query getQuery();
default Query toQuery() {
Query query = getQuery();
// iterate through Query and do things to each term
return query;
}
}
public class ContainsQuery implements BaseQuery {
#Override
protected Query getQuery() {
PhraseQuery.Builder queryBuilder = new PhraseQuery.Builder();
queryBuilder.add(new Term("field", "value");
return queryBuilder.build();
}
}
As you can't update the changes (setTerms or similar), not even in this implementation (PhraseQuery), maybe this works.
You could first retrieve them, and loop over them. Whatever modification you wish to do, update the term or create a new one, or even discard those unwanted.
Then, assign query to a newly constructed object with the modified terms. Something like a manual update/set for a Query object. In the example I just add the terms to the builder, but you could include the previous parameters as well (slop, ...).
default Query toQuery()
{
Query query = getQuery();
Term[] terms= query.getTerms();
List<Term> modifiedTerms = new ArrayList<>();
for (Term t : terms)
{
/*Your modifications here f.e --> you copy one, create two terms and discard one
Term toAdd=null;
toAdd= t.clone();
...
toAdd = new Term((t.field()+"suffix"), t.text());
...
toAdd = new Term("5","6");
...
(do nothing-discard)
if (toAdd!=null)
modifiedTerms.add(toAdd);*/
}
PhraseQuery.Builder builder = new PhraseQuery.Builder();
for (int i=0;i<modifiedTerms.size();i++)
builder.add(modifiedTerms.get(i),i);
query = builder.build();
return query;
}
/* override the reference and assign a new one very similar to s
set/update, if those where implemented.
The last query will be erased on the next GC
so there's not a big gap here. Also this is most surely
a not needed micro-optimization, no worries. */
The way to do this is using the QueryTermExtractor.
WeightedTerm[] terms = QueryTermExtractor.getTerms(query);
for (WeightedTerm term : terms) {
System.out.println("THE TERM: " + term.getTerm());
}
The issue I was having is that all examples I found were calling .getTerms() on a org.apache.lucene.search.Query but .getTerms() seems to no longer be implemented on the base Query class.
Also, #aran's suggested approach of constructing a new Query object is an appropriate method to "modifying" the terms on an already constructed immutable Query object.
I'm writing a dynamic query using hibernates criteria language. I'm stunned as I'm unable to find any information regarding restricting the root entity dynamically (i.e. without specifying the id property).
I have an interface IEntity. There are several entities implementing IEntity. Some of them have an ID-property id while others have another ID-property (shadowId).
I want to write a single method covering both cases. Here is what I got so far:
#Override
public boolean querySomething(final IEntity ent) {
final Criteria criteria =
currentSession().createCriteria(HibernateUtils.deproxy(ent.getClass()));
criteria.createAlias("sharedProperty", "prop");
//This does not work:
criteria.add(Property.forName("this").eq(ent));
criteria.setProjection(Projections.count("prop.anotherProperty"));
final Number result = (Number) criteria.uniqueResult();
return result != null && result.longValue() > 0;
}
I would like to avoid to have a if statement like
if (ent instanceof TypeWithPropertyId){
criteria.add(Property.forName("id").eq(ent));
} else {
criteria.add(Property.forName("shadowId").eq(ent));
}
Edit
Note: HibernateUtils.deproxy(Class<?> clazz) removes any Proxy and returns the original class.
As of now, I didn't find anything in the criteria api which could help me. But I found IdentifierEqExpression by accident. Sadly IdentifierEqExpression still needs the ID Value and can not extract it itself. So for the moment I'm using a custom extension:
public class ThisEqualsExpression extends IdentifierEqExpression {
public ThisEqualsExpression(final Object value, final SessionFactory sf) {
super(sf.getClassMetadata(deproxy(value.getClass())).getIdentifier(value, null));
}
}
I would prefer not using this hack-ish approach, but I still like this better than an if-statement:
public boolean querySomething(final IEntity ent) {
final Criteria criteria =
currentSession().createCriteria(HibernateUtils.deproxy(ent.getClass()));
criteria.createAlias("sharedProperty", "prop");
//This does work:
criteria.add(new ThisEqualsExpression(ent, currentSession().getSessionFactory()));
criteria.setProjection(Projections.count("prop.anotherProperty"));
final Number result = (Number) criteria.uniqueResult();
return result != null && result.longValue() > 0;
}
Edit 2:
As requested some clarification:
In pure SQL, i have something like
SELECT COUNT(bar.property)
FROM Foo foo
INNER JOIN Bar bar ON foo.bar_id=bar.id
But I want something like
SELECT COUNT(bar.property)
FROM Foo foo
INNER JOIN Bar bar ON foo.bar_id=bar.id
WHERE foo.id=<some ID> --This is the important part
Back to Java:
Foos ID-Property depends on its type. It may be id (which I used in the SQL example above), but it could also be some other property. I was wondering if there was a way to restrict a criterias root entity without knowing the propertys name.
I have the following in a Question entity:
#NamedQuery(name = "Question.allApproved",
query = "SELECT q FROM Question q WHERE q.status = 'APPROVED'")
and
#Enumerated(EnumType.STRING)
private Status status;
// usual accessors
I am getting this exception:
Exception Description: Error compiling the query
[Question.countApproved: SELECT COUNT(q) FROM Question q WHERE q.status = 'APPROVED'], line 1, column 47: invalid enum equal
expression, cannot compare enum value of type
[myCompnay.application.Status] with a non enum value
of type [java.lang.String]. at
org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:501)
How do I fix this?
I think you should use your (fully qualified) Status enum instead of literal value, so something like this: (assuming your Status enum is in com.myexample package)
#NamedQuery(name = "Question.allApproved",
query = "SELECT q
FROM Question q
WHERE q.status = com.myexample.Status.APPROVED").
4 years since the initial post, there are some developments. Using spring 4 and Hibernate 4 it's now possible to 'trick' Hibernate using a SpEL expression. For example:
The enum:
package com.mycompany.enums
public enum Status {
INITIAL, PENDING, REJECTED, APPROVED, SHIPPED, DELIVERED, COMPLETE;
}
Here's a wrapper class called 'Filter' which we'll pass to the repository filtering method.
package com.mycompany.enums
public class Filter implements Serializable {
/** The id of the filtered item */
private Integer id;
/** The status of the filtered item */
private Status status;
// more filter criteria here...
// getters, setters, equals(), hashCode() - omitted for brevity
/**
* Returns the name of the status constant or null if the status is null. This is used in the repositories to filter
* queries by the status using a the SPEL (T) expression, taking advantage of the status qualified name. For example:
* {#code :#{T(com.mycompany.enums.Status).#filter.statusName}}
*
* #return the status constant name or null if the status is null
*/
public String getStatusName() {
return null == status ? status : status.name();
}
}
Finally, in the repository, we can now use the Filter class as the single parameter and make the query translate what appears to be a mixture of literals and SpEL expressions to a Status object:
The repository:
package com.mycompany.repository
#Repository
public interface OrderRepository extends CrudRepository<Order, Integer> {
#Query("SELECT o from Order o "
+ "WHERE o.id = COALESCE(:#{#filter.id},o.id) "
+ "AND o.status = COALESCE(:#{T(com.mycompany.enums.Status).#filter.statusName},o.status)")
public List<Order> getFilteredOrders(#Param(value = "filter") Filter filter);
}
This works perfectly, but for some odd reason I haven't figured out yet, if you enable SQL debugging in Hibernate and turn on the binder logging, you'll not be able to see Hibernate binding this expression to query variables.
Please use below property in application.properties
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
I want to know whether a given record is present in a database or not. so far I have achieved this by writing a JPA query and the running it by getSingleResult() method. this would throw a NoResultException if the record with the given parameter does not exist. Of course, it's not a must for the record to exist, so it's the normal behaviour sometimes, that's why I asked to myself, is it neccessary to throw an Exception which I have to handle by a catch block? As far as I know the cost of Exception handling is quite big, so I'm not very satisfied with this solution, also, I don't even need the object, I only need to know it's existence in the DB.
Is there a better way to check whether an object exist or not? eg. using getResultList() and checking it's size maybe?
If you just want to know whether the object exists, send a SELECT COUNT to your database. That will return 0 or 1.
The cost of the exception handling isn't that big (unless you do that millions of times during a normal operation), so I wouldn't bother.
But the code doesn't really reflect your intention. Since getSingleResult() calls getResultList() internally, it's clearer like so:
public boolean objExists(...) {
return getResultList(...).size() == 1;
}
If you query by object id and you have caching enabled, that will become a simple lookup in the cache if the object has already been loaded.
Try to avoid loading the entity into the session (getSingleResult()) just to check for it's existence. A count is better here. With the Criteria Query API it would look something like this:
public <E extends AbstractEntity> boolean exists(final Class<E> entityClass, final int id) {
final EntityManager em = getEntityManager();
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<Long> cq = cb.createQuery(Long.class);
final Root<E> from = cq.from(entityClass);
cq.select(cb.count(from));
cq.where(cb.equal(from.get(AbstractEntity_.id), id));
final TypedQuery<Long> tq = em.createQuery(cq);
return tq.getSingleResult() > 0;
}
Simply use count(e) in your query, so no NoResultException will be thrown and you will avoid loading the entity object
So the Java code can be as follow:
public boolean isRecordExist() {
String query = "select count(e) from YOUR_ENTITY e where ....";
// you will always get a single result
Long count = (Long) entityManager.createQuery( query ).getSingleResult();
return ( ( count.equals( 0L ) ) ? false : true );
}
Hope that helps someone :)
If you are searching by primary key you can also use Entitymanger.find(entityClass, primaryKey) which returns null when the entity does not exist.
here is a generic approach to work with a type T and an arbitrary ID value
public boolean exists(Object key) {
EntityManager entityManager = getEntityManager();
Metamodel metamodel = entityManager.getMetamodel();
EntityType<T> entity = metamodel.entity(entityClass);
SingularAttribute<T, ? extends Object> declaredId = entity.getDeclaredId(key.getClass());
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
javax.persistence.criteria.CriteriaQuery<T> cq = cb.createQuery(entityClass);
Root<T> from = cq.from(entityClass);
Predicate condition = cb.equal(from.get(declaredId), key);
cq.where(condition);
TypedQuery<T> q = entityManager.createQuery(cq);
return q.getResultList().size() > 0;
}
Try this
public boolean exists(Object id){
return getEntityManager().find(entityClass, id)!=null;
}
A much easier solution to this problem is expanding the repository with a function of the return type Optional<Datatype>. Using this within a stream with a filter applied, you can easily check if the object exists with .isPresent()
For example:
public interface ChatArchiveRepo extends JpaRepository<ChatArchive, Long> {
List<ChatArchive> findByUsername(String username);
Optional<ChatArchive> findByConversationUid(String conversationUid);
}
And within your function or stream:
.filter(conversation -> !chatArchiveRepo.findByConversationUid(conversation.getUid()).isPresent())
I would just add a custom method in jpa repository
#Query("SELECT COUNT(ID) FROM 'name' WHERE transactionId =:id")
int findIfTxnPresentById(#Param("id") Long txnId);
I have a Person class which has a String collection of aliases representing additional names that person may go by. For example, Clark Kent may have aliases "Superman" and "Man of Steel". Dwight Howard also has an alias of "Superman".
#Entity
class Person {
#CollectionOfElements(fetch=FetchType.EAGER)
Set<String> aliases = new TreeSet<String>();
Hibernate creates two tables in my database, Person and Person_aliases. Person_aliases is a join table with the columns Person_id and element. Let's say Person_aliases has the following data
--------------------------------
| Person_id | element |
--------------------------------
| Clark Kent | Superman |
| Clark Kent | Man of Steel |
| Dwight Howard | Superman |
| Bruce Wayne | Batman |
--------------------------------
I want to make a hibernate Criteria query for all persons who go by the alias of "Superman".
For reasons too long to list here, I'd really like to make this a Criteria query, not an HQL query (unless it's possible to add an HQL restriction on a Criteria object, in which case I'm all ears) or a raw SQL query. Since according to How do I query for objects with a value in a String collection using Hibernate Criteria? it is impossible to refer to elements of value-type collections using the CriteriaAPI I thought I'd resort to adding an SqlRestriction on my criteria object.
Criteria crit = session.createCriteria(Person.class);
crit.add(Restrictions.sqlRestriction("XXXXX.element='superman'");
in the hopes that Hibernate will create an SQL statement like
select *
from
Person this_
left outer join
Person_aliases aliases2_
on this_.id=aliases2_.Person_id
where
XXXXX.element='superman'
However, I need to fill in the XXXXX with the table alias for the Person_aliases table in the SQL query, which in this case would be 'aliases2_'. I noticed that if I needed the reference to the Person table alias I could use {alias}. But this won't work because Person is the primary table for this Criteria, not Person_aliases.
What do I fill in for the XXXXX? If there is no nice substition token like {alias} then is there a way I could get hibernate to tell me what that alias is going to be? I noticed a method called generateAlias() org.hibernate.util.StringHelper class. Would this help me predict what the alias would be?
I'd really, really like to avoid hard coding 'aliases2_'.
Thanks for your time!
as xmedeko alludes to, when you want to do:
crit.add(Restrictions.sqlRestriction(
"{alias}.joinedEntity.property='something'"));
you need to instead do:
crit.createCriteria("joinedEntity").add(Restrictions.sqlRestriction(
"{alias}.property='something'"));
This has solved similar problems for me without going to HQL
It seems that the Criteria API doesn't allow to query collections of elements, see HHH-869 (which is still open). So either try the suggested workaround - I didn't - or switch to HQL. The following HQL query would work:
from Person p where :alias in elements(p.aliases)
try to create another Criteria like
Criteria crit = session.createCriteria(Person.class, "person");
Criteria subC = crit.createCriteria("Person_aliases", "Person_aliases");
subC.add(Restrictions.sqlRestriction("{alias}.element='superman'");
May this link help you? It advices:
List persons = sess.createCriteria(Person.class)
.createCriteria("company")
.add(Restrictions.sqlRestriction("companyName || name like (?)", "%Fritz%", Hibernate.STRING))
.list();
The question is actually quite old, but since I encountered the same problem today and no answer satisfied my needs, I came up with the following solution, based on the comment by Brett Meyer on HHH-6353, that this issues won't be fixed.
Basically, I extended the SQLCriterion class to be able to handle more than the base table alias. For convenience reasons I wrote a small container class that links the user given alias with the matching subcriteria instance to be able to replace the user given alias with the alias hibernate created for the subcriteria.
Here is the code of the MultipleAliasSQLCriterion class
public class MultipleAliasSQLCriterion extends SQLCriterion
{
/**
* Convenience container class to pack the info necessary to replace the alias generated at construction time
* with the alias generated by hibernate
*/
public static final class SubCriteriaAliasContainer
{
/** The alias assigned at construction time */
private String alias;
/** The criteria constructed with the specified alias */
private Criteria subCriteria;
/**
* #param aAlias
* - the alias assigned by criteria construction time
* #param aSubCriteria
* - the criteria
*/
public SubCriteriaAliasContainer(final String aAlias, final Criteria aSubCriteria)
{
this.alias = aAlias;
this.subCriteria = aSubCriteria;
}
/**
* #return String - the alias
*/
public String getAlias()
{
return this.alias;
}
/**
* #return Criteria - the criteria
*/
public Criteria getSubCriteria()
{
return this.subCriteria;
}
}
private final SubCriteriaAliasContainer[] subCriteriaAliases;
/**
* This method constructs a new native SQL restriction with support for multiple aliases
*
* #param sql
* - the native SQL restriction
* #param aSubCriteriaAliases
* - the aliases
*/
public MultipleAliasSQLCriterion(final String sql, final SubCriteriaAliasContainer... aSubCriteriaAliases)
{
super(sql, ArrayHelper.EMPTY_OBJECT_ARRAY, ArrayHelper.EMPTY_TYPE_ARRAY);
this.subCriteriaAliases = aSubCriteriaAliases;
}
#Override
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException
{
// First replace the alias of the base table {alias}
String sql = super.toSqlString(criteria, criteriaQuery);
if (!ArrayUtils.isEmpty(this.subCriteriaAliases))
{
for (final SubCriteriaAliasContainer subCriteriaAlias : this.subCriteriaAliases)
{
sql = StringHelper.replace(sql, subCriteriaAlias.getAlias(), criteriaQuery.getSQLAlias(subCriteriaAlias.getSubCriteria()));
}
}
return sql;
}
}
I use it like this
final String sqlRestriction = "...";
final String bankAccountAlias = "ba";
final Criteria bankAccountCriteria = customerCriteria.createCriteria("bankAccount", bankAccountAlias);
SubCriteriaAliasContainer bankAccountSubAliasCon = new SubCriteriaAliasContainer(bankAccountAlias, bankAccountCriteria);
customerCriteria.add(new MultipleAliasSQLCriterion(sqlRestriction, bankAccountSubAliasCon));
But there is no need to specify the alias at criteria creation - you can also specify it at the SQL restriciton and pass it to a container.
final String sqlRestriction = "... VALUES(ba.status_date), (ba.account_number) ...";
final Criteria bankAccountCriteria = customerCriteria.createCriteria("bankAccount");
SubCriteriaAliasContainer bankAccountSubAliasCon = new SubCriteriaAliasContainer("ba", bankAccountCriteria);
customerCriteria.add(new MultipleAliasSQLCriterion(sqlRestriction, bankAccountSubAliasCon));
See this Hibernate bugs and use attached files:
https://hibernate.atlassian.net/browse/HHH-2952
https://hibernate.atlassian.net/browse/HHH-2381
org.hibernate.criterion.CriteriaQuery has a method getColumnsUsingProjection which gives you the aliased column name.
You could implement your own Criterion, using org.hibernate.criterion.PropertyExpression as an example.
public class Products {
private Brands brand;
...
}
public class Brands {
private long id;
...
}
...
DetachedCriteria dc=DetachedCriteria.forClass(Products.class, "prod");
dc.add(Restrictions.ge("prod.brand.id", Long.parseLong("12345")));