Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
602 views
in Technique[技术] by (71.8m points)

jakarta ee - Prevent JTA transaction marked as rollback only when a RuntimeException is catched in an EJB call

I have deployed an Stateful EJB 3 on Wildfly that participates in a JTA transaction. This EJB is an extension User provider for Keycloak.

One of the EJB operations is invoked at some point and could produce a RuntimeException. I need the global transaction to commit so I added a try-catch block in the ejb method.

@Stateful
@Local(EjbUserStorageProvider.class)
public class EjbUserStorageProvider implements UserStorageProvider,
        UserLookupProvider,
        UserRegistrationProvider,
        UserQueryProvider,
        CredentialInputUpdater,
        CredentialInputValidator,
        OnUserCache
{

    ...

    @Override
    public int getUsersCount(RealmModel realm) {
        try {
            Object count = em.createNamedQuery("getUserCount").getSingleResult();
            return ((Number)count).intValue();
        } catch(RuntimeException e) {
            e.printStackTrace();
            return 0;
        }
    }
}

The problem is that, despite capturing the exception, the transaction is rolledback.

This is the problematic bit:

org.keycloak.models.utils.KeycloakModelUtils

    public static void runJobInTransaction(KeycloakSessionFactory factory, KeycloakSessionTask task) {
        KeycloakSession session = factory.create();
        KeycloakTransaction tx = session.getTransactionManager();
        try {
            tx.begin();
            task.run(session);

            if (tx.isActive()) {
                if (tx.getRollbackOnly()) {
                    tx.rollback();
                } else {
                    tx.commit();
                }
            }
        } catch (RuntimeException re) {
            if (tx.isActive()) {
                tx.rollback();
            }
            throw re;
        } finally {
            session.close();
        }
    }

For some reason tx.getRollbackOnly() returns true so the JTA transaction is rolledback.

How could I prevent the transaction from being marked as rollback only?

UPDATE

I tried using @Transactional but was ignored:

    @Transactional(value = Transactional.TxType.NEVER, dontRollbackOn = {RuntimeException.class, PersistenceException.class})
    @Override
    public int getUsersCount(RealmModel realm) {

I also used @TransactionAttribute(NOT_SUPPORTED) but in this case Keycloak can't start:

09:36:30,784 ERROR [org.jboss.as.ejb3.invocation] (ServerService Thread Pool -- 51) WFLYEJB0034: EJB Invocation failed on component EjbUserStorageProvider for method public int es.mma.edm.keycloak.storage.user.EjbUserStorageProvider.getUsersCount(org.keycloak.models.RealmModel): javax.ejb.ConcurrentAccessTimeoutException: WFLYEJB0228: EJB 3.1 FR 4.3.14.1 concurrent access timeout on EjbUserStorageProvider - could not obtain lock within 5000 MILLISECONDS
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.component.stateful.StatefulSessionSynchronizationInterceptor.processInvocation(StatefulSessionSynchronizationInterceptor.java:94)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.as.ee@7.2.0.GA-redhat-00005//org.jboss.as.ee.concurrent.ConcurrentContextInterceptor.processInvocation(ConcurrentContextInterceptor.java:45)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InitialInterceptor.processInvocation(InitialInterceptor.java:40)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.ChainedInterceptor.processInvocation(ChainedInterceptor.java:53)
    at org.jboss.as.ee@7.2.0.GA-redhat-00005//org.jboss.as.ee.component.interceptors.ComponentDispatcherInterceptor.processInvocation(ComponentDispatcherInterceptor.java:52)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.component.stateful.StatefulComponentInstanceInterceptor.processInvocation(StatefulComponentInstanceInterceptor.java:59)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.component.interceptors.AdditionalSetupInterceptor.processInvocation(AdditionalSetupInterceptor.java:54)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInNoTx(CMTTxInterceptor.java:216)
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.tx.CMTTxInterceptor.notSupported(CMTTxInterceptor.java:337)
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.tx.CMTTxInterceptor.processInvocation(CMTTxInterceptor.java:142)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.component.interceptors.CurrentInvocationContextInterceptor.processInvocation(CurrentInvocationContextInterceptor.java:41)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.component.invocationmetrics.WaitTimeInterceptor.processInvocation(WaitTimeInterceptor.java:47)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.security.SecurityContextInterceptor.processInvocation(SecurityContextInterceptor.java:100)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.deployment.processors.StartupAwaitInterceptor.processInvocation(StartupAwaitInterceptor.java:22)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.component.interceptors.ShutDownInterceptorFactory$1.processInvocation(ShutDownInterceptorFactory.java:64)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.as.ejb3@7.2.0.GA-redhat-00005//org.jboss.as.ejb3.component.interceptors.LoggingInterceptor.processInvocation(LoggingInterceptor.java:67)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.as.ee@7.2.0.GA-redhat-00005//org.jboss.as.ee.component.NamespaceContextInterceptor.processInvocation(NamespaceContextInterceptor.java:50)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.ContextClassLoaderInterceptor.processInvocation(ContextClassLoaderInterceptor.java:60)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.run(InterceptorContext.java:438)
    at org.wildfly.security.elytron-private@1.6.1.Final-redhat-00001//org.wildfly.security.manager.WildFlySecurityManager.doChecked(WildFlySecurityManager.java:619)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.AccessCheckingInterceptor.processInvocation(AccessCheckingInterceptor.java:57)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
    at org.jboss.invocation@1.5.1.Final-redhat-1//org.jboss.invocation.ChainedInterceptor.processInvocation(ChainedInterceptor.java:53)
    at org.jboss.as.ee@7.2.0.GA-redhat-00005//org.jboss.as.ee.component.ViewService$View.invoke(ViewService.java:198)
    at org.jboss.as.ee@7.2.0.GA-redhat-00005//org.jboss.as.ee.component.ViewDescription$1.processInvocation(ViewDescription.java:185)
    at org.jboss.as.ee@7.2.0.GA-redhat-00005//org.jboss.as.ee.component.ProxyInvocationHandler.invoke(ProxyInvocationHandler.java:81)
    at deployment.edm-keycloak-user-storage.jar//es.mma.edm.keycloak.storage.user.EjbUserStorageProvider$$$view1.getUsersCount(Unknown Source)
    at org.keycloak.keycloak-services@4.8.3.Final-redhat-00001//org.keycloak.storage.UserStorageManager.getUsersCount(UserStorageManager.java:453)
    at org.keycloak.keycloak-model-infinispan@4.8.3.Final-redhat-00001//org.keycloak.models.cache.infinispan.UserCacheSession.getUsersCount(UserCacheSession.java:543)
    at org.keycloak.keycloak-model-infinispan@4.8.3.Final-redhat-00001//org.keycloak.models.cache.infinispan.UserCacheSession.getUsersCount(UserCacheSession.java:548)
    at org.keycloak.keycloak-services@4.8.3.Final-redhat-00001//org.keycloak.services.managers.ApplianceBootstrap.isNoMasterUser(ApplianceBootstrap.java:55)
    at org.keycloak.keycloak-services@4.8.3.Final-redhat-00001//org.keycloak.services.resources.KeycloakApplication$2.run(KeycloakApplication.java:168)
    at org.keycloak.keycloak-server-spi-private@4.8.3.Final-redhat-00001//org.keycloak.models.utils.KeycloakModelUtils.runJobInTransaction(KeycloakModelUtils.java:227)
    at org.keycloak.keycloak-services@4.8.3.Final-redhat-00001//org.keycloak.services.resources.KeycloakApplication.<init>(KeycloakApplication.java:164)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
    at org.jboss.resteasy.resteasy-jaxrs@3.6.1.SP2-redhat-00001//org.jboss.resteasy.core.ConstructorInjectorImpl.construct(ConstructorInjectorImpl.java:154)
    at org.jboss.resteasy.resteasy-jaxrs@3.6.1.SP2-redhat-00001//org.jboss.resteasy.spi.ResteasyProviderFactory.createProviderInstance(ResteasyProviderFactory.java:2757)
    at org.jboss.resteasy.resteasy-jaxrs@3.6.1.SP2-redhat-00001//org.jboss.resteasy.spi.ResteasyDeployment.createApplication(ResteasyDeployment.java:363)
    at org.jboss.resteasy.resteasy-jaxrs@3.6.1.SP2-redhat-00001//org.jboss.resteasy.spi.ResteasyDeployment.startInternal(ResteasyDeployment.java:276)
    at org.jboss.resteasy.resteasy-jaxrs@3.6.1.SP2-redhat-00001//org.jboss.resteasy.spi.ResteasyDeployment.start(ResteasyDeployment.java:88)
    at org.jboss.resteasy.resteasy-jaxrs@3.6.1.SP2-redhat-00001//org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.init(ServletContainerDispatcher.java:119)
    at org.jboss.resteasy.resteasy-jaxrs@3.6.1.SP2-redhat-00001//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.init(HttpServletDispatcher.java:36)
    at io.undertow.servlet@2.0.15.Final-redhat-00001//io.undertow.servlet.core.LifecyleInterceptorInvocation.proceed(LifecyleInterceptorInvocation.java:117)
    at org.wildfly.extension.undertow@7.2.0.GA-redhat-00005//org.wildfly.extension.undertow.secu

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is the expected behaviour, required by the JPA spec (section 3.1.1 page 79)

Runtime exceptions thrown by the methods of the EntityManager interface other than the LockTimeoutException will cause the current transaction to be marked for rollback if the persistence context is joined to that transaction.

To workaround this you need to declare the method to be executed in a new transaction, using REQUIRES_NEW as TransactionAttribute


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...