What's the best way to make sure a transactional method does not propagate to a readonly transactional method

50 Views Asked by At

I'm currently facing an issue with two methods of the same Spring service, here's a basic example:

  @Transactional(readOnly = true)
  public Optional<SomeEntity> readSomeEntity(string idEntity) {
     return this.someEntityRepository.findById(idEntity);
  }

  @Transactional
  public Optional<SomeEntity> readOrCreateSomeEntity(SomeEntity entity) {
    if (this.someEntityRepository.existsById(entityInfo.getId()) {
      // How to make so that this call goes through the Spring Proxy and actually
      // gets @Transactional(readOnly = true) applied ?
      return this.readSomeEntity(entityId.getId());
    }
    else {
      return Optional.of(this.someEntityRepository.save(entity));
    }
  
  }

How to actually make sure that during the @Transactional (not read-only) context of readOrCreateSomeEntity, the actual call of readSomeEntity is purely wrapped in a readonly transactional context? And that the Transactional context of readOrCreateSomeEntity is not propagated?

I thought about two options here:

  • Use a self reference @Autowired MyService self. But this is an issue because it results in a circular reference and a context instanciation error.
  • I also thought about @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW), but it won't even be handled by Spring since I'm not calling the readSomeEntity method through the proxy!

Is it possible to achieve this, and if so, is it only possible through self reference?

From a purely technical standpoint I would prefer that the readSomeEntity is indeed 100% read-only, because hey, it's a readonly method, and I don't get why Spring would propagate its not readonly transactional context to the read submethod.. Am I wrong thinking like this?

In the end, should I care about this? Is it too much to enforce that this reading method actually does not do updates by 100% wanting a read only transactional context?

Thanks for your insight.


2

There are 2 best solutions below

1
Pp88 On

You have to put

@Transactional(readOnly = true, propagation = Propagation.NOT_SUPPORTED)
public Optional<SomeEntity> readSomeEntity(string idEntity) {
   return this.someEntityRepository.findById(idEntity);
}

in a separate bean unlikly. transactional method called in the same class don't use the proxy at all I think.

Anyway I don't see any benefits in doing this. Why you don't save directly the entity in the other method?

@Transactional
public Optional<SomeEntity> readOrCreateSomeEntity(SomeEntity entity) {
    return Optional.of(this.someEntityRepository.save(entity));
}
0
dorian.naa On

Self-answer:

Move the "exists" logic in another service so that you can call the appropriate transaction context from your service calling these methods :

Your calling service

public void something() {
   if (calledService.existsById(1L) { // replace with the id you need
      // call the read() method since entity already exists.
      // You make sure 
      // If your something() method is actually Transactional too, you may use NESTED propagation for the other services method
   }
   else {
     // Call the create() method. You'll get a non-read-only transactional context.
   }
}

Your called service

@Transactional
public void create() { /*...*/ }

@Transactional(readonly = true}
public void read() { /* ... */ }

@Transactional(readonly = true}
public void existsById(Long id) { /* ... * }

But in the end, I wouldn't bother too much about that. I'd just wrap the whole logic in the same method and use @Transactional. No need to make things over-complicated for such small readability/performance issues.