Java開発におけるJPAオプティミスティックロック例外

この記事では、JPAテクノロジとJava開発におけるその使用方法について説明します。

Java開発の専門家インドは技術のユースケースを説明しています.JAAとHibernate、MySqlデータベース、Mavenです。この記事を読んで、彼らが何を言いたいのかを知ってください。

JPAは単なる仕様であり、CRUD操作に永続性を提供する必要があります。 Hibernate、Eclipseリンクなど永続性プロバイダの例です。

  • テクノロジー:** JPAとHibernate、Maven、MySqlデータベース

  • 用途:** JPA、休止状態、Eclipseリンクを使用してJava開発アプリケーションを開発している場合は、例外以下になることがよくあります。

javax.persistence.OptimisticLockException: Row was updated or deleted by another
transaction (or unsaved-value mapping was incorrect).

問題は、JPA永続性プロバイダが最新バージョンのオブジェクトではないオブジェクトを更新しようとしていることです。一般に、リレーショナル・データベースは、レコードがテーブル内で更新されるたびにバージョンを維持します。テーブルのレコードを更新する場合は、データベースから最新バージョンのレコードを取り出して更新する必要があります。 ORMを使用しているので、最後のバージョンのオブジェクトを取り出してマージする必要があります。

したがって、このドキュメントでは、このエラーが発生したときとそれを解決する方法を確認できます。

1.プロジェクトの構成

jpa-lock-project-structure]

プロジェクトの構造は上のスクリーンショットのようになります。

プロジェクト構造を見ると、それは必須プロジェクトの `pom.xml`です。このmavenファイルでは、jpa、hibernate、java、databaseなどの依存関係を設定することができます。私はデータストレージとしてMySQLデータベースを使用しています。

エンティティ用、ビジネスロジック用、アプリケーションテスト用の3つのパッケージ。 1つのDTOパッケージがある場合は、多層アプリケーションエンティティで作業している場合は、クライアントレイヤに直接公開されません。エンティティは、エンティティにマップされるDTO(データ転送オブジェクト)に変換されます。

プロジェクトの依存関係

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>naveen.examples</groupId>
    <artifactId>jpa</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>jpa</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <hibernate.version>4.3.6.Final</hibernate.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <!-- JPA -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <!-- For connection pooling -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-c3p0</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <!-- Database -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.31</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

3. JPAの設定

これはHibernateがHibernateの依存関係を追加したものであるため、他のORMがこれらの依存関係を追加する場合は、

persistence.xml

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
    http://xmlns.jcp.org/xml/ns/persistence/persistence__2__1.xsd"
    version="2.1">

    <persistence-unit name="jpa-example" transaction-type="RESOURCE__LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <properties>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/employee"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="root"/>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="hibernate.show__sql" value="true"/>
            <property name="hibernate.format__sql" value="true"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
            <property name="hibernate.hbm2ddl.auto" value="validate"/>
            <!-- Configuring Connection Pool -->
            <property name="hibernate.c3p0.min__size" value="5"/>
            <property name="hibernate.c3p0.max__size" value="20"/>
            <property name="hibernate.c3p0.timeout" value="500"/>
            <property name="hibernate.c3p0.max__statements" value="50"/>
            <property name="hibernate.c3p0.idle__test__period" value="2000"/>
        </properties>
    </persistence-unit>
</persistence>

これは `persistence.xml`です。ここでは、データベースクレデンシャル、パーシスタンス名、エンティティマネージャファクトリによって必要なすべての属性について言及する必要があります。

4. CRUD

これは、データベース内で持続するエンティティクラスです。

EmployeeBE.java

…​.//Import statements

@NamedQueries({ @NamedQuery(name = EmployeeBE.FIND ALL, query = "SELECT e FROM EmployeeBE e order by e.name "), }) @Entity @Table(name = "T EMP") public class EmployeeBE implements Serializable{ private static final long serialVersionUID = 1607726899931733607L;

public static final String FIND__ALL = "naveen.examples.jpa.entity.EmployeeBE.find__all";
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Column(name = "NAME")
private String name;
    @Column(name = "version__num")
    @Version
    private int version;
// Getters and setters

}

EmployeeCrud.java

....//Interface for CRUD operations.
package naveen.examples.jpa.business;

import java.util.List;

import naveen.examples.jpa.entity.EmployeeBE;

public interface EmployeeCrud {

    List<EmployeeBE> findAllEmployeeBECol();

    EmployeeBE saveEmployee(EmployeeBE employeeBE);

    EmployeeBE updateEmployee(EmployeeBE employeeBE);

    EmployeeBE findById(int id);

}

EmployeeCrudImpl.java

…​.//Implementatino class for CRUD operations public class EmployeeCrudImpl implements EmployeeCrud{

public EmployeeBE saveEmployee(EmployeeBE employeeBE) {
    EntityManager em = EntityManagerUtil.getEntityManager();
    em.getTransaction().begin();
    em.persist(employeeBE);
    em.getTransaction().commit();
    return employeeBE;
}
public EmployeeBE updateEmployee(EmployeeBE employeeBE) {
    EntityManager em = EntityManagerUtil.getEntityManager();
    em.getTransaction().begin();
    em.merge(employeeBE);
    em.getTransaction().commit();
    return employeeBE;
}
@SuppressWarnings("unchecked")
public List<EmployeeBE> findAllEmployeeBECol() {
    EntityManager em = EntityManagerUtil.getEntityManager();
    Query query = em.createNamedQuery(EmployeeBE.FIND__ALL);
    return query.getResultList();
}
public EmployeeBE findById(int id) {
    EntityManager em = EntityManagerUtil.getEntityManager();
    return em.find(EmployeeBE.class, id);
}
EntityManagerUtil.java

....//Entity Manager
public class EntityManagerUtil {

    private static EntityManager  entityManager;

    private EntityManagerUtil() {
    }

    public static EntityManager getEntityManager() {
        if(entityManager==null){
            EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("jpa-example");
            return emFactory.createEntityManager();
        }
        return entityManager;
    }
}

上記のコードスニペットを見ると

  1. インタフェース、ビジネスメソッドを定義する

  2. InterfaceImpl、ビジネスメソッドの実装

  3. エンティティマネージャオブジェクトを取得するEntityManagerUtility.

EntityManagerUtil`クラスでは、 persistence.xml`ファイルに記述されているjpa-exampleパラメータを渡しているので、その永続ユニットに接続します。

デモ

JPAオプティミスティック・ロック例外のヒット方法を示します。

Application.java

public class Application {

    public static void main(String args[]) {

        EmployeeCrud employeeCrud = new EmployeeCrudImpl();

        List<EmployeeBE> employeeBEs = employeeCrud.findAllEmployeeBECol();
        if(!employeeBEs.isEmpty()){
            EmployeeBE employeeBE = employeeBEs.get(0);
            employeeBE.setName("Updated");
            employeeCrud.updateEmployee(employeeBE);
           //Here Optimistic lock exception
            employeeCrud.updateEmployee(employeeBE);
        }else{
            EmployeeBE employeeBE = new EmployeeBE();
            employeeBE.setName("Naveen");
            employeeBE = employeeCrud.saveEmployee(employeeBE);
        }
    }

jpa-lock-screen-1

アプリを実行する前にDBにデータがありません

jpa-lock-screen-2

アプリケーションを実行すると

jpa-lock-screen-3

jpa-lock-screen-4

テーブルにID 16とバージョン0のデータがあることがわかっている場合。

デバッグモードでアプリケーションを実行すると

jpa-lock-screen-5

最初の更新が完了したが、同じ従業員オブジェクトがコード内にあることを確認すると、最初の更新メソッドが完了したときに既にコミットされているため、バージョンは1になります。

jpa-lock-screen-6

スクリーンショットを見ると、バージョンは1になりますが、オブジェクトはバージョン0を意味する古いオブジェクトになります。次に、古いオブジェクトで2番目の更新メソッドが実行された場合、オプティミスティックロック例外が発生します。

jpa-lock-screen-7

実行を続行すると、オプティミスティック・ロック例外が発生します。

6.解決策

このエラーを解決するには、2つの方法があります。

  1. データベースから最新のオブジェクトを取得し、古いオブジェクトの値を設定する

これらの値を新しいオブジェクトに永続化してマージする必要がある場合

  1. 古いオブジェクトの場合、データベースから最新のバージョンを設定します.

jpa-lock-screen-8

私は2番目のアプローチに従っています。スクリーンショットが表示されたら、最新のバージョン番号を古いオブジェクトに設定しています

jpa-lock-screen-9

コンソールでの実行後、エラーは発生しません。

jpa-lock-screen-10

これは、2番目の更新メソッド用に生成されたSQLクエリです。したがって、例外は発生していません。

Java開発インド の専門家は、JPAテクノロジとJavaプロジェクトでのその使用に関する最善の知識を共有しました。詳細が必要な場合は、すでにこの技術をプロジェクトに適用している開発者に尋ねることができます。

結論

これらの2つの方法を使用することで、この非常に迷惑なエラーを解決できます。これは非常に簡単な例であるため、簡単に見つけることができますが、リアルタイムアプリケーションでは、これを非常に簡単に見つけることはできません。したがって、この例外が発生するたびに、アプリケーションをデバッグモードで実行し、表示されている概略コードスニペットに移動し、デバッグでF5キーを押し、エンティティが更新されている別の場所の対応する永続プロバイダのエンティティマネージャインプリメンテーションに深く入ります。次に、あなたの要件に応じて、これらの2つの方法のeithr4に従ってください。データベースのバージョン番号を確認してください。変更ごとに、永続性プロバイダの実装に基づいて増加する場合と増加しない場合があります。

参考文献

パーシスタンス/ロック]

guest post リンク://タグ/hibernate/[hibernate] jpa lock optimistic lock