spring.svg

【Spring Data JPA】マッピング(Entity)

SpringBoot
2024/03/06

Spring Data JPA の導入や基本についてはこちらを参照してください。

マッピングの基本

@Id

Entity には必ず主キーとなるフィールドが必要であり、@Idを付与します。

@Entity
public class User {
  @Id
  private String id;

  private String name;

  private int age;
}

@Table, @Column

通常は Entity 名とテーブル名は同じにしますが、異なる Entity 名を使用する場合は@Tableを用いてテーブルと紐付けをします。 またフィールドも同様に、テーブルのフィールド名と異なるフィールドを定義する場合は、@Columnで紐付けをします。

@Entity
@Table(name = 'user')
public class User {
  @Id
  private String id;

  @Column(name = "first_name")
  private String name;

  private int age;
}

@GeneratedValue

@GeneratedValueでは、主キーを自動で生成する方法として以下のいずれかを指定します。

説明
GenerationType.AUTODB 毎に適切な生成方法を自動で選択
GenerationType.IDENTITY自動インクリメント
GenerationType.SEQUENCEシーケンスを使用
GenerationType.TABLEキー生成用テーブルを使用

自動インクリメントを使用する場合は、以下のように@GeneratedValueを付与します。

※ Oracle のように自動インクリメントがないものは使用できません。

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

idは自動インクリメントにより自動で登録されるため、登録項目から外されます。 つまり以下のクエリが実行されることになります。

insert into user (name, age) values (?1, ?2);

@Temporal

日付に関する型のフィールドには、@Temporalを付与する必要があります。 @Temporalのプロパティには、フィールドの方に合わせてDateTimeTimestampのいずれかを指定します。

@Temporal(TemporalType.DATE)
private Date date;

@Temporal(TemporalType.TIME)
private Date time;

@Temporal(TemporalType.TIMESTAMP)
private Date timestamp;

Dateではなく、LocalDateTimeを使用する場合は、以下のように@ColumncolumnDefinitionを設定します。

@Column(columnDefinition = "DATE")
private LocalDate date;

@Column(columnDefinition = "TIME")
private LocalTime time;

@Column(columnDefinition = "TIMESTAMP")
private LocalDateTime timestamp;

@Enumerated

以下のような列挙型をフィールドとする場合は、@Enumeratedを付与します。

public enum EnumField {
  HOGE,
  FUGA,
  PIYO;
}
@Enumerated(EnumType.STRING)
private EnumField value;

これにより、'HOGE''FUGA'といった文字列として DB に設定されます。 参照する際は、列挙型へと変換されます。

また以下のように値を持つ列挙型の場合は、EnumType.ORDINALを指定します。

public enum EnumField {
  HOGE(0),
  FUGA(1),
  PIYO(2);

  private int status;

  EnumField(int status) {
    this.status = status;
  }
}
@Enumerated(EnumType.ORDINAL)
private EnumField value;

これにより、01といった値が DB に設定されます。 上記と同じく、参照する際は列挙型に変換されます。

@Embedded / @Embeddable

@Embeddedを付与することで、複数のフィールドを 1 つのクラスにまとめることができます。

まず@Embeddableを付与したクラスを作成します。 このクラスにまとめたいフィールドを定義します。

@Embeddable
public class UserName {
  private String firstName;
  private String lastName;
}

あとは Entity で@Embeddedを付与したフィールドを定義するだけです。

@Entity
public class User {
  @Id
  private String id;

  @Embedded
  private UserName name;
}

@Embeddableのフィールド名とテーブルのフィールド名が異なる場合は、Entity 側で以下のように@AttributeOverridesを設定します。

@Embedded
@AttributeOverrides({
  @AttributeOverride(name = "firstName", column = @Column(name = "user_first_name")),
  @AttributeOverride(name = "lastName", column = @Column(name = "last_first_name"))
})
private UserName name;

@Embeddedを使用する際はいくつか注意点があります。

1 つ目は JSON の変換です。 デフォルトだと以下のような階層構造になります。

{
  "id": "taro",
  "name": {
    "firstName": "太郎",
    "lastName": "山田"
  }
}

"name"を省きたい場合は、@JsonUnwrappedを使用します。

@Embedded
@AttributeOverrides({
  @AttributeOverride(name = "firstName", column = @Column(name = "user_first_name")),
  @AttributeOverride(name = "lastName", column = @Column(name = "last_first_name"))
})
@JsonUnwrapped
private UserName name;
{
  "id": "taro",
  "firstName": "太郎",
  "lastName": "山田"
}

2 つ目はクエリの生成です。 例えばfirstNameを条件のレコードを取得する場合は、findByNameFirstName()と、Entity のフィールド名と@Embeddableのフィールド名を続けます。

また JPQL でも以下のように Entity と@Embeddableのフィールドを指定する必要があります。

select u.name.firstName from user u

@Transient

@Transientを付与した項目は、永続化の対象外となります。 つまり、参照、登録、更新時にこの項目は含まれなくなります。

@Entity
public class User {
  @Id
  private String id;

  private String name;

  @Transient
  private int age;
}

複合キー

主キーが 2 つ以上あるマッピングの方法です。 正直複雑さが増すので、可能であればサロゲートキー(代替キー)を使用することを勧めます

@EmbeddedId

主キー用の@Embeddableを付与したクラスを作成します。 注意点として、必ずSerializableimplementsしてください。

CartPK.java
@Embeddable
public class CartPK implements Serializable {
  private static final long serialVersionUID = 4099926204418277863L;
  private String userId;
  private String productId;
}

Entity クラスでは、@Idの代わりに@EmbeddedIdを付与します。

Cart.java
@Entity
public class Cart {
  @EmbeddedId
  private CartPK pk;

  private int count;
}

Repository を作成する際は、ジェネリクスに主キー用のクラスを指定してください。

CartRepository.java
@Repository
public interface CartRepository extends JpaRepository<Cart, CartPK> {
}

findById()など主キーを指定するメソッドには、主キー用のクラスを指定するようになります。

CartPK pk = new CartPK("taro", "0001");
Cart cart = repository.findById(pk).orElseThrow();

@IdClass

@EmbeddedIdと同様に主キー用のクラスを作成します。@Embeddableは不要です。 注意点は同じく、Serializableimplementsしてください。

CartPK.java
public class CartPK implements Serializable {
  private static final long serialVersionUID = 4099926204418277863L;
  private String userId;
  private String productId;
}

@EmbeddedIdと異なる点は、Entity でも主キー用のフィールド(@Id)をそれぞれ定義することです。 まず@IdClassで主キー用のクラスを指定します。 その後にキーとなるフィールドを主キー用のクラスと同じフィールド名となるように定義します。

Cart.java
@Entity
@IdClass(CartPK.class)
public class Cart {
  @Id
  private String userId;

  @Id
  private String productId;

  private int count;
}

Repository の作成についてなどは@EmbeddedIdと同じです。

終わりに

この記事ではマッピングの基本について説明していきました。 実際にはこれらに加えて「結合」があります。

時間があれば結合についての記事も書きたいと思っています。