I had an embarrassing incident yesterday. Code which had passed my automated integration test suite failed in production. This proved to involve StatelessSessions, Scrollable Results and transaction commitment.
Code:
private ScrollableResults query(StatelessSession sess) {
return sess.createQuery(hql)
...
.scroll(ScrollMode.FORWARD_ONLY);
}
private void process() {
...
StatelessSession sess = getBroker().getStatelessSession();
getBroker().beginStatelessSessionTransaction();
ScrollableResults results = query(sess);
getBroker().commitStatelessSessionTransaction();
while ( true ) {
int transCount = 0;
while ( results.next() ) {
transCount++;
OrderRec order = (OrderRec) results.get(0);
// do non-database related stuff with order
}
if (transCount < MAX_ROWS_PER_DIP) {
// we're done
break;
} else {
// don't hog the processor.
pause();
}
}
}
(note these "broker" calls are calls to a Thread-local-pattern based wrapper around Hibernate. This was originally based on an old suggestion from Hibernate and is not suspected here).
This code worked in my automated integration test suite. When it was tried in production, however, it failed:
2011-03-23 02:30:00,254 [sked-5] ERROR util.JDBCExceptionReporter - Operation
not allowed after ResultSet closed
2011-03-23 02:30:00,255 [sked-5] ERROR HibernateException
org.hibernate.exception.GenericJDBCException: could not advance using next()
at
org.hibernate.exception.SQLStateConverter.handledNonSpecificException
(SQLStateConverter.java:126)
at org.hibernate.exception.SQLStateConverter.convert
(SQLStateConverter.java:114)
at org.hibernate.exception.JDBCExceptionHelper.convert
(JDBCExceptionHelper.java:66)
at org.hibernate.exception.JDBCExceptionHelper.convert
(JDBCExceptionHelper.java:52)
at org.hibernate.impl.ScrollableResultsImpl.next
(ScrollableResultsImpl.java:127)
This exception was able to be overcome by the following code change (committing the transaction only after the scrollable results were walked).
Code:
private void process() {
...
StatelessSession sess = getBroker().getStatelessSession();
getBroker().beginStatelessSessionTransaction();
ScrollableResults results = query(sess);
while ( true ) {
int transCount = 0;
while ( results.next() ) {
transCount++;
OrderRec order = (OrderRec) results.get(0);
// do non-database related stuff with order
}
if (transCount < MAX_ROWS_PER_DIP) {
// we're done
break;
} else {
// don't hog the processor.
pause();
}
}
getBroker().commitStatelessSessionTransaction();
}
I had assumed that as long as the Session remained open, the results were readable. Is this not the case for stateless sessions?
Anyway, I still wanted to understand the difference between my integration test environment and my production environment. Eventually, I realized that my production environment was using C3P0 to provide connections whereas my test environment was not. Turning C3P0 on in the test environment caused the first code to fail in the test environment as it did in production.
I wonder if this is a bug. If my first code sample is correct then it should work under C3P0 or Hibernate's default connection provider. If it is incorrect, then it should fail either way. It doesn't seem right to me that the choice of connection pool should affect the way transactions work, but I could be wrong about this. Can someone explain the inconsistency?