Using Spring + Hibernate and transactional annotations.
I'm trying to test the following:
- call a method that changes a User object then calls a
@Transactional
service method to persist it - read the object back from the DB and insure it's values are correct after the method
The first problem I had was reading the User object in step 2 just returned the one in the Hibernate level 1 cache and did not actually read from the database.
I therefore manually evicted the object from the cache using the Session to force a read from the database. However, when I do this, the object values are never persisted within the unit test (I know it rolls back after the test is complete because of the settings I specified).
I tried manually flushing the session after the call to the @Transactional
service method, and that DID commit the changes. However, that was not what I expected. I thought that a @Transactional
service method would insure the transaction was committed and session flushed before it returned.
I know in general Spring will decide when to do this management, but I thought the "unit of work" in a @Transactional
method was that method.
In any case, now I'm trying to figure out how I would test a @Transactional
method in general.
Here's a junit test method that is failing:
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
@TransactionConfiguration(transactionManager = "userTransactionManager", defaultRollback = true)
@WebAppConfiguration()
@ContextConfiguration(locations = { "classpath:test-applicationContext.xml",
"classpath:test-spring-servlet.xml",
"classpath:test-applicationContext-security.xml" })
public class HibernateTest {
@Autowired
@Qualifier("userSessionFactory")
private SessionFactory sessionFactory;
@Autowired
private UserService userService;
@Autowired
private PaymentService paymentService;
@Autowired
private QueryService queryService;
@Autowired
private NodeService nodeService;
@Autowired
private UserUtils userUtils;
@Autowired
private UserContext userContext;
@Test
public void testTransactions() {
// read the user
User user1 = userService.readUser(new Long(77));
// change the display name
user1.setDisplayName("somethingNew");
// update the user using service method that is marked @Transactional
userService.updateUserSamePassword(user1);
// when I manually flush the session everything works, suggesting the
// @Transactional has not flushed it at the end of the method marked
// @Transactional, which implies it is leaving the transaction open?
// session.flush();
// evict the user from hibernate level 1 cache to insure we are reading
// raw from the database on next read
sessionFactory.getCurrentSession().evict(user1);
// try to read the user again
User user2 = userService.readUser(new Long(77));
System.out.println("user1 displayName is " + user1.getDisplayName());
System.out.println("user2 displayName is " + user2.getDisplayName());
assertEquals(user1.getDisplayName(), user2.getDisplayName());
}
}
If I manually flush the session, then the test succeeds. However, I would have expected the @Transactional
method to take care of committing and flushing the session.
The service method for updateUserSamePassword is here:
@Transactional("userTransactionManager")
@Override
public void updateUserSamePassword(User user) {
userDAO.updateUser(user);
}
The DAO method is here:
@Override
public void updateUser(User user) {
Session session = sessionFactory.getCurrentSession();
session.update(user);
}
SesssionFactory is autowired:
@Autowired
@Qualifier("userSessionFactory")
private SessionFactory sessionFactory;
I'm using XML application context configuration. I have:
<context:annotation-config />
<tx:annotation-driven transaction-manager="userTransactionManager" />
And
<bean id="userDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${user.jdbc.driverClass}"/>
<property name="jdbcUrl" value="${user.jdbc.jdbcUrl}" />
<property name="user" value="${user.jdbc.user}" />
<property name="password" value="${user.jdbc.password}" />
<property name="initialPoolSize" value="3" />
<property name="minPoolSize" value="1" />
<property name="maxPoolSize" value="17" />
</bean>
<bean id="userSessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="userDataSource" />
<property name="configLocation" value="classpath:user.hibernate.cfg.xml" />
</bean>
<bean id="userTransactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="dataSource" ref="userDataSource" />
<property name="sessionFactory" ref="userSessionFactory" />
</bean>
There is also a component scan on the services and dao classes. As I said, this is working in production.
I thought that if I have a method marked @Transactional
that by the end of that method (e.g. the update method here), Spring would have forced the Session to commit and flush.
I can see only a few options:
I misconfigured something, even though this is working for me in general (just not the unit tests). Any guesses? Any ideas for how to test this?
Something about the unit test config themselves is not behaving the way the app would.
Transactions and sessions don't work like that. My only deduction is that Spring is leaving the transaction and/or the session open after calling that update method. So when I manually evict the user on the Session object, those changes haven't been committed yet.
Can anyone confirm if this is expected behavior? Shouldn't @Transaction
have forced commit and flush on session? If not, then how would one test a method marked @Transactional
and that the methods actually work with transactions?
I.e., how should I rewrite my Unit test here?
Any other ideas?