[Seasar-user:22013] Re: 一意制約違反発生時の更新処理について

あきやまじろう [E-MAIL ADDRESS DELETED]
2014年 12月 10日 (水) 11:40:47 JST


小林 (koichik) 様

あきやまです。

> の2行目の登録処理をRequiresNewの別メソッドに
> してみてはどうでしょうか。
> もし一意制約違反が発生しても、insertのトランザクションが
> ロールバックされるだけで、selectおよびupdateを行う
> トランザクション (B) には影響がないはずです。
>
> こうも細切れになるとトランザクションの意味が
> あるのか疑問ですが。。。
上記の方法でうまくいく事ができました。

> 後者に関しては、以下の設定で試してみましたが、「既にロールバックとしてマーク」が出力され、期待通りの結果になりませんでした。
>
> に関しては、RequiresNew用のインターセプタに設定が
> 加えられていますが、「ロールバックとしてマーク」
> されているのはRequiredのトランザクション (一番外側の
> トランザクションA?) ですね。
> 何らかの例外がRequiredな宣言的トランザクションの
> 設定されたメソッドを突き抜けているものと思われます。
>
> 最初のメールからは「ロールバックとしてマーク」
> されているのはトランザクションBのことかと
> 思ってましたが、状況が違うようですね。
> ログからも内側のトランザクションはなぜか
> コミットできてますし。
> これは一意制約違反が発生が発生しないケース?
> それだと本題の解決には役に立たないような。。。
上記に関してはinsertTxメソッドで一意制約違反例外をスローしていたのが、原因でした。
一意制約違反例外をinsertTxメソッド内でcatchする事で解消しました。

ただし、ご指摘の通り別トランザクションにする事で、
1トランザクションで処理をする意味がなくなってしまっているので、
元々検討していた処理全体のtry-catchによる再トライ処理や、
アプリ側での同期処理(パフォーマンスが劣化したので、できれば避けたかったのですが・・・)なども含め、再度検討してみたいと思います。

色々とご助言ありがとうございました。


2014年12月10日 1:00 Koichi Kobayashi <koichik @ improvement.jp>:

> 小林 (koichik) です。
>
> そういえばPostgreSQLはそういうものだと以前も
> 話題になってましたね。
>
> であるなら、Java側でどうしようとトランザクションを
> コミットすることはできないので、ロールバックと
> マークされること自体は全く問題ないと思います。
>
> となると、insertはupdateとは別のトランザクションで
> 実行することになるのでは。
>
> > > > 4.BテーブルのレコードをSELECT FOR UPDATEで取得
> > > > →取得できなかった場合、レコードを登録
> > > > →一意制約違反が発生した場合、レコードを更新
>
> の2行目の登録処理をRequiresNewの別メソッドに
> してみてはどうでしょうか。
> もし一意制約違反が発生しても、insertのトランザクションが
> ロールバックされるだけで、selectおよびupdateを行う
> トランザクション (B) には影響がないはずです。
>
> こうも細切れになるとトランザクションの意味が
> あるのか疑問ですが。。。
>
>
> なお、
>
> > 後者に関しては、以下の設定で試してみましたが、「既にロールバックとしてマーク」が出力され、期待通りの結果になりませんでした。
>
> に関しては、RequiresNew用のインターセプタに設定が
> 加えられていますが、「ロールバックとしてマーク」
> されているのはRequiredのトランザクション (一番外側の
> トランザクションA?) ですね。
> 何らかの例外がRequiredな宣言的トランザクションの
> 設定されたメソッドを突き抜けているものと思われます。
>
> 最初のメールからは「ロールバックとしてマーク」
> されているのはトランザクションBのことかと
> 思ってましたが、状況が違うようですね。
> ログからも内側のトランザクションはなぜか
> コミットできてますし。
> これは一意制約違反が発生が発生しないケース?
> それだと本題の解決には役に立たないような。。。
>
>
> On Tue, 9 Dec 2014 10:24:15 +0900, あきやまじろう <mayama0130 @ gmail.com> wrote:
>
> > 小林 (koichik) 様
> >
> > あきやまです。
> >
> > ご回答ありがとうございます。
> >
> > > 対策としては、トランザクション境界の中で呼び出される
> > > メソッド (上記のbar()に相当) には宣言的トランザクションを
> > > 設定しないようにするか、特定の例外ではロールバックと
> > > マークされないように設定する方法があります。
> > > # 前者の方がよいかと思います。
> > >
> > > 後者については上記ドキュメントの「例外発生時の動作」に
> > > 説明があります。
> > > 実際に設定する場合はj2ee.diconをコピーして、
> > > S2にバンドルされているejb3tx.diconを参考に
> > > 一意制約違反発生時の例外でロールバックしないように
> > > 設定してください。
> > 前者に関してはPostgreSQLの場合、一意制約違反が発生した時点でトランザクションがアボートするらしく、
> > トランザクションを張りなおさないとDB操作ができない状態でした。
> > 後者に関しては、以下の設定で試してみましたが、「既にロールバックとしてマーク」が出力され、期待通りの結果になりませんでした。
> >
> > 【ソース】
> > class xxxService {
> >   public void execute() {
> >     try {
> >       insertTx();
> >     } catch (...) {
> >       update()
> >     }
> >   }
> >
> >   public void insertTx() {
> >   }
> >
> >   public void update() {
> >   }
> > }
> >
> > 【j2ee.dicon】
> > <component name="requiredTx"
> >   class="org.seasar.extension.tx.RequiredInterceptor"/>
> > <component name="requiresNewTx"
> >   class="org.seasar.extension.tx.RequiresNewInterceptor"/>
> > <component name="requiresNewEntityExistsCommitTx"
> >   class="org.seasar.extension.tx.RequiresNewInterceptor">
> >   <initMethod name="addCommitRule">
> >     <arg>@javax.persistence.EntityExistsException @ class</arg>
> >   </initMethod>
> > </component>
> > <component name="mandatoryTx"
> >   class="org.seasar.extension.tx.MandatoryInterceptor"/>
> > <component name="notSupportedTx"
> >   class="org.seasar.extension.tx.NotSupportedInterceptor"/>
> > <component name="neverTx"
> >   class="org.seasar.extension.tx.NeverInterceptor"/>
> >
> >
> > 【customeizer.dicon】
> > <component name="serviceCustomizer"
> >   class="org.seasar.framework.container.customizer.CustomizerChain">
> >   <initMethod name="addCustomizer">
> >     <arg>requiredTxCustomizer</arg>
> >   </initMethod>
> >   <initMethod name="addCustomizer">
> >     <arg>
> >       <component
> > class="org.seasar.framework.container.customizer.AspectCustomizer">
> >         <property
> > name="interceptorName">"j2ee.requiresNewEntityExistsCommitTx"</property>
> >         <property name="pointcut">"insertTx"</property>
> >       </component>
> >     </arg>
> >   </initMethod>
> > </component>
> >
> > 【アプリケーションログ】
> > org.seasar.framework.exception.SIllegalStateException:
> > [ESSR0308]既にロールバックとしてマークされています
> >   at
> >
> org.seasar.extension.jta.TransactionImpl.throwIllegalStateException(TransactionImpl.java:138)
> >   at
> >
> org.seasar.extension.jta.TransactionImpl.assertActive(TransactionImpl.java:123)
> >   at
> >
> org.seasar.extension.jta.TransactionImpl.enlistResource(TransactionImpl.java:452)
> >   at
> >
> org.seasar.framework.util.TransactionUtil.enlistResource(TransactionUtil.java:64)
> >   at
> >
> org.seasar.extension.dbcp.impl.ConnectionPoolImpl.checkOut(ConnectionPoolImpl.java:371)
> >   at
> >
> org.seasar.extension.dbcp.impl.DataSourceImpl.getConnection(DataSourceImpl.java:59)
> >   at
> >
> org.seasar.extension.jdbc.util.DataSourceUtil.getConnection(DataSourceUtil.java:51)
> >   at
> >
> org.seasar.extension.jdbc.manager.JdbcManagerImpl.getJdbcContext(JdbcManagerImpl.java:381)
> >   at
> >
> org.seasar.extension.jdbc.query.AbstractAutoUpdate.executeInternal(AbstractAutoUpdate.java:121)
> >   at
> >
> org.seasar.extension.jdbc.query.AutoUpdateImpl.executeInternal(AutoUpdateImpl.java:145)
> >   at
> >
> org.seasar.extension.jdbc.query.AbstractAutoUpdate.execute(AbstractAutoUpdate.java:90)
> >   at
> >
> org.seasar.extension.jdbc.service.S2AbstractService.update(S2AbstractService.java:149)
> >   at jp.co.xxxx.dbaccess.service.xxxxService.insert(xxxxService.java:66)
> >   at
> >
> jp.co.xxxx.dbaccess.service.xxxxService$$EnhancedByS2AOP$$7ae165.$$insert2$$invokeSuperMethod$$(xxxxService$$EnhancedByS2AOP$$7ae165.java)
> >   at
> >
> jp.co.xxxx.dbaccess.service.xxxxService$$EnhancedByS2AOP$$7ae165$$MethodInvocation$$insert21.proceed(MethodInvocationClassGenerator.java)
> >   at
> >
> org.seasar.extension.tx.DefaultTransactionCallback.execute(DefaultTransactionCallback.java:58)
> >   at
> >
> org.seasar.extension.tx.adapter.JTATransactionManagerAdapter.required(JTATransactionManagerAdapter.java:65)
> >   at
> >
> org.seasar.extension.tx.RequiredInterceptor.invoke(RequiredInterceptor.java:50)
> >   at
> >
> jp.co.xxxx.dbaccess.service.xxxxService$$EnhancedByS2AOP$$7ae165$$MethodInvocation$$insert21.proceed(MethodInvocationClassGenerator.java)
> >   at
> >
> jp.co.xxxx.dbaccess.service.xxxxService$$EnhancedByS2AOP$$7ae165.insert2(xxxxService$$EnhancedByS2AOP$$7ae165.java)
> >   at
> >
> jp.co.xxxx.dbaccess.service.xxxxServiceTest.testUpdateDelFlg1(xxxxServiceTest.java:75)
> >   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
> >   at
> >
> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
> >   at
> >
> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
> >   at java.lang.reflect.Method.invoke(Method.java:597)
> >   at junit.framework.TestCase.runTest(TestCase.java:168)
> >   at
> >
> org.seasar.framework.unit.S2FrameworkTestCase.doRunTest(S2FrameworkTestCase.java:519)
> >   at org.seasar.extension.unit.S2TestCase.doRunTest(S2TestCase.java:103)
> >   at
> >
> org.seasar.framework.unit.S2FrameworkTestCase.runBare(S2FrameworkTestCase.java:308)
> >   at junit.framework.TestResult$1.protect(TestResult.java:110)
> >   at junit.framework.TestResult.runProtected(TestResult.java:128)
> >   at junit.framework.TestResult.run(TestResult.java:113)
> >   at junit.framework.TestCase.run(TestCase.java:124)
> >   at junit.framework.TestSuite.runTest(TestSuite.java:243)
> >   at junit.framework.TestSuite.run(TestSuite.java:238)
> >   at
> >
> org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:83)
> >   at
> >
> org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
> >   at
> >
> org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
> >   at
> >
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
> >   at
> >
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
> >   at
> >
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
> >   at
> >
> org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
> >   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
> >   at
> >
> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
> >   at
> >
> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
> >   at java.lang.reflect.Method.invoke(Method.java:597)
> >   at
> >
> jp.co.dgic.eclipse.jdt.internal.junit.runner.DJUnitRunner.main(DJUnitRunner.java:49)
> >
> > 【s2.log】
> > 2014-12-09 10:13:27,911 DEBUG TransactionImpl -
> > トランザクションを開始しました。tx=[FormatId=4360, GlobalId=1418087601966/7, BranchId=]
> > 2014-12-09 10:13:27,911 DEBUG TransactionImpl -
> > トランザクションを開始しました。tx=[FormatId=4360, GlobalId=1418087601966/8, BranchId=]
> > 2014-12-09 10:13:27,920 DEBUG ConnectionPoolImpl -
> > 論理的なコネクションを取得しました。tx=[FormatId=4360, GlobalId=1418087601966/8, BranchId=]
> > 2014-12-09 10:13:27,923 DEBUG AutoInsertImpl - insert into ・・・
> > 2014-12-09 10:13:27,935 DEBUG TransactionImpl -
> > トランザクションをコミットしました。tx=[FormatId=4360, GlobalId=1418087601966/8, BranchId=]
> > 2014-12-09 10:13:27,935 DEBUG ConnectionWrapperImpl -
> > 論理的なコネクションを閉じました。tx=[FormatId=4360, GlobalId=1418087601966/8, BranchId=]
> > 2014-12-09 10:13:27,958 DEBUG TransactionImpl -
> > トランザクションをロールバックしました。tx=[FormatId=4360, GlobalId=1418087601966/7,
> BranchId=]
> >
> >
> > 2014年12月9日 3:48 Koichi Kobayashi <koichik @ improvement.jp>:
> >
> > > 小林 (koichik) です。
> > >
> > > おそらくですが、以下のドキュメントに書いてある
> > > ことかと思います。
> > >
> > > http://s2container.seasar.org/2.4/ja/tx.html
> > >
> > > 「トランザクションを開始した場合でも引き継いだ場合でも、
> > > このインターセプタが適用されたメソッドが例外をスローした場合は、
> > > 例外の種類に応じてトランザクションがロールバックされるように
> > > マークします。」
> > >
> > > つまり、
> > >
> > > public void foo() {
> > >   try {
> > >     bar();
> > >   } catch (...) {
> > >     ...
> > >   }
> > > }
> > >
> > > public void bar() {
> > >   throw ...;
> > > }
> > >
> > > foo()とbar()の両方に宣言的トランザクションが
> > > 適用される場合、foo()でトランザクションを開始して
> > > bar()はそのトランザクションの中で呼び出されますが、
> > > bar()がスローする例外によってfoo()で開始した
> > > トランザクションは「ロールバックされるようにマーク」
> > > されます。
> > > そのため、foo()でその例外をキャッチしても、
> > > foo()からリターンする際にトランザクションは
> > > ロールバックされます。
> > >
> > > 対策としては、トランザクション境界の中で呼び出される
> > > メソッド (上記のbar()に相当) には宣言的トランザクションを
> > > 設定しないようにするか、特定の例外ではロールバックと
> > > マークされないように設定する方法があります。
> > > # 前者の方がよいかと思います。
> > >
> > > 後者については上記ドキュメントの「例外発生時の動作」に
> > > 説明があります。
> > > 実際に設定する場合はj2ee.diconをコピーして、
> > > S2にバンドルされているejb3tx.diconを参考に
> > > 一意制約違反発生時の例外でロールバックしないように
> > > 設定してください。
> > >
> > >
> > > On Mon, 8 Dec 2014 19:02:32 +0900, あきやまじろう <mayama0130 @ gmail.com>
> wrote:
> > >
> > > > お世話になっております。あきやまと申します。
> > > >
> > > > 一意制約違反発生時の更新処理について教えてください。
> > > >
> > > > [動作環境]
> > > > ・S2Container 2.4.45
> > > > ・PostgreSQL 9.0.13
> > > >
> > > > 現在、以下の処理を実装しようとしていますが、
> > > >
> > > > 1.トランザクション開始(トランザクションA)
> > > > 2.SELECT FOR UPDATEでAテーブルからレコードを取得
> > > > 3.トランザクション開始(トランザクションB)
> > > > 4.BテーブルのレコードをSELECT FOR UPDATEで取得
> > > > →取得できなかった場合、レコードを登録
> > > > →一意制約違反が発生した場合、レコードを更新
> > > > 5.トランザクションBをコミット
> > > > 6.CテーブルのレコードをSELECT FOR UPDATEで取得
> > > > →取得できなかった場合、レコードを登録
> > > > →一意制約違反が発生した場合、レコードを更新
> > > > 7.トランザクションCをコミット
> > > > 8.トランザクションAをコミット
> > > >
> > > > 項番4で一意制約違反が発生した場合にレコードを更新したいのですが、
> > > > 「既にロールバックとしてマークされています」とログ出力され、更新できなくなり困っています。
> > > > 項番1から8までをtry-catchして、項番1から再トライすれば、うまくいくのですが、
> > > > 項番6でも一意制約違反の可能性があり、その場合try-catchをネストしないといけないので、できれば避けたいです。
> > > > 何かいい対処方法があれば、ご教授の程よろしくお願いします。
> > >
> > >
> > > --
> > > {
> > >   name: "Koichi Kobayashi",
> > >   mail: "koichik @ improvement.jp",
> > >   blog: "http://d.hatena.ne.jp/koichik/",
> > >   twitter: "@koichik"
> > > }
> > >
> > > _______________________________________________
> > > Seasar-user mailing list
> > > Seasar-user @ ml.seasar.org
> > > https://ml.seasar.org/mailman/listinfo/seasar-user
> > >
>
>
> --
> {
>   name: "Koichi Kobayashi",
>   mail: "koichik @ improvement.jp",
>   blog: "http://d.hatena.ne.jp/koichik/",
>   twitter: "@koichik"
> }
>
> _______________________________________________
> Seasar-user mailing list
> Seasar-user @ ml.seasar.org
> https://ml.seasar.org/mailman/listinfo/seasar-user
>
-------------- next part --------------
HTML$B$NE:IU%U%!%$%k$rJ]4I$7$^$7$?(B...
URL: <http://ml.seasar.org/archives/seasar-user/attachments/20141210/2dc60411/attachment.html>


Seasar-user メーリングリストの案内