Hibernateで学ぶJPA入門の要点をまとめる

### 1. 動機

会社でstruts2フレームワークを使ったWebアプリケーション開発を行っている。このアプリケーションではDBアクセスでJDBCではなくHibernateを使っているが、何も知識がなく困っていた。そんな中、以下の良書に巡り合えたので内容を理解するとともに要点をまとめた。

- Seasar2とHibernateで学ぶデータベースアクセス　JPA入門
  中村年宏　著、毎日コミュニケーションズ発行

### 2. 登場するキーワード

- JPA
- JPQL
- O/Rマッピング
- N+1問題
- 遅延ローディング

### 3. 用語説明
#### a. O/Rマッピング

Javaオブジェクト（Object）とリレーショナルデータベース（Relational）上のデータを関連付けること。
JPAはO/Rマッピング機能を持つことが大きな特徴。一方、JDBC（※）はオブジェクトとDBの関連付けをプログラミングする必要がある。（プログラムが煩雑になる。）

------
※JDBC：JavaプログラムからSQLを発行してデータベースへアクセスするためのAPI

#### b. JPA（Java Persistence API）
Java EE（※）に含まれるAPI。
JPAは仕様でしかなく、JPAを実装したプロダクトが複数存在あり、代表格はHibernate（ハイバネート）、TopLink Essentials、Open JPAの３つである。
環境面：JavaEE, Java SEで使用可能。JDKは5.0以上が必要。

------
※Java EE ：Java SEにサーバーサイド機能を追加したもの。詳細は以下を参照。

- 【Java入門】JavaSEとEEの違いやJRE・JDKを世界一分かりやすく解説
  https://www.sejuku.net/blog/12902
- EclipseのJavaとJavaEEの違い
  https://qiita.com/kojocho/items/b5d3646012b7a6411c3d


#### c. Hibernate

米Red Hat社によって提供されるオープンソースのJPA実装。
HibernateをJPA実装として利用するにはHibernate Core、Hibernate Annotations、Hibernate EntityManagerの３プロダクトが必要。
JDBCと比較すると以下の４処理が不要になる。（詳細は後述）

- SQL文
- ResultSetからのデータ詰替え
- リソースのクローズ
- 例外ハンドリング

### 4. データの取得・更新・追加・削除の実装方法

JPAではO/RマッピングによってテーブルとJavaクラスを対応付け、テーブルの１レコードをJavaクラスの１インスタンスとして扱う。この時。Javaクラスを**エンティティクラス**と呼び、そのインスタンスを**エンティティ** と呼び、エンティティは**EntityManager**を通して扱う。

EntityManagerはアプリケーションからエンティティの取得要求があると、まず永続コンテキストにキャッシュされているか確認し、キャッシュが存在する場合はキャッシュされたエンティティを返す。

イメージはこんな感じ。

![画像１.png](https://qiita-image-store.s3.amazonaws.com/0/92364/bfb84570-299f-dd36-4749-ddb41f8ebde1.png)


#### a. データの取得
エンティティの取得方法は主キーによる取得(1)とJPQL(2)による取得の2種類ある。
##### (1) エンティティによる取得

主キーでエンティティを取得するにはEntityManagerの***findメソッド***を使用する。

| 項目   | 説明                                                         |
| ------ | ------------------------------------------------------------ |
| 引数   | 第1引数はエンティティクラス、第2引数は主キーの値を渡す       |
| 戻り値 | エンティティが存在する場合はそのエンティティ。<br />存在しない場合はnull |

```java
public void findById() throws Exception{
	// EntityManager(em)をDIでインジェクトしておく
	// Empクラス（テーブル）の主キーが「1」のレコード（エンティティ）を取得
	Emp emp = em.find(Emp.class, 1L);
}
```

##### (2) JPQLによる取得

主キー指定でエンティティを取得しないケースでは、EntityManagerのfindメソッドが使用できず、JPQLでの実装が必要になる。JPQLはJPA独自の問い合わせ言語であり、SQLとは完全に異なる。
JPQLではEntityManagerの***createQueryメソッド***に引数を渡すことでQueryオブジェクトを作成できる。Queryにはバインド変数のパラメータやページング情報を渡すことができる。
エンティティはQueryのgetResultListメソッド、getSingleResultメソッドを実行することで取得できる。

| 項目   | 説明                                                         |
| ------ | ------------------------------------------------------------ |
| 引数   | jpql（問い合わせ文）                                         |
| 戻り値 | 条件に合致するレコードがある場合は合致したエンティティリスト<br /><u>存在しない場合は空のリスト（サイズ＝0）</u> |

#### b.  データの更新

更新したいエンティティをEntityManagerで取得し、そのエンティティの状態を変更することでデータを更新できる。EntityManagerが変更前のオリジナルデータを管理しており、適切なタイミングでオリジナルデータと現在のデータを比較し更新を自動で検出する。エンティティが更新されていることを検出すると自動的にUpdate文が発行される。

（この際発行されるUpdate文には自動的に楽観的排他制御で使用しているバージョンカラム（後述）の条件が追加される。）

※JPAではデータ更新を担うメソッドは存在しない

```java
public void updateEntity() throws Exception{
	// EntityManager(em)をDIでインジェクトしておく
	// Empクラス（テーブル）の主キーが「1」のレコード（エンティティ）を取得
	Emp emp = em.find(Emp.class, 1L);
    emp.setEmpName("NEWNAME");
    // 自動的にUpdate文が発行される
}
```

#### c.  データの追加

エンティティを追加するには新規に生成したエンティティをEntityManagerの***persistメソッド***に渡す。
操作対象のエンティティクラス定義にて主キーレコードに＠GeneratedValueアノテーションを指定すると、主キー要素に値を設定せずとも自動設定される。

```java
public void persistEntity() throws Exception{
	// EntityManager(em)をDIでインジェクトしておく
	// 次のEmpエンティティを生成
    Emp emp = new Emp();
    emp.setEmpNo (123);
    emp.setEnpName("123Name");
    em.persist(emp);
    // 自動的にInsert文が発行される
}
```

#### d.  データの削除

エンティティを削除するにはEntityManagerの***removeメソッド***に削除したいエンティティを渡す。
EntityManagerは適切なタイミングでSQLのDelete文を発行する。

（この際発行されるDelete文には自動的に楽観的排他制御で使用しているバージョンカラム（後述）の条件が追加される。）

```java
public void removeEntity() throws Exception{
	// EntityManager(em)をDIでインジェクトしておく
	// 次のエンティティを削除する
    em.remove(emp);
    // 自動的にDelete文が発行される
}
```

### 5. O/Rマッピング
#### a. アノテーション
O/Rマッピングに必要なアノテーションは以下のとおり。
これらのアノテーションはすべて「javax.persistence」パッケージに属するものである。

***@ManyToOneや@OneToMany、@OneToOneのfetch属性は基本的に「fetchType.LAZY」（後述）を指定すること。***

| No        | アノテーション     | 指定場所   | 表す意味                                                     |
| --------- | ------------------ | ---------- | ------------------------------------------------------------ |
| 1         | @Entity            | クラス     | エンティティであること<br />エンティティ名とはJavaの世界でエンティティを特定するための名称で、JPQL内で使用される<br />`例：@Entity(name = "Hoge")` |
| 2         | @Id                | フィールド | 主キーであること                                             |
| 3         | @Transient         | フィールド | 非永続的であること<br />データベースのカラムと対応付けしたくない場合に付与する |
| 4         | @GeneratedValue    | フィールド | 主キー値を自動生成すること                                   |
| 5         | @ManyToOne         | フィールド | 関連が多対一であること                                       |
| 6         | @OneToMany         | フィールド | 関連が一対多であること                                       |
| 7         | @OneToOne          | フィールド | 関連が一対一であること                                       |
| 8         | @Table             | クラス     | マッピング対象のテーブルを明示する                           |
| 9         | @Column            | フィールド | マッピング対象カラムであることを明示する<br /><u>NOT NULL制約のないカラムはNULLを表現するためにラッパー型を使用しなければならない</u> |
| <u>1</u>0 | @JoinColumn        | フィールド | 結合元のカラムと結合先のカラムを明示する                     |
| 11        | @SequenceGenerator | フィールド | 主キーを生成するシーケンスの定義                             |
| 12        | @TableGenerator    | フィールド | 主キーを生成するテーブルの定義                               |
| 13        | @Version           | フィールド | 楽観的排他制御（後述）を利用するためのバージョン番号のフィールドであること<br />フィールド型はInteger型かLong型にすべき |

#### b. 遅延ローディング
あるテーブルが親子構成になっている場合、子テーブルを親テーブル取得時に取得せず、必要になったときに（裏側で）取得する仕組み。

@ManyToOneや@OneToMany、@OneToOneのfetch属性はデフォルトが「fetchType.EAGER」になっているため、特段意図がない場合は遅延ローディング「fetchType.LAZY」を使用する。



#### C. N+1問題
任意のテーブルからN個のレコード（親エンティティ）を取得するためにSELECT文を1回発行、
N個のレコードが関連するデータ（子エンティティ）を取得するためにSELECT文を1回ずつ、計N回発行してしまうこと。意図せず大量のSQLが発行されることになるため注意が必要。
解決方法は以下の２通りある。

- JPQLでJOIN FETCHを使用する
- @Fetchアノテーションを使用する

------
参考

- JPAの性能問題と対策
  https://qiita.com/itagakishintaro/items/04b6933a29e92f8ee4b7

### 6. JPQL
JPQLはJPA実装によって解釈され、使用するデータベースに適したSQLに変換されてから実行される。SQLはデータベース毎に方言を持つが、JPQLを使用することで方言による差異を多少吸収できる。

#### a. 文法と用語
##### (1) エンティティ名と識別変数
JPQLのFROM区はエンティティ名（≠テーブル名）を指定する。
識別変数(以下SQLでは”e”)はエンティティ名の後ろに宣言し、それを使用して抽出項目等を指定する。
※エンティティ名は大文字小文字が区別される

```sql
select e from Employee e;
```

##### (2) フェッチ結合
遅延ローディングが適用されると、子エンティティは必要になったときにしか取得されない。一方、最初から子エンティティも必要と分かっている場合には１つのSQLで同時に取得する方が効率的である。そのような場合に***フェッチ結合***と使用する。
フェッチ結合を使用したSQLは以下のとおり。

```sql
select distinct d d from Department d inner join fetch d.employees where d.name = "ABC";
```

### 7. エンティティのライフサイクル
#### a. エンティティの４つの状態
エンティティは取得されると永続コンテキストで管理される。永続コンテキスト内では以下の４つの状態に分かれて管理されている。これらの状態はEntityManagerのメソッド実行や永続コンテキストの終了などによって変化し、***エンティティのライフサイクル***と呼ばれる。図示すると以下のとおり。

- 新規の（new）
  新規のエンティティはEntityManagerのpersistメソッドやmergeメソッドを仕様しない限り、新規のエンティティのまま。

- 管理された（managed）
  エンティティをEntityManagerのfindメソッドやJPQLを使って取得した場合、エンティティをEntityManagerのpersistメソッドがmergeメソッドに渡した場合、エンティティは永続コンテキストに管理される。管理されたエンティティに対する永続フィールドの変更は自動で検出される。

- 分離された（detached）
  分離されたエンティティに対して変更処理をしても、EntityManagerのmergeメソッドでエンティティを再び管理された状態にしない限り、変更は永続コンテキストに反映されない。

- 削除された（removed）
  管理されたエンティティをEntityManagerのremoveメソッドに渡すと、永続コンテキストから削除される。

![画像２.png](https://qiita-image-store.s3.amazonaws.com/0/92364/a7ebde1d-8369-c7cf-cbbc-e997cfc9e392.png)

#### b. データベースの同期化
EntityManagerは管理されたエンティティと削除されたエンティティをデータベースに反映させる。この処理を***データベースの同期化***という。同期化により、永続コンテキストに蓄積されたエンティティの変更情報がデータベースに書き込まれる。

データベースの同期化が行われるデフォルトのタイミングは以下のとおり。

- EntityManagerのflushメソッドが呼ばれた時
- トランザクションがコミットされる直前
- 問い合わせを実行する直前

### 8. JPAの楽観的排他制御
排他制御には特定のデータをロックし、他のトランザクションからのCRUDを禁止する悲観的排他制御と、データをロックせず、更新が衝突した場合に他方の処理を失敗させる楽観的排他制御がある。

JPAではデータベースにバージョン番号（@Version付与）を管理する整数型カラムを用意することで、このバージョン番号をもとに楽観的排他制御を実現できる。

※楽観的排他制御は同一のデータが同時に更新される頻度が少ないことを前提に使用される方法

------
### ＊番外編＊知っていると便利なこと

- Hibernateで発行されたSQLをログで確認したい
  →persistence.xml（※）のhibernate.show_sqlプロパティに"true"を設定する。

  ` <property name = "hibernate.show_sql" value = "true" />   `

- Hibernateで発行されたログ内のSQLを整形して表示したい
  →persistence.xml（※）のhibernate.format_sqlプロパティに"true"を設定する。

  ` <property name = "hibernate.format_sql" value = "true" />   `

------
　　※データソース名やトランザクションのタイプなどJPA共通の設定やJPA実装独自の設定を定義