LoginSignup
16
15

More than 5 years have passed since last update.

Spring Data JPAで主キーに値オブジェクトを使う方法(複合キーバージョンも)

Last updated at Posted at 2017-10-30

概要

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();
    }
}
16
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
15