EJB3 トランザクションの伝播
-
01-07-2019 - |
質問
次のようなステートレス Bean があります。
@Stateless
public class MyStatelessBean implements MyStatelessLocal, MyStatelessRemote {
@PersistenceContext(unitName="myPC")
private EntityManager mgr;
@TransationAttribute(TransactionAttributeType.SUPPORTED)
public void processObjects(List<Object> objs) {
// this method just processes the data; no need for a transaction
for(Object obj : objs) {
this.process(obj);
}
}
@TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
public void process(Object obj) {
// do some work with obj that must be in the scope of a transaction
this.mgr.merge(obj);
// ...
this.mgr.merge(obj);
// ...
this.mgr.flush();
}
}
通常の使用法は、クライアントが processObjects(...) を呼び出すことですが、実際にはエンティティ マネージャーと対話しません。必要なことを実行し、処理するオブジェクトごとに個別に process(...) を呼び出します。process(...) の継続時間は比較的短いですが、processObjects(...) がすべてを実行するには非常に長い時間がかかる可能性があります。したがって、オープンなトランザクションを維持したくありません。私 する 独自のトランザクション内で動作するには、個々の process(...) 操作が必要です。これは呼び出しごとに新しいトランザクションである必要があります。最後に、クライアントが process(...) を直接呼び出すためのオプションを開いたままにしておきたいと思います。
さまざまな種類のトランザクションを試してみました。Never、Not Supported、Supported (processObjects)、required、required new (on process) ですが、merge() が呼び出されるたびに TransactionRequiredException が発生します。
メソッドを 2 つの異なる Bean に分割することで、それを機能させることができました。
@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
@EJB
private MyStatelessBean2 myBean2;
public void processObjects(List<Object> objs) {
// this method just processes the data; no need for a transaction
for(Object obj : objs) {
this.myBean2.process(obj);
}
}
}
@Stateless
public class MyStatelessBean2 implements MyStatelessLocal2, MyStatelessRemote2 {
@PersistenceContext(unitName="myPC")
private EntityManager mgr;
@TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
public void process(Object obj) {
// do some work with obj that must be in the scope of a transaction
this.mgr.merge(obj);
// ...
this.mgr.merge(obj);
// ...
this.mgr.flush();
}
}
しかし、これを 1 つのクラスで達成できるかどうかにはまだ興味があります。個々のメソッドにより具体的なアノテーションが与えられている場合でも、トランザクション マネージャーは Bean レベルでのみ動作するように見えます。では、トランザクションがその同じインスタンス内の他のメソッドの呼び出しを開始しないように 1 つのメソッドをマークすると、そのメソッドがどのようにマークされているかに関係なく、トランザクションも作成されなくなります。
JBoss Application Server 4.2.1.GA を使用していますが、具体的でない回答は歓迎/推奨されます。
解決
これを行うもう 1 つの方法は、実際に同じ Bean 上に両方のメソッドを配置し、 @EJB
それ自体を参照してください!そんな感じ:
// supposing processObjects defined on MyStatelessRemote1 and process defined on MyStatelessLocal1
@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
@EJB
private MyStatelessLocal1 myBean2;
public void processObjects(List<Object> objs) {
// this method just processes the data; no need for a transaction
for(Object obj : objs) {
this.myBean2.process(obj);
}
}
@TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
public void process(Object obj) {
// do some work with obj that must be in the scope of a transaction
this.mgr.merge(obj);
// ...
this.mgr.merge(obj);
// ...
this.mgr.flush();
}
}
このようにして、実際に「強制」します。 process()
このメソッドはプロキシの ejb スタック経由でアクセスされるため、 @TransactionAttribute
実際には、依然として 1 つのクラスのみが維持されます。ふう!
他のヒント
マット、あなたの質問はかなり古典的なものですが、ハーヴァル/パスカルによる自己参照の解決策は素晴らしいと思います。ここでは触れていない、より一般的な解決策があります。
これは、EJB「ユーザー」トランザクションの場合です。セッション Bean 内にいるため、セッション コンテキストからユーザー トランザクションを取得できます。ユーザー トランザクションでコードがどのように表示されるかは次のとおりです。
// supposing processObjects defined on MyStatelessRemote1 and process defined on MyStatelessLocal1
@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
@Resource
private SessionContext ctx;
@EJB
private MyStatelessLocal1 myBean2;
public void processObjects(List<Object> objs) {
// this method just processes the data; no need for a transaction
for(Object obj : objs) {
this.myBean2.process(obj);
}
}
public void process(Object obj) {
UserTransaction tx = ctx.getUserTransaction();
tx.begin();
// do some work with obj that must be in the scope of a transaction
this.mgr.merge(obj);
// ...
this.mgr.merge(obj);
// ...
this.mgr.flush();
tx.commit();
}
}
問題は、各 Bean がトランザクションの動作を制御するプロキシでラップされていることだと思います。ある Bean から別の Bean に呼び出すときは、その Bean のプロキシを経由することになり、トランザクションの動作はプロキシによって変更される可能性があります。
ただし、Bean が異なるトランザクション属性を使用してそれ自体のメソッドを呼び出す場合、呼び出しはプロキシを経由しないため、動作は変わりません。
マット、当然のことながら、私はあなたと全く同じ結論に達しました。
TransactionAttributeType は、Bean の境界を越える場合にのみ考慮されます。同じ Bean 内でメソッドを呼び出す場合、メソッドにどのような Type が設定されていても、TransactionAttributeTypes は効果がありません。
私が見る限り、EJB Persistence Spec には、このような状況下でどのような動作が行われるべきかを指定するものは何もありません。
私もJbossでこれを経験しました。Glassfish でも試してみて、結果をお知らせします。
ある日誰かがこれに遭遇した場合に備えて:
JBoss で循環依存関係 (たとえば、自己参照を許可する) を回避するには、次のようにアノテーション 'IgnoreDependency' を使用します。
@ignodependency @ejb自分が自分自身を
私はまだ試していません(これから試します)が、 @EJB
注釈は SessionContext.getBusinessObject()
方法。これは、循環参照によって問題が発生する可能性を回避するもう 1 つの方法です。ただし、少なくともステートレス Bean のインジェクションでは機能するようです。
私は両方の技術を (おそらく異なる開発者によって) 採用されている大規模なシステムに取り組んでいますが、どちらが「正しい」方法なのかわかりません。
と関係があると思います @TransationAttribute(TransactionAttributeType.Never) メソッド上で プロセスオブジェクト.
TransactionAttributeType.Never
http://docs.sun.com/app/docs/doc/819-3669/6n5sg7cm3?a=view
クライアントがトランザクション内で実行され、Enterprise Beanのメソッドを呼び出す場合、コンテナはRemoteexceptionをスローします。クライアントがトランザクションに関連付けられていない場合、コンテナはメソッドを実行する前に新しいトランザクションを開始しません。
あなたがメソッドのクライアントであると仮定します プロセスオブジェクト クライアントコードから。おそらくクライアントはトランザクションに関連付けられていないため、メソッド呼び出しは TransactionAttributeType.Never そもそも幸せです。次に、あなたは プロセス からのメソッド プロセスオブジェクト それは持っていますが TransactionAttributeType.Required アノテーションは Bean メソッド呼び出しではないため、トランザクション ポリシーは適用されません。電話をかけるとき マージ まだトランザクションに関連付けられていないため、例外が発生します。
使ってみてください TransactionAttributeType.Required 両方の Bean メソッドについて、それがうまくいくかどうかを確認します。
Kevin が言及した循環依存関係の問題がありました。ただし、提案されたアノテーション @IgnoreDependency は jboss 固有のアノテーションであり、Glassfish などには対応するアノテーションがありません。
デフォルトの EJB リファレンスでは動作しないため、この解決策には少し違和感を感じました。
したがって、私は bluecarbon のソリューションにチャンスを与え、内部トランザクションを「手動で」開始しました。
これ以外に、別の Bean に inner process() を実装する以外に解決策はありません。これも、技術的な詳細のためにクラス モデルを混乱させたいだけなので、これも見苦しいです。