Hello Board,
I am writing a JUnit Test in Spring and I don't understand the behaviour of the transaction.
In the @Before-method I create a SessionFactory and a HibernateTransactionManager which starts a transaction with transactionManager.getTransaction(new DefaultTransactionDefinition()). This transaction should be rolled back in the @After-method. The rollback is successfull as long as I only call sessionFactory.getCurrentSession().saveOrUpdate(). If I call sessionFactory.getCurrentSession().createQuery() after the persisting of the domain objects, the rollback doesn't work and the domain objects stay in the database.
Why does Hibernate behave like this? Does the sessionFactory.getCurrentSession().createQuery() start a separate transaction which gets automatically committed?
Here is the code of the testcase:
Code:
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Properties;
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import de.kraemerit.msl.repository.HibernateArticleRepository;
import de.kraemerit.msl.repository.HibernateUnitRepository;
//Unit-Test only with Hibernate
public class HibernateArticleTest {
private HibernateUnitRepository unitRepository;
private HibernateArticleRepository articleRepository;
private PlatformTransactionManager transactionManager;
private TransactionStatus transactionStatus;
@Before
public void setUp() throws Exception {
// setup the repository to test
SessionFactory sessionFactory = createTestSessionFactory();
unitRepository = new HibernateUnitRepository(sessionFactory);
articleRepository = new HibernateArticleRepository(sessionFactory);
// begin a transaction
transactionManager = new HibernateTransactionManager(sessionFactory);
transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
}
@Test
public void testUnit() {
Unit unit = new Unit();
unit.setDescription("mm");
unit.setFactor(1L);
unitRepository.update(unit);
Long id = unit.getId();
unit = new Unit();
unit.setDescription("m");
unit.setFactor(1000L);
unitRepository.update(unit);
System.out.println(transactionStatus.toString());
//getById() commits the transaction, rollback in tearDown doesn't work
//therefore property "hibernate.hbm2ddl.auto -> create"
unit = unitRepository.getById(id);
assertTrue(unit.getDescription().equals("mm"));
}
@After
public void tearDown() throws Exception {
// transactionManager.commit(transactionStatus);
// rollback the transaction to avoid corrupting other tests
transactionManager.rollback(transactionStatus);
}
private SessionFactory createTestSessionFactory() throws Exception {
// create a FactoryBean to help create a Hibernate SessionFactory
LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();
factoryBean.setDataSource(createDataSource());
Resource[] mappingLocations = new ClassPathResource[] {
new ClassPathResource("Unit.hbm.xml", HibernateUnitRepository.class),
new ClassPathResource("Article.hbm.xml", HibernateUnitRepository.class)
};
factoryBean.setMappingLocations(mappingLocations);
factoryBean.setHibernateProperties(createHibernateProperties());
// initialize according to the Spring InitializingBean contract
factoryBean.afterPropertiesSet();
// get the created session factory
return (SessionFactory) factoryBean.getObject();
}
private DataSource createDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
// use the MySql JDBC driver
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/xxx?autoReconnect=true");
dataSource.setUsername("xxx");
dataSource.setPassword("xxx");
return dataSource;
}
private Properties createHibernateProperties() {
Properties properties = new Properties();
// turn on formatted SQL logging (very useful to verify Hibernate is
// issuing proper SQL)
properties.setProperty("hibernate.show_sql", "true");
properties.setProperty("hibernate.format_sql", "true");
// properties.setProperty("hibernate.hbm2ddl.auto", "create");
properties.setProperty("hibernate.hbm2ddl.auto", "update");
return properties;
}
}
Here is the code of the repository:
Code:
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import de.kraemerit.msl.domain.Unit;
public class HibernateUnitRepository implements UnitRepository {
private SessionFactory sessionFactory;
/**
* Creates a new Hibernate category repository.
* @param sessionFactory the Hibernate session factory
*/
public HibernateUnitRepository(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
public void update(Unit unit) {
getCurrentSession().saveOrUpdate(unit);
}
@Override
public Unit getById(Long id) {
return (Unit) getCurrentSession().createQuery("Select u From Unit u where u.id = ?").setLong(0, id).uniqueResult();
}
@Override
public Unit getByDescription(String description) {
return (Unit) getCurrentSession().createQuery("Select u From Unit u where u.description = ?").setString(0, description).uniqueResult();
}
/**
* Returns the transactional session
* @return the transactional session
*/
protected Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}