Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【JPA】@MappedSuperClassでEntityクラスの共通カラムプロパティをまとめる

@MappedSuperClass

主キー用IDや最終更新日時など、RDB上の各テーブルには同一定義のカラムが存在することが少なくありません。
これがJPAのEntityにマッピングされると、複数のEntityクラスに同一定義のカラムプロパティが散在することになります。

(ソースはKotlin)

User.kt
@Entity
@Table(name = "USERS")
@Where(clause="IS_DELETED=0")
data class User(

    // 頻出
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID", nullable = false)
    var id: Int? = null,

    @Column(name = "NAME", nullable = false)
    val name: String = "",

    @Column(name = "EMAIL", nullable = false)
    val email: String = "",

    // 頻出
    @Column(name = "CREATED_AT", nullable = false)
    val createdAt: Date = Date(),

    // 頻出
    @Column(name = "UPDATED_AT", nullable = false)
    val updatedAt: Date = Date(),

    // 頻出
    @Column(name = "IS_DELETED", nullable = false)
    var isDeleted: Boolean = false

)
Article.kt
@Entity
@Table(name = "ARTICLES")
@Where(clause = "IS_DELETED=0")
data class Article(

    // 頻出
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID", nullable = false)
    var id: Int? = null,

    @Column(name = "USER_ID", nullable = false)
    val userId: Int? = null,

    @Column(name = "TITLE", nullable = false)
    val title: String = "",

    @Column(name = "DESCRIPTION", nullable = false)
    val description: String = "",

    // 頻出
    @Column(name = "CREATED_AT", nullable = false)
    val createdAt: Date = Date(),

    // 頻出
    @Column(name = "UPDATED_AT", nullable = false)
    val updatedAt: Date = Date(),

    // 頻出
    @Column(name = "IS_DELETED", nullable = false)
    var isDeleted: Boolean = false,

    @ManyToOne(targetEntity = User::class, cascade = [], fetch = FetchType.EAGER, optional = true)
    @JoinColumn(name = "USER_ID", referencedColumnName = "ID", insertable = false, updatable = false)
    val user: User? = null

)

@MappedSuperClass で修飾されたクラスを定義し、その中に共通カラムプロパティを定義した上で、各Entityクラスで継承することで、共通のカラムを派生Entity先クラスで書かずに済みます。

@MappedSuperClass で修飾されたクラス自体のテーブルへのマッピングは行われません)

TransactionalEntity
/**
 * 共通するカラムのプロパティをここで定義
 */
@MappedSuperclass
abstract class TransactionalEntity(

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID", nullable = false)
    var id: Int? = null,

    @Column(name = "CREATED_AT", nullable = false)
    val createdAt: Date = Date(),

    @Column(name = "UPDATED_AT", nullable = false)
    val updatedAt: Date = Date(),

    @Column(name = "IS_DELETED", nullable = false)
    var isDeleted: Boolean = false

)
User.kt(修正後)
@Entity
@Table(name = "USERS")
@Where(clause="IS_DELETED=0")
data class User(

    @Column(name = "NAME", nullable = false)
    val name: String = "",

    @Column(name = "EMAIL", nullable = false)
    val email: String = "",

    @OneToMany(targetEntity = User::class, cascade = [], fetch = FetchType.LAZY, mappedBy = "id")
    val articles: List<Article>? = null

) : TransactionalEntity() // TransactionalEntityを継承する
Article.kt(修正後)

@Entity
@Table(name = "ARTICLES")
@Where(clause = "IS_DELETED=0")
data class Article(

    @Column(name = "USER_ID", nullable = false)
    val userId: Int? = null,

    @Column(name = "TITLE", nullable = false)
    val title: String = "",

    @Column(name = "DESCRIPTION", nullable = false)
    val description: String = "",

    @ManyToOne(targetEntity = User::class, cascade = [], fetch = FetchType.EAGER, optional = true)
    @JoinColumn(name = "USER_ID", referencedColumnName = "ID", insertable = false, updatable = false)
    val user: User? = null

) : TransactionalEntity() // TransactionalEntityを継承する

継承の是非等ありますが、かなりスッキリとしたEntityが書けるようになります。