GeekLondon.com Help icon Syndication Feed icon 

Article Popularity Indicator

Hibernate setParameter versus setEntity

Here's an odd one. I must investigate and find out what's really going on. I don't even pretend to understand this bug! See updates at end. Unfortunately this doesn't fail in my unit tests (against HSQL DB rather than PostgreSQL) and similar code elsewhere in my app doesn't fail either, so I can't build a SSCCE.

I retrieve a User entity and use it in a named query. The query is being created as a Spring HibernateCallback within a Sring Hibernate template executeWithNativeSession() method, so although I'm in a Spring environment it ought not to be affecting the behaviour.

The User entity is not null, and I am constructing a Query object from the string from Quota where user = :user (a static final constant to the Quota class). I've checked and it's an authentic Hibernate QueryImpl object, incidentally.

The original (broken) code which does not work:

final Query quotaQuery = session.getNamedQuery(Quota.FIND_QUOTA_BY_USER);
quotaQuery.setParameter("user", user);
log.info("Executing: " + quotaQuery);
final Quota quota = (Quota)quotaQuery.uniqueResult();

Here's the failure in the logs:

INFO: Executing: QueryImpl(from Quota where user = :user)
13-Jun-2008 15:52:27 org.hibernate.util.JDBCExceptionReporter logExceptions
WARNING: SQL Error: 0, SQLState: 22023
13-Jun-2008 15:52:27 org.hibernate.util.JDBCExceptionReporter logExceptions
SEVERE: No value specified for parameter 1.

That's followed by the usual stack trace dumped by the JDBC driver. The replacement code which does work is:

final Query quotaQuery = session.getNamedQuery(Quota.FIND_QUOTA_BY_USER);
quotaQuery.setEntity("user", user);
log.info("Executing: " + quotaQuery);
final Quota quota = (Quota)quotaQuery.uniqueResult();

I'm baffled. I know calling setParameter can cause problems where null values are involved, but I thought that as long as the type was compatible with the HQL parameter there weren't any other issues. Even assuming that there are other issues I don't follow why it works ok in my unit tests against HQL, but not in the development environment against PostgreSQL and works elsewhere in my code with very similar queries.

Odds are that I'm misunderstanding a subtlety of setParameter versus setEntity (their entries in the Hibernate API documentation are virtually identical), but I think in future I will consider setParameter to be bad practice and try to use the type specific set methods at least until such time as I figure out exactly what is failing here!

For now, file under "voodoo fixes" as I really don't understand why this has resolved the problem. I'll post an update if I get time to investigate this properly.

Update 1: The following approach works OK too, so it looks like it may be some kind of bug in the type inference used by the two argument setParameter method; that or my query type really isn't as unambiguous as I thought:

final Type type = Hibernate.entity(User.class);
quotaQuery.setParameter("user", user, type);

Update 2: I understand the basis of this now. The problem is indeed with the type inference process. The heuristic first looks up the query to see what type it is expecting - in this case it's expecting not a User, but a org.hibernate.type.OneToOneType representing the Quota class's association with the User type. By default, if you don't ask for a specific type, this is what it will use.

Unfortunately because I loaded User via a query earlier on, this is actually a proxy that has the ManyToOneType (it was loaded via a many-to-one association with another entity). So at this point the types are incompatible.

The reason the two argument setParameter approach was working elsewhere is that in those cases the query parameter type was a ManyToOneType already, so they were compatible, or in the case of the unit tests the entity wasn't a proxy in the first place. I'm a little surprised that Hibernate can't spot that the association-wrapped proxy entity is actually compatible in these circumstances, but this does seem to be the issue.

The other problem of course is that instead of throwing a suitable type incompatibility exception the parameter is simply not passed on to the query. Oops. That seems quite naughty. Perhaps it's excusable on performance grounds. Do we want the type checking in a potentially performance sensitive spot? Or perhaps it's a simple oversight. It definitely is the origin of the problem though; the following abomination produces the same error with no type-related exceptions:

quotaQuery.setParameter("user", user, 
   new OneToOneType(
      "com.fatmoggy.users.User",
      ForeignKeyDirection.FOREIGN_KEY_TO_PARENT,
      "id",
      false,
      false,
      false,
      "com.fatmoggy.users.Quota",
      null));

Whereas this (equally abominable) one works just dandy:

quotaQuery.setParameter("user", user, new ManyToOneType("com.fatmoggy.users.User"));

Note that if I try to do something more conspicuously stupid such as casting to a primitive type, the cast will be carried out explicitly and so we'll get a type related error after all. I.e. this will produce the expected ClassCastException:

quotaQuery.setParameter("user", user, Hibernate.STRING_TYPE); // Fails "cleanly" with a class cast exception.

Happily the solution is simple. Always specify the type, either by calling the three argument setParameter method, or by calling the type-specific set method (such as setEntity) on the query object.

Posted at Jun 13, 2008 4:17:27 PM, and last updated Jun 13, 2008 6:56:31 PM