问题
My Grails app has a a service method that updates a list of artists from last.fm's web service.
@Transactional(propagation = Propagation.NOT_SUPPORTED)
void updateLastFmArtists(Range idRange = null) {
Artist.list().each { Artist artist ->
// We could be updating a lot of artists here, the process could take up
// to an hour and we don't want to wrap all that in a single transaction
Artist.withTransaction { status ->
try {
updateArtistInfo(artist)
} catch (IOException ex) {
status.setRollbackOnly()
}
}
}
}
Each individual artist is updated within it's own transaction, which should be rolled back if an IOException
is thrown. However, I noticed the following behaviour:
If an attempt to update an artist throws an IOException
- causing the transaction to be rolled back - then the update of the next artist always fails due to the following error
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.example.Artist.topTracks, no session or session was closed
If I change the code above so that each artist is updated within it's own session, this seems to fix the problem,
Artist.withNewSession { session ->
Artist.withTransaction { status ->
try {
updateArtistInfo(artist)
} catch (IOException ex) {
status.setRollbackOnly()
}
}
}
But I don't understand why I need to do this, i.e. why is that rolling back a transaction seems to close the session?
回答1:
It's normal that rollback makes the session unusable, as it's an unrecoverable error like all Hibernate exceptions. See for example the javadoc of class ObjectNotFoundException
:
/*
* ...
*
* Like all Hibernate exceptions, this exception is considered
* unrecoverable.
*
*/
The reason is that the session is a state synchronizer component between the database and objects in memory. The way to treat a rollback in the database would be to rolllback the changes in the objects in memory.
Because this functionality would be hard to implement and of limited use, the decision was taken to make these type of exceptions unrecoverable.
You can try to catch it and continue to use the session but there is no guarantee that the session will be in a consistent state.
EDIT:
Here is further references other than the Javadoc, found in the documentation:
An exception thrown by Hibernate means you have to rollback your database transaction and close the Session immediately (this is discussed in more detail later in the chapter). If your Session is bound to the application, you have to stop the application. Rolling back the database transaction does not put your business objects back into the state they were at the start of the transaction. This means that the database state and the business objects will be out of sync. Usually this is not a problem, because exceptions are not recoverable and you will have to start over after rollback anyway.
and also:
If the Session throws an exception, including any SQLException, immediately rollback the database transaction, call Session.close() and discard the Session instance. Certain methods of Session will not leave the session in a consistent state. No exception thrown by Hibernate can be treated as recoverable. Ensure that the Session will be closed by calling close() in a finally block.
来源:https://stackoverflow.com/questions/21638706/grails-programmatic-transaction-handling