java – Can you check my generic solution to Hibernate’s CriteriaQuery and share your opinion?


I’ve created a little class which helps me to generalize many query cases. I have a DbService and couple methods in it. Can you please take a look at it and share some criticism. The usage of that tool is really easy to me and I do wanna know how to make it more safe and optimize.

 public class DbServiceImpl implements DbService {
 private final EntityManager entityManager;

 @Inject
 DbServiceImpl(EntityManager entityManager) {
    this.entityManager = entityManager;
 }   

 @Override
 public <T, V> List<V> createQuery(Query<T, V> query) {
    try {
        if (query != null && query.entityType.getAnnotation(Entity.class) != null) {
            CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

            CriteriaQuery<V> criteriaQuery = criteriaBuilder.createQuery(query.returnType);

            query.root = criteriaQuery.from(query.entityType);
            query.builder = criteriaBuilder;

            Selection<? extends V> selection = query.select(); //invokes on the call.
            Predicate predicate = query.where(); 

            if (predicate != null) {
                criteriaQuery.where(predicate); //null of a simple select without where clause.
            }

            criteriaQuery.select(selection); //null is allowed, it will just select * from the entity

            return entityManager.createQuery(criteriaQuery).getResultList();
        }
      } catch (Exception exception) {
        exception.printStackTrace();
      }
    return new ArrayList<>(); //a simple check of .isEmpty() covers that case.
   }

 @SuppressWarnings("unchecked") 
 public abstract static class Query<T, V> {
    private final Class<T> entityType;
    private final Class<V> returnType;

    //accessible only by outer class's methods.
    private Root<T> root; 
    private CriteriaBuilder builder; 

    public Query() {
        this.entityType=(Class<T>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()(0);
        this.returnType=(Class<V>) ((ParameterizedType) getClass()
                .getGenericSuperclass()).getActualTypeArguments()(0);
    }

    protected abstract Selection<? extends V> select();

    protected abstract Predicate where();

    protected Root<T> root() {
        return root;
    }

    protected CriteriaBuilder builder() {
        return builder;
    }
  }
}

And here are some use cases. Passed types are as follows – entity & return (User,Long)

  List<Long> data = dbService.createQuery(new DbServiceImpl.Query<User, Long>() {
        @Override
        protected Selection<? extends Long> select() {
            return builder().count(root());
        }
        @Override
        protected Predicate where() {
            return root();
        }
    });

This allows me to find the count of a table so understandable, without a line of pure HQL or any SQL code. One more example:

  List<User> users = dbService.createQuery(new DbServiceImpl.Query<User, User>() {
        @Override
        protected Selection<? extends User> select() {
            return root();
        }

        @Override
        protected Predicate where() {
            return builder().equal(root().get("username"), username);
        }
    });

One with a join:

    List<Recipe> recipeList= dbService.createQuery(new DbServiceImpl.Query<Recipe, Recipe>() {
        @Override
        protected Selection<? extends Recipe> select() {
            return null;
        }

        @Override
        protected Predicate where() {
            root().fetch("author", JoinType.LEFT);
            return builder().equal(root().get("author").get("id"),user.getId());
        }
    });

The code structure remains the same but the query is different.
The access modifiers I used allowed me to encapsulate the behaviour on the use cases. What do u think ? How broken or useful is that? I would appreciate any help. Thanks 🙂