概要
DBからデータを読み込み、ある一部分を変更したあと新しいデータとして保存する時に、clone()
メソッドを使うと思います。
本記事は、再帰的ジェネリクスを活用してイイ感じにcloneしてみようというものです。
再帰的ジェネリクスとは
型パラメータをもつクラスを継承するときに、その型パラメータに対して自身のクラス名を指定するアレです。
コードでいうとこういうものです。
public class Entity<T extends Entity<T>> {
}
とするやつですね。
こうすることで何が嬉しいのかというと、戻り値が親クラスである継承元のメソッドをオーバーライドしたときに、その戻り値を自分のクラスにすることができます。
今回のcloneメソッドを例にすると、
public class Entity<T extends Entity<T>> implements Cloneable {
@Override
public T clone() {
return (T) super.clone();
}
}
このクラスを定義する時点でキャストする必要が一回ありますが、以降継承するときには意識する必要がなくなります。
Spring用のBaseEntityをつくる
複合キーを想定しない作りにはなるのですが、新しくEntityをコピーするときにID
だけ省いたEntityをクローンすると言ったことが可能です。
単純に、先程のclone()
内でID
をnull
にしてあげればいいだけです。
@Data
@MappedSuperclass
public class BaseEntity<T extends BaseEntity<T>> implements Cloneable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Override
@SuppressWarnings("unchecked")
public T clone() {
try {
T clonedEntity = (T) super.clone();
clonedEntity.setId(null);
return clonedEntity;
} catch (CloneNotSupportedException e) {
// Cloneableを実装しているためスローされない。握り潰し方をJavaAPIにあわせる。
throw new InternalError(e);
}
}
}
これを継承したクラスでは、常にID
がnull
になっているEntityをクローンすることができるようになります。
先程のBaseEntity
を継承してもう一枚クラスを重ねることもできます。
フィールドの意味合いをまとめたいときに有効だと思います。
下記コードは監査証跡を自動で残すクラスで、クローン時は先のクラスに渡さないというものです。
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public class AuditingEntity<T extends AuditingEntity<T>> extends BaseEntity<T> {
@CreatedBy
protected String createdBy;
@CreatedDate
protected LocalDateTime createdAt;
@LastModifiedBy
protected String updatedBy;
@LastModifiedDate
protected LocalDateTime updatedAt;
@Override
public T clone() {
T clonedEntity = super.clone();
clonedEntity.setCreatedBy(null);
clonedEntity.setCreatedAt(null);
clonedEntity.setUpdatedBy(null);
clonedEntity.setUpdatedAt(null);
return clonedEntity;
}
}
おわりに
Entityをクローンした時にいちいち不要なフィールドをnull
にしてから使う必要がなくなるので、ロジック側のコードがすっきりすると思います。ぜひ活用してみてください。