Jerseyのbookmarkサンプルを動かす-java RESTful service-

# はじめに
java で RESTful サービスを作ろうと思い Jersey を使う事にしました。
実用的なサンプルで学習したいと思い、見つけましたが、実際に動かした記事が見つからないので書きました。
結果的に多くが JPA 関係の対応になってしまい、動かすだけで終わってしまった印象です。

# 使用する環境

使うものは、
1. 開発ツールは Eclipse(2024年)
2. JAX-RSの実装は Jersey 4.0
3. WEBサーバーは Tomcat 10
4. データベースは PostgreSQL 15
5. JPAは EclipseLink

Bookmark サンプルの概要については以下に説明されています。

https://docs.oracle.com/cd/E92951_01/wls/RESTF/develop-restful-service.htm#GUID-998C27DF-1F59-4ADF-B20C-4256AEA5B001

JAX-RS は仕様で、実装の一つとして Jersey があります。
JAX-RS は Java EE の仕様ですが、Java SE を使う Tomcat でも動かせます。

データ操作部分で使うJPAも特に必要では無いのですが、Java EE の bookmark サンプルでは使われています。
実際に動かせたほうが理解しやすいと思いました。

# Eclipse で Jersey を動かすプロジェクトの作成
別の記事にまとめています。

https://qiita.com/edogawara/items/f59d00208d120de1c6c5

:::note warn
pom.xml で jersey-container-servlet に修正して Servlet3.0 を有効にしておいてください。
JSON は、あえて jettison を使います。
:::

# bookmark サンプルのソースをプロジェクトに追加する
ソースコードは Maven で公開されています。

https://repo1.maven.org/maven2/org/glassfish/jersey/examples/bookmark/4.0.0-M1/bookmark-4.0.0-M1-sources.jar

:::note warn
このバージョンは Tomcat10 以降でしか動きません。
:::

ダウンロードして展開
src/main/java に comフォルダーをコピー
src/main/resources に META-INFフォルダーをコピー
完成時は以下の様な構成になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834038/69cfcb0d-237f-dcb9-d80a-cc6612e57393.jpeg" width="50%">

maven に不足分のモジュールを追加します

```xml:pom.xml
	<dependency>
  		<groupId>org.glassfish.jersey.media</groupId>
		<artifactId>jersey-media-json-jettison</artifactId>
        <version>4.0.0-M1</version>
	</dependency>

	<dependency>
   		<groupId>jakarta.persistence</groupId>
   		<artifactId>jakarta.persistence-api</artifactId>
   		<version>3.2.0</version>
	</dependency>

	<dependency>
   		<groupId>jakarta.transaction</groupId>
   		<artifactId>jakarta.transaction-api</artifactId>
   		<version>2.0.1</version>
	</dependency>

	<dependency>
   		<groupId>jakarta.ws.rs</groupId>
   		<artifactId>jakarta.ws.rs-api</artifactId>
    	<version>4.0.0</version>
    </dependency>
```
# JPAについて
個人的には、O/Rマッピングとか好きではなく、JDBCでゴリゴリ書きたい向きです。

見た限りの印象としては、
エンティティの実体は Java Bean で、このクラスがデータベースの**テーブル**に対応します。
アノテーションでデータベース上のテーブルと関連付けられます。
アプリケーションはJDBCを意識することなく、このエンティティを操作する事で裏ではデータベースと同期しているイメージです。

ちゃんとした説明が必要な人は下記のような記事を参考にされると良いと思います。

https://qiita.com/amadablam/items/487e91c173a198cfe6be

# BookmarkサンプルのJPA
使用するデータベースのJDBCドライバーと紐づけします。
これは persistence.xml に記述します。
このファイルの場所は、アプリケーションの META-INF 直下に作成します。
(WEBアプリでは、{コンテキストパス}/WEB-INF/classes/META-INF の下)
サンプルに記載されている内容は、

```xml: persistence.xml
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit name="BookmarkPU" transaction-type="JTA">
        <provider>oracle.toplink.essentials.PersistenceProvider</provider>
        <properties>
            <property name="toplink.jdbc.url" value="jdbc:derby://localhost:1527/BookmarkDB"/>
            <property name="toplink.jdbc.user" value="REST"/>
            <property name="toplink.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
            <property name="toplink.jdbc.password" value="REST"/>
            <property name="toplink.ddl-generation" value="drop-and-create-tables"/>
        </properties>
    </persistence-unit>
</persistence>
```
サンプルではデータベースとして **Apache Derby** を対象としています。
ここを PostgreSQL に変えれば済むだろうと考えました。
ところで、**toplink** って何でしょう？
調べると、今は **EclipseLink** を使うのが良さそうです。

## JPAにEclipseLinkを使用するように書き換え

```xml:persistence.xml
<persistence-unit name="BookmarkPU" >
  <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
  <exclude-unlisted-classes>false</exclude-unlisted-classes>
  <properties>
    <property name="jakarta.persistence.jdbc.driver" value="org.postgresql.Driver"/>
    <property name="jakarta.persistence.jdbc.url" value="jdbc:postgresql://127.0.0.1:5432/testdb"/>
    <property name="jakarta.persistence.jdbc.user" value="postgres"/>
    <property name="jakarta.persistence.jdbc.password" value="postgres"/>
  </properties>
</persistence-unit>
```
maven で追加します。

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

## EclipseのTomcatへのJDBCドライバの登録
Tomcat で使う場合には、JDBCドライバはサーバー側に持たせておくほうが良いでしょう。
JNDIも使えるし、個別のアプリケーションごとに、maven で追加する必要も無くなります。
方法としては、JDBCドライバのjarファイルを Eclipse の Tomcat のインストール先のディレクトリ(lib)に直接コピーします。
場所はデフォルトなら、

```
{EclipseインストールDIR}/tomcat/{バージョン}/lib
```

## PostgreSQL 上のデータの準備
用意するテーブルとカラムは以下の通りです。

```sql
create table public."users" (
  userid text not null
  , username text
  , password text
  , email text
)
create table public."bookmarks" (
  bmid text not null
  , userid text
  , sdesc text
  , ldesc text
  , updated text
  , uri text
)
```

テストデータも数件登録しておくと良いでしょう。

# 実行してみる
その前に、API の basePath を考えます。
![basepath.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834038/2b9e4eeb-a0bd-cf85-4146-092352cc2412.jpeg)
Tomcat を使うとコンテキストパスがありますが、これは host の一部という位置づけになります。
Bookmark サンプルでは、MyApplication.java が、

```java
@ApplicationPath("/")
public class MyApplication extends ResourceConfig {
```

となっています。
変更しても良いので、"/api" に変えました。
こうすると、コンテンツにある index.jsp が実行されるので、必要なリンク等を追加しておくと便利です。
ここでは、プロジェクト名を "bookmark" としました。

Eclipseで「実行」し表示されたブラウザで、
localhost:8081/bookmark/api/users/
とすると、下記のエラーが出ました。

```
jakarta.servlet.ServletException: java.lang.NullPointerException: Cannot invoke "jakarta.persistence.EntityManagerFactory.createEntityManager()" because "this.emf" is null
```

エラーの場所は、

```java
   @PersistenceUnit(unitName = "BookmarkPU")
   EntityManagerFactory emf ; 
```

という事が解りました。アノテーションが働いていないのが原因です。

```java
   // @PersistenceUnit(unitName = "BookmarkPU")
   EntityManagerFactory emf = 
      Persistence.createEntityManagerFactory("BookmarkPU");
```

と明示的に書くと動作しました。
以下の様に実際のデータは json 形式で返ってきている事がわかります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3834038/e540eddd-5f12-e75d-3edc-9a1c4d843580.jpeg" width="100%">

# JTA の更新処理
検索は正常に動くものの、更新では JTA が設定されていないと動きません。
JTA は **分散トランザクション** を制御する機構です。
JTA は通常 WEBコンテナに実装されていますが、Tomcat ではありません。
別途モジュールを追加する必要があるようです。
検索した限りでは、**JOTM** が見つかりますが、組み込もうとしても　Tomcat 起動時にエラーが出て動作しません。(JOTOMは2009年頃から更新されていません)

結局、JTA を使わない方法として persistence.xml 内の transaction-type を "RESOURCE_LOCAL"にしました。

```xml
<persistence-unit name="BookmarkPU" transaction-type="RESOURCE_LOCAL">
```

プログラムも修正する必要があります。
サンプルの TransactionManager.java を次のように書き換えます。

```java:TransactionManager.java
public final class TransactionManager {

	public static void manage(Transactional t) {
	    // UserTransaction utx = getUtx();
	    EntityTransaction utx = t.em.getTransaction();
	    try {
	        utx.begin();
	        // if (t.joinTransaction) {
	        //    t.em.joinTransaction();
	        // }
	        t.transact();
	        utx.commit();
	    } catch (Exception e) {
	        // try {
	            utx.rollback();
	        // } catch (SystemException se) {
	        //     throw new WebApplicationException(se);
	        // }
	        throw new WebApplicationException(e);
	    } finally {
	        t.em.close();
        }
    }
}
```
以上で、更新処理も動作します。

# Tomcat9 を使う場合
Java EE のパッケージの名前空間が **jakarta** ではなく、**javax** を使います。
これによって、使用する各ライブラリのバージョンも変わるので注意が必要です。
また、Java11 以降では Jersey2.x は動きません。(不具合が出る) Java8 を使う必要があります。
例えば、次のような一見 JPA が悪いかのようなエラーが出ました。

```
javax.servlet.ServletException: A MultiException has 2 exceptions.  They are:
1. javax.persistence.PersistenceException: No Persistence provider for EntityManager named BookmarkPU
2. java.lang.IllegalStateException: Unable to perform operation: create on org.glassfish.jersey.examples.bookmark.resource.UsersResource
```

実際には、Tomcat9 の JRE を Java8 に変更すると何事も無く正常に動作しました。

# 最後に
結局、トラブルへの対応という点で終わってしまったのが残念です。