JPA で ID をラップした専用クラスをマッピングする

完全なソースは [こちら](https://github.com/opengl-8080/Samples/tree/master/java/jpa)。

**追記**

Wildfly (Hibernate) だと `persist()` の後に生成された ID がエンティティにセットされませんでした。
実装依存かもしれません。。。

# 環境
## AP サーバ
Payara Micro 4.1.1.161.1
(JPA 実装：EclipseLink)

## DB
Derby (Payara に組み込まれているやつ)

# 実装
```java:Sample.java
package sample.domain;

import java.util.Objects;

import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

@Entity
@Table(name="SAMPLE_TABLE")
@NamedQuery(name="Sample.findAll", query="SELECT sample FROM Sample sample ORDER BY sample.id ASC")
public class Sample {
    
    @EmbeddedId
    private final SampleId id = new SampleId();
    @Embedded
    private SampleValue value;
    
    public void setValue(SampleValue value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Sample [id=" + id + ", value=" + value + "]";
    }

    @Override
    public int hashCode() {
        （省略）
    }
    
    @Override
    public boolean equals(Object obj) {
        （省略）
    }
}
```

```java:SampleId.java
package sample.domain;

import java.util.Objects;

import javax.persistence.Embeddable;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;

@Embeddable
public class SampleId {
    
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private final Integer id;

    public SampleId(int id) {
        this.id = id;
    }
    
    SampleId() {
        this.id = null;
    }
    
    @Override
    public String toString() {
        return "SampleId [id=" + id + "]";
    }

    @Override
    public boolean equals(Object obj) {
        （省略）
    }
    
    @Override
    public int hashCode() {
        （省略）
    }
}
```

```java:HelloResource.java
package sample.web;

import java.util.stream.Collectors;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;

import sample.domain.Sample;
import sample.domain.SampleValue;

@Stateless
@Path("hello")
public class HelloResource {
    @PersistenceContext(unitName="SampleUnit")
    private EntityManager em;
    
    @GET
    public String hello(@QueryParam("value") String value) {
        Sample sample = new Sample();
        sample.setValue(new SampleValue(value));
        
        System.out.println("[before persist]" + sample);
        
        this.em.persist(sample);
        
        System.out.println("[after persist]" + sample);
        
        this.em.flush();
        
        System.out.println("[after flush]" + sample);
        
        
        TypedQuery<Sample> query = this.em.createNamedQuery("Sample.findAll", Sample.class);
        return query.getResultList().stream().map(Sample::toString).collect(Collectors.joining("\n"));
    }
}
```

# 動作確認
```bash:curlで検証
$ curl http://localhost:8080/jpa-sample/api/hello?value=foo
Sample [id=SampleId [id=1], value=SampleValue [value=foo]]
```

```text:サーバー側出力
[before persist]Sample [id=SampleId [id=null], value=SampleValue [value=foo]]
[after persist]Sample [id=SampleId [id=null], value=SampleValue [value=foo]]
[after flush]Sample [id=SampleId [id=1], value=SampleValue [value=foo]]
```

- `id` が自動採番されてデータベースに保存されている。
- `flush` したら、 `SampleId` にも採番された `ID` がちゃんと設定されている（`final` でも構わずに！）。

# 説明
```java:Sample.java
import javax.persistence.EmbeddedId;

（省略）

public class Sample {
    
    @EmbeddedId
    private final SampleId id = new SampleId();
```

- エンティティのキーとなるフィールドは `@EmbeddedId` でアノテートする。
- キーのフィールド（`id`）には、デフォルトで ID クラス（`SampleId`）のインスタンスを設定しておく。
    - `id` を `null` にしていると `persist()` したときに落ちる。
    - `SampleId` オブジェクトが持つ実際の ID 値は `null` になるようにしておく。

```java:SampleId.java
import javax.persistence.Embeddable;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;

@Embeddable
public class SampleId {
    
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private final Integer id;

    public SampleId(int id) {
        this.id = id;
    }
    
    SampleId() {
        this.id = null;
    }
```

- ID となるクラスは `@Embeddable` でアノテートする。
- ID の値が設定されるフィールドに `@GeneratedValue` などのアノテートを設定する。
    - カラム名が実際と異なるなら、このクラスのフィールドを `@Column` でアノテートする。
- デフォルトコンストラクタで `new` した場合は `id` が `null` になるようにしておく。


`final` なのに値が書き換わっているあたり黒魔術感がハンパないけど、とりあえず動いた。