apache / apache/grails-core

Unexpected transactional behavior when defining GORM data service as abstract class

Open
#14,441 1 comment 0 reactions 0 assignees View on GitHub
relates-to: grails-data-hibernate5
Dominant language
Groovy
Stars
2.9k
Forks
975
Avg merge
1d 22h
Merged PRs (30d)
92

Description

The documentation says this regarding how transactions will be applied when defining a GORM data service as an abstract class:

> In addition, all public methods of the domain class will be automatically wrapped in the appropriate transaction handling.

The behavior I am seeing is different though. I have two examples here to illustrate.

In this first example, the createOrUpdate method is not wrapped in a transaction unless I add the @Transactional annotation at the class level, which the documentation says I should not have to do.

```groovy
@Service(User)
@Transactional // without this, createOrUpdate is not transactional!
abstract class UserService {

abstract User get(Serializable id)

abstract List list(Map args)

abstract Long count()

abstract void delete(Serializable id)

abstract User save(User user)

User createOrUpdate(User user, Set roles) {

user = this.save(user)

if (roles != null && !roles.isEmpty()) {
// delete any existing user role mappings
UserRole.removeAll(user)
// add new user role mappings
for (Role role : roles) {
UserRole.create(user, role)
}
}

return user
}

}
```

In this second example, something even stranger is happening. The save method is not wrapped in a transaction unless I add an annotation specifically for the save method (the class level annotation is not enough).

```groovy
@Service(ReplenishmentRequest)
@Transactional
abstract class ReplenishmentRequestService {

@Autowired
SpringSecurityService springSecurityService

abstract ReplenishmentRequest get(Serializable id)

abstract List list(Map args)

abstract Long count()

abstract void delete(Serializable id)

@Transactional // without this, save is not transactional!
ReplenishmentRequest save(ReplenishmentRequest replenishmentRequest) {
// details elided
}

}
```

I haven't yet tried to walk through the entire sequence at runtime to see what is going wrong, but the behavior does not match the documentation. It is also surprising that in the second case the class level annotation is not enough to make the save method transactional. Any insight you can provide is greatly appreciated.

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the behavior in the UserService and ReplenishmentRequestService examples, comparing class-level and method-level @Transactional annotations. Trace how the abstract GORM data service methods are wrapped and verify whether createOrUpdate and save run transactionally without extra annotations. Done means the implementation matches the documented transaction behavior or the documentation clearly describes the actual behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
groovy
Domain
backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.