LibertyによるWebサービスアプリ開発メモ: (3)JPA経由でのDBアクセス

# はじめに
Webサービス(REST)アプリを開発してLiberty上で動かすまでの流れについて、試したことを備忘録として記載していきます。
今回はJPAを使ったDBアクセスを試します。

## 関連記事
[LibertyによるWebサービスアプリ開発メモ: (1)環境構築](https://qiita.com/tomotagwork/items/3a63ac7955d053a4feb4)
[LibertyによるWebサービスアプリ開発メモ: (2)MavenプロジェクトによるJAX-RSアプリ開発](https://qiita.com/tomotagwork/items/80b2030d625b6d329a27)
[LibertyによるWebサービスアプリ開発メモ: (3)JPA経由でのDBアクセス](https://qiita.com/tomotagwork/items/2391a79f4fe36449f2a4)
[LibertyによるWebサービスアプリ開発メモ: (4)ロギング](https://qiita.com/tomotagwork/items/30e07835c009cea53dd7)
[LibertyによるWebサービスアプリ開発メモ: (5)JUnitによる単体テスト](https://qiita.com/tomotagwork/items/e35d4984df285307fd2b)
[LibertyによるWebサービスアプリ開発メモ: (6)Eclipse-GitHub連携](https://qiita.com/tomotagwork/items/63a1c75be844f28a0582)

# 前提
アプリケーション・サーバー: WAS Liberty
データベース: IBM DB2

ちなみにLibertyのJPA2.1実装はEclipseLinkがベースになっているようです。
参考: [Configuring JPA in Liberty](https://www.ibm.com/support/knowledgecenter/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/twlp_config_jpa.html)

>Java™ Persistence API (JPA) 2.1 for WebSphere Application Server is built on the EclipseLink open source project. EclipseLink is the reference implementation for all version of the JPA specification. The provider of JPA for this product is org.eclipse.persistence.jpa.PersistenceProvider.

# DB側の準備

アクセスするテーブルを準備します。
テーブルありきでそれとマップするオブジェクトを作成したり、オブジェクトありきでそれに合わせてテーブル作成したり、と色々アプローチが取れるようですが、ここではテーブル先に作ることにします。
色々眺めていると、どうやらJPAでアクセスする際の対象としては主キーを持たないといけないらしい(@Idアノテーションが必須)。ここでは単純なサンプルを想定しているので主キーとなるカラムを自動生成させる想定とします。

DB2のインスタンスやデータベース等の環境はセットアップしてある前提で、以下のようにACCOUNT_TABというテーブルを作成します。

```sql:
create table db2admin.ACCOUNT_TAB (
     id int generated always as identity , 
     employeeNumber varchar(20) , 
     data01 varchar(32000) ,
     data02 varchar(32000)
    );
```

id, employeeNumber, data01, data02というカラムを持つテーブルを作成しています(idはユニークな番号を自動で割り振られる)。

適当にいくつかレコードも追加しておきます。

# アプリケーション・サーバーの設定
JPAのフィーチャーをLibertyサーバーの構成ファイルserver.xmlの<featureManager>に追加します。

```xml:server.xml
 ...

    <featureManager>
        <feature>javaee-7.0</feature>
        <feature>localConnector-1.0</feature>
    	<feature>jpa-2.1</feature>
    </featureManager>

  ...
```

また、Liberty上のアプリケーションから当該テーブルが乗っているデータベースにアクセスできるようにデータソースの定義を追加します。
まずJDBCドライバー経由でアクセスするためにDB2接続用のJDBCドライバーを入手します(db2jcc4.jar, db2jcc.jar, db2jcc_license_cisuz.jar)。ここでは、Liberty導入ディレクトリ下のusr/shared/resources以下に配置します。
server.xmlに以下のような定義を追加します。

```xml:server.xml
  ...

    <dataSource id="DB2Sample" jdbcDriverRef="db2-driver" jndiName="jdbc/DB2Sample" type="javax.sql.DataSource" transactional="false">
		<properties.db2.jcc databaseName="SAMPLE" password="{xor}xxxxx" serverName="DB2Server01" user="db2admin" portNumber="50000"/>
    </dataSource>
    <jdbcDriver id="db2-driver" libraryRef="db2-liberty"/>
    <library id="db2-liberty">
        <fileset dir="${wlp.user.dir}/shared/resources" id="db2-fileset"/>
    </library> 

  ...
```

# アプリケーション

## Mavenプロジェクト作成
前回の記事 [LibertyによるWebサービスアプリ開発メモ: (2)MavenプロジェクトによるJAX-RSアプリ開発](https://qiita.com/tomotagwork/items/80b2030d625b6d329a27) に従いMavenプロジェクト「MavenLibertyTest02」を作成します。

pom.xmlのdependencyにeclipselinkの依存関係を追加して、プロジェクトを右クリック - [Maven] - [Update Project]を選択し、project configurationのアップデートを行う。

```xml:pom.xml
  <dependencies>
    	<dependency>
		  	<groupId>net.wasdev.maven.tools.targets</groupId>
		  	<artifactId>liberty-target</artifactId>
		  	<version>17.0.0.2</version>
		  	<type>pom</type>
		  	<scope>provided</scope>
  		</dependency>
   		
  		<dependency>
	    	<groupId>javax</groupId> 
	    	<artifactId>javaee-api</artifactId>
	    	<version>7.0</version>        
	    	<scope>provided</scope>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.eclipse.persistence/eclipselink -->
		<dependency>
		    <groupId>org.eclipse.persistence</groupId>
		    <artifactId>eclipselink</artifactId>
		    <version>2.6.3</version>
		</dependency>
		
  </dependencies>
```

## Entityクラスの作成

アクセスするテーブルとオブジェクトのマッピングを行うためのクラスを作成します。

```java:Account.java
package com.ibm.jaxrs.sample.jpa01;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Table(name = "ACCOUNT_TAB")
@Entity
public class Account {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private int id;

  private String employeeNumber;

  private String data01;

  private String data02;

  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getEmployeeNumber() {
    return employeeNumber;
  }
  public void setEmployeeNumber(String employeeNumber) {
    this.employeeNumber = employeeNumber;
  }
  public String getData01() {
    return data01;
  }
  public void setData01(String data01) {
    this.data01 = data01;
  }
  public String getData02() {
    return data02;
  }
  public void setData02(String data02) {
    this.data02 = data02;
  }
}
```

基本は、アクセス対象となるテーブルのカラムに相当するフィールドを定義して、そのsetter,getterメソッドを定義します。Entityクラスとして定義するため@Entityアノテーションを指定します。テーブル名がクラス名と異なる場合は@Tableアノテーションでテーブル名を指定できます。
また、@Idアノテーションで、主キーとなるフィールドを指定する必要があります。(テーブルに主キーが必要。)

## Webサービス用のクラスを作成
ここではRESTでサービスを呼び出してJPA経由でデータをアクセスすることを想定します。

```java:HelloWorldJPA01.java
package com.ibm.jaxrs.sample;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import com.ibm.jaxrs.sample.jpa01.Account;

@Path("/helloworldjpa")
public class HelloWorldJPA01 {

  EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPATest01");
  EntityManager em = emf.createEntityManager();

  @GET
  @Path("/jpa01")
  @Produces("application/json")
  public Response getRecord() {

    List<Account> accounts =
        em.createQuery("select a FROM Account a", Account.class).getResultList();

    Response response = Response.status(Response.Status.OK).entity(accounts).build();

    return response;
  }

  @POST
  @Path("/jpa01")
  @Produces("application/json")
  public Response insertRecord(
      @DefaultValue("X000") @QueryParam("empNumber") final String empNumber,
      @DefaultValue("000") @QueryParam("data01") final String data01,
      @DefaultValue("999") @QueryParam("data02") final String data02) {

    Account account = new Account();
    account.setEmployeeNumber(empNumber);
    account.setData01(data01);
    account.setData02(data02);

    em.getTransaction().begin();
    em.persist(account);
    em.getTransaction().commit();

    Response response = Response.status(Response.Status.OK).entity(account).build();

    return response;
  }

}
```

ここで、EntityManagerFactory でJPAでDBアクセスするためのオブジェクトを取得していますが、createEntityManagerFactoryの引数で指定している値は、後述のpersistence.xmlで定義しているpersistence-unitの名前と合わせます。
ここで取得されるEntityManagerクラスを使用してDBアクセスを行うことになります。
```em.createQuery("select a FROM Account a", Account.class).getResultList();``` の部分がテーブルの情報を取得している部分、```em.persist(account);```の部分がレコードをインサートしている部分です。

Webサービス構成用のクラスを作成します(この辺は普通のWebサービスのアプリと同様)。

```java:HelloWorldAppConfig 
package com.ibm.jaxrs.sample;

import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/rest/*")
public class HelloWorldAppConfig extends Application {

  @Override
  public Set<Class<?>> getClasses() {
    Set<Class<?>> classes = new HashSet<Class<?>>();
    classes.add(com.ibm.jaxrs.sample.HelloWorldJPA01.class);
    return classes;
  }
}
```

「GET .../rest/jpa01 」を発行することで、テーブル上の全データを取得
「POST .../rest/jpa01?empNumber=xxx&data01=yyy&data02=zzz」で、指定した値のレコードを1件追加
という操作を想定しています。

階層としてはこんな感じ。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/132331/a24b647c-db67-1bf0-8cba-5ecc44a76db3.png)


## データベース設定情報の作成
JPAでは、データベースに対する設定情報をpersistence.xmlというファイルで保持します。

src/main/resources/META-INF/フォルダに、persistence.xmlファイルを作成します。
(最初はフォルダが無いのでフォルダから作成します。)

```xml:persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" 
			 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">
  <persistence-unit name="JPATest01" transaction-type="RESOURCE_LOCAL">
  	<non-jta-data-source>jdbc/DB2Sample</non-jta-data-source>
  	<class>com.ibm.jaxrs.sample.jpa01.Account</class>
  </persistence-unit>
</persistence>
```

ここでは、Liberty上に登録されているデータソースを参照し、管理対象のクラスとして上で作成したAccountクラスを指定しています。

階層としてはこんな感じ。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/132331/3e644a88-d4d3-f381-3c74-7bb09d178d72.png)

これを作成してプロジェクトを右クリック - [Maven] - [Update Project]を選択し、project configurationのアップデートを行うと、persistence.xmlがJPA Contentとして認識される。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/132331/95e3bf10-2898-5082-4d69-06fa3961e0e7.png)

# 稼働確認
アプリをデプロイしてWebサービス経由でアクセスしてみます。
FireFoxのHTTPRequesterなどを使ってPOSTメソッドでデータのインサートをしてみます。

```
POST http://localhost:9080/MavenLibertyTest02/rest/helloworldjpa/jpa01?empNumber=B002&data01=BBB&data02=222
Content-Type: application/json

 -- response --
200 OK
X-Powered-By:  Servlet/3.1
Content-Type:  application/json
Date:  Mon, 09 Oct 2017 23:42:46 GMT
Content-Language:  ja-JP
Content-Length:  63

{"id":2,"employeeNumber":"B002","data02":"222","data01":"BBB"}
```

次にGETメソッドでデータの取得をしてみます。

```
GET http://localhost:9080/MavenLibertyTest02/rest/helloworldjpa/jpa01

 -- response --
200 OK
X-Powered-By:  Servlet/3.1
Content-Type:  application/json
Date:  Mon, 09 Oct 2017 23:45:09 GMT
Content-Language:  ja-JP
Content-Length:  128

[{"id":1,"employeeNumber":"A001","data02":"111","data01":"AAA"},{"id":2,"employeeNumber":"B002","data02":"222","data01":"BBB"}]
```

先にインサートしたデータ含めた全データが返されていることが確認できます。

これでWebサービス - JPA経由でのDBアクセス(参照/更新)が確認できました。