简体   繁体   中英

Grails - Why is withTransaction needed?

First some background: I'm using a modified version of Spring Security to perform Active Directory authentication and also check for possible access permissions stored in a database. This means that there is a call in a normal Groovy class to load information from the database:

if (Holders.config.loadRolesFromDatabase)
{
  Set<DomainClassRole> roles = DomainClassUser.findByUsername(username)?.roles
  if (roles)
    authorities.addAll(roles.collect({ new SimpleGrantedAuthority('ROLE_' + it.name) }))
}

This was working great with Hibernate 4.3.6.1 and Tomcat 7.0.54, however, after upgrading both (to 4.3.10.18 and 8.0.14.1) it now produces a "HibernateException: No Session found for current thread" exception when calling the dynamic finder method. After doing some research I decided to wrap this code in a withTransaction block:

if (Holders.config.loadRolesFromDatabase)
{
  DomainClassUser.withTransaction({
    Set<DomainClassRole> roles = DomainClassUser.findByUsername(username)?.roles
    if (roles)
      authorities.addAll(roles.collect({ new SimpleGrantedAuthority('ROLE_' + it.name) }))
  })
}

This fixes the error, however, I'm not really sure why this is required. My current understanding of withTransaction is that it is used to create transactions that can be rolled back in case of an exception, etc. However, I don't need to perform any rollbacks here (it's all read only calls), why would I still need a transaction to perform this call?

There's a static withSession method that looks like it'd be what you need here, but it's not; unfortunately it only makes the current Hibernate Session available, but if there's no active/current session, it doesn't create one. But withTransaction does, because when using Hibernate in Grails the Spring PlatformTransactionManager is a HibernateTransactionManager , which ensures that there is an active session for the duration of a transaction, and flushes and closes it before committing (unless there was an explicit or automatic (exception-triggered) rollback).

So it's a bit of a hack to use withTransaction in this way, since you're relying on a side effect, but you're going to the database anyway, so the transaction overhead (really just the initial calls on the connection to set autocommit to false, the isolation level, etc. and the no-op commit call at the end) is minor and in general not a concern. What we really need is something in between withSession and withTransaction that makes the session available and creates one for the duration of the current code, but without the unnecessary transaction.

This is why you need Transactions even when reading data. All database statements must be enrolled in a physical transaction so it's not like you don't use them. When you don;t explicitly have a transaction boundary you simply tun in auto-commit mode, so each statement runs in a distinct database transaction (which is more overhead). Not to mention the excessive strain put on your connection pooling solution.

You should you use withNewSession() . According to the documentation it is available since prehistorical Grails 1.2.0.

Short recap:

  • withSession() - makes current Hibernate session available (if it exists, otherwise you get "No Session found for current thread")
  • withNewSession() - creates new Hibernate session without starting a transaction (I believe the best solution for you)
  • withTransation() - starts and commits a transaction (not exactly the best choice for a simple read-only operation)

(tested on Grails 3.1.13)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM