Hibernate is great, but often one has to specify queries as HQL in Strings, or as criteria which allow building of invalid queries.

It would be great to use the java type system to help enforce correct queries. I am aware of some tools to do this, but all the ones I have seen require either a code generation step, or preclude the use of standard getters/setters.

Here is a proof of concept of one approach that I think could work. It allows things like:

public List < Person > getPeopleByName ( final String firstName, final String lastName ) { return new Query < Person > ( ) { public CompletedQuery spec ( Person person ) { // <- Only Person can be passed to here. return where ( person. getFirstname ( ) ) . equalTo ( firstName ) // <- Only comparisons are valid at this step, a .and(... would be a compilefailure. . and ( person. getLastname ( ) ) . equalTo ( lastName ) . select ( ) ; // <- Omitting this would be a compile failure due to return type. } } . list ( HibernateUtil. getSessionFactory ( ) . getCurrentSession ( ) ) ; // <- Method returns a List<Person> } public List<Person> getPeopleByName(final String firstName, final String lastName) { return new Query<Person>() { public CompletedQuery spec(Person person) { // <- Only Person can be passed to here. return where(person.getFirstname()) .equalTo(firstName) // <- Only comparisons are valid at this step, a .and(... would be a compilefailure. .and(person.getLastname()) .equalTo(lastName) .select(); // <- Omitting this would be a compile failure due to return type. } }.list(HibernateUtil.getSessionFactory().getCurrentSession()); // <- Method returns a List<Person> }

A toString on the above Query<Person> when passed firstname benji and lastname weber would yield:

FROM Person WHERE firstname = 'benji' AND lastname = 'weber'

If we were to try to supply a non-string value for first name we would get a compile failure.

public List < Person > getPeopleByName ( final String firstName, final String lastName ) { return new Query < Person > ( ) { public CompletedQuery spec ( Person person ) { return where ( person. getFirstname ( ) ) . equalTo ( firstName ) . and ( person. getLastname ( ) ) . equalTo ( 5 ) // <- Compile failure . select ( ) ; } } . list ( HibernateUtil. getSessionFactory ( ) . getCurrentSession ( ) ) ; } public List<Person> getPeopleByName(final String firstName, final String lastName) { return new Query<Person>() { public CompletedQuery spec(Person person) { return where(person.getFirstname()) .equalTo(firstName) .and(person.getLastname()) .equalTo(5) // <- Compile failure .select(); } }.list(HibernateUtil.getSessionFactory().getCurrentSession()); }

This approach can even work querying through relationships

public List < Person > getPeopleByAddress ( final Address address ) { return new Query < Person > ( ) { public CompletedQuery spec ( Person person ) { return where ( person. getAddress ( ) . getId ( ) ) . equalTo ( address. getId ( ) ) // < - Only Integers are allowed here. . select ( ) ; } } . list ( HibernateUtil. getSessionFactory ( ) . getCurrentSession ( ) ) ; } public List<Person> getPeopleByAddress(final Address address) { return new Query<Person>() { public CompletedQuery spec(Person person) { return where(person.getAddress().getId()) .equalTo(address.getId()) // < - Only Integers are allowed here. .select(); } }.list(HibernateUtil.getSessionFactory().getCurrentSession()); }

A toString on the above Query<Person> when passed an Address with id 2 would yield:

FROM Person WHERE address.id = 2

To implement this I used a few tricks:

Query<T> is an abstract class that uses Gafter’s Gadget to obtain a Class<T> this is used to get the entity name that we are querying, and later to create an instance.

A Dynamic Proxy class is created for this Class and passed to the spec(T value) method.

This dynamic proxy records all calls made to it. If the call is to a method that returns another mockable object (e.g. getAddress() in the above example) then another recording-proxy is returned.

The query builder then uses the recorded method calls to determine which properties are being queried

The need to return a CompletedQuery interface forces the implementor to type a complete query or they will get a compile failure.

Interfaces represent the valid progressions at each step of the query building. e.g. after a where() you need to supply a comparison equalTo(), or like() etc. After a comparison you can complete the query or add another restriction with and();

Here is the example implementation of Query that enables this. Full demo code here