2012-12-16

JPA and Hibernate mapping with MySQL – common errors and exceptions

Few tips on using Java Persistence API and Hibernate (with Hibernate Annotations and Hibernate EntityManager)

(Hibernate mapování s MySQL – nejčastější chyby a výjimky)

Some exceptions seem complicated for someone who doesn't have a clue what's going on under Hibernate's hood.

During the developement of my latest project, I've explained several weird-looking exceptions to my co-worker, and we also discussed the reason to use some code patterns encouraged (if not required) by Hibernate mapping.

So I have decided do put some of the exceptions and their explanation here. The respective error messages comming from database are MySQL specific, but for other RDBMS it will be similar.

Some tips for investigating the exception

Sometimes the important part is almost at the end – either at the end of a very long line, or at the end of stack trace. So don't panic when you see something like:

org.springframework.orm.hibernate3.HibernateJdbcException: JDBC exception on Hibernate data access: SQLException for SQL

Current habit of handlig exceptions is to add it's getMessage() at the end of the message of the exception to be thrown. This way, when the exception bubbles from the deepness of several libraries (like JDBC driver, Hibernate Core, Hibernate EntityManager and Java Persistence API (JPA)), the first line is usually very long. Given that the actual important information (Database's mes­sage) comes from the very lowest layer, it ends up at the end of the line.

For the case when you find nothing useful at the first line, scroll down the stacktrace to see the root causes. Those are the lines starting with „Caused by:“. Read them carefully, because they are something like a scenario, whereas the lines starting with „at“ are kind of scaffolding or sceneries.

When you haven't found anything familiar even in the root causes, still it may be useful to scan the „at“ lines to see where and when exactly the exception happened, because there's a small chance that you've find a bug, or you have triggered an exception that is not (yet) well documented and discussed in forums to get simple answer just by googling for the Exception text. However, there are some time-proven libraries like JDBC driver which are very unlikely to have bugs, and almost all possible exceptions have been discussed somewhere – so you can skip their „at“ lines when investigating; just keep in mind that your exception appeared in the context of this library.

Last note for those who don't know – the exceptions in the stack trace are in reverse order in which they appeared. In other words, if you split the stack trace by the „Caused by“ lines and reverse the blocks order, you would get the actual call stack in the moment when the deepest root cause happened.

Three main sources of Hibernate + JPA exceptions

Most commonly appearing exceptions come from these situations:

1) You've an error in your mapping (annotations) and Hibernate refuses to create the EntityManagerFactory.

To be written later (when I get some of these again :).

2) You've an error in your mapping, which proves in runtime – like, NOT NULL checks performed by database when you persist an entity with null property.

To be written later.

3) You've an error in program logic in your entity or DAO methods.

This makes Hibernate complain such like „trying to persist deleted entity“. Most common cause is that you have some references across the network of objects, which prevent Hibernate to perform the requested operation.

In case of deletion, check all direct AND indirect bi-directional references between the entity to be deleted and other entities – including collections. If the references reach to an entity that keeps the other side of the relation, Hibernate can't delete the refering entity before you release the other side of the relation.

Let's see the example:

@Entity
class Order

  @OneToMany( ... )
  Set<OrderItem> items = new HashSet();
  // BTW: Don't use List unless you have a column to store the items' positions to
  // and have mapped it with org.hibernate.annotations.IndexColumn.

  /** Removes the item from the order. */
  public boolean removeItem( OrderItem item ){
    Item.setOrder( null );
    return this.items.remove( subj );
  }

  ...

}

4) You've an error in handling of the transaction.

java.lang.IllegalArgumentException: Removing a detached instance ...

To be written later.

Long story in short:

Option 1: getReference()

public void remove( Item item ) {
  this.em.remove( this.em.getReference( Item.class, item.getId() ) );
}

Option 2: merge()

public void remove( Item item ) {
  this.em.remove( this.em.merge( Item.class, item.getId() ) );
}

There is additional overhead to merge since in addition to doing a lookup, it will merge into it if it exists, and it will create a new instance if it doesn't. – http://forums.java.net/…/thread.jspa?…

Generally speaking, using merge() is slower, but safer when you're not sure in which state you will call the remove() method.


Common Exceptions I've seen when when learning JPA with Hibernate

Caused by: org.hibernate.exception.GenericJDBCException: could not insert: [cz.dynawest.isir.entities.User]
Caused by: java.sql.SQLException: Field 'created' doesn't have a default value

This one is easy – probably you have a NOT NULL column, but haven't annotated it – so Hibernate, when it sees null value, it does not include the column in the INSERT query. Add @Column( nullable=false ) to the mentioned property.

@Column( nullable=false )
private Date created;

This will not solve the problem, but at least you will catch it sooner – before Hibernate sends the SQL to the database, it will throw an exception with the message saying which property you did not set.

Full stack trace:

org.springframework.orm.hibernate3.HibernateJdbcException: JDBC exception on Hibernate data access: SQLException for SQL [insert into isir_sled_users (aktivace_klic, mail, pass) values (?, ?, ?)]; SQL state [HY000]; error code [1364]; could not insert: [cz.dynawest.isir.entities.User]; nested exception is org.hibernate.exception.GenericJDBCException: could not insert: [cz.dynawest.isir.entities.User]
        at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:642)
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:95)
        at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:212)
        at org.springframework.orm.jpa.JpaAccessor.translateIfNecessary(JpaAccessor.java:152)
        at org.springframework.orm.jpa.JpaTemplate.execute(JpaTemplate.java:189)
        at org.springframework.orm.jpa.JpaTemplate.persist(JpaTemplate.java:266)
        at cz.dynawest.isir.dao.UserDaoImpl.create(UserDaoImpl.java:42)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
        at $Proxy25.create(Unknown Source)
        at cz.pohlidame.entities.UserTest.testCreateUser(UserTest.java:67)
Caused by: org.hibernate.exception.GenericJDBCException: could not insert: [cz.dynawest.isir.entities.User]
        at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:126)
        at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:114)
        at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
        at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:64)
        at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2186)
        at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2666)
        at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71)
        at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
        at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321)
        at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
        at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
        at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:49)
        at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154)
        at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110)
        at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
        at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:645)
        at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:619)
        at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:623)
        at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:220)
        at org.springframework.orm.jpa.JpaTemplate$5.doInJpa(JpaTemplate.java:268)
        at org.springframework.orm.jpa.JpaTemplate.execute(JpaTemplate.java:184)
        ... 51 more
Caused by: java.sql.SQLException: Field 'created' doesn't have a default value
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1055)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3491)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3423)
        at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1936)
        at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2060)
        at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2542)
        at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1734)
        at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2019)
        at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1937)
        at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1922)
        at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:94)
        at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:57)


0