Mike Petersen
Mike Petersen

Reputation: 177

Guice @Transactional does not start an transaction

I have started to use Guice method-level transactions like described here. I have a message like

@Inject
private EntityManager entityManager;

@Transactional
public UserSession createSession(User user, String browser) {
    UserSession session = new UserSession(user, browser);
    entityManager.persist(session);
}

From the short description i thought wis should be enough. But i get an error cause no transaction is started. It works only if i start and commit it by myself.

The Object is created by Guice on the Start of my Application in an initializer. the same Instance is used for each request.

Why is it not working?

Upvotes: 9

Views: 6299

Answers (5)

sxc731
sxc731

Reputation: 2638

In addition to other answers, particularly Jeff Bowman and uldall's:

Make sure your Guice version is aligned with your JDK/JRE runtime

I just ran into this problem because I was using a Java 17 runtime with a version of Guice (5.0.1) that doesn't support it; it requires at least 5.1.0!

If think you're using the right Guice version yet it still breaks...

You may need to debug your CLASSPATH. If you're using a dependency manager:

  1. Make sure your CLASSPATH doesn't happen to have a "parasite" obsolete version inserted as a transitive dependency. With Gradle, the :dependencies task will help debug that.
  2. Your IDE could have a bug whereby its CLASSPATH conflict resolution (supposed to override older versions of transitive dependencies with newer ones) may not work as expected. This happened to me with Eclipse Buildship and I had to explicitly prevent a project from bringing in the obsolete dependency with:
    implementation("another-dependency:x.y.z") {
        exclude group: 'com.google.inject', module: 'guice'
    }
    

Bonus: verifying that Guice's instrumentation is working

It's a little annoying that there is no feedback when Guice fails to properly instrument the code, beside a subsequent exception being thrown (eg javax.persistence.TransactionRequiredException).

In order to verify that it's actually working, it may be useful to inspect a call stack within a @Transactional method, as in:

@Transactional
void myTransactionalMethod() {
  StackWalker.getInstance().forEach(System.out::println);
  // rest of method
}

If Guice's instrumentation is working correctly, you'll find com.google.inject.persist.jpa.JpaLocalTxnInterceptor in the call stack on stdout.

You can also use your IDE's debugger; simply put a breakpoint at the beginning of the @Transactional method and inspect the call stack.

This allows verifying that even as of the current version of Guice (5.1.0), Guice still doesn't support the javax.transaction.Transactional annotation, per uldall's answer.

Upvotes: 0

uldall
uldall

Reputation: 2558

I had a problem similar to yours and it was resolved by switching from @javax.transaction.Transactional to @com.google.inject.persist.Transactional. Apparently Guice-Persist doesn't support the @Transactional annotation from the Java Transaction API.

Upvotes: 3

derabbink
derabbink

Reputation: 2429

I had this problem in the following situation, so I thought I'd post my solution here, too:

BusinessLogic requires two constructor arguments: MyDao, which I could theoretically get from guice, and some other object that I could not get from guice.
So I created a BusinessLogicProvider (extends AbstactProvider), but it's not used with bind(BusinessLogic.class).toProvider(BusinessLogicProvider)). Now I just bind the BusinessLogicProvider like any guice-served type: bind(BusinessLogicProvider.class);

Inside my BusinessLogicProvider class I can now use @Inject on a private Provider<MyDao> daoProvider;

Later, in the BusinessLogicProvider's public BusinessLogic get() method, I can then call BusinessLogic's constructor with the two arguments required: daoProvider.get() and the other object I cannot get from guice.

The Pitfall: when the @Injected private Provider<MyDao> daoProvider; of my BusinessLogicProvider is not of type Provider<MyDao> (but simpy of type MyDao), it will not work.
Even if the @Injected MyDao would have come from guice, guice needs to create a new one every time you instantiate BusinessLogic.

Upvotes: 0

Jeff Bowman
Jeff Bowman

Reputation: 95654

@Transactional method annotations work through AOP, in which Guice fulfills a request for Foo by creating a proxy object that intercepts those annotated method calls and (optionally) forwards them to the actual object. Make sure that the following are true:

  1. You have created the object with the @Transactional method through Guice, since Guice otherwise won't have any chance to provide the proxy instead.

  2. Neither the class nor the method is marked final, since AOP can't override those easily.

  3. You have installed JpaPersistModule, or some other form of PersistModule. Note from that source code that this module is actually what binds the MethodInterceptor to the @Transactional annotation.

If this doesn't fit your needs exactly, remember that you can always go with the AOP documentation to write your own method interceptor. Good luck!

Upvotes: 7

Mike Petersen
Mike Petersen

Reputation: 177

After checking everything again it did not work. The funny part is that it worked in between, but only every hundred times.

After some additional testing i found that i need to recreate the Classes on each Request. Before i just created them at application start. Now it seems to work perfectly.

Thanks for tips helped me to investigate it further.

Upvotes: 2

Related Questions