So here's the general situation :) I'm trying to test / determine Hibernate's behavior when attempting to update a previously deleted object. I'd like to know that the object was deleted, but what I seem to be getting instead is a NullPointerException... there's a lot of info that I posted. It's exciting! Read on :) I would appreciate any help that you can give... all I want to do is get basic CRUD working with fairly deterministic behavior, but it doesn't seem to be cooperating.
Hibernate version:
3.2.6ga
Mapping documents:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.ocpsoft.data.mocks.MockPersistentObject" table="mock_objects">
<id name="id" column="my_id">
<generator class="native" />
</id>
<natural-id>
<property name="stringField" />
</natural-id>
<version name="version" />
<property name="longField" />
<property name="integerField" />
</class>
</hibernate-mapping>
Code:
package com.ocpsoft.data.hibernate;
import org.hibernate.cfg.Configuration;
import com.ocpsoft.data.mocks.MockPersistentObject;
import com.ocpsoft.util.HibernateUtil;
public class MockOcpSoftHibernateSchema
{
public static void init()
{
Configuration config = new Configuration();
config.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
config.setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver");
config.setProperty("hibernate.connection.url", "jdbc:hsqldb:mem:baseball");
config.setProperty("hibernate.connection.username", "sa");
config.setProperty("hibernate.connection.password", "");
config.setProperty("hibernate.connection.pool_size", "1");
config.setProperty("hibernate.connection.autocommit", "true");
config.setProperty("hibernate.cache.provider_class", "org.hibernate.cache.HashtableCacheProvider");
config.setProperty("hibernate.hbm2ddl.auto", "create-drop");
config.setProperty("hibernate.show_sql", "true");
config.setProperty("hibernate.transaction.factory_class", "org.hibernate.transaction.JDBCTransactionFactory");
config.setProperty("hibernate.current_session_context_class", "thread");
config.addClass(MockPersistentObject.class);
HibernateUtil.setConfig(config);
}
}
Code between sessionFactory.openSession() and session.close():This is the test case that I am trying to run... and everything works, except when I call the final commit(), I get a NullPointerException instead of a "StaleObjectException" or "ObjectNotFoundException."
Code:
@Test(expected = NoSuchObjectException.class)
public void testCannotUpdateNonexistentObject() throws DuplicateObjectException, NoSuchObjectException
{
try
{
Transaction trans = this.dao.beginTransaction();
this.dao.deleteAll();
trans.commit();
trans = this.dao.beginTransaction();
MockPersistentObject entity = new MockPersistentObject();
entity.setIntegerField(1);
entity.setLongField(new Long(1));
entity.setStringField("value1");
entity = this.dao.create(entity);
trans.commit();
trans = this.dao.beginTransaction();
this.dao.delete(entity);
trans.commit();
entity.setIntegerField(234);
trans = this.dao.beginTransaction();
this.dao.update(entity);
trans.commit();
}
catch (NoSuchObjectException e)
{
HibernateUtil.closeSession();
throw e;
}
}
The relevant related code:
Code:
package com.ocpsoft.data.transaction;
import org.hibernate.Session;
import com.ocpsoft.data.Transaction;
import com.ocpsoft.util.HibernateUtil;
public class HibernateTransaction implements Transaction
{
Session session;
public HibernateTransaction()
{
this.session = HibernateUtil.currentSession();
if (this.session.isOpen() && this.session.getTransaction().isActive())
{
throw new TransactionException("An active transaction already exists.");
}
this.session = HibernateUtil.currentSession();
this.session.beginTransaction();
}
public void commit()
{
try
{
this.session.getTransaction().commit();
}
catch (RuntimeException e)
{
this.rollback();
throw e;
}
finally
{
this.session = HibernateUtil.currentSession();
}
}
public void rollback()
{
try
{
this.session.getTransaction().rollback();
}
catch (RuntimeException e)
{
throw e;
}
finally
{
this.session = HibernateUtil.currentSession();
}
}
}
The Dao:
Code:
package com.ocpsoft.data.dao;
import java.io.Serializable;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.LockMode;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.Session;
import org.hibernate.StaleObjectStateException;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.exception.ConstraintViolationException;
import com.ocpsoft.data.Dao;
import com.ocpsoft.data.PersistentObject;
import com.ocpsoft.data.Transaction;
import com.ocpsoft.data.transaction.HibernateTransaction;
import com.ocpsoft.exceptions.DuplicateObjectException;
import com.ocpsoft.exceptions.NoSuchObjectException;
import com.ocpsoft.util.HibernateUtil;
abstract public class AbstractHibernateDao<T extends PersistentObject> implements Dao<T, Serializable>
{
private Class<T> persistentClass;
protected AbstractHibernateDao(final Class<T> type)
{
this.persistentClass = type;
}
public Transaction beginTransaction()
{
return new HibernateTransaction();
}
@SuppressWarnings("unchecked")
public T create(final T entity) throws DuplicateObjectException
{
try
{
this.getSession().save(entity);
this.getSession().flush();
}
catch (ConstraintViolationException e)
{
throw new DuplicateObjectException(e);
}
return entity;
}
public void delete(final T entity) throws NoSuchObjectException
{
try
{
if (entity.getId() instanceof Long)
{
this.getSession().delete(entity);
this.getSession().flush();
}
else
{
throw new RuntimeException("Object to be deleted must have a set Id");
}
}
catch (StaleObjectStateException e)
{
throw new NoSuchObjectException(e);
}
}
@Override
public List<T> deleteAll()
{
try
{
List<T> list = this.findAll();
for (T obj : list)
{
this.delete(obj);
}
return list;
}
catch (NoSuchObjectException e)
{
throw new RuntimeException("Object became stale during operation", e);
}
}
@Override
public List<T> deleteByExample(final T exampleInstance)
{
try
{
List<T> list = this.findByExample(exampleInstance);
for (T obj : list)
{
this.delete(obj);
}
return list;
}
catch (NoSuchObjectException e)
{
throw new RuntimeException("Object became stale during operation, e");
}
}
@Override
public List<T> deleteByExample(final T exampleInstance, final String... excludeProperty)
{
try
{
List<T> list = this.findByExample(exampleInstance, excludeProperty);
for (T obj : list)
{
this.delete(obj);
}
return list;
}
catch (NoSuchObjectException e)
{
throw new RuntimeException("Object became stale during operation, e");
}
}
@SuppressWarnings("unchecked")
public List<T> findAll()
{
return this.findByCriteria();
}
@SuppressWarnings("unchecked")
protected List<T> findByCriteria(final Criterion... criterion)
{
Criteria crit = this.getSession().createCriteria(this.getPersistentClass());
for (Criterion c : criterion)
{
crit.add(c);
}
return crit.list();
}
@SuppressWarnings("unchecked")
public List<T> findByExample(final T exampleInstance)
{
Criteria criteria = this.getSession().createCriteria(this.getPersistentClass());
criteria.add(Example.create(exampleInstance).excludeZeroes());
return criteria.list();
}
@SuppressWarnings("unchecked")
public List<T> findByExample(final T exampleInstance, final String... excludeProperty)
{
Criteria crit = this.getSession().createCriteria(this.getPersistentClass());
Example example = Example.create(exampleInstance);
for (String exclude : excludeProperty)
{
example.excludeProperty(exclude);
}
crit.add(example);
return crit.list();
}
@SuppressWarnings("unchecked")
@Override
public T findById(final Serializable id) throws NoSuchObjectException
{
T entity;
try
{
entity = (T) this.getSession().load(this.getPersistentClass(), id, LockMode.UPGRADE);
return entity;
}
catch (ObjectNotFoundException e)
{
throw new NoSuchObjectException(e);
}
}
@SuppressWarnings("unchecked")
@Override
public T findUnique(final T exampleInstance) throws NoSuchObjectException, InvalidResultException
{
T result = null;
Criteria criteria = this.getSession().createCriteria(this.getPersistentClass());
criteria.add(Example.create(exampleInstance).excludeZeroes());
List<T> rows = criteria.list();
if ((rows instanceof List) && (rows.size() == 1))
{
result = rows.get(0);
}
else if (((rows instanceof List) && (rows.size() == 0)))
{
throw new NoSuchObjectException("No such record exists in the database");
}
else
{
throw new InvalidResultException("More than one record matched");
}
return result;
}
private Class<T> getPersistentClass()
{
return this.persistentClass;
}
protected Session getSession()
{
return HibernateUtil.currentSession();
}
@Override
public void update(final T entity) throws NoSuchObjectException
{
try
{
this.getSession().update(entity);
}
catch (StaleObjectStateException e)
{
throw new NoSuchObjectException(e);
}
}
}
Full stack trace of any exception that occurs:Code:
java.lang.Exception: Unexpected exception, expected<com.ocpsoft.exceptions.NoSuchObjectException> but was<java.lang.NullPointerException>
at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:91)
at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75)
at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45)
at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:66)
at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35)
at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42)
at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: java.lang.NullPointerException
at org.hibernate.event.def.DefaultFlushEntityEventListener.checkNaturalId(DefaultFlushEntityEventListener.java:89)
at org.hibernate.event.def.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:169)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:120)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:196)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:76)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at com.ocpsoft.data.transaction.HibernateTransaction.commit(HibernateTransaction.java:29)
at com.ocpsoft.data.dao.AbstractHibernateDaoTest.testCannotUpdateNonexistentObject(AbstractHibernateDaoTest.java:521)
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.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99)
at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81)
... 14 more
Name and version of the database you are using:Java Derby DB
The generated SQL (show_sql=true):Debug level Hibernate log excerpt:Code:
20:49:14,474 INFO Environment:514 - Hibernate 3.2.6
20:49:14,482 INFO Environment:547 - hibernate.properties not found
20:49:14,486 INFO Environment:681 - Bytecode provider name : cglib
20:49:14,497 INFO Environment:598 - using JDK 1.4 java.sql.Timestamp handling
20:49:14,575 INFO Configuration:591 - Reading mappings from resource: com/ocpsoft/data/mocks/MockPersistentObject.hbm.xml
20:49:14,576 INFO Configuration:536 - Reading mappings from resource: com/ocpsoft/data/mocks/MockPersistentObject.hbm.xml
20:49:14,818 INFO HbmBinder:300 - Mapping class: com.ocpsoft.data.mocks.MockPersistentObject -> mock_objects
20:49:14,913 INFO DriverManagerConnectionProvider:41 - Using Hibernate built-in connection pool (not for production use!)
20:49:14,914 INFO DriverManagerConnectionProvider:42 - Hibernate connection pool size: 1
20:49:14,914 INFO DriverManagerConnectionProvider:45 - autocommit mode: true
20:49:14,920 INFO DriverManagerConnectionProvider:80 - using driver: org.hsqldb.jdbcDriver at URL: jdbc:hsqldb:mem:baseball
20:49:14,921 INFO DriverManagerConnectionProvider:86 - connection properties: {user=sa, password=****, autocommit=true}
20:49:15,117 INFO SettingsFactory:89 - RDBMS: HSQL Database Engine, version: 1.8.0
20:49:15,118 INFO SettingsFactory:90 - JDBC driver: HSQL Database Engine Driver, version: 1.8.0
20:49:15,133 INFO Dialect:152 - Using dialect: org.hibernate.dialect.HSQLDialect
20:49:15,138 INFO TransactionFactoryFactory:34 - Transaction strategy: org.hibernate.transaction.JDBCTransactionFactory
20:49:15,141 INFO TransactionManagerLookupFactory:33 - No TransactionManagerLookup configured (in JTA environment, use of read-write or transactional second-level cache is not recommended)
20:49:15,141 INFO SettingsFactory:143 - Automatic flush during beforeCompletion(): disabled
20:49:15,142 INFO SettingsFactory:147 - Automatic session close at end of transaction: disabled
20:49:15,142 INFO SettingsFactory:154 - JDBC batch size: 15
20:49:15,143 INFO SettingsFactory:157 - JDBC batch updates for versioned data: disabled
20:49:15,144 INFO SettingsFactory:162 - Scrollable result sets: enabled
20:49:15,144 INFO SettingsFactory:170 - JDBC3 getGeneratedKeys(): disabled
20:49:15,145 INFO SettingsFactory:178 - Connection release mode: auto
20:49:15,146 INFO SettingsFactory:205 - Default batch fetch size: 1
20:49:15,146 INFO SettingsFactory:209 - Generate SQL with comments: disabled
20:49:15,147 INFO SettingsFactory:213 - Order SQL updates by primary key: disabled
20:49:15,147 INFO SettingsFactory:217 - Order SQL inserts for batching: disabled
20:49:15,147 INFO SettingsFactory:386 - Query translator: org.hibernate.hql.ast.ASTQueryTranslatorFactory
20:49:15,150 INFO ASTQueryTranslatorFactory:24 - Using ASTQueryTranslatorFactory
20:49:15,150 INFO SettingsFactory:225 - Query language substitutions: {}
20:49:15,151 INFO SettingsFactory:230 - JPA-QL strict compliance: disabled
20:49:15,151 INFO SettingsFactory:235 - Second-level cache: enabled
20:49:15,152 INFO SettingsFactory:239 - Query cache: disabled
20:49:15,152 INFO SettingsFactory:373 - Cache provider: org.hibernate.cache.HashtableCacheProvider
20:49:15,154 INFO SettingsFactory:254 - Optimize cache for minimal puts: disabled
20:49:15,154 INFO SettingsFactory:263 - Structured second-level cache entries: disabled
20:49:15,159 INFO SettingsFactory:283 - Echoing all SQL to stdout
20:49:15,159 INFO SettingsFactory:290 - Statistics: disabled
20:49:15,160 INFO SettingsFactory:294 - Deleted entity synthetic identifier rollback: disabled
20:49:15,160 INFO SettingsFactory:309 - Default entity-mode: pojo
20:49:15,160 INFO SettingsFactory:313 - Named query checking : enabled
20:49:15,199 INFO SessionFactoryImpl:161 - building session factory
20:49:15,449 INFO SessionFactoryObjectFactory:82 - Not binding factory to JNDI, no JNDI name configured
20:49:15,453 INFO SchemaExport:154 - Running hbm2ddl schema export
20:49:15,455 DEBUG SchemaExport:170 - import file not found: /import.sql
20:49:15,455 INFO SchemaExport:179 - exporting generated schema to database
20:49:15,456 DEBUG SchemaExport:303 - drop table mock_objects if exists
20:49:15,457 DEBUG SchemaExport:303 - create table mock_objects (my_id bigint generated by default as identity (start with 1), version integer not null, stringField varchar(255) not null, longField bigint, integerField integer, primary key (my_id), unique (stringField))
20:49:15,464 INFO SchemaExport:196 - schema export complete
20:49:15,578 DEBUG JDBCTransaction:54 - begin
20:49:15,579 DEBUG JDBCTransaction:59 - current autocommit status: true
20:49:15,579 DEBUG JDBCTransaction:62 - disabling autocommit
Hibernate: select this_.my_id as my1_0_0_, this_.version as version0_0_, this_.stringField as stringFi3_0_0_, this_.longField as longField0_0_, this_.integerField as integerF5_0_0_ from mock_objects this_
20:49:15,602 DEBUG JDBCTransaction:103 - commit
20:49:15,602 DEBUG JDBCTransaction:193 - re-enabling autocommit
20:49:15,602 DEBUG JDBCTransaction:116 - committed JDBC Connection
20:49:15,605 DEBUG JDBCTransaction:54 - begin
20:49:15,606 DEBUG JDBCTransaction:59 - current autocommit status: true
20:49:15,606 DEBUG JDBCTransaction:62 - disabling autocommit
Hibernate: insert into mock_objects (my_id, version, stringField, longField, integerField) values (null, ?, ?, ?, ?)
Hibernate: call identity()
20:49:15,626 DEBUG JDBCTransaction:103 - commit
20:49:15,627 DEBUG JDBCTransaction:193 - re-enabling autocommit
20:49:15,627 DEBUG JDBCTransaction:116 - committed JDBC Connection
20:49:15,628 DEBUG JDBCTransaction:54 - begin
20:49:15,628 DEBUG JDBCTransaction:59 - current autocommit status: true
20:49:15,629 DEBUG JDBCTransaction:62 - disabling autocommit
Hibernate: delete from mock_objects where my_id=? and version=?
20:49:15,641 DEBUG JDBCTransaction:103 - commit
20:49:15,641 DEBUG JDBCTransaction:193 - re-enabling autocommit
20:49:15,642 DEBUG JDBCTransaction:116 - committed JDBC Connection
20:49:15,642 DEBUG JDBCTransaction:54 - begin
20:49:15,643 DEBUG JDBCTransaction:59 - current autocommit status: true
20:49:15,643 DEBUG JDBCTransaction:62 - disabling autocommit
20:49:15,644 DEBUG JDBCTransaction:103 - commit
Hibernate: select mockpersis_.stringField as stringFi3_0_ from mock_objects mockpersis_ where mockpersis_.my_id=?
20:49:15,645 DEBUG JDBCTransaction:152 - rollback
20:49:15,646 DEBUG JDBCTransaction:193 - re-enabling autocommit
20:49:15,646 DEBUG JDBCTransaction:163 - rolled back JDBC Connection
Problems with Session and transaction handling?
Yep! I think I'm doing somethin' wrong!!! Please help!