Hibernate criteria: why no Subqueries.isNull(DetachedCriteria)? - java

I want to translate a script like this into criteria:
SELECT ...
FROM A
WHERE
A.some_date > (select bdate from B where ...)
OR (select bdate from B where ...) IS NULL
So, an A should be returned if either A.Some_date > B.bdate or if B.bdate is NULL.
I was expecting there to be a Subqueries.notNull(DetachedCriteria) (like there is a SubQueries.notExists(DetachedCriteria)) but this method does not exist nor did I find something else to pull this off.
I could of course work around this by returning a count and check if this is > 0 or such but then I need to write 2 identical (except for the Projection) DetachedCriteria's.
Does anyone know if/how to have the is NULL check for the above case or why this isn't provided in the Hibernate criteria API? Perhaps there's a good reason...

class MySubqueries:
public class MySubqueries {
public static Criterion isNull(DetachedCriteria dc) {
return new IsNullSubqueryExpression(null, null, dc);
}
}
class IsNullSubqueryExpression:
public class IsNullSubqueryExpression extends SubqueryExpression {
protected IsNullSubqueryExpression(String op, String quantifier, DetachedCriteria dc) {
super(op, quantifier, dc);
}
#Override
protected String toLeftSqlString(Criteria criteria, CriteriaQuery criteriaQuery) {
return "";
}
#Override
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
return super.toSqlString(criteria, criteriaQuery) + " IS NULL";
}
}
use:
detachedCriteria.add(MySubqueries.isNull(subDetachedCriteria))

I think
Subqueries.eq(null, yourDetachedCriteria)
should work.

Related

JPA EntityManager createQuery() error

This is failing:
public List<TypeActionCommerciale> requestTypeActionCommercialeSansNip() throws PersistenceException {
Query query = createQuery("from TypeActionCommercialeImpl where type != :type1");
query.setParameter("type1", TypeActionCommercialeEnum.NIP);
return (List<TypeActionCommerciale>) query.list();
}
exception:
Hibernate: select typeaction0_.id as id1_102_, typeaction0_.libelle as
libelle3_102_, typeaction0_.code as code4_102_, typeaction0_.type as
type5_102_ from apex.typeActionCommerciale typeaction0_ where
typeaction0_.type<>?
ERROR
org.hibernate.engine.jdbc.spi.SqlExceptionHelper.logExceptions(SqlExceptionHelper.java:129)
No value specified for parameter 1 org.hibernate.exception.DataException: could not extract ResultSet at
i use setProperties but i have the same error:
public List<TypeActionCommerciale> requestTypeActionCommercialeSansNip() throws PersistenceException {
Query query = createQuery("from TypeActionCommercialeImpl where type <> :type1");
final Map<String, Object> properties = new HashMap<>();
properties.put("type1", TypeActionCommercialeEnum.NIP);
query.setProperties(properties);
return (List<TypeActionCommerciale>) query.list();
}
The problem is here query.setParameter("type1", TypeActionCommercialeEnum.NIP);
The enum type is not defined in hibernate so you must store the name of enum and use it for the query (the easy way) then use:
query.setString("type1", TypeActionCommercialeEnum.NIP.name());
To use enum directly (the hard way) you must implement your CustomUserType . You can find here how https://docs.jboss.org/hibernate/orm/5.0/manual/en-US/html/ch06.html#types-custom
The main advantages of use CustomUserType are:
you can store into DB an integer (that is more smaller) instead of a string that represents the enum.
Delegate the parsing to hibernate during storing and retrieving of object.
You can use the enum directly into the query (like you are trying to do)
Try to use <> instead of != like this:
"from TypeActionCommercialeImpl where type <> :type1"
i resolve my pb i have a class public class EnumUserType<E extends Enum<E>> implements UserType and i implement this method:
#Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException {
String name = rs.getString(names[0]);
Object result = null;
if (!rs.wasNull()) {
result = Enum.valueOf(clazz, name);
}
return result;
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)
throws HibernateException, SQLException {
if (null == value) {
st.setNull(index, Types.VARCHAR);
} else {
st.setString(index, ((Enum<E>) value).name());
}
}

Get parameter name of class to pass it to other method in java

I would like to pass parameter name as a parameter to other method, f.e:
I have class:
public class Foo() {
public Bar bar;
public Bar anotherBar;
public Bar yetAnotherBar;
public void doSomethingWithBar() {
common.doingSomething(
getMostImportantBarParameterName()
);
}
}
And in this class I would to have method:
public String getMostImportantBarParameterName() {
return Foo.bar;
}
but instead of returning value of bar, I would like to get a name of parameter bar, so it should just return "bar".
For now I have to do this that way:
public String getMostImportantBarParameterName() {
return "bar";
}
Why I wanna achieve something like that?
I am trying as much I can to avoid using strings in my code, cause in refactorization process I will bypass (skip) it accidentally.
But if I will have "hard coded" parameters that way, when I will later rename this parameter it will be automatically replaced in all instances by Eclipse IDE (Using LALT+LSHIFT+R)
Also my method: common.doingSomething() use parameter in runtime, So I won't get compilation error, which it makes hard to maintain this method.
I don't write unit test, cause I can't yet.
Please give me some help on this. Thanks
----------------- EDIT ------------------------
Real life usage.
I would like to have method to access database records in generic way.
Common database operation in my application is:
Getting records from TableName where Parameter = SomeValue
So I would like to have generic method for that in generic entity listed below:
#MappedSuperclass
public abstract class GenericModel<T extends GenericModel> {
#Transient protected Class<T> entityClass;
private List<T> getByParameterAndValue(String parameter, String value) {
List<T> entities = new ArrayList<T>();
String sqlString = "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e."+ parameter + " = :value";
TypedQuery<T> query = JPA.em().createQuery(sqlString, entityClass).setParameter("value", value);
try {
entities = query.getResultList();
} catch (NoResultException e1) {
entities = null;
} catch (Exception e) {
Index.toLog("error","Unsupported error in Generic model class in " + entityClass);
}
return entities;
}
which is extended by real entities f.e.:
public class User extends GenericModel<User> {
public String name;
public String email;
public String date;
public String department;
public List<User> getUsersByDepartments(String dep) {
return getByParameterAndValue("department", dep);
}
}
The problem is that in JPA TypedQuery:
TypedQuery<User> query = em.createQuery("SELECT u FROM User u WHERE u.department = :department", User.class);
return query.setParameter("department", department).getSingleResult();
First of all, I think you should reconsider your approach. Using field names like this (either by reflection or hard coded Strings) is not very robust. In general, reflection should be avoided if possible.
What are you trying to achieve? What will common.doingSomething be doing with the field name?
It might be better to model the importance explicitly with an accessor:
class Foo {
private Bar bar;
private Bar anotherBar;
private Bar yetAnotherBar;
public Bar getMostImportantBar() {
return bar;
}
}
To answer your question about generics. You can either select the field by its index or by its name. Both are not robust, for when you change the field name, the String used to get it via reflection will not change with it, and if you change the order of the fields, the index will be wrong.
Here's how to do it:
Class foo = Foo.class;
Field[] fields = foo.getFields();
// get by index
Field firstField = fields[0];
String firstFieldName = firstField.getName();
// get by name
Field barField = foo.getField("bar");
String barFieldName = barField.getName();
EDIT (after reading updated question):
In any Object Relational Mapping solution there is a boundary where the object-oriented realm ends and the relational realm begins. With your solution you are pulling that boundary a bit further into your code, in order to gain ease of use for your specific model classes and queries. The consequence of that is that you get more 'boiler plate' style code as part of your application (the GenericModel class) and that the boundary becomes more visible (the reference to a field by index or name using reflection). This type of code is generally harder to understand, test and maintain. On the other hand, once you get it right it doesn't change that often (if your assumption about the query type you usually need turns out to be valid).
So I think this is not a ridiculous use case for reflection, even though I myself would probably still stick to JPA and accept the similarity of the queries. With a good JPA framework, expressing these queries does not incur a lot of code.
About the hard-coded field names vs indexes, I advise you to go with the field names because they are easier to understand and debug for your successors. I would make sure the field name is expressed in the model class where the field resides, to make it as clear as possible that the two belong together, similar to the example you gave:
public class User extends GenericModel<User> {
public static final String FIELD_NAME = "name";
public static final String FIELD_EMAIL = "email";
public static final String FIELD_DATE = "date";
public static final String FIELD_DEPARTMENT = "department";
private String name;
private String email;
private String date;
private String department;
// the byXXX naming scheme is a quite common shorthand for lookups
public List<User> byDepartment(String department) {
return getByParameterAndValue(FIELD_DEPARTMENT, department);
}
BTW I think getByParameterAndValue cannot be private (must be at least default). Also I don't think you should initialize List<T> entities = new ArrayList<T>() at the start. You can do that in the catch(Exception e) to avoid unnecessary initialization if the query succeeds or returns no results. An your fields should be private (shown above).
Of course, this approach still results in one lookup method for each field. A different solution is to create a service for this and leave the model objects aenemic (without behavior):
public class DaoService {
public <T extends GenericModel> List<T> get(Class<T> entityClass, String fieldName, String value) {
List<entityClass> entities;
String sqlString = "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e."+ fieldName+ " = :value";
TypedQuery<T> query = JPA.em().createQuery(sqlString, entityClass).setParameter("value", value);
try {
entities = query.getResultList();
} catch (NoResultException e) {
entities = null;
} catch (Exception e) {
entities = new ArrayList<T>()
}
return entities;
}
}
Usage:
List<User> = daoService.get(User.class, User.FIELD_DEPARTMENT, value);
Here's another (slightly wild) idea I just had. Each model class is also a query template:
public abstract class ModelQuery<T extends ModelQuery> {
// TODO set from constructor
private Class<T> entityClass;
private Field[] allFields = entityClass.getFields();
private List<T> getByTemplate() {
List<Field> queryFields = new ArrayList<Field>();
String sql = selectFieldsAndCreateSql(queryFields);
TypedQuery<T> query = setQueryParameters(queryFields, sql);
return executeQuery(query);
}
private String selectFieldsAndCreateSql(List<Field> queryFields) throws IllegalAccessException {
StringBuilder sql = new StringBuilder();
sql.append("SELECT e FROM ")
.append(entityClass.getSimpleName())
.append("e WHERE ");
for (Field field : allFields) {
if (field.get(this) != null) {
sql.append("e.")
.append(field.getName())
.append(" = :")
.append(field.getName());
// keep track of the fields used in the query
queryFields.add(field);
}
}
return sql.toString();
}
private TypedQuery<T> setQueryParameters(List<Field> queryFields, String sql) throws IllegalAccessException {
TypedQuery<T> query = JPA.em().createQuery(sql, entityClass);
for (Field field : queryFields) {
query.setParameter(field.getName(), field.get(this));
}
return query;
}
private List<T> executeQuery(TypedQuery<T> query) {
List<T> entities;
try {
entities = query.getResultList();
} catch (NoResultException e1) {
entities = null;
} catch (Exception e) {
entities = new ArrayList<T>();
}
return entities;
}
}
Usage:
User userQuery = new User();
userQuery.setDepartment("finance");
List<User> results = userQuery.getByTemplate();
I guess there are more ways to skin this cat. Good luck with finding your optimal solution!
To get private field names
use foo.getDeclaredFields(); instead of foo.getFields();
Here are also you have some minor issue
fields[0] means, the first declared field, in which 0 is again hard coded
If you change the order of declaration then again it could be a trouble for you, which will never get refracted
I would recommend using
1.) The Class.forName() SPI logic where you can inject the expected business logic on the fly.
2.) The Spring DI with interfaces and implementations using auto wiring

how to group by columns without projection in hibernate criteria?

i am trying to fetch only max(assetHistoryId) but my below code returing 3 columns max(assetHistoryId), eventId, and assetIdentifier in result.
how to group the columns with out projection using criteria.
you can find my code below.
final Criteria agcriteria = createCriteria(someclass.class);
agcriteria.add(Restrictions.in("eventId", listOfEventIds));
agcriteria.add(Restrictions.ne("action", "T"));
agcriteria.add(
Restrictions.between("modifyDate", lastProcessedTime,
batchStartTime));
agcriteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("assetIdentifier"))
.add(Projections.groupProperty("eventId"))
.add(Projections.max("assetHistoryId")));
val = agcriteria.list();
please help me any one ?
If i understand you correctly, you want only max(assetHistoryId) without any other column details.
You can try something like this:
Criteria agcriteria = createCriteria(someclass.class);
agcriteria.setProjection(Projections.projectionList()
.add(Projections.max("assetHistoryId")));
You can add restrictions to it, if any... like this: agcriteria.add(Criteria c); or the same set of conditions
agcriteria.add(Restrictions.in("eventId", listOfEventIds));
agcriteria.add(Restrictions.ne("action", "T"));
agcriteria.add(
Restrictions.between("modifyDate", lastProcessedTime,
batchStartTime));
Ok, boys and girls. I know it's a necro and Hibernate Criteria Api was deprecated long ago. But still there are systems which use this API, so hope it will be useful.
I could not find a way to do it with built-in hibernate projections, so I've decided to make my own ones. First of all we will need to create a new projection class which will produce nothing in SELECT clause, but still have it in group clause.
public class NoPropertyGroupProjection extends SimpleProjection {
private String propertyName;
protected NoPropertyGroupProjection(String propertyName) {
this.propertyName = propertyName;
}
#Override
public boolean isGrouped() {
return true;
}
#Override
public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
return new Type[] { };
}
#Override
public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery) throws HibernateException {
return "";
}
#Override
public String toGroupSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
return StringHelper.join( ", ", criteriaQuery.getColumns( propertyName, criteria ) );
}
#Override
public String toString() {
return propertyName;
}
}
That's a copy of PropertyProjection from the version of Hibernate I have with some changes.
It won't work alone (it is just much complicated to force it work alone), but in most cases we still need something to be selected.
So the next thing we need is to fix ProjectionList as it will break with empty column we're trying to pass it. So, here's the next class. Shame elements list is private, but we have sufficient getters to achieve our goal.
public class ProjectionListWithOnlyGroupBySupport extends ProjectionList {
#Override
public String toSqlString(Criteria criteria, int loc, CriteriaQuery criteriaQuery) throws HibernateException {
final StringBuilder buf = new StringBuilder();
String separator = "";
for ( int i = 0; i < this.getLength(); i++ ) {
Projection projection = this.getProjection(i);
String addition = projection.toSqlString( criteria, loc, criteriaQuery );
if (!"".equals(addition)) {
buf.append(separator).append(addition);
loc += getColumnAliases(loc, criteria, criteriaQuery, projection).length;
separator = ", ";
}
}
return buf.toString();
}
private static String[] getColumnAliases(int loc, Criteria criteria, CriteriaQuery criteriaQuery, Projection projection) {
return projection instanceof EnhancedProjection
? ( (EnhancedProjection) projection ).getColumnAliases( loc, criteria, criteriaQuery )
: projection.getColumnAliases( loc );
}
}
Again, small adjustments for the original class. Now we have everything needed to accomplish our goal. But for convenience we will create one more class.
public final class AdvancedProjections {
public static NoPropertyGroupProjection groupBy(String propertyName) {
return new NoPropertyGroupProjection( propertyName );
}
public static ProjectionList projectionList() {
return new ProjectionListWithOnlyGroupBySupport();
}
}
After we've created all these classes, we can change the code from the question:
final Criteria agcriteria = createCriteria(someclass.class);
agcriteria.add(Restrictions.in("eventId", listOfEventIds));
agcriteria.add(Restrictions.ne("action", "T"));
agcriteria.add(
Restrictions.between("modifyDate", lastProcessedTime,
batchStartTime));
agcriteria.setProjection(AdvancedProjections.projectionList()
.add(Projections.max("assetHistoryId"))
.add(AdvancedProjections.groupBy("assetIdentifier"))
.add(AdvancedProjections.groupBy("eventId")));
val = agcriteria.list();
Voila!

Returning a QueryDSL BooleanExpression that evaluates to true

Say I have CustomerQueryInfo bean with the following properties:
String firstName
String lastName
StatusEnum status
I want to perform a "QueryDSL" search using this an object of this type that will return a list of customers List<Customer>.
If one of the fields of CustomerQueryInfo is null, I don't want to use it in the search. Thus a CustomerQueryInfo object with all three fields set to null will return all customers.
I am looking for best practices to perform such a search with QueryDSL.
Is something like this OK:
private BooleanExpression isFirstNameLike(String firstName){
if(firstName==null)
return true BooleanExpression somehow;
return QCustomer.customer.firstName.like(firstName);
}
private BooleanExpression isStatutEq(StatusEnum status){
if(status==null)
return true BooleanExpression somehow;
return QCustomer.customer.status.eq(status);
}
then:
return query.from(customer).where(isFirstNameLike(customerQueryInfo.getFirstName).and(isLastNameLike(customerQueryInfo.getLastName).and(isStatusEq(customerQueryInfo.getStatus))).list;
How do I return a BooleanExpression that evaluates to true?
If the above approach is not advisable, then what is the recommended best practice?
How do I return a BooleanExpression that evaluates to true?
BooleanExpression alwaysTrue = Expressions.asBoolean(true).isTrue();
You can safely use null predicates like this
private BooleanExpression isFirstNameLike(String firstName){
return firstName != null ? customer.firstName.like(firstName) : null;
}
private BooleanExpression isStatusEq(StatusEnum status){
return status != null ? customer.status.eq(status) : null;
}
And use the varargs aspect of where
query.from(customer)
.where(
isFirstNameLike(customerQueryInfo.getFirstName()),
isLastNameLike(customerQueryInfo.getLastName()),
isStatusEq(customerQueryInfo.getStatus()))
.list(customer);
With java 8 and BooleanBuilder you can achieve elegant way like this:
Java 8 Optional & Lambda
public final class ProductQuery {
public static BooleanExpression nameEqualTo(String name){
return ofNullable(name).map(QProduct.product.name::eq).orElse(null);
}
}
BooleanBuilder
BooleanBuilder where = new BooleanBuilder()
.and(ProductQuery.nameEqualTo(name));
I create a QueryDSLHelper class which has static methods which do the null check before adding the expression. Something like this:
public static void goe(BooleanBuilder builder, DateTimePath<Date> path, Date value) {
if(date!=null) {
builder.and(path.goe(value));
}
}
public static void like(BooleanBuilder builder, StringPath path, String value) {
if(value!=null) {
builder.and(path.like(value));
}
}
Now I can just statically import those methods and call them on one line:
like(builder, book.isbn, isbn);
This is extremely useful and very clean/readable when implementing 'filter' or 'filterByExample' queries.
Although, the answer above from Timo is probably better solution.
You could do something like this:
private BooleanExpression isFirstNameLike(String firstName){
final QCustomer customer = QCustomer.customer;
return customer.firstName.isNull().or(customer.firstName.like(firstName));
}
private BooleanExpression isStatutEq(StatusEnum status){
final QCustomer customer = QCustomer.customer;
return customer.status.isNull().or(customer.status.eq(status));
}
Or if you want to really match firstName and/or status then change
the
.isNull().or(...) to isNotNull().and(...)

In-line View Definitions Couch Db Ektorp

I use Couch DB with Ektorp at Spring 3. I read the document and have tried to implement examples. I am so new to these technologies. This is the point where I didn't understand:
#View( name = "all", map = "function(doc) { if (doc.type == 'Sofa' ) emit( null, doc._id )}")
public class SofaRepository extends CouchDbRepositorySupport<Sofa> {
#View( name = "avg_sofa_size", map = "function(doc) {...}", reduce = "function(doc) {...}")
public int getAverageSofaSize() {
ViewResult r = db.queryView(createQuery("avg_sofa_size"));
return r.getRows().get(0).getValueAsInt();
}
}
How does that wievs work and how to define them, what happens at that lines?
CouchDbRepositorySupport out of the box provides the following methods to the SofaRepository:
public void add(Sofa entity);
public void update(Sofa entity);
public void remove(Sofa entity);
public Sofa get(String id);
public Sofa get(String id, String rev);
public List<T> getAll();
public boolean contains(String docId);
By having this inline view annotation for CouchDbRepositorySupport:
#View( name = "all", map = "function(doc) { if (doc.type == 'Sofa' ) emit( null, doc._id )}")
You redefine the return from a getAll() method.
You also adding another method getAverageSofaSize() to your repository, with inline View:
#View( name = "avg_sofa_size", map = "function(doc) {...}", reduce = "function(doc) {...}")
which explicitly provides a query that db.queryView(createQuery("avg_sofa_size")); undersntad. db here is a CouchDbConnector that is able to create, delete, purge, find, etc..
Take a look at more documentation about defining in line Views

Categories