web-dev-qa-db-ja.com

Spring @Transactional読み取り専用伝播

コマンドパターンを使用して、1つのトランザクションのコンテキスト内でWebレイヤーがHibernateエンティティを操作できるようにする実験を行っています(したがって、遅延読み込みの例外を回避します)。しかし、私は今、トランザクションをどのように扱うべきかと混同しています。

私のコマンドは、_@Transactional_アノテーションが付けられたサービスレイヤーメソッドを呼び出します。これらのサービスレイヤーメソッドの一部は読み取り専用です。 @Transactional(readOnly=true)-一部は読み取り/書き込み可能です。

私のサービスレイヤーは、Webレイヤーの代わりに渡されたコマンドを実行するコマンドハンドラーを公開します。

_@Transactional
public Command handle( Command cmd ) throws CommandException
_

私は、コマンドハンドラーのhandle()メソッドをトランザクション対応にするのが正しいと思います。これが混乱の原因です。コマンドの実装が複数のサービスレイヤーメソッドを呼び出す場合、コマンドハンドラーは、コマンド内で呼び出された操作が読み取り専用、読み取り/書き込み、または組み合わせかどうかを知る方法がありません。 2の。

この例では、伝播の仕組みがわかりません。 handle()メソッド_readOnly=true_を作成した場合、コマンドが@Transactional(realOnly=false)アノテーションが付けられたサービスレイヤーメソッドを呼び出すとどうなりますか?

これについての理解を深めていただき、コメントをお待ちしております...

アンドリュー

55
DrewEaster

まず第一に、Springは永続化を行わないため、readOnlyが正確に意味するものを指定できません。この属性はプロバイダーへの単なるヒントであり、動作はこの場合はHibernateに依存します。

readOnlyとしてtrueを指定すると、現在のHibernateセッションでフラッシュモードがFlushMode.NEVERに設定され、セッションがトランザクションをコミットできなくなります。

さらに、JDBC接続でsetReadOnly(true)が呼び出されます。これは、基になるデータベースへのヒントでもあります。データベースがサポートしている場合(ほとんどの場合サポートしています)、これは基本的にFlushMode.NEVERと同じ効果がありますが、手動でフラッシュすることさえできないため、より強力です。

次に、トランザクションの伝播がどのように機能するかを見てみましょう。

readOnlytrueに明示的に設定しない場合、読み取り/書き込みトランザクションが発生します。トランザクション属性(REQUIRES_NEWなど)に応じて、ある時点でトランザクションが中断され、新しいトランザクションが開始されて最終的にコミットされ、その後、最初のトランザクションが再開されます。

OK、もうすぐです。 readOnlyがこのシナリオにもたらすものを見てみましょう。

read/writeトランザクション内のメソッドがreadOnlyトランザクションを必要とするメソッドを呼び出す場合、最初のメソッドは中断する必要があります。そうしないと、最後にフラッシュ/コミットが発生するためです2番目の方法の。

逆に、readOnlyを必要とするreadOnlyトランザクション内からメソッドを呼び出すと、再度フラッシュ/コミットできないため、最初のトランザクションが中断されます。 2番目の方法ではそれが必要です。

readOnly-to-readOnlyおよびread/write-to-read/writeの場合、外側のトランザクションを中断する必要はありません(特に伝播を指定しない限り) 、明らかに)。

68
candiru

前のトランザクションが継続するため、readOnly = trueからreadOnly = falseを呼び出しても機能しません。

この例では、サービスレイヤーのhandle()メソッドが新しい読み取り/書き込みトランザクションを開始しています。ハンドルメソッドが読み取り専用の注釈を付けたサービスメソッドを呼び出す場合、読み取り専用は既存の読み取り/書き込みトランザクションに参加するため、効果はありません。

これらのメソッドが読み取り専用であることが不可欠な場合は、Propagation.REQUIRES_NEWでアノテーションを付けることができ、既存の読み取り/書き込みトランザクションに参加するのではなく、新しい読み取り専用トランザクションを開始します。

これが実際の例です。CircuitStateRepositoryは、スプリングデータJPAリポジトリです。

BeanSは、transactional = read-only Bean1を呼び出します。これは、ルックアップを実行し、新しいオブジェクトを保存するtransactional = read-write Bean2を呼び出します。

  • Bean1は読み取り専用のtxを開始します。

31 09:39:44.199 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager-[nz.co.vodafone.wcim.business.Bean1.startSomething]:PROPAGATION_REQUIRED、ISOLATION_DEFAULT、readOnlyという名前の新しいトランザクションを作成します。 ''

  • Bean 2はそれに参加します。

    31 09:39:44.230 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager-既存のトランザクションへの参加

    データベースには何もコミットされません。

Bean2を変更します@Transactional追加する注釈propagation=Propagation.REQUIRES_NEW

  • Bean1は読み取り専用のtxを開始します。

    31 09:31:36.418 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager-[nz.co.vodafone.wcim.business.Bean1.startSomething]:PROPAGATION_REQUIRED、ISOLATION_DEFAULT、readOnlyという名前の新しいトランザクションを作成します。 ''

  • Bean2は新しい読み取り/書き込みtxを開始します

    31 09:31:36.449 [pool-1-thread-1] DEBUG o.s.orm.jpa.JpaTransactionManager-現在のトランザクションを中断し、[nz.co.vodafone.wcim.business.Bean2.createSomething]という名前の新しいトランザクションを作成します

また、Bean2による変更はデータベースにコミットされます。

以下は、spring-data、hibernate、およびOracleでテストした例です。

@Named
public class BeanS {    
    @Inject
    Bean1 bean1;

    @Scheduled(fixedRate = 20000)
    public void runSomething() {
        bean1.startSomething();
    }
}

@Named
@Transactional(readOnly = true)
public class Bean1 {    
    Logger log = LoggerFactory.getLogger(Bean1.class);

    @Inject
    private CircuitStateRepository csr;

    @Inject
    private Bean2 bean2;

    public void startSomething() {    
        Iterable<CircuitState> s = csr.findAll();
        CircuitState c = s.iterator().next();
        log.info("GOT CIRCUIT {}", c.getCircuitId());
        bean2.createSomething(c.getCircuitId());    
    }
}

@Named
@Transactional(readOnly = false)
public class Bean2 {    
    @Inject
    CircuitStateRepository csr;

    public void createSomething(String circuitId) {
        CircuitState c = new CircuitState(circuitId + "-New-" + new DateTime().toString("hhmmss"), new DateTime());

        csr.save(c);
     }
}
19
dan carter

デフォルトでは、トランザクションの伝播は必須です。つまり、同じトランザクションがトランザクションの呼び出し元からトランザクションの呼び出し先に伝播します。この場合、読み取り専用ステータスも伝播します。例えば。読み取り専用トランザクションが読み取り/書き込みトランザクションを呼び出す場合、トランザクション全体が読み取り専用になります。

ビューでセッションを開くパターンを使用して、遅延読み込みを許可できますか?このように、ハンドルメソッドはトランザクションである必要はまったくありません。

11
sijk

現在のアクティブなトランザクションの設定は無視されるようで、新しいトランザクションにのみ設定を適用します。

 org.springframework.transaction.PlatformTransactionManager 
 TransactionStatus getTransaction(TransactionDefinition definition)
 throws TransactionException 
指定に従って、現在アクティブなトランザクションを返すか、新しいトランザクションを作成します。 
分離レベルやタイムアウトなどのパラメーターは新しいトランザクションにのみ適用されるため、アクティブなトランザクションに参加する場合は無視されることに注意してください。
さらに、すべてのトランザクション定義設定がサポートされるわけではありませんすべてのトランザクションマネージャー:適切なトランザクションマネージャーの実装は、サポートされていない設定に遭遇した場合に例外をスローする必要があります。
上記のルールの例外は読み取り専用フラグです。 。基本的に、読み取り専用フラグは潜在的な最適化の単なるヒントです。
5
Andrew Chen