将代理的EntityManager注入资源时,出现IncompatibleClassChangeError

问题描述

我正在尝试将EntityManager注入jersey资源和相关的存储库服务中。这个想法是在REST请求的边界启动和提交事务,因此必须使用相同的实体管理器。为此,我使用an operation创建了一个自定义HK2范围@TransactionScope

该应用程序作为Java SE应用程序运行,球衣资源仅是其中的一部分。其他服务在后台运行,通过调度程序或JMS触发,它们也使用相同的作用域。 Java 11,最新版本的jersey,HK2,Eclipselink等。

定义了范围@Proxiable

    @Scope
    @Proxiable(proxyForSameScope = false)
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface TransactionScope { }

使用范围在根服务定位器中注册EntityManager的创建:

    bindFactory(EMFactory.class)
            .to(EntityManager.class)
            .in(TransactionScope.class);

使用EntityManager的存储库服务为@Singleton。这些提供围绕数据库实体的CRUD操作。多亏了代理注入,他们在相同的范围内获得了相同的实体管理器。

想法是:

  1. 在交易边界处,开始HK2操作。对于球衣REST请求,这是通过ContainerRequestFilter实现的;对于后台服务,是运行作业的调度程序,或者是接收JMS消息的消息侦听器。
  2. 球衣资源/计划的作业/消息处理器开始数据库事务entityManager.getTransaction().begin(),然后执行特定的逻辑。
  3. 已注入存储库服务,并为数据库实体提供CRUD操作。
  4. 交易已提交。
  5. HK2操作已关闭

将实体管理器注入后台服务时,所有这些都可以正常工作。但是,一旦我尝试将其注入到球衣资源中,就会抛出IncompatibleClassChangeError

ERROR c.s.s.e.RuntimeExceptionMapper - Caught A MultiException has 2 exceptions.  They are:
1. java.lang.IncompatibleClassChangeError: class javax.persistence.EntityManager_$$_jvsted_0 has interface javax.persistence.EntityManager as super class
2. java.lang.IllegalArgumentException: While attempting to create a Proxy for javax.persistence.EntityManager in scope com.skalio.rsem.TransactionScope an error occured while creating the proxy
 at org.jvnet.hk2.internal.ProxyUtilities.generateProxy(ProxyUtilities.java:211)

研究此错误后,通常建议检查类路径上是否存在重复和冲突的库。我相信这里不是这种情况(至少我什么也找不到)。

我不能使用@RequestScoped,因为它只能用于球衣资源,而不能用于后台服务。

有什么建议我可以做什么?

更新

我注意到以下工作原理:

  • @TransactionScope标记@Unproxiable
  • 通过EntityManager注入javax.inject.Provider

这对我来说可能是前进的方向,即使我发现它不是那么干净。开发人员可能会忘记必须使用提供者注入。

有关依赖项的更新

我已经研究了更多的依赖关系。给我带来一点惊喜:EclipseLink不使用javax.persistence:javax.persistence-api,而是使用org.eclipse.persistence:jakarta.persistence。无论如何,它仍然是包含javax.persistence包的类路径上的唯一位置。

[INFO] +- org.eclipse.persistence:eclipselink:jar:2.7.7:compile
[INFO] |  +- org.eclipse.persistence:jakarta.persistence:jar:2.2.3:compile
[INFO] |  \- org.eclipse.persistence:commonj.sdo:jar:2.1.1:compile
[INFO] +- org.eclipse.persistence:org.eclipse.persistence.jpa.modelgen.processor:jar:2.7.7:provided
[INFO] |  +- org.eclipse.persistence:org.eclipse.persistence.core:jar:2.7.7:provided
[INFO] |  |  \- org.eclipse.persistence:org.eclipse.persistence.asm:jar:2.7.7:provided
[INFO] |  \- org.eclipse.persistence:org.eclipse.persistence.jpa:jar:2.7.7:provided
[INFO] |     +- org.eclipse.persistence:org.eclipse.persistence.antlr:jar:2.7.7:provided
[INFO] |     \- org.eclipse.persistence:org.eclipse.persistence.jpa.jpql:jar:2.7.7:provided

有关服务定位器的更新

由于仅部分代码位于jersey,其余部分位于“后台”,因此我尽可能在“根” ServiceLocator中进行注册。为了提供球衣访问权限,我将定位器从球衣连接到了根定位器。

    // configures the jersey container
    public class ApplicationConfig extends ResourceConfig {

        public ApplicationConfig(ServiceLocator parentLocator) {
    
            // These services need to be registered into jersey's locator
            register(new JerseyContainerBinder());

            // bridge services into jersey
            register(new ServiceLocatorBridge(parentLocator));
        }
    }

JerseyContainerBinder当前为空,我可以使用它来扩展或覆盖根定位符绑定。 ServiceLocatorBridge获取球衣的定位器并将其连接:ExtrasUtilities.bridgeServiceLocator(jerseyLocator,sourceLocator);

因此,这是在背景中注入某些东西与在球衣资源中注入东西之间的区别:它必须首先经过桥。不确定是否重要。啊,如果球衣人只允许重复使用现有的定位器...

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)