@Asynchronous EJB 和hibernate 连接问题
@Asynchronous EJB and hibernate connection issues
@Stateless
public class MyStatelessBeanA {
@Resource
private SessionContext sessionCtx;
public byte[] methodA(){
MyStatelessBeanA myStatelessProxy1 = this.sessionCtx.getBusinessObject(MyStatelessBeanA.class);
MyStatelessBeanA myStatelessProxy2 = this.sessionCtx.getBusinessObject(MyStatelessBeanA.class);
Future<byte[]> proxy1Future = myStatelessProxy1.asynchMethod();
Future<byte[]> proxy2Future = myStatelessProxy2.asynchMethod();
byte[] firstArray = proxy1Future.get();
byte[] secondArray = proxy2Future.get();
return ...
}
@Asynchronous
public Future<byte[]> asynchMethod(){
byte[] byteArray = ...
...do something including select from various table...
return new AsynchResult<byte[]>(byteArray);
}
基本上我想做的是从两个代理对象并行调用 asynchMethod()
两次。
问题?
2019-11-05 17:20:23,354 WARN [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (EJB default - 3) SQL Error: 0, SQLState: null
2019-11-05 17:20:23,354 ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (EJB default - 3) IJ031041: Connection handle has been closed and is unusable
2019-11-05 17:20:23,354 INFO [org.jboss.jca.core.connectionmanager.listener.TxConnectionListener] (EJB default - 2) IJ000311: Throwable from unregister connection: java.lang.IllegalStateException: IJ000152: Trying to return an unknown connection: org.jboss.jca.adapters.jdbc.jdk7.WrappedConnectionJDK7@69ebfad0
at org.jboss.jca.core.connectionmanager.ccm.CachedConnectionManagerImpl.unregisterConnection(CachedConnectionManagerImpl.java:408)
at org.jboss.jca.core.connectionmanager.listener.TxConnectionListener.connectionClosed(TxConnectionListener.java:645)
at org.jboss.jca.adapters.jdbc.BaseWrapperManagedConnection.returnHandle(BaseWrapperManagedConnection.java:617)
at org.jboss.jca.adapters.jdbc.BaseWrapperManagedConnection.closeHandle(BaseWrapperManagedConnection.java:562)
at org.jboss.jca.adapters.jdbc.WrappedConnection.returnConnection(WrappedConnection.java:298)
at org.jboss.jca.adapters.jdbc.WrappedConnection.close(WrappedConnection.java:256)
at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.closeConnection(DatasourceConnectionProviderImpl.java:127)
at org.hibernate.internal.AbstractSessionImpl$NonContextualJdbcConnectionAccess.releaseConnection(AbstractSessionImpl.java:397)
at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.releaseConnection(LogicalConnectionManagedImpl.java:172)
at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.afterStatement(LogicalConnectionManagedImpl.java:125)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.afterStatementExecution(JdbcCoordinatorImpl.java:281)
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:145)
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86)
at org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167)
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3956)
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:116)
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1129)
at org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:997)
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:157)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:266)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:68)
对 asynchMethod() 的两次调用已正确分配给两个不同的线程:
2019-11-05 17:20:22,566 INFO [**.*******.*******.*******.MyStatelessBeanA] (EJB default - 2) method=asynchMethod START
2019-11-05 17:20:22,655 INFO [**.*******.*******.*******.MyStatelessBeanA] (EJB default - 3) method=asynchMethod START
是否有可能一个代理对象以某种方式关闭另一个代理对象的连接?我不知道是否有足够的信息来猜测问题的正确解决方案,但我正在寻找所有可能的方法(CachedConnectionManagerImpl 源代码、TxConnectionListener 源代码),但似乎超出了我的技能范围。
如果有人可以提供帮助或提供一些提示,因为我完全坚持这一点。
谢谢,
大卫
添加了可能有用的信息
Standalone.xml休眠部分
<cache-container name="hibernate" default-cache="local-query" module="org.hibernate.infinispan">
<local-cache name="entity">
<transaction mode="NON_XA"/>
<eviction strategy="LRU" max-entries="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<local-cache name="local-query">
<eviction strategy="LRU" max-entries="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<local-cache name="timestamps"/>
</cache-container>
Persistence.xml
<?xml version="1.0" encoding="UTF-8" ?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="***" transaction-type="RESOURCE_LOCAL">
<jar-file>file:./target/test-classes</jar-file>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.archive.autodetection" value="class,hbm" />
<property name="hibernate.connection.url" value="${dbunit.connectionUrl}" />
<property name="hibernate.connection.driver_class" value="${dbunit.driverClass}" />
<property name="hibernate.dialect" value="${dbunit.jpa-dialect}" />
<property name="hibernate.connection.username" value="${dbunit.username}" />
<property name="hibernate.connection.password" value="${dbunit.password}" />
<property name="javax.persistence.validation.mode" value="NONE" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
这不是一个完整的答案,因为我无法确定真正的根本原因,但我会在这里写下我发现的内容和可能的修复方法。
我在 Wildfly 应用程序服务器(在撰写本文时也是一个相当新的版本)中遇到过这个问题,并且似乎记得很久以前在 GlassFish 服务器上看到过同样的问题。问题似乎出在 Hibernate 而不是 EJB 容器或 JTA 管理器,尽管我对此不是 100% 确定。尽管有警告和烦人的堆栈跟踪,代码似乎按照您的期望正确运行。事务已提交并且不会由于错误而回滚,调用似乎 运行 在单独的事务中具有自己的连接,而且我没有注意到某种连接泄漏会在长时间后导致问题。所以至少在功能上看起来还不错。
这里的主要怀疑是建立连接和事务的范围与关闭它们的范围不同。我不知道是异步调用还是 EJB 的自注入触发了这个问题,以及其中一个是否足以复制它。我怀疑对 @Asynchronous
方法的调用将为实体管理器获取数据库连接并启动事务,这在某种程度上与调用异步方法的线程或其他上下文相关。但是当然,该方法是异步的,实际执行将由 EJB 容器在单独的线程中处理,该方法立即 returns,如果其 return 类型,则 return 什么都不做无效或 returning a Future
以获得结果。当该方法完成时,EJB 容器负责事务提交或回滚以及释放连接。 Hibernate 似乎不喜欢在不同的上下文或线程中看到这种情况,因此会出现错误。但是错误发生在事务完成之后(因此代码有效)并且因为它发生在某些容器管理的线程中,而不是调用者的线程中,所以异常永远不会冒泡到您的代码中。
如果仅此而已,那么异步 EJB 方法可以始终如一地重现此问题。但它似乎并没有这样做。 EJB 的自注入似乎也是触发它的必要条件。事实上,在 GlassFish 项目中我遇到了这个问题,我不记得使用了异步方法,但我确实让 EJB 获得了它自己的另一个实例 class。因此,也许仅此一项就足以获得这种行为。为什么在那种情况下会发生这种情况我还没有搞清楚,但我会在举一个可能的解决方案示例后回过头来讨论它。
不管是什么原因,我使用的一种似乎可以消除错误的解决方法是简单地在 EJB class 本身中使用 @EJB
注入,而不是通过 @Resource
-注入 SessionContext
。所以你的代码可以重写如下:
@Stateless
public class MyStatelessBeanA {
@EJB
private MyStatelessBeanA myStatelessProxy1;
@EJB
private MyStatelessBeanA myStatelessProxy2;
public byte[] methodA(){
Future<byte[]> proxy1Future = myStatelessProxy1.asynchMethod();
Future<byte[]> proxy2Future = myStatelessProxy2.asynchMethod();
byte[] firstArray = proxy1Future.get();
byte[] secondArray = proxy2Future.get();
return ...
}
@Asynchronous
public Future<byte[]> asynchMethod(){
byte[] byteArray = ...
...do something including select from various table...
return new AsynchResult<byte[]>(byteArray);
}
这可能看起来有点可怕,因为感觉它会导致某些无限递归的注入。但那不会发生。请记住,在某些 EJB 或托管 bean 中用 @EJB
或 @Inject
注释的字段实际上是一个代理对象,并且当该代理完成时,您的 class 的实际实例的查找或实例化完成用来。那些 myStatelessProxy1
和 myStatelessProxy2
字段只是代理占位符,一旦对它们调用方法,MyStatelessBeanA
的实例就会分配给代理,完成自己的注入并获得资源就像数据库连接和事务上下文。而那个实例又可能有这两个字段,但它们也是代理,如果不使用它们,就不会发生进一步的递归。如果您在注入实例上调用的方法依赖于它自己的注入字段之一,然后又在其上调用一个执行相同操作的方法,则您可能会不小心创建一些无休止的递归。因为这不同于同一对象实例中的递归,所以它可能不会立即显而易见,并且编译器或 IDE 可能不会警告您,所以要小心。
从 EJB 2.1 API 开始应该支持这种自注入。奇怪的是,这样做似乎导致不再抛出 Hibernate 错误。回到我之前提到的问题,一个可能的原因可能是创建 EJB 代理的时间点。在一种情况下,我们要求 SessionContext
创建一个实例,这发生在 EJB 的一个方法中。在另一种情况下,我们通过 @EJB
注入点获取实例,这将在调用 EJB 方法之前完成。这是两种不同的语境。一个在您的代码中,可能有自己的数据库连接,并且可能在事务中 运行ning。另一个在容器代码内,在事务边界之外,可能在建立数据库连接之前。现在,作为代理,我们可能希望它们只有在我们使用它们时才会启动,但谁知道在创建代理对象时容器可能会预先做哪些工作以及交给它们的是什么。也许当前的 Hibernate 版本可以正确处理这个问题,但是当混合使用异步方法时仍然会出错。
自注入可能会解决您的问题,但如果您需要这些 EJB 实例的动态数量,则自注入将不起作用,这可以在从 SessionContext
获取它们时完成。但在那种情况下,代码设计可能存在问题。一次注入就足够了,因为您可以为多个异步方法调用重用那个注入。他们每个 运行 在各自的线程中。他们也会有自己的交易。异步方法不能加入其调用者的事务,因为调用者的事务完成点将不再是它退出其事务边界时。它将在未来的“某个时间点”完成。这搞乱了语义。唯一的选择是让调用者在退出其方法后等待,直到它启动的异步调用完成,但这在某些情况下会破坏它们的目的(比如当您不需要 return 值时)。如果您必须生成不可预测数量的异步任务,则 ManagedExecutorService
可能更合适。它是 Java EE 的并发实用程序的一部分,它是在 JEE 7 中引入的。
最后,另一种解决方案可能是制作一些额外的帮助程序 EJB class,它会注入另一个,然后使用它。这种划分可能有点人为,因为它只是为了处理容器管理的影响而存在,而不是作为您的要求的直接结果,但它简单而有效。与自注入相反,它也很容易推理,并且可以帮助编写测试和使用模拟对象。
@Stateless
public class MyStatelessBeanA {
@Resource
private SessionContext sessionCtx;
public byte[] methodA(){
MyStatelessBeanA myStatelessProxy1 = this.sessionCtx.getBusinessObject(MyStatelessBeanA.class);
MyStatelessBeanA myStatelessProxy2 = this.sessionCtx.getBusinessObject(MyStatelessBeanA.class);
Future<byte[]> proxy1Future = myStatelessProxy1.asynchMethod();
Future<byte[]> proxy2Future = myStatelessProxy2.asynchMethod();
byte[] firstArray = proxy1Future.get();
byte[] secondArray = proxy2Future.get();
return ...
}
@Asynchronous
public Future<byte[]> asynchMethod(){
byte[] byteArray = ...
...do something including select from various table...
return new AsynchResult<byte[]>(byteArray);
}
基本上我想做的是从两个代理对象并行调用 asynchMethod()
两次。
问题?
2019-11-05 17:20:23,354 WARN [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (EJB default - 3) SQL Error: 0, SQLState: null
2019-11-05 17:20:23,354 ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (EJB default - 3) IJ031041: Connection handle has been closed and is unusable
2019-11-05 17:20:23,354 INFO [org.jboss.jca.core.connectionmanager.listener.TxConnectionListener] (EJB default - 2) IJ000311: Throwable from unregister connection: java.lang.IllegalStateException: IJ000152: Trying to return an unknown connection: org.jboss.jca.adapters.jdbc.jdk7.WrappedConnectionJDK7@69ebfad0
at org.jboss.jca.core.connectionmanager.ccm.CachedConnectionManagerImpl.unregisterConnection(CachedConnectionManagerImpl.java:408)
at org.jboss.jca.core.connectionmanager.listener.TxConnectionListener.connectionClosed(TxConnectionListener.java:645)
at org.jboss.jca.adapters.jdbc.BaseWrapperManagedConnection.returnHandle(BaseWrapperManagedConnection.java:617)
at org.jboss.jca.adapters.jdbc.BaseWrapperManagedConnection.closeHandle(BaseWrapperManagedConnection.java:562)
at org.jboss.jca.adapters.jdbc.WrappedConnection.returnConnection(WrappedConnection.java:298)
at org.jboss.jca.adapters.jdbc.WrappedConnection.close(WrappedConnection.java:256)
at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.closeConnection(DatasourceConnectionProviderImpl.java:127)
at org.hibernate.internal.AbstractSessionImpl$NonContextualJdbcConnectionAccess.releaseConnection(AbstractSessionImpl.java:397)
at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.releaseConnection(LogicalConnectionManagedImpl.java:172)
at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.afterStatement(LogicalConnectionManagedImpl.java:125)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.afterStatementExecution(JdbcCoordinatorImpl.java:281)
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:145)
at org.hibernate.loader.plan.exec.internal.AbstractLoadPlanBasedLoader.executeLoad(AbstractLoadPlanBasedLoader.java:86)
at org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader.load(AbstractLoadPlanBasedEntityLoader.java:167)
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3956)
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:508)
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:478)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:219)
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:116)
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1129)
at org.hibernate.internal.SessionImpl.immediateLoad(SessionImpl.java:997)
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:157)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:266)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:68)
对 asynchMethod() 的两次调用已正确分配给两个不同的线程:
2019-11-05 17:20:22,566 INFO [**.*******.*******.*******.MyStatelessBeanA] (EJB default - 2) method=asynchMethod START
2019-11-05 17:20:22,655 INFO [**.*******.*******.*******.MyStatelessBeanA] (EJB default - 3) method=asynchMethod START
是否有可能一个代理对象以某种方式关闭另一个代理对象的连接?我不知道是否有足够的信息来猜测问题的正确解决方案,但我正在寻找所有可能的方法(CachedConnectionManagerImpl 源代码、TxConnectionListener 源代码),但似乎超出了我的技能范围。
如果有人可以提供帮助或提供一些提示,因为我完全坚持这一点。
谢谢, 大卫
添加了可能有用的信息
Standalone.xml休眠部分
<cache-container name="hibernate" default-cache="local-query" module="org.hibernate.infinispan">
<local-cache name="entity">
<transaction mode="NON_XA"/>
<eviction strategy="LRU" max-entries="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<local-cache name="local-query">
<eviction strategy="LRU" max-entries="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<local-cache name="timestamps"/>
</cache-container>
Persistence.xml
<?xml version="1.0" encoding="UTF-8" ?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="***" transaction-type="RESOURCE_LOCAL">
<jar-file>file:./target/test-classes</jar-file>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.archive.autodetection" value="class,hbm" />
<property name="hibernate.connection.url" value="${dbunit.connectionUrl}" />
<property name="hibernate.connection.driver_class" value="${dbunit.driverClass}" />
<property name="hibernate.dialect" value="${dbunit.jpa-dialect}" />
<property name="hibernate.connection.username" value="${dbunit.username}" />
<property name="hibernate.connection.password" value="${dbunit.password}" />
<property name="javax.persistence.validation.mode" value="NONE" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
这不是一个完整的答案,因为我无法确定真正的根本原因,但我会在这里写下我发现的内容和可能的修复方法。
我在 Wildfly 应用程序服务器(在撰写本文时也是一个相当新的版本)中遇到过这个问题,并且似乎记得很久以前在 GlassFish 服务器上看到过同样的问题。问题似乎出在 Hibernate 而不是 EJB 容器或 JTA 管理器,尽管我对此不是 100% 确定。尽管有警告和烦人的堆栈跟踪,代码似乎按照您的期望正确运行。事务已提交并且不会由于错误而回滚,调用似乎 运行 在单独的事务中具有自己的连接,而且我没有注意到某种连接泄漏会在长时间后导致问题。所以至少在功能上看起来还不错。
这里的主要怀疑是建立连接和事务的范围与关闭它们的范围不同。我不知道是异步调用还是 EJB 的自注入触发了这个问题,以及其中一个是否足以复制它。我怀疑对 @Asynchronous
方法的调用将为实体管理器获取数据库连接并启动事务,这在某种程度上与调用异步方法的线程或其他上下文相关。但是当然,该方法是异步的,实际执行将由 EJB 容器在单独的线程中处理,该方法立即 returns,如果其 return 类型,则 return 什么都不做无效或 returning a Future
以获得结果。当该方法完成时,EJB 容器负责事务提交或回滚以及释放连接。 Hibernate 似乎不喜欢在不同的上下文或线程中看到这种情况,因此会出现错误。但是错误发生在事务完成之后(因此代码有效)并且因为它发生在某些容器管理的线程中,而不是调用者的线程中,所以异常永远不会冒泡到您的代码中。
如果仅此而已,那么异步 EJB 方法可以始终如一地重现此问题。但它似乎并没有这样做。 EJB 的自注入似乎也是触发它的必要条件。事实上,在 GlassFish 项目中我遇到了这个问题,我不记得使用了异步方法,但我确实让 EJB 获得了它自己的另一个实例 class。因此,也许仅此一项就足以获得这种行为。为什么在那种情况下会发生这种情况我还没有搞清楚,但我会在举一个可能的解决方案示例后回过头来讨论它。
不管是什么原因,我使用的一种似乎可以消除错误的解决方法是简单地在 EJB class 本身中使用 @EJB
注入,而不是通过 @Resource
-注入 SessionContext
。所以你的代码可以重写如下:
@Stateless
public class MyStatelessBeanA {
@EJB
private MyStatelessBeanA myStatelessProxy1;
@EJB
private MyStatelessBeanA myStatelessProxy2;
public byte[] methodA(){
Future<byte[]> proxy1Future = myStatelessProxy1.asynchMethod();
Future<byte[]> proxy2Future = myStatelessProxy2.asynchMethod();
byte[] firstArray = proxy1Future.get();
byte[] secondArray = proxy2Future.get();
return ...
}
@Asynchronous
public Future<byte[]> asynchMethod(){
byte[] byteArray = ...
...do something including select from various table...
return new AsynchResult<byte[]>(byteArray);
}
这可能看起来有点可怕,因为感觉它会导致某些无限递归的注入。但那不会发生。请记住,在某些 EJB 或托管 bean 中用 @EJB
或 @Inject
注释的字段实际上是一个代理对象,并且当该代理完成时,您的 class 的实际实例的查找或实例化完成用来。那些 myStatelessProxy1
和 myStatelessProxy2
字段只是代理占位符,一旦对它们调用方法,MyStatelessBeanA
的实例就会分配给代理,完成自己的注入并获得资源就像数据库连接和事务上下文。而那个实例又可能有这两个字段,但它们也是代理,如果不使用它们,就不会发生进一步的递归。如果您在注入实例上调用的方法依赖于它自己的注入字段之一,然后又在其上调用一个执行相同操作的方法,则您可能会不小心创建一些无休止的递归。因为这不同于同一对象实例中的递归,所以它可能不会立即显而易见,并且编译器或 IDE 可能不会警告您,所以要小心。
从 EJB 2.1 API 开始应该支持这种自注入。奇怪的是,这样做似乎导致不再抛出 Hibernate 错误。回到我之前提到的问题,一个可能的原因可能是创建 EJB 代理的时间点。在一种情况下,我们要求 SessionContext
创建一个实例,这发生在 EJB 的一个方法中。在另一种情况下,我们通过 @EJB
注入点获取实例,这将在调用 EJB 方法之前完成。这是两种不同的语境。一个在您的代码中,可能有自己的数据库连接,并且可能在事务中 运行ning。另一个在容器代码内,在事务边界之外,可能在建立数据库连接之前。现在,作为代理,我们可能希望它们只有在我们使用它们时才会启动,但谁知道在创建代理对象时容器可能会预先做哪些工作以及交给它们的是什么。也许当前的 Hibernate 版本可以正确处理这个问题,但是当混合使用异步方法时仍然会出错。
自注入可能会解决您的问题,但如果您需要这些 EJB 实例的动态数量,则自注入将不起作用,这可以在从 SessionContext
获取它们时完成。但在那种情况下,代码设计可能存在问题。一次注入就足够了,因为您可以为多个异步方法调用重用那个注入。他们每个 运行 在各自的线程中。他们也会有自己的交易。异步方法不能加入其调用者的事务,因为调用者的事务完成点将不再是它退出其事务边界时。它将在未来的“某个时间点”完成。这搞乱了语义。唯一的选择是让调用者在退出其方法后等待,直到它启动的异步调用完成,但这在某些情况下会破坏它们的目的(比如当您不需要 return 值时)。如果您必须生成不可预测数量的异步任务,则 ManagedExecutorService
可能更合适。它是 Java EE 的并发实用程序的一部分,它是在 JEE 7 中引入的。
最后,另一种解决方案可能是制作一些额外的帮助程序 EJB class,它会注入另一个,然后使用它。这种划分可能有点人为,因为它只是为了处理容器管理的影响而存在,而不是作为您的要求的直接结果,但它简单而有效。与自注入相反,它也很容易推理,并且可以帮助编写测试和使用模拟对象。