I wrote a test to concurrently delete and retrieve Person entities from a DB via Hibernate 3.2.1. The Person entity has three relationships: Person.address -> Address; Person.name -> Name; Person.aliases ->> Name. When the address field is declared BEFORE the name relationships, the test SUCCEEDS. When the address field is declared AFTER the name relationships, the test FAILS with the exception listed at the bottom. Unless I misunderstand something, field declaration order should not affect behaviour.
Am I doing something wrong, or is this a bug?
I am using HSQLDB 1.8.0.7, but got the same results on PostgreSQL 8.1. You will need GroboUtils to run the test:
http://groboutils.sourceforge.net/.
My test code uses Spring 2.0.1, but I do not believe this is Spring related. Here is my test code:
Code:
package deletetest;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Address
{
static private final long serialVersionUID = 1L;
@Id
private String id;
@Column(nullable=false)
private String value;
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
}
Code:
package deletetest;
import java.io.File;
import java.util.UUID;
import junit.framework.JUnit4TestAdapter;
import net.sourceforge.groboutils.junit.v1.MultiThreadedTestRunner;
import net.sourceforge.groboutils.junit.v1.TestRunnable;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
import flintstone.station.enginemanager.integration.IntegrationTest;
public class DeleteTest
{
private DeleteTestService deleteTestService;
public static junit.framework.Test suite()
{
return new JUnit4TestAdapter(DeleteTest.class);
}
@Before
public void setUp() throws Exception
{
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
"deletetest/applicationContext.xml");
deleteTestService = (DeleteTestService)appContext.getBean("deleteTestService");
LocalSessionFactoryBean lsfb = (LocalSessionFactoryBean)appContext.getBean("&sessionFactory");
lsfb.createDatabaseSchema();
}
@Test
public void testConcurrentDeleteAndRetrieve() throws Throwable
{
final int numBeans = 100;
final Object[] beans = new Object[numBeans];
for (int i = 0; i < numBeans; i++)
{
Person bean = new Person();
bean.setId(UUID.randomUUID().toString());
Address address = new Address();
address.setId(UUID.randomUUID().toString());
address.setValue("value");
bean.setAddress(address);
beans[i] = bean;
bean.setId((String)deleteTestService.create(bean));
}
class DeleteTestRunnable extends TestRunnable {
boolean done = false;
@Override
public void runTest() throws Throwable
{
for (int i = 0; i < numBeans; i++)
{
deleteTestService.delete(beans[i]);
}
done = true;
}
}
final DeleteTestRunnable deleteTestRunnable = new DeleteTestRunnable();
class RetrieveTestRunnable extends TestRunnable {
@Override
public void runTest() throws Throwable
{
while (!deleteTestRunnable.done)
{
deleteTestService.findAll(beans[0].getClass());
}
}
}
MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(new TestRunnable[] {
deleteTestRunnable,
new RetrieveTestRunnable()
});
mttr.runTestRunnables(1000 * 1000);
}
}
Code:
package deletetest;
import java.io.Serializable;
import java.util.List;
public interface DeleteTestDao
{
public <T> List<T> findAll(Class<T> aClass);
public <T> void delete(T entity);
public Serializable create(Object entity);
}
Code:
package deletetest;
import java.io.Serializable;
import java.util.List;
import flintstone.biz.ServiceException;
import flintstone.server.StaleObjectException;
public interface DeleteTestService
{
<T> List<T> findAll(Class<T> entityClass) throws ServiceException;
void delete(Object entity) throws ServiceException, StaleObjectException;
Serializable create(Object entity);
}
Code:
package deletetest;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
@Entity
public class Name
{
@Id
private String id;
@Column(nullable=false)
private String value;
@OneToOne(mappedBy="name")
private Person person_name;
@ManyToOne
private Person person_aliases;
public Person getPerson_aliases()
{
return person_aliases;
}
public void setPerson_aliases(Person person_aliases)
{
this.person_aliases = person_aliases;
}
public Person getPerson_name()
{
return person_name;
}
public void setPerson_name(Person person_name)
{
this.person_name = person_name;
}
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
}
Code:
package deletetest;
//import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
@Entity
public class Person
{
static private final long serialVersionUID = 1L;
@Id
private String id;
/* If address field is here, test SUCCEEDS
@OneToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private Address address;*/
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER,mappedBy="person_aliases")
private List<Name> aliases = new ArrayList<Name>();
@OneToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private Name name;
/* If address field is here, test FAILS */
@OneToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private Address address;
public List<Name> getAliases()
{
return aliases;
}
public void setAliases(List<Name> aliases)
{
this.aliases = aliases;
}
public Address getAddress()
{
return address;
}
public void setAddress(Address address)
{
this.address = address;
}
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
public Name getName()
{
return name;
}
public void setName(Name name)
{
this.name = name;
}
}
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
<!-- PERSISTENCE CONFIG - START ======================================== -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="org.hsqldb.jdbcDriver"/>
<property name="jdbcUrl" value="jdbc:hsqldb:mem:aname"/>
<property name="user" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation"><value>deletetest/hibernate.cfg.xml</value></property>
<property name="configurationClass">
<value>org.hibernate.cfg.AnnotationConfiguration</value>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.connection.release_mode">auto</prop><!--TODO this should not be necessary if using Hibernate 3.1+-->
<prop key="hibernate.show_sql">false</prop>
</props>
</property>
<property name="schemaUpdate" value="false"/><!--does not seem to work with
Hibernate 3.2.1-->
</bean>
<bean id="jdbcExceptionTranslator" class= "org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory"/>
<property name="jdbcExceptionTranslator" ref="jdbcExceptionTranslator"/>
</bean>
<bean id="deleteTestDao" class="deletetest.impl.DeleteTestDaoImpl">
<property name="hibernateTemplate" ref="hibernateTemplate"/>
</bean>
<!-- PERSISTENCE CONFIG - END ========================================== -->
<bean id="deleteTestService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
<property name="target">
<bean class="deletetest.impl.DeleteTestServiceImpl">
<property name="dao" ref="deleteTestDao"/>
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="find*">PROPAGATION_REQUIRED,readOnly,-Exception</prop>
<prop key="create*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="delete*">PROPAGATION_REQUIRED,-Exception</prop>
</props>
</property>
</bean>
</beans>
Code:
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<mapping class="deletetest.Person"/>
<mapping class="deletetest.Address"/>
<mapping class="deletetest.Name"/>
</session-factory>
</hibernate-configuration>
Code:
package deletetest.impl;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import deletetest.DeleteTestDao;
public class DeleteTestDaoImpl extends HibernateDaoSupport implements DeleteTestDao
{
public <T> void delete(T entity)
{
getHibernateTemplate().delete(entity);
}
@SuppressWarnings("unchecked")
public <T> List<T> findAll(final Class<T> aClass)
{
return getHibernateTemplate().executeFind(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException,
SQLException
{
return findByCriteria(aClass, session);
}
});
}
@SuppressWarnings("unchecked")
private <T> List<T> findByCriteria(Class<T> aClass, Session session,
Criterion... criterion) throws HibernateException
{
Criteria crit = session.createCriteria(aClass);
for (Criterion c : criterion)
{
crit.add(c);
}
return crit.list();
}
public Serializable create(Object entity)
{
return getHibernateTemplate().save(entity);
}
}
Code:
package deletetest.impl;
import java.io.Serializable;
import java.util.List;
import deletetest.DeleteTestDao;
import deletetest.DeleteTestService;
import flintstone.biz.ServiceException;
import flintstone.server.StaleObjectException;
public class DeleteTestServiceImpl implements DeleteTestService
{
private DeleteTestDao dao;
public void setDao(DeleteTestDao dao)
{
this.dao = dao;
}
public void delete(Object entity) throws ServiceException, StaleObjectException
{
dao.delete(entity);
}
public <T> List<T> findAll(Class<T> entityClass) throws ServiceException
{
return dao.findAll(entityClass);
}
public Serializable create(Object entity)
{
return dao.create(entity);
}
}