1

In the context of spring we always need to make sure, that a method with @Transactional is public and called from the outside of the service class. The reason behind that is documented multiple times in stackoverflow or in spring guides.

With Quarkus there seems to be the same case. Let's take the following example:

@Path("/testing")
public final class TestingResource {
    @Inject
    JustATestService justATestService;

    @GET
    @Path("testa")
    public Response testA() {
        justATestService.doWithPublicTransaction();
        return Response.ok().build();
    }

    @GET
    @Path("testb")
    public Response testB() {
        justATestService.doWithPrivateTransaction();
        return Response.ok().build();
    }
}

And here is the service class:

@ApplicationScoped
public class JustATestService {
    
    @Inject
    TransactionManager tm;
    
    @Transactional
    public void doSomethingInTransaction() throws SystemException {
        System.out.println("Active transaction: " + tm.getStatus());
    }
    
    public void doWithPrivateTransaction() throws SystemException {
        System.out.println("Not in a transaction: " + tm.getStatus());
        doSomethingInAPrivateMethode();
        System.out.println("Still not in a transaction: " + tm.getStatus());
    }

    @Transactional
    private void doSomethingInAPrivateMethode() throws SystemException {
        System.out.println("Private method will not start a transaction: " + tm.getStatus());
    }

}

The results will be:

  • testing/testa will show Status.STATUS_ACTIVE (=0) in the console
  • testing/testa will always show Status.STATUS_NO_TRANSACTION (=6) in the console

Is my assumption right? Do we need to make sure a @Transactional annotated method needs to be called from the outside of the service to work? Quarkus will behave in this case "like" spring?

beendr
  • 654
  • 7
  • 21
  • 1
    YES. In the CDI world (Quarkus' Arc is a cousin of CDI, incorporating most of its goodness, while executing much of the needed work at build time), the `@Transactional` annotation is simply an interceptor binding, specified in the JTA specification (if I am not mistaken). It even has a specific priority, meaning one can safely execute other interceptors before/after the transaction. And interceptors are implemented in a similar way to Spring without bytecode weaving, so the same restrictions apply. – Nikos Paraskevopoulos Jun 29 '21 at 15:42
  • 3
    They need to be `public`, `protected` or package-private (default visibility). They must not be `private`. Self-interception works, so you can call a `@Transactional` method from within the same class and the transactional interceptor _will_ be called, but again, the method must not be `private`. – Ladicek Jun 29 '21 at 15:53
  • Folks, maybe add those as anwers? – geoand Jun 29 '21 at 17:32

2 Answers2

2

I made this an answer for clarity as suggested by geoand. As per Ladicek comment:

They need to be public, protected or package-private (default visibility). They must not be private. Self-interception works, so you can call a @Transactional method from within the same class and the transactional interceptor will be called, but again, the method must not be private. – Ladicek Jun 29, 2021 at 15:53

I also have some experience myself using public, protected and package-private methods working. 'private' methods are sure not to work.

1

You are right, the same rule applies to Quarkus: a CDI interceptor triggered by @Transactional cannot handle self-invocation.

Read the discussion in this issue:

In your test you have @transactional on a method that is called by other methods on the same class. CDI interceptors are not applied for self invocation, only for external invocation (e.g. when you inject the bean and invoke on it).

giulianopz
  • 184
  • 1
  • 8