概要
Spring Data JPAで主キーに値オブジェクトを使う方法のメモ。
複合キーにする場合のやり方も。
単一主キーの場合
単一主キーの場合はとても簡単にできる。
シンプルなAPIを作りながら手順を示す。
事前準備
Spring Initializerでwebとlombokにチェックを入れてプロジェクト作成。
テーブル
以下のスキーマでテーブルを作る。
カラムはcodeとnameを持ち、codeを主キーとする。
CREATE TABLE customers (
code VARCHAR(4) NOT NULL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY(code)
);
INSERT INTO customers VALUES('1111', 'tanaka');
INSERT INTO customers VALUES('2222', 'suzuki');
値オブジェクト
今回はシンプルにcodeフィールドを持つだけ
@Data
public class Code implements Serializable {
private String code;
}
※ @Data
はlombokのアノテーションです。
エンティティ
フィールドに上で作った値オブジェクトを指定する。
@Embedded
アノテーションをつけるのがポイント。
主キーとして扱うので@Id
アノテーションもつける。
@Entity
@Data
@Table(name = "customers")
public class Customer {
@Embedded
@Id
private Code code;
private String name;
}
リポジトリ
引数に値オブジェクトを渡すようにする。
public interface CustomerRepository extends JpaRepository<Customer, Code> {
public List<Customer> findByCode(Code code);
}
コントローラ
適当に値オブジェクト作って、リポジトリ叩いてデータ返すだけ
@RestController
@RequestMapping("/customer")
@RequiredArgsConstructor
public class CustomerController {
private final CustomerRepository repository;
@GetMapping
public String getCustomer() {
Code code = new Code();
code.setCode("1111");
return repository.findByCode(code).toString();
}
}
叩いてみる
$ curl -X GET "http://localhost:8090/customer"
[Customer(code=Code(code=1111), name=tanaka)]
解説
- エンティティのフィールドに値オブジェクトを指定する際は
@Embedded
をつける - リポジトリの
extends JpaRepository<Customer, Code>
のところ、ジェネリクスの2個目の部分は値オブジェクトの型にする
複合主キーの場合
テーブル
codeとnameの複合キーにする
CREATE TABLE customers (
code VARCHAR(4) NOT NULL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY(code,name) // nameを追加
);
INSERT INTO customers VALUES('1111', 'tanaka');
INSERT INTO customers VALUES('2222', 'suzuki');
複合主キー用のクラスを作る
@Embeddable
@Data
public class CustomerId implements Serializable {
@Embedded
private Code code;
private String name;
}
エンティティ
上で作った複合主キー用のクラスをフィールドに宣言して、@EmbeddedId
アノテーションをつける。
引数なしのコンストラクタを作らないと怒られる。
@Entity
@Data
@Table(name = "customers")
@NoArgsConstructor
public class Customer {
@EmbeddedId
private CustomerId customerId;
}
リポジトリ
メソッド名が変わる。
findBy複合主キークラス名_複合主キークラスのフィールド名
になる。
public interface CustomerRepository extends JpaRepository<Customer, CustomerId> {
public List<Customer> findByCustomerId_Code(Code code);
}
コントローラ
@RestController
@RequestMapping("/customer")
@RequiredArgsConstructor
public class CustomerController {
private final CustomerRepository repository;
@GetMapping
public String getCustomer() {
Code code = new Code();
return repository.findByCustomerId_Code(code).toString();
}
}
叩いてみる
$ curl -X GET "http://localhost:8080/customer"
Customer(customerId=CustomerId(code=Code(code=1111), name=tanaka))
解説
- 複合キー用のクラス
CustomerId
を作って、エンティティでCustomerId
型のフィールドを宣言する - リポジトリのメソッド名は
findByCustomerId_Code(Code code)
とかfindByCustomerId_name(String name)
って感じにする
所感
- 階層的なデータ構造がちょっと気になる。実装の都合でデータの構造が崩れてる感。この場合Entityからcodeを取得するには、
customer.getCustomerId().getCode()
ってやんないといけない。 - 折衷案として、以下の感じでゲッターを作ってあげれば、使うときにはこの階層構造を気にすること無く使えるのかな、と思っている。
@Entity
@Data
@Table(name = "customers")
@NoArgsConstructor
public class Customer {
@EmbeddedId
private CustomerId customerId;
// これを追加
public Code getCode() {
return customerId.getCode();
}
// これを追加
public String getName() {
return customerId.getName();
}
}