ぺんぎんらぼ

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です。たまに絵を描きます。

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です

Eclipseで始めるJava SEでJPA (Jakarta Persistence) アプリケーション

データベースにアクセスするアプリケーションを作るとき、どのような方法でデータベースアクセスを実装してますか?やっぱりMyBatis?まさかのJDBC
Jakarta EEではデータベースアクセスのフレームワークとしてJPA (Jakarta Persistence PI) というものが用意されています。日本では残念ながら知名度が低いフレームワークです。

今回は、この「JPA」を使って、データベースから取得した値を画面に表示するコンソールアプリケーションを作成します。
なんと、Javaクラスを2つ、設定ファイルを2つ作成するだけなので、ぜひ、Java SEによるJPAアプリケーションを体験してみてください。

アプリケーションの作成

Gradleプロジェクトの作成

EclipseのBuildshipプラグインを使って、Gradleプロジェクトを作成します。

プロジェクト名を入力するだけで、数クリックでプロジェクトが作成できます。

検証目的のプロジェクトなので、フラットなシングルプロジェクトとして作成するのもよいでしょう。
Buildshipプラグインで、Gradleのシングルプロジェクトを作成する方法は、以下の記事で解説しています。

penguinlabo.hatenablog.com

build.gradleの編集

build.gradleを編集して、Jakarta EEのプロジェクトとして必要な設定を記載します。

plugins {
    id 'java-library'
}
apply plugin: 'eclipse'

sourceCompatibility = '17'
targetCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.hibernate:hibernate-core:6.4.4.Final'
    implementation 'com.h2database:h2:2.2.224'
}
依存関係の設定
dependencies {
    implementation 'org.hibernate:hibernate-core:6.4.4.Final'
    implementation 'com.h2database:h2:2.2.224'
}

JPAのライブラリを依存関係に追加します。
ここで指定しているライブラリはJPAの実装となるhibernateを指定しています。
また、今回はインメモリデータベースであるH2を使うので、H2のJDBCドライバも依存関係に追加しています。

Gradleプロジェクトのリフレッシュ

build.gradleファイルの変更内容をプロジェクトに反映する必要があります。

プロジェクトを右クリックして、[Gradle] - [Gradleプロジェクトのリフレッシュ]を実行します。

JREシステム・ライブラリー」がbuild.gradleファイルで指定したバージョンのJavaになっていること、「プロジェクトと外部の依存関係」にhibernateのライブラリとH2のJDBCドライバが追加されていることが確認できます。

エンティティの作成

エンティティとは、データベースのテーブルと一対一となるクラスです。

  • テーブル = エンティティクラス
  • テーブルの列 = エンティティクラスのフィールド

という形でクラスを作成します。
今回は以下のテーブルに対応するエンティティクラスを作成します。

テーブル名 : profile

列名 列型 属性
id 数値 主キー
name 文字列 非NULL値
birthday 日付

このテーブルのエンティティクラスは以下のようになります。

import java.io.Serializable;
import java.time.LocalDate;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class Profile implements Serializable {
    @Id
    private int id;
    
    @Column(nullable = false)
    private String name;

    private LocalDate birthday;

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public LocalDate getBirthday() { return birthday; }
    public void setBirthday(LocalDate birthday) { this.birthday = birthday; }
}

クラスは単純なPOJPと同様で、フィールドとフィールドのGetter、Setterの実装です。

エンティティの実装方法はクラス名をテーブル名、フィールド名を列名と同じ名前にします。
そして、クラスに@Entityアノテーションを、主キーに対応するフィールドに@Idアノテーションを指定します。
また、非NULL値の列に対応するフィールドに@Column(nullable = false)アノテーションを指定します。

mainメソッドの作成

データベースにアクセスするmainメソッドを実装したクラスを作成します。

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

public class Main {

    public static void main(String[] args) {
        try (EntityManagerFactory emf = Persistence.createEntityManagerFactory("defaultPU");
                EntityManager em = emf.createEntityManager()) {
            em.getTransaction().begin();
            Profile entity = em.find(Profile.class, 1);
            System.out.println("Hello " + entity.getName());
            em.getTransaction().commit();
        }
    }
}

主キーを使ってテーブルからレコードを取得する部分は、

em.find(Profile.class, 1)

です。引数の「1」が検索する主キーの値で、復帰値はレコードの内容が格納されたProfileクラスになります。

設定ファイルの作成

Jakarta Persistence 3.1 まではpersistence.xmlという設定ファイルが必須になります。
このファイルはMETA-INFフォルダに作成する必要があります。src/main/resourcesMETA-INFフォルダを作成して、そのフォルダにpersistence.xmlファイルを作成します。

次のバージョンとなるJakarta Persistence 3.2では、PersistenceConfigurationというクラスを使って、設定ファイルレスでJPAが使えるようになる予定です。
指定しているスキーマファイル「persistence_3_0.xsd」やversion属性が3.0と古いバージョンになっていますが、3.1用のスキーマファイルが提供されておらず、3.1でもpersistence.xmlの内容に変更がないため、3.0を指定しています。
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
             version="3.0">
    <persistence-unit name="defaultPU" transaction-type="RESOURCE_LOCAL"> 
        <properties>
            <property name="jakarta.persistence.jdbc.driver"   value="org.h2.Driver" />
            <property name="jakarta.persistence.jdbc.url"      value="jdbc:h2:mem:test" />
            <property name="jakarta.persistence.jdbc.user"     value="sa" />
            <property name="jakarta.persistence.jdbc.password" value="" />
            <property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>
            <property name="jakarta.persistence.sql-load-script-source" value="data.sql"/>
        </properties>
    </persistence-unit>
</persistence>

今回のお試し実装ではデータベースの接続情報と2つのプロパティを設定しています。

<property name="jakarta.persistence.jdbc.driver"   value="org.h2.Driver" />
<property name="jakarta.persistence.jdbc.url"      value="jdbc:h2:mem:test" />
<property name="jakarta.persistence.jdbc.user"     value="sa" />
<property name="jakarta.persistence.jdbc.password" value="" />

インメモリデータベースであるH2の接続情報を定義しています。他のデータベースに接続する場合は、この部分を変更します。

<property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>

起動時にデータベースに対するアクションです。エンティティクラスに対応するテーブルを削除(drop)して、エンティティクラスの定義内容に従ってテーブルを作成(create)する設定です。

<property name="jakarta.persistence.sql-load-script-source" value="data.sql"/>

データ読み込み用のスクリプトファイルからDML文を読み込んで実行します。基本的に、このスクリプトファイルにテーブルに挿入するSQL文を記載して、テーブルを初期化します。

データ読み込み用のスクリプトの作成

先ほど作成したpersistence.xmljakarta.persistence.sql-load-script-sourceで定義したdata.sqlファイルを作成します。
ファイル名のみでパスが指定されていないので、src/main/resources直下に配置する必要があります。

INSERT INTO profile (id, name, birthday) VALUES (1, 'Matsuki', '2021-12-31')

profileテーブルに主キー列idに値が「1」のレコードをINSERTするSQLです。

作成完了!!

以上で、必要なファイルの作成は終わりです。
以下のようなファイル配置になります。

アプリケーションの動作確認

作成したアプリケーションを実行します。

正しく動作していれば、コンソールに以下のように表示されます。

2月 18, 2024 8:14:37 午前 org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [name: defaultPU]
2月 18, 2024 8:14:37 午前 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 6.4.4.Final
2月 18, 2024 8:14:37 午前 org.hibernate.cache.internal.RegionFactoryInitiator initiateService
INFO: HHH000026: Second-level cache disabled
2月 18, 2024 8:14:37 午前 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using built-in connection pool (not intended for production use)
2月 18, 2024 8:14:37 午前 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: Loaded JDBC driver class: org.h2.Driver
2月 18, 2024 8:14:37 午前 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001012: Connecting with JDBC URL [jdbc:h2:mem:test]
2月 18, 2024 8:14:37 午前 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {password=****, user=sa}
2月 18, 2024 8:14:37 午前 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
2月 18, 2024 8:14:37 午前 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH10001115: Connection pool size: 20 (min=1)
2月 18, 2024 8:14:39 午前 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2月 18, 2024 8:14:39 午前 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@2007435e] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
2月 18, 2024 8:14:39 午前 org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@16a2ed51] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Hello Matsuki
2月 18, 2024 8:14:39 午前 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:h2:mem:test]