トランザクションとJPAを理解し、友達にする方法

おそらく誰もがリレーショナルデータベースのトランザクションについて知っているでしょう、誰もがACIDについて聞いたことがあります。 それでも、知ることと感じることには違いがあります。開発者のバックエンドで再訓練しなければならなかったときに、私自身がこれに出会いました。 その瞬間、そのような記事は私を大いに助けてくれると思います。あなたにとっても役に立つことを願っています。



エンタープライズアプリケーションを開発する場合、ORMテクノロジーを使用してデータベースとやり取りすることが多く、Javaの世界では、JPA(Java Persistence API)テクノロジーとその実装であるHibernateおよびEclipseLinkが最もよく知られています。 JPAを使用すると、ドメインオブジェクトの観点からデータベースと対話し、キャッシュを提供し、中間層にクラスターが存在する場合にキャッシュレプリケーションを実行できます。



通常の方法:



  1. RESTリクエストがバックエンドに届き、リクエストボディのドキュメントを更新します-新しい状態。
  2. トランザクションを開始します。
  3. バックエンドは、EntityManagerにドキュメントの既存の状態を要求します。EntityManagerは、データベースからドキュメントを読み取ったり、キャッシュからドキュメントを取得したりできます。
  4. 次に、リクエストの本文に到着したオブジェクトを取得し、それを見て、データベース内のレコードを表すオブジェクトの状態と比較します。
  5. この比較に基づいて、必要な変更を加えます。
  6. トランザクションをコミットします。
  7. 回答をクライアントに返します。


犬はここでどこにruきましたか? おそらく、おそらくキャッシュからデータを取得した可能性があります。おそらく、サーバーが同じドキュメントを変更するために競合する要求を処理しており、これらすべての比較を行うとデータが正確に破損します。 この疑わしい信頼性のデータとRESTリクエストの本文に基づいて、データベースに変更を加えることを決定し、コミットします。 質問は、どのような種類のがらくたをデータベースに書き込みましたか?



これは、トランザクションが私たちを助ける場所です。 それらを理解するための鍵は、トランザクションがどの条件を通過しないか、つまり、いつロールバックするかです。 変更がデータベース定数に違反すると、トランザクションのロールバックが発生します。 それらの中で最も重要なのは:





そのため、トランザクションが成功した場合、コミットした「スクラップ」が少し高くなり、定数を満たします。 定数を構成して、それらを満たすデータが有効なビジネスエンティティになるようにします。



最も原始的で人工的な例を次に示します。



@Entity public class Document { @Id private String name; @Lob private String content; // getters and setters  }
      
      





 @ApplicationScoped @Transactional //     -      public class DocumentService { @PersistenceContext private EntityManager entityManager; public void createDocument(String name, String content) { //          , //         Document documentEntity = entityManager.find(Document.class, name); if (documentEntity != null) { throw new WebApplicationException(Response.Status.CONFLICT); //  ! } //             documentEntity = new Document(); documentEntity.setName(name); documentEntity.setContent(content); entityManager.persist(documentEntity); } }
      
      





ここで、同じ名前のドキュメントの競合作成の場合、または混乱から受け取ったデータが古くなった場合、コミット時にConstraintViolationExceptionが発生し、バックエンドはクライアントに500エラーを返します。 ユーザーは少し後に操作を繰り返し、適切なエラーメッセージを受け取るか、ドキュメントを作成します。



実際、500個のエラーはあまり望ましくありません。トリックはほとんど発生しないということですが、アプリケーションの使用が頻繁に発生するような仕様である場合は、より洗練された何かを考える必要があります。



もっと複雑なものを試してみましょう。 文書を削除から保護できるようにしたいとします。 新しいテーブルを作成します。



 @Entity public class DocumentLock { @Id @GeneratedValue private Long id; @OneToOne private Document document; @Basic private String lockedBy; // getters, setters }
      
      





そして、Documentクラスに追加します。



  @OneToOne(mappedBy = "document") private DocumentLock lock;
      
      





ここで、ドキュメントを削除から保護するには、ドキュメントを参照するDocumentLockを作成します。 ドキュメントを削除するロジック:



  public void deleteDocument(String name) { Document documentEntity = entityManager.find(Document.class, name); if (documentEntity == null) { throw new NotFoundException(); } DocumentLock lock = documentEntity.getLock(); if (lock != null) { throw new WebApplicationException( "Document is locked by " + lock.getLockedBy(), Response.Status.BAD_REQUEST); } entityManager.remove(documentEntity); }
      
      





見て、ロックがないことを確認しましたが、キャッシュされたデータを使用しました。おそらくすでに古いか、チェック中に古い可能性があります。 この場合、ドキュメントを削除すると、コードはデータの参照整合性に違反しようとします。つまり、トランザクションは失敗します。 いくつかのコメント:



  1. カスケード削除が無効になっていることを確認してください。カスケード削除の場合、ドキュメントを削除すると、それを参照するすべてのレコードが削除されます。 つまり ビジネスロックレコードを保持しても何も害はありません。
  2. 実際、上記のコードを使用すると、1つのドキュメントに複数のロックを掛けることができます。 別の一意の一意性を構成する必要があります。
  3. この例は純粋に合成的なもので、ほとんどの場合、個別のテーブルを持たずに、ビジネスロックの所有者のデータを直接ドキュメントに入れるのが理にかなっています。 そして、明示的な悲観的ロックを使用して、ドキュメントを削除するときにこのビジネスロックが存在しないことを確認します。


実際のタスクでは、参照整合性は、階層的に編成されたデータ(組織スタッフ、ディレクトリ、およびファイル構造)を保存するときに非常に役立ちます。 この場合、たとえば、上司を削除して競争的に部下を割り当てた場合、参照整合性により、これらの操作のうち1つだけが正常に完了し、組織構造が有効のままになります(監督を除く各従業員に上司がいます)。 同時に、両方の操作の開始時に、それぞれが実行可能に見えました。



要約すると、データベースに変更を加えるかどうかを決定する際に、古くて疑わしいデータ(JPAを介してデータベースを操作する場合によくある)を使用し、競合する変更が競合的に行われたとしても、トランザクションメカニズムは違反することを許可しません参照整合性は、課された制約に対応せず、このトランザクションによって結合され、この嘆かわしい結果につながるすべてのアクションは、原子性の原則に従ってキャンセルされます。 データをモデル化し、定数をきちんと設定するときは、このことに留意してください。



All Articles