Deprecation of Hibernate Criteria and how it we can still prevent it


#1

With my Update from Spring 1.x to 2.x a few days ago I got in touch with the current version (5.x) of hibernate. I was heavily surprised to see all the deprecation warnings on hibernate criteria methods which our whole company is using heavily. Especially the advice to use JPA Criteria felt like a punch in the face.

I took this topic in our Community of Practice meeting today and the feedback was very clear. Nobody here is interested in using JPA Criteria - really nobody. We were just thinking about using native queries instead because losing one abstraction layer to the database is less pain than JPA Criteria. I can not believe that we are the only developers thinking like this?!!?

Probably my words are a little bit to hard but I think the decision to move from Hibernate criteria to JPA criteria is the worst in history of Hibernate.

Still thinking why to throw away a Porsche just to use the “standardized” VW Beetle?!


#2

The development effort to maintain two API implementations that do the same thing cannot be covered by the current team which only has 5 members.

There will be an extension in 6.0. Also, if the community really needs It, you’d see more Pull Requests for it, which is not the case.


#3

Hi Vlad,

sorry that my answer is so delayed. Thanks for responding so fast. I see that effort is a really big problem and I’m very surprised that you are only 5 people on this project.

But I’m still not sure if your impression about the hibernate criteria api is really right. In the last weeks I talked to also to some friends of mine about this topic. I would say at least half of them like and use it (the hibernate criteria API) in their company projects.

Probably it would be nice to start a survey somewhere to get some real feedback. At least I would appreciate it.

On the other hand … if you don’t have enough resources … and in comparison to all other droppable features this one is the least painful one, then there is probably no other way.


#4

Although the JPA Criteria API might be a little too verbose and less intuitive than the legacy one, the Metamodel allows it to be typesafe, which is a very strong argument for people to use the JPA Criteria API instead.

Probably it would be nice to start a survey somewhere to get some real feedback. At least I would appreciate it.

The effort to migrate to JPA Criteria API is lower than maintaining a separate project. I don’t see people contributing to the legacy Criteria which is a strong indicator that few want to contribute to the old Criteria API.

Until the old Criteria will be removed, you can still use it. Therefore, you have it HIbernate 5.x, so it’s not a problem on the short-run. So, until it gets removed, you can still use it. And, if you don’t migrate to the Hibernate version which will not feature the old API, then this is not going to be a problem for you.


#5

We are rather standing at the opposite. While native queries provide you with all possibilities to get the most out of you database, they also bear some drawbacks that were the main motivation to come up with criteria query frameworks: being portable, being type-safe, being SQL-injection-safe. In my opinion, there is clearly a need for programmatic query creation, which is essentially what a criteria API provides, irrespective of whether that is the Hibernate or JPA criteria API. And I find the decision to deprecate Hibernate’s criteria API in favor of JPA is wise. It is wise even though we know that JPA criteria API has its glitches (like, for instance, nasty casts of Fetch joins to Join in order to be able to use them as such).

I would rather like to see a survey about what precisely should be improved with JPA criteria API than an opinion poll.

We are using JPA criteria API intensively. Admittedly, programming complex dynamic queries can easily become write-once code, and such code tends to be difficult to grasp. Still, it’s a powerful tool. I would even argue that in case of complex dynamic queries that vary (a lot) depending on (user) input, JPA criteria code is less error-prone and more safe than sticking together a lengthy (native) query string.

Last comment: Frameworks like JOOQ or similar approaches are clearly an alternative. I wouldn’t be surprised if such approaches make it into JPA, deprecating criteria API then.


#6

Both entity queries and native SQL are needed in an enterprise application. You don’t use one or the other. Most of the time you use both in a project.

And I find the decision to deprecate Hibernate’s criteria API in favor of JPA is wise. It is wise even though we know that JPA criteria API has its glitches (like, for instance, nasty casts of Fetch joins to Join in order to be able to use them as such).

But we can fix those in JPA Criteria too.

We are using JPA criteria API intensively. Admittedly, programming complex dynamic queries can easily become write-once code, and such code tends to be difficult to grasp. Still, it’s a powerful tool. I would even argue that in case of complex dynamic queries that vary (a lot) depending on (user) input, JPA criteria code is less error-prone and more safe than sticking together a lengthy (native) query string.

Since you are already using the JPA Criteria and you consider a powerful tool, you have a solid alternative to the legacy Hibernate Criteria.

Last comment: Frameworks like JOOQ or similar approaches are clearly an alternative. I wouldn’t be surprised if such approaches make it into JPA, deprecating criteria API then.

You can combine both Hibernate and jOOQ in your project. While JPA Criteria allows you to build entity queries which allow you to easily fetch entities along with their associations, jOOQ is for native SQL queries.

I would rather like to see a survey about what precisely should be improved with JPA criteria API than an opinion poll.

The JPA spec is on GitHub. Anyone can now add issues and help shape the future of JPA.


#7

I’m actually surprised by this statement mainly because I would have expected a Spring-based application to most likely make use of Spring Data and its repository interfaces using @Query. Did you migrate a legacy Hibernate application to be wrapped by Spring Boot?

That is certainly your choice …

For me, one of the short-comings of the Hibernate Criteria API and even HQL / JPQL string-based queries has always been the human error factor. Every application and development shop is different, but I cannot tell you how may times I have run into issues where a developer changed the entity model but missed a reference to a property somewhere or just out-right had a typo. What’s worse is when those mistakes make their way into production code in an application with global visibility.

So the fact that the JPA Criteria can be coupled with the JPA static-metamodel to build type safe and compile-time checked queries was a significant win-win for me.

The API is verbose, but I did end up wrapping a good bit of my common uses into a small number of classes that made working with it much more bearable. That’s particularly why I’m surprised if you don’t use Spring Data as its a great wrapper around the API, at least for things that aren’t overly complex.

I asked the audience while doing a presentation at DevNexus 2017 what version of Hiberante were they using in their applications; mind you this is when Hibernate 5.1 and 5.2 were current. More than half of the room of attendees (~50 out of 100 people or so) said they were still using 3.6.

I only mention the above because yes change is hard and adopting is even harder. But as I’ve already said, I think its important to look at the larger picture and options which are available. Perhaps Spring Data (if you aren’t using it) might be a great replacement for a majority of your use cases to minimize the JPA Criteria API footprint?

Hopefully as more people begin to adopt 5.2 and 5.3 and take note of the deprecations that exist, more will provide feedback, particularly if we intend to remove something and user popularity indicates otherwise that we should not.

Unfortunately thus far, that just hasn’t been the case for the legacy Criteria API.

Its less about resources and more about forward design.

Both implementations are a means to the same end. What differs is the approach and route which they take and as a result that could mean a feature must be implemented multiple ways in order for it to be exposed to both implementations. That’s just not a design we want to carry forward indefinitely.


#8

We have several old projects mostly JEE and some new ones with SpringBoot (which we’re actually migrating to SpringBoot 2).

First you are right @Naros. Spring Data (especially the autogenerated repositories) take a lot of the pain.

But apart from that we also have many much heavier queries. I just copied one (very simple) example from our code (even without any join or special fetch):

Session hibernateSession = entityManager.unwrap(org.hibernate.Session.class);

Criteria criteria = hibernateSession.createCriteria(DS.class);
criteria.add(Restrictions.or(
    Restrictions.isNull("bereitgestelltfuer"),
    Restrictions.eq("bereitgestelltfuer", kunde)
));
criteria.add(Restrictions.eq("ism", ism));
criteria.add(Restrictions.eq("status", BEREITGESTELLT));
if (since != null)  criteria.add(Restrictions.gt("created", since));
if (dsType != null) criteria.add(Restrictions.eq("type", dsType));
criteria.addOrder(Order.asc("created"));

List<DS> result1 = criteria.list();

The code is so nice to read and to write. Written in Kotlin with static imports it looks nearly perfect which means that there is no technical stuff around, but only the key information that are required to understand the query:

val list = session.createCriteria(DS::class.java).apply {
  add(or(isNull("bereitgestelltfuer"), eq("bereitgestelltfuer", kunde)))
  add(eq("ism", ism))
  add(eq("status", BEREITGESTELLT))
  if (since != null)  add(gt("created", since))
  if (dsType != null) add(eq("type", dsType))
  addOrder(asc("created"))
}.list() as List<IDS>

I just tried to convert it to JPA to show the difference (without meta modell). I’m not sure if it could be done better or if it’s maybe even wrong:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(DS.class);
Root<DS> root = query.from(DS.class);

List<Predicate> conditions = new ArrayList<>();

ParameterExpression<Kunde> kundeParameter = cb.parameter(Kunde.class);
conditions.add(cb.or(
    cb.isNull(kundeParameter),
    cb.equal(root.<Kunde>get("bereitgestelltfuer"), kundeParameter)
));
conditions.add(cb.equal(root.<ISM>get("ism"), ism));
conditions.add(cb.equal(root.<DSStatus>get("status"), BEREITGESTELLT));
if (since != null) conditions.add(cb.greaterThan(root.<Date>get("created"), since));
if (dsType != null) conditions.add(cb.equal(root.<String>get("created"), dsType));

CriteriaQuery finalQuery = query
    .select(root)
    .where(conditions.toArray(new Predicate[conditions.size()]))
    .orderBy(cb.asc(root.<Date>get("created")));

List<QLDS> result2 = entityManager.createQuery(finalQuery).getResultList();

}

I think JPA criteria has the very clear focus to be type safe. Other points (like readability) were placed behind. The concepts are completely different. That is also the reason why I don’t think it is a good idea to start working on JPA (@vlad).

but I cannot tell you how may times I have run into issues where a developer changed the entity model but missed a reference to a property somewhere or just out-right had a typo

This on the other side is something we nearly never see due to a lot of unit tests and the refactoring power of IntelliJ.

With some time (and maybe one or two beers) you could see here a very general discussion about the heavyweight, slow changing but also very thoughtful JEE APIs vs. lightweight APIs of other frameworks. One of the strengths of hibernate is that is serves both very well.


#9

I believe their goal was to provide a super flexible API. I believe most people agree its verbose; however, nothing prohibits the developer from wrapping the API with their own abstraction to suit their usage needs. At the end of the day, the Hibernate Criteria API itself is just an abstraction as well.

Take a peak at something like this

public class JPACriteria<T> {
  private Class<T> clazz;
  private EntityManager entityManager;
  private CriteriaQuery<T> query;
  private Root<T> root;
  private List<Predicate> predicates = new ArrayList<>();

  public static <T> JPACriteria<T> createCriteria(
        Class<T> clazz, 
        EntityManager entityManager) {
    return new JPACriteria<T>( clazz, entityManager );
  }

  private JPACriteria(Class<T> clazz, EntityManager entityManager) {
    this.clazz = clazz;
    this.entityManager = entityManager;

    this.query = entityManager.getCriteriaBuilder().createQuery( clazz );
    this.root = this.query.from( clazz );
  }

  public List<T> getResultList() { ... }
  public T getSingleResult() { ... }

  ...
}

My point here is to illustrate a lot of that boilerplate code can be abstracted away. In fact, that is precisely what I did when I moved to using the JPA Criteria API myself. There will be times it may be just easier to use the JPA Criteria API directly than try to abstract something complex and that’s fine. Target the 80% rule of your code and you’ll find that the transition isn’t as difficult as you may think.

The project team at the team wasn’t using IntelliJ but rather Eclipse. In hindsight it would have been nice to use IntelliJ; however that would still not have caught some of the bugs and unit tests are still prone to human error here too.

Just as in your example, you’re using root.<String>get("created") which won’t be changed by any refactoring tool if you change that property to something like createdBy.

What if instead you had used root.get( DS_.CREATED ) by enabling JPA static metamodel… The beauty is the JPA static metamodel makes the code more readable (the removal of the generic cast) and not only does it remain type-safe, but it also eliminates all those pesky string references so its compile-type safe. Now when you change that entity model and recompile, the static metamodel changes and any references to the old attribute in the metamodel class will cause compile-time failures so as a developer you can fix those all at the same time.

Absolutely, but again I believe trying to over-simplify an API for the sake of readability can often be detrimental to that API by not allowing it to be as flexible for the users and leaving it up to the user to abstract it farther based on their specific needs. To me, that is the best of both worlds.


#10

Hi,
would it be possible to extract legacy criteria API to separate project? Or that is what Vlad has meant by extension in 6.0?
Maybe people haven’t made pull requests because the API was good as it is, at least that is my case, I’ve been able to solve all I wanted and for some edge cases I’ve used native SQL or other means.


#11

It all depends on how easy would be to abstract the Criteria functionality. If you are interested, you can try to do it yourself. But you should wait for the 6.0 to be integrated in master as there are too many changes coming and you don’t want to rework everything you’ve done for 5.x.