Embrace the JPA 2 Criteria API

A bit of history and a problem statement of sorts

I’ve been a big fan of criteria queries for writing dynamic JPA queries for a long time. Sure I could make the same thing happen with a bit of logic and a whole lot of string concatenation but that gets old really quick. My first experience with criteria queries was with Hibernate 3 so lets start there and we will follow a query from SQL to JPA 2 Criteria.

We are all probably all most familiar with SQL so lets make that our starting point.

Assuming we have table in the database of tasks lets select those which are assigned to a specific user which have not been closed.

select * from Task t where t.assignee=1 and t.dateClosed is null

However, we are living in an ORM world though so lets see it in JPQL (same syntax in HQL)

List<Task> tasks = entityManager.createQuery("select t from Task t where t.assignee=:user"
    + " and t.dateClosed is null",Task.class).setParameter("user", myUser).getResultList();

Now lets migrate our query to use the Hibernate 3 Criteria API

Session session = (Session) entityManager.getDelegate();
    Criteria criteria = session.createCriteria(Task.class);
    criteria.add(Restrictions.eq("assignee", myUser));
    criteria.add(Restrictions.isNull("dateClosed"));
    List<Task> tasks = criteria.list();

Obviously this is more verbose but it also has some readability advantages in my opinion. Even if you disagree with regard to readability you can see that adding restrictions to the query in a programmatic way is much simpler than with JPQL/HQL.

However the one thing always bugged with with JPQL/HQL and Hibernate’s Criteria API is that everything is done with strings and doing things with strings makes refactoring difficult even with good tooling. This is Java and we want something type-safe.

JPA 2 Criteria API to the rescue.

CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Task> cq = cb.createQuery(Task.class);
    Root<Task> root = cq.from(Task.class);
    List<Predicate> predicates = new ArrayList<>();
    predicates.add(cb.equal(root.get(Task_.assignee), myUser));
    predicates.add(cb.isNull(root.get(Task_.dateClosed));
    cq.where(predicates.toArray(new Predicate[predicates.size()]));
    List<Task> tasks = em.createQuery(cq).getResultList();

I will readily admit that upon first taking a look at the JPA 2.0 Criteria API I was really turned off by its verbosity and complexity. I was turned off so much that I flat out ignored it for the first six months it was available to me. I still however desired a type-safe way to write my queries so that they would hold up better during refactoring.

Finally I blocked out a day to tinker with the API and see if I could convert a few of my more simple queries. After only a few minutes I was off and running and haven’t looked back. Here are a couple things I’ve learned during my time tinkering.

  • The code is longer but I can read and understand it much faster than a JPQL/HQL query.

  • I can write a criteria query faster due to IDE code completion and not having to review my entity structure to find the name of that specific column I’m trying to reference.

  • As the complexity of the query goes up the benefits of the criteria query grow, but you will be forced to do some learning. I have yet to find a query that I have not been able to convert. The API was very well thought out from this perspective.

  • My speed of development is faster almost 100% of the time as my criteria queries execute and return the desired results on the first try. I can’t say the same for my JPQl/HQL which are parsing a potentially very long string with lots of opportunities for syntax issues.

It isn’t however all sunshine and lollipops

This isn’t to say the API is perfect. It actually fights programmatic creation of queries a little bit. The API is designed so that the base of your query can reused. For example each time you call the criteriaQuery.where(Predicate... predicates) it replaces what was previously set. To work around this you need to store your predicates in a separate list and then add them all at once in array form (varargs). It would be nice if a criteriaQuery.where(List<Predicate> predicates) was exposed like it is for groupBy and orderBy. Additionally here are some other pain points.

  • Why is a Fetch<Z,X> not a Path like Join<Z,X> is? This means I need to define my "join" twice it I want it "join fetched". Hopefully this is fixed in JPA 2.1

  • Metamodel generation is broken in Eclipse. It doesn’t handle the cyclic nature of the metamodel classes. Thus when I do a project clean I will end up with 20 or so import failed errors. These are easily resolved by making a meaningless edit to the class to trigger an incremental build but it shouldn’t be this way.

Just do it

Hopefully I’ve inspired you to take another look if you’ve previous dismissed the API as I had. Block out a few hours and give it a shot. I’ll try to share my utility classes that I’ve written in an upcoming post that have made things a it easier for me, but in the meantime you really should get down and dirty with the api to understand it fully.

You’ll be glad you did.

Cody Lerum - August 08, 2013

Composite Primary Key with @ManyToOne

Here is a fun issue that I ran into the other day while working with a legacy database.

Three tables:

Nodes

  • NodeID(pk)

Interfaces

  • InterfaceID(pk)

  • NodeID(fk)

InterfaceTraffic_Detail

  • InterfaceID(pk)

  • DateTime(pk)

  • NodeID(fk)

The InterfaceTraffic_Detail table turned out to be a little tricky for me since I have never worked with composite indexes in hibernate. It took a little trial and error but here is what I came up with

My environment is Hibernate 3.4

NmsNode.java

@Name("nmsNode")
    @Entity
    @Table(name="Nodes")
    public class NmsNode implements Serializable {
      private static final long serialVersionUID = 1462044144509159489L;
    
      private int id;
      private List&lt;NmsInterface&gt; interfaces;
    
      @Id
      @Column(name="NodeID")
      public int getId() {
        return id;
      }
    
      public void setId(int id) {
        this.id = id;
      }
    
      @OneToMany(mappedBy="node", fetch=FetchType.LAZY)
      public List&lt;NmsInterface&gt; getInterfaces() {
        return interfaces;
      }
    
      public void setInterfaces(List&lt;NmsInterface&gt; interfaces) {
        this.interfaces = interfaces;
      }
    }

NmsInterface.java

@Name("nmsInterface")
    @Entity
    @Table(name="Interfaces")
    public class NmsInterface implements Serializable
    {
    
       private static final long serialVersionUID = 1L;
    
       private int id;
       private NmsNode node;
    
    
       @Id
       @Column(name="InterfaceID")
       public int getId()
       {
          return id;
       }
       public void setId(int id)
       {
          this.id = id;
       }
    
       @ManyToOne
       @JoinColumn(name="NodeID")
       public NmsNode getNode()
       {
          return node;
       }
       public void setNode(NmsNode node)
       {
          this.node = node;
       }
    }

InterfaceTraffic_Detail.java

@Entity
    @Table(name="InterfaceTraffic_Detail")
    @AssociationOverrides
       (
          {
               @AssociationOverride(name = "id.nmsInterface",
                     joinColumns = @JoinColumn(name = "InterfaceID"))
          }
       )
    public class InterfaceTraffic_Detail implements Serializable
    {
       private static final long serialVersionUID = 1L;
       private InterfaceTraffic_PrimaryKey id;
       private NmsNode nmsNode;
    
       @EmbeddedId
       @AttributeOverrides
       (
             {
                @AttributeOverride(name="id.dateTime", column=@Column(name="DateTime"))
             }
       )
       public InterfaceTraffic_PrimaryKey getId()
       {
          return id;
       }
       public void setId(InterfaceTraffic_PrimaryKey id)
       {
          this.id = id;
       }
       @ManyToOne
       @JoinColumn(name="NodeID")
       public NmsNode getNmsNode()
       {
          return nmsNode;
       }
       public void setNmsNode(NmsNode nmsNode)
       {
          this.nmsNode = nmsNode;
       }
    }

InterfaceTraffic_PrimaryKey.java

@Embeddable
    public class InterfaceTraffic_PrimaryKey implements Serializable
    {
       private static final long serialVersionUID = 1L;
       private NmsInterface nmsInterface;
       private Date dateTime;
    
       @ManyToOne
       public NmsInterface getNmsInterface()
       {
          return nmsInterface;
       }
       public void setNmsInterface(NmsInterface nmsInterface)
       {
          this.nmsInterface = nmsInterface;
       }
       public Date getDateTime()
       {
          return dateTime;
       }
       public void setDateTime(Date dateTime)
       {
          this.dateTime = dateTime;
       }
    
       public int hashCode() {
          return (int) dateTime.hashCode() + nmsInterface.getId();
      }
    
      public boolean equals(Object obj) {
          if (obj == this) return true;
          if (obj == null) return false;
          if (!(obj instanceof InterfaceTraffic_PrimaryKey)) return false;
          InterfaceTraffic_PrimaryKey pk = (InterfaceTraffic_PrimaryKey) obj;
          return pk.nmsInterface.getId() == nmsInterface.getId() &amp;&amp; pk.dateTime.equals(dateTime);
      }
    }

The important part here is the @EmbeddedId and @Embeddable on the InterfaceTraffic_Detail and InterfaceTraffic_PrimaryKey as well as the name "id.nmsInterface" on the @AssociationOverride so that I could reference the variable on the embedded class and define the @JoinColumn

Hopefully this saves someone else some time.

tags: java hibernate
Cody Lerum - January 12, 2010

Hibernate Optimistic Locking and MySQL

So I ran into a fun little issue the other day while working with MySQL and Hibernate.

Hibernate provides optimistic locking via the Version function. This can either be a numeric field or a timestamp. I use Hibernate Annotations, so for me it simply involves placing the following annotation inside my entity.

private Date dateModified;
    
    @Version
    private Date getDateModified() {
      return dateModified;
    }
    private void setDateModified(Date dateModified) {
      this.dateModified = dateModified;
    }

This worked great for me, and provided both an optimistic lock as well as a dateModified field I could use to show the last time the entity was updated. I should say that this worked great during development while I was using hypersonic as my database. When I deployed to a lab server for testing against our MySQL database I started to run into some OptimisticLockExceptions

After doing some digging around I found out that MySQL doesn’t store date/time with millisecond precision. This is a well known feature request / bug and it doesn’t appear that it will be fixed anytime soon. So I was forced to refactor my code so that the "version" was a int and still maintain a dateModified field since the user interface had made use of it in various places.

Thankfully Hibernate provides Call Back annotations which allow me to trigger an update of the dateModified when a Persist or Update is called. I also used this logic to automatically set the dateCreated field on entities when needed.

I also added an @Deprecated to the setters for dateCreated and dateModified to discourage others from setting those fields manually.

Here is what the final code looks like.

private int version;
    private Date dateCreated;
    private Date dateModified;
    
    @NotNull
    public Date getDateCreated() {
      return dateCreated;
    }
    
    @Deprecated
    public void setDateCreated(Date dateCreated) {
      this.dateCreated = dateCreated;
    }
    
    @PrePersist
    public void updateDateCreated() {
      dateCreated = new Date();
      dateModified = dateCreated;
    }
    
    @NotNull
    public Date getDateModified() {
      return dateModified;
    }
    
    @Deprecated
    public void setDateModified(Date dateModified) {
      this.dateModified = dateModified;
    }
    
    @PreUpdate
    public void updateDateModified() {
      dateModified = new Date();
    }

Update!

It looks like mysql has this on their roadmap and outlines their plans with a fairly extensive worklog. However this is slated for MySQL 6.0 or 7.0 and thus will be a few years out. Maybe it’s time to checkout what PostgreSQL is all about.

Cody Lerum - December 29, 2009
About outjected.com

Outjected is an infrequent blog about the frequent issues and annoyances that pop up while coding.

These are living posts and will be updated as errors or improvements are found.
About Me
Google+