JavaRush /Java Blog /Random-JA /JPA: テクノロジーの紹介
Viacheslav
レベル 3

JPA: テクノロジーの紹介

Random-JA グループに公開済み
現代の開発の世界には、作業を楽にするために設計されたさまざまな仕様が溢れています。ツールを理解すれば、適切なツールを選択できます。知らないと、人生がより困難になる可能性があります。このレビューは、JPA - Java Persistence API の概念に関する秘密のベールを取り除きます。読んだ後は、この不思議な世界にさらに深く入り込んでいただければ幸いです。
JPA : テクノロジー入門 - 1

導入

ご存知のとおり、プログラムの主なタスクの 1 つはデータの保存と処理です。古き良き時代には、人々は単にデータをファイルに保存していました。しかし、読み込みと編集の同時アクセスが必要になるとすぐに、負荷がかかると (つまり、複数のリクエストが同時に到着すると)、データを単にファイルに保存するだけでは問題が発生します。データベースがどのような問題をどのように解決するかについて詳しくは、「データベースの構造」の記事を読むことをお勧めします。これは、データをデータベースに保存することにしたことを意味します。長い間、Java は JDBC API (Java Database Connectivity) を使用してデータベースを操作できました。JDBC について詳しくは、「JDBC またはすべての始まり」を参照してください。しかし、時が経ち、開発者はそのたびに、Java オブジェクトをデータベースに保存する際の簡単な操作のために、同じタイプの不要な「メンテナンス」コード (いわゆるボイラープレート コード) を記述する必要が生じ、またその逆に、データベースからのデータを使用して Java オブジェクトを作成する必要がありました。データベース。そして、これらの問題を解決するためにORMという概念が生まれました。 ORM - オブジェクト リレーショナル マッピング、またはロシア語のオブジェクト リレーショナル マッピングに翻訳されました。データベースとオブジェクト指向プログラミング言語の概念を結び付けるプログラミング技術です。簡単に説明すると、ORM は Java オブジェクトとデータベース内のレコードの間の接続です。ORM は JPA: テクノロジー入門 - 2本質的に、Java オブジェクトをデータベース内のデータとして表現できる (またはその逆) という概念です。これは、JPA 仕様 - Java Persistence API の形式で具体化されました。仕様はすでにこの概念を表現する Java API の記述です。仕様は、ORM の概念に従って動作するためにどのようなツールを提供する必要があるか (つまり、どのようなインターフェイスを介して作業できるか) を示しています。そして、これらの資金の使い方。仕様にはツールの実装については記載されていません。これにより、1 つの仕様に対して異なる実装を使用することが可能になります。これを単純化して、仕様は API の説明であると言うことができます。JPA 仕様のテキストは、Oracle の Web サイト「 JSR 338: JavaTM Persistence API 」で参照できます。したがって、JPA を使用するには、そのテクノロジーを使用するための実装が必要です。JPA 実装は JPA プロバイダーとも呼ばれます。最も注目すべき JPA 実装の 1 つはHibernateです。したがって、検討することを提案します。
JPA: テクノロジー入門 - 3

プロジェクトの作成

JPA は Java に関するものであるため、Java プロジェクトが必要になります。ディレクトリ構造を手動で作成し、必要なライブラリを自分で追加することもできます。しかし、プロジェクトのアセンブリを自動化するためのシステムを使用する方がはるかに便利で正確です (つまり、本質的に、これはプロジェクトのアセンブリを管理する単なるプログラムです。ディレクトリを作成し、必要なライブラリをクラスパスに追加するなど)。 。)。そのようなシステムの 1 つが Gradle です。Gradle の詳細については、「Gradle の概要」を参照してください。ご存知のとおり、Gradle の機能 (つまり、できること) は、さまざまな Gradle プラグインを使用して実装されます。Gradle と「 Gradle Build Init Plugin 」プラグインを使ってみましょう。コマンドを実行しましょう:

gradle init --type java-application
Gradle は必要なディレクトリ構造を作成し、ビルド スクリプト内にプロジェクトの基本的な宣言的記述を作成しますbuild.gradle。それで、アプリケーションがあります。アプリケーションで何を記述またはモデル化したいのかを考える必要があります。たとえば、app.quickdatabasediagrams.com など JPA: テクノロジー入門 - 4のモデリング ツールを使用してみましょう。ここで、説明したのは「ドメイン モデル」であると言う価値があります。ドメインとは「主題領域」です。一般にドメインとはラテン語で「所有」を意味します。中世において、これは王や封建領主が所有していた地域に与えられた名前でした。そしてフランス語では、それは単に「地域」を意味する「ドメーヌ」という言葉になりました。したがって、「ドメイン モデル」=「サブジェクト モデル」と説明しました。このモデルの各要素は、ある種の「エッセンス」、つまり実生活からのものです。私たちの場合、これらはエンティティです: カテゴリ ( Category)、件名 ( Topic)。たとえばモデルという名前で、エンティティ用に別のパッケージを作成しましょう。そして、エンティティを記述する Java クラスをそこに追加しましょう。Java コードでは、このようなエンティティは通常のPOJOであり、次のようになります。
public class Category {
    private Long id;
    private String title;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
クラスの内容をコピーして、類推してクラスを作成してみましょうTopic。彼が異なるのは、自分が属するカテゴリーについて知っていることだけです。Topicしたがって、カテゴリ フィールドとそれを操作するためのメソッドをクラスに 追加しましょう。
private Category category;

public Category getCategory() {
	return category;
}

public void setCategory(Category category) {
	this.category = category;
}
これで、独自のドメイン モデルを持つ Java アプリケーションが完成しました。ここで、JPA プロジェクトへの接続を開始します。
JPA: テクノロジー入門 - 5

JPAの追加

したがって、覚えているとおり、JPA はデータベースに何かを保存することを意味します。したがって、データベースが必要です。プロジェクトでデータベース接続を使用するには、データベースに接続するための依存関係ライブラリを追加する必要があります。覚えているとおり、私たちは Gradle を使用して、ビルド スクリプトを作成しましたbuild.gradle。その中で、プロジェクトに必要な依存関係について説明します。依存関係は、それなしではコードが動作できないライブラリです。まず、データベースへの接続の依存関係について説明します。これは、単に JDBC を操作する場合と同じ方法で行います。

dependencies {
	implementation 'com.h2database:h2:1.4.199'
これでデータベースができました。これで、Java オブジェクトをデータベース概念 (Java から SQL へ) にマッピングする役割を担うレイヤーをアプリケーションに追加できるようになりました。覚えているとおり、これには Hibernate と呼ばれる JPA 仕様の実装を使用します。

dependencies {
	implementation 'com.h2database:h2:1.4.199'
	implementation 'org.hibernate:hibernate-core:5.4.2.Final'
次に、JPA を構成する必要があります。仕様とセクション「8.1 永続ユニット」を読むと、永続ユニットが構成、メタデータ、エンティティのある種の組み合わせであることがわかります。また、JPA が機能するには、構成ファイル内に と呼ばれる永続ユニットを少なくとも 1 つ記述する必要がありますpersistence.xml。その場所は仕様の章「8.2 永続ユニットのパッケージ化」で説明されています。このセクションによると、Java SE 環境がある場合は、それを META-INF ディレクトリのルートに配置する必要があります。
JPA: テクノロジー入門 - 6
JPA 仕様の「 」章に示されている例から内容をコピーしてみましょう8.2.1 persistence.xml file
<persistence>
	<persistence-unit name="JavaRush">
        <description>Persistence Unit For test</description>
        <class>hibernate.model.Category</class>
        <class>hibernate.model.Topic</class>
    </persistence-unit>
</persistence>
しかし、これだけでは十分ではありません。JPA プロバイダーが誰であるかを伝える必要があります。JPA 仕様を実装する人:
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
次に、設定を追加してみましょう(properties)。それらの一部 ( で始まるjavax.persistence) は標準の JPA 構成であり、JPA 仕様のセクション「8.2.1.9 プロパティ」で説明されています。一部の設定はプロバイダー固有です (私たちの場合、それらは JPA プロバイダーとしての Hibernate に影響します。設定ブロックは次のようになります:
<properties>
    <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
    <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE" />
    <property name="javax.persistence.jdbc.user" value="sa" />
    <property name="javax.persistence.jdbc.password" value="" />
    <property name="hibernate.show_sql" value="true" />
    <property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
これで、JPA 互換の config ができましたpersistence.xml。JPA プロバイダー Hibernate と H2 データベースがあり、ドメイン モデルである 2 つのクラスもあります。最後にこれをすべて機能させましょう。カタログでは/test/java、私たちの Gradle が単体テスト用のテンプレートを生成し、それを AppTest と名付けました。使ってみましょう。JPA 仕様の「7.1 永続コンテキスト」の章に記載されているように、JPA 世界のエンティティは永続コンテキストと呼ばれる空間に存在します。ただし、永続コンテキストを直接操作することはありません。Entity Managerこのために、「エンティティ マネージャー」を使用します。コンテキストとそこにどのような存在が住んでいるかを知っているのは彼です。私たちは「オム」と対話しますEntity Manager。あとは、これをどこから入手できるかを理解するだけですEntity Manager。JPA 仕様の「7.2.2 アプリケーション管理エンティティ マネージャーの取得」の章によれば、 を使用する必要がありますEntityManagerFactory。したがって、JPA 仕様を準備して、「7.3.2 Java SE 環境でのエンティティ マネージャー ファクトリの取得」の章から例を取得し、単純な単体テストの形式でフォーマットしてみましょう。
@Test
public void shouldStartHibernate() {
	EntityManagerFactory emf = Persistence.createEntityManagerFactory( "JavaRush" );
	EntityManager entityManager = emf.createEntityManager();
}
このテストでは、すでに「認識されない JPApersistence.xml XSD バージョン」というエラーが表示されます。その理由は、persistence.xmlJPA 仕様のセクション「8.3persistence.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_2.xsd"
             version="2.2">
さらに、要素の順序も重要です。したがって、providerクラスをリストする前に指定する必要があります。この後、テストは正常に実行されます。JPA直接接続が完了しました。次に進む前に、残りのテストについて考えてみましょう。各テストには が必要ですEntityManagerEntityManager実行の開始時に各テストに独自のテストがあることを確認してください。さらに、データベースは常に新しいものにしたいと考えています。inmemoryオプションを使用しているため、 を閉じるだけで十分ですEntityManagerFactory。作成はFactoryコストのかかる作業です。しかし、テストの場合はそれが正当化されます。JUnit を使用すると、各テストの実行前 (Before) と実行後 (After) に実行されるメソッドを指定できます。
public class AppTest {
    private EntityManager em;

    @Before
    public void init() {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory( "JavaRush" );
        em = emf.createEntityManager();
    }

    @After
    public void close() {
        em.getEntityManagerFactory().close();
        em.close();
    }
ここで、テストを実行する前に、新しいテストが作成されますEntityManagerFactory。これには、新しいデータベースの作成が伴います。hibernate.hbm2ddl.autoという意味がありますcreate。そして新しい工場から新しいものを入手しますEntityManager
JPA: テクノロジー入門 - 7

エンティティ

覚えているとおり、以前にドメイン モデルを記述するクラスを作成しました。これらが私たちの「本質」であるとすでに述べました。これは、 を使用して管理するエンティティですEntityManager。カテゴリの本質を保存するための簡単なテストを書いてみましょう。
@Test
public void shouldPersistCategory() {
	Category cat = new Category();
	cat.setTitle("new category");
	// JUnit обеспечит тест свежим EntityManager'ом
	em.persist(cat);
}
しかし、このテストはすぐには機能しません。なぜなら... エンティティが何であるかを理解するのに役立つさまざまなエラーが表示されます。
  • Unknown entity: hibernate.model.Category
    Categoryなぜ Hibernate はこれが 何であるかを理解しないのでしょうかentity? 重要なのは、エンティティは JPA 標準に従って記述されなければならないということです。JPA 仕様の「2.1 エンティティ クラス」の章に記載されているように、
    エンティティ クラスには アノテーション を付ける必要があります。@Entity

  • No identifier specified for entity: hibernate.model.Category
    エンティティには、あるレコードを別のレコードから区別するために使用できる一意の識別子が必要です。
    JPA仕様の「2.4 主キーとエンティティのアイデンティティ」の章によれば、「すべてのエンティティは主キーを持つ必要がある」、つまり すべてのエンティティには「主キー」が必要です。このような主キーはアノテーションで指定する必要があります@Id

  • ids for this class must be manually assigned before calling save()
    ID はどこかから取得したものでなければなりません。手動で指定することも、自動的に取得することもできます。
    したがって、「11.2.3.3 GeneratedValue」および「11.1.20 GeneratedValue アノテーション」の章で示すように、アノテーションを指定できます@GeneratedValue

したがって、カテゴリ クラスをエンティティにするには、次の変更を加える必要があります。
@Entity
public class Category {
    @Id
    @GeneratedValue
    private Long id;
さらに、注釈は@Idどれを使用するかを示しますAccess Type。アクセス タイプの詳細については、JPA 仕様の「2.3 アクセス タイプ」セクションを参照してください。非常に簡単に言うと、なぜなら... @Idフィールド ( ) の上に指定したfield場合、アクセス タイプは ではなくfield-based、 default になりますproperty-based。したがって、JPA プロバイダーはフィールドから値を直接読み取り、保存します。@Idゲッターの上に配置すると、property-basedアクセスが使用されます。ゲッターとセッターを介して。テストを実行すると、(オプションのおかげで) どのようなリクエストがデータベースに送信されたかも確認できますhibernate.show_sql。ただし、保存すると、「」は表示されませんinsert。実際には何も保存していないことが判明しましたか? JPA では、次のメソッドを使用して永続コンテキストとデータベースを同期できますflush
entityManager.flush();
しかし、今実行すると、「トランザクションは進行中でありません」というエラーが発生します。ここで、JPA がトランザクションをどのように使用するかを学びましょう。
JPA: テクノロジー入門 - 8

JPAトランザクション

覚えているとおり、JPA は永続化コンテキストの概念に基づいています。ここは実体が住む場所です。そして、 を通じてエンティティを管理しますEntityManager。コマンドを実行するとpersist、エンティティがコンテキスト内に配置されます。より正確には、EntityManagerこれを行う必要があることを伝えます。ただし、このコンテキストは単なるストレージ領域です。「一次キャッシュ」と呼ばれることもあります。ただし、データベースに接続する必要があります。このコマンドはflush、以前はエラーで失敗しましたが、永続コンテキストのデータをデータベースと同期します。ただし、これには転送が必要であり、この転送はトランザクションです。JPA のトランザクションについては、仕様の「7.5 トランザクションの制御」セクションで説明されています。JPA でトランザクションを使用するための特別な API があります。
entityManager.getTransaction().begin();
entityManager.getTransaction().commit();
テストの前後に実行されるコードにトランザクション管理を追加する必要があります。
@Before
public void init() {
	EntityManagerFactory emf = Persistence.createEntityManagerFactory( "JavaRush" );
	em = emf.createEntityManager();
	em.getTransaction().begin();
}
@After
public void close() {
	if (em.getTransaction().isActive()) {
		em.getTransaction().commit();
        }
	em.getEntityManagerFactory().close();
	em.close();
}
追加後、以前は存在しなかった SQL の式が挿入ログに表示されます。
JPA: テクノロジー入門 - 9
EntityManagerトランザクションに蓄積された変更はデータベースにコミット (確認および保存) されました。では、自分の本質を探ってみましょう。ID でエンティティを検索するテストを作成しましょう。
@Test
public void shouldFindCategory() {
	Category cat = new Category();
	cat.setTitle("test");
	em.persist(cat);
	Category result = em.find(Category.class, 1L);
	assertNotNull(result);
}
この場合、以前に保存したエンティティを受け取りますが、ログには SELECT クエリが表示されません。そしてすべては、「エンティティ マネージャー、ID=1 のカテゴリ エンティティを見つけてください。」という言葉に基づいています。そして、エンティティ マネージャーはまずコンテキストを調べ (一種のキャッシュを使用します)、見つからない場合にのみデータベースを調べます。SELECTID を 2 に変更する価値があります (そのようなことはありません。保存したインスタンスは 1 つだけです)。リクエストが表示されることがわかります。コンテキスト内にエンティティが見つからず、EntityManagerデータベースがエンティティを見つけようとしているため、コンテキスト内のエンティティの状態を制御するために使用できるさまざまなコマンドがあります。エンティティがある状態から別の状態への遷移は、エンティティのライフ サイクルと呼ばれますlifecycle
JPA: テクノロジー入門 - 10

エンティティのライフサイクル

エンティティのライフサイクルは、JPA仕様の「3.2 エンティティインスタンスのライフサイクル」の章に記載されています。なぜなら エンティティはコンテキスト内に存在し、 によって制御されているEntityManager場合、エンティティは制御されていると言います。管理された。エンティティの生涯の段階を見てみましょう。
// 1. New or Transient (временный)
Category cat = new Category();
cat.setTitle("new category");
// 2. Managed or Persistent
entityManager.persist(cat);
// 3. Транзакция завершена, все сущности в контексте detached
entityManager.getTransaction().begin();
entityManager.getTransaction().commit();
// 4. Сущность изымаем из контекста, она становится detached
entityManager.detach(cat);
// 5. Сущность из detached можно снова сделать managed
Category managed = entityManager.merge(cat);
// 6. И можно сделать Removed. Интересно, что cat всё равно detached
entityManager.remove(managed);
それを統合した図は次のとおりです。
JPA: テクノロジー入門 - 11
JPA: テクノロジー入門 - 12

マッピング

JPA では、エンティティ間の関係を記述することができます。ドメイン モデルを扱ったときに、エンティティ相互の関係をすでに調べたことを思い出してください。次に、quickdatabasediagrams.comリソースを使用しました。
JPA: テクノロジー入門 - 13
エンティティ間の接続を確立することは、マッピングまたは関連付け (関連付けマッピング) と呼ばれます。JPA を使用して確立できる関連付けの種類を以下に示します。
JPA : テクノロジー入門 - 14
Topicトピックを説明するエンティティを見てみましょう。Topicに対する態度については何と言えますかCategory?多くはTopic1 つのカテゴリに属します。したがって、関連付けが必要ですManyToOne。この関係を JPA で表現してみましょう。
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
どの注釈を付けるかを覚えておくには、最後の部分がその上に注釈が示されるフィールドを担当していることを覚えておくとよいでしょう。ToOne- 特定のインスタンス。ToMany- コレクション。これで、接続は一方向になりました。双方向のコミュニケーションにしましょう。このカテゴリに含まれるCategoryすべての人についての知識を追加しましょう。リストがあるため、Topicこれは で終わる必要があります。それは、「多くの話題に対して」という態度です。疑問は残ります -または: ToManyTopicOneToManyManyToMany
JPA: テクノロジー入門 - 15
同じトピックに関する良い回答は、ここで読むことができます:「ORM oneToMany、manyToMany 関係を I'm Five のように説明する」。カテゴリにトピックとの関連がある場合ToMany、これらの各トピックはカテゴリを 1 つだけ持つことができ、その場合は になりますOne。それ以外の場合は、になりますMany。したがって、Categoryすべてのトピックのリストは次のようになります。
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "topic_id")
private Set<Topic> topics = new HashSet<>();
Categoryそして、基本的にすべてのトピックのリストを取得するゲッターを作成すること を忘れないでください。
public Set<Topic> getTopics() {
	return this.topics;
}
双方向の関係を自動的に追跡するのは非常に困難です。したがって、JPA はこの責任を開発者に移します。これが意味するのは、Topicとのエンティティ関係を確立するときはCategory、データの一貫性を自分たちで確保する必要があるということです。これは次のように簡単に実行できます。
public void setCategory(Category category) {
	category.getTopics().add(this);
	this.category = category;
}
チェックする簡単なテストを書いてみましょう。
@Test
public void shouldPersistCategoryAndTopics() {
	Category cat = new Category();
	cat.setTitle("test");
	Topic topic = new Topic();
	topic.setTitle("topic");
	topic.setCategory(cat);
 	em.persist(cat);
}
マッピングはまったく別のトピックです。このレビューの目的は、これを達成する手段を理解することです。マッピングの詳細については、こちらをご覧ください。
JPA : テクノロジー入門 - 16

JPQL

JPA は、Java Persistence Query Language でのクエリという興味深いツールを導入しました。この言語は SQL に似ていますが、SQL テーブルではなく Java オブジェクト モデルを使用します。例を見てみましょう:
@Test
public void shouldPerformQuery() {
	Category cat = new Category();
	cat.setTitle("query");
	em.persist(cat);
	Query query = em.createQuery("SELECT c from Category c WHERE c.title = 'query'");
 	assertNotNull(query.getSingleResult());
}
ご覧のとおり、クエリではCategoryテーブルではなくエンティティへの参照を使用しました。そして、このエンティティのフィールドにもありますtitle。JPQL は多くの便利な機能を提供するため、独自の記事を作成する価値があります。詳細についてはレビューをご覧ください。
JPA : テクノロジー入門 - 17

基準API

最後に、Criteria API について触れたいと思います。JPA は、動的クエリ構築ツールを導入します。Criteria API の使用例:
@Test
public void shouldFindWithCriteriaAPI() {
	Category cat = new Category();
	em.persist(cat);
	CriteriaBuilder cb = em.getCriteriaBuilder();
	CriteriaQuery<Category> query = cb.createQuery(Category.class);
	Root<Category> c = query.from(Category.class);
	query.select(c);
	List<Category> resultList = em.createQuery(query).getResultList();
	assertEquals(1, resultList.size());
}
この例は、リクエスト " SELECT c FROM Category c" を実行するのと同じです。 Criteria API は強力なツールです。詳細については、ここで読むことができます。

結論

ご覧のとおり、JPA は膨大な数の機能とツールを提供します。どれも経験と知識が必要です。JPAのレビューの枠内であっても、詳細な調査はおろか、すべてに言及することはできませんでした。しかし、これを読んだ後、ORM と JPA とは何か、それがどのように機能し、それを使って何ができるのかがより明確になったことを願っています。さて、おやつとして、さまざまな材料を提供します。 #ヴィアチェスラフ
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION