首页 > 解决方案 > 仅当在 EJB 调用中捕获到 RuntimeException 时才阻止将 JTA 事务标记为回滚

问题描述

我在 Wildfly 上部署了一个参与 JTA 事务的有状态 EJB 3。此 EJB 是 Keycloak 的扩展用户提供程序。

其中一个 EJB 操作在某个时间点被调用,并可能产生 RuntimeException。我需要提交全局事务,所以我在 ejb 方法中添加了一个 try-catch 块。

@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;
        }
    }
}

问题是,尽管捕获了异常,事务还是回滚了。

这是有问题的位:

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();
        }
    }

由于某种原因tx.getRollbackOnly()返回 true,因此 JTA 事务被回滚。

如何防止事务被标记为仅回滚?

更新

我尝试使用 @Transactional 但被忽略了:

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

我也使用过@TransactionAttribute(NOT_SUPPORTED),但在这种情况下 Keycloak 无法启动:

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.security.RunAsLifecycleInterceptor.init(RunAsLifecycleInterceptor.java:78)
    at io.undertow.servlet@2.0.15.Final-redhat-00001//io.undertow.servlet.core.LifecyleInterceptorInvocation.proceed(LifecyleInterceptorInvocation.java:103)
    at io.undertow.servlet@2.0.15.Final-redhat-00001//io.undertow.servlet.core.ManagedServlet$DefaultInstanceStrategy.start(ManagedServlet.java:303)
    at io.undertow.servlet@2.0.15.Final-redhat-00001//io.undertow.servlet.core.ManagedServlet.createServlet(ManagedServlet.java:143)
    at io.undertow.servlet@2.0.15.Final-redhat-00001//io.undertow.servlet.core.DeploymentManagerImpl$2.call(DeploymentManagerImpl.java:583)
    at io.undertow.servlet@2.0.15.Final-redhat-00001//io.undertow.servlet.core.DeploymentManagerImpl$2.call(DeploymentManagerImpl.java:554)
    at io.undertow.servlet@2.0.15.Final-redhat-00001//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:42)
    at io.undertow.servlet@2.0.15.Final-redhat-00001//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at org.wildfly.extension.undertow@7.2.0.GA-redhat-00005//org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
    at org.wildfly.extension.undertow@7.2.0.GA-redhat-00005//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
    at org.wildfly.extension.undertow@7.2.0.GA-redhat-00005//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
    at org.wildfly.extension.undertow@7.2.0.GA-redhat-00005//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
    at org.wildfly.extension.undertow@7.2.0.GA-redhat-00005//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1502)
    at io.undertow.servlet@2.0.15.Final-redhat-00001//io.undertow.servlet.core.DeploymentManagerImpl.start(DeploymentManagerImpl.java:596)
    at org.wildfly.extension.undertow@7.2.0.GA-redhat-00005//org.wildfly.extension.undertow.deployment.UndertowDeploymentService.startContext(UndertowDeploymentService.java:97)
    at org.wildfly.extension.undertow@7.2.0.GA-redhat-00005//org.wildfly.extension.undertow.deployment.UndertowDeploymentService$1.run(UndertowDeploymentService.java:78)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at org.jboss.threads@2.3.2.Final-redhat-1//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads@2.3.2.Final-redhat-1//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1985)
    at org.jboss.threads@2.3.2.Final-redhat-1//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1487)
    at org.jboss.threads@2.3.2.Final-redhat-1//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1378)
    at java.base/java.lang.Thread.run(Thread.java:834)
    at org.jboss.threads@2.3.2.Final-redhat-1//org.jboss.threads.JBossThread.run(JBossThread.java:485)

标签: jakarta-eeejbwildflykeycloakjta

解决方案


这是 JPA 规范要求的预期行为(第 79 页第 3.1.1 节)

如果持久性上下文连接到该事务,则由 EntityManager 接口的方法(而非 LockTimeoutException)引发的运行时异常将导致当前事务被标记为回滚。

要解决此问题,您需要声明要在新事务中执行的方法,使用REQUIRES_NEWasTransactionAttribute


推荐阅读