My application thinks it’s saving to the database when it isn’t
We’ve recently added Spring 3.0, Hibernate 3.5 to some of our web applications and have been using the Java Persistence API, version 2.0, with good results. The other morning I received a mail from Duncan saying that a form he was working on had stopped saving data. The form appeared to be working fine, you could submit the form and received the confirmation screen and there were no errors in the logs, but there was not a trace of the submission in the database. This was despite the fact that our database integration tests were passing.
It turns out the problem was easily fixed: The visisbility of our save method had been switched from public to default and Spring’s @Transactional annotation only works on public methods. However it raised big alarm bells for us: our data tier can silently throw away data giving no clue to the end user or administrator that there’s a problem. We needed to update our code to prevent a similar regression in the future.
public class MyObjDatabase {
@PersistenceContext private EntityManager em;
@Transactional public void save(MyObj obj) {
em.persist(obj);
}
}
First step: write a test that fails
The Spring 3.0.x reference manual provides fairly comprehensive documentation for writing integration tests to make sure your Spring and ORM code interacts correctly with your database. By adding a series of annotations to your test class you can easily test all the CRUD operations of your DAO:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"file:test/META-INF/test-spring-context.xml",
"file:web/WEB-INF/spring/application-context.xml"})
@TransactionConfiguration
@Transactional
public class MyObjDatabaseIntegrationTest {
Now we already had a test for our broken code. It saved our data using the DAO’s save method and then used Spring’s JdbcTemplate to query that the data was there. Trouble was that this test was passing! The reason for this is easily deduced: Our code was broken because it wasn’t operating in a transaction but Spring’s test framework sets up the transaction for you. To resolve this I added an additional test case which didn’t use Spring’s transaction test support:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"file:test/META-INF/test-spring-context.xml",
"file:web/WEB-INF/spring/application-context.xml"})
public class MyObjDatabaseTransactionIntegrationTest {
@Inject private MyObjDatabase database;
@Inject private DataSource dataSource;
@Test public void testSaveParticipatesInATransaction() {
MyObj obj = testObj();
database.save(obj);
final SimpleJdbcTemplate simpleJdbcTemplate =
new SimpleJdbcTemplate(dataSource);
assertNotNull(obj.getId());
assertEquals(1, simpleJdbcTemplate.queryForList(
"SELECT * FROM myobj WHERE id = ?", obj.getId()).size());
}
}
This test fail: Excellent! I’m not happy yet because I don’t really want to have to write this kind of test for every method that touches the database. I’d also like to know what’s going on: surely I can configure Hibernate to throw an exception when it’s not doing anything.
Second step: work out why there’s no error
I’ve had a good read of the JPA specification and Javadocs and the relevant section in the Spring reference manual. I also debugged the code a little.
- The javadocs for
EntityManager.persist()
method say it should throw aTransactionRequiredException
– if invoked on a container-managed entity manager of typePersistenceContextType.TRANSACTION
and there is no transaction - The specification says injected persistence contexts (i.e. by an annotation) are container managed
- The javadocs for
type
element of thePersistenceContext
annotation say its default value is PersistenceContextType.TRANSACTION - The EntityManagerFactory implementation that’s being called org.hibernate.ejb.EntityManagerFactoryImpl only creates entity managers of type PersistenceContextType.EXTENDED
This makes me think I’ve found a bug in Hibernate and I’ve submitted a post on the Hibernate forum to check. However it’s a little cloudy because the JPA specification seems to assume only JEE containers operating with JTA will support container managed persistent contexts. We’re using Spring’s transactions in Tomcat (not a JEE container) with additional Spring code wiring the JPA annotations so maybe it’s a bug with Spring instead.
Possible solutions
public class MyObjDatabase {
@PersistenceContext private EntityManager em;
@Transactional public void save(MyObj obj) {
em.persist(obj);
em.flush();
}
}
- We obviously reintroduce the missing public modifier to ensure the @Transactional is effective
- We can flush the entity manager after we persist our object. The contract for the flush method says that it must throw a
TransactionRequiredException
if no transaction is in progress
This is still not satisfactory: I shouldn’t need to add a call to flush. If I don’t hear back from the hibernate forum I’m going to try the Spring community instead.
Nice post. Did you figure out another way of knowing that a transaction is not present (apart from the flush(), bacause as you said its somehow ugly and less performant)
Posted a possible solution here https://forum.hibernate.org/viewtopic.php?f=1&t=1002889&p=2432543
Which is possibly cleaner then flush() ( degrades performance ) and if you dont want to make the method Transactional but instead throw an exception (because presumably you want the caller to handle transactions)