JPA (Java Persistence API) 標準註解 (@Entity、@Table、@Column、@Inheritance 等) 滿足基礎 OR 映射,但實戰中常面臨抽象類需映射表、欄位值域約束、無符號整數、條件式非空、多對多關聯攜帶屬性等進階需求。本文基於 Apache OpenJPA,展示如何透過自定義註解 + @EntityListeners 回調機制,優雅擴展映射能力,實現領域驅動的資料庫約束表達。
| 註解 | 核心屬性 | 說明 |
|---|---|---|
| @Entity | name | 聲明持久化實體類,指定實體名稱 |
| @Table | name、schema、catalog、uniqueConstraints | 描述對應資料庫表資訊 |
| @Column | name、columnDefinition、length、precision、scale、nullable、insertable、updatable、table | 定義欄位屬性,支援次表映射 |
| @Inheritance | strategy (SINGLE_TABLE/JOINED/TABLE_PER_CLASS) | 繼承關系映射策略 |
範例:
@Entity(name="OperatingSystem")
@Table(name="OperatingSystem")
@Category(value="System")
@Inheritance(strategy=InheritanceType.JOINED)
public class OperatingSystem extends System {
@Column(name="OSVersion", length=64)
@Basic
private String OSVersion;
// ...
}
核心機制:JPA @EntityListeners 指定回調類,在 @PrePersist/@PreUpdate 等生命週期攔截,透過反射檢測自定義註解,執行驗證邏輯,拋出異常阻止持久化操作。
場景:TABLE_PER_CLASS 策略下,抽象類預設不映射表且不可持久化。但業務需抽象類擁有表結構 (供子類繼承欄位),又禁止直接實例化。
// 定義
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface AbstractWithTable {}
// 使用
@AbstractWithTable
@Entity
@EntityListeners(TestAWTListener.class)
public class TestAWT {
@Basic public String p;
}
// 回調攔截
public class TestAWTListener {
@PrePersist
public void prePersist(Object obj) throws Exception {
if (obj.getClass().isAnnotationPresent(AbstractWithTable.class)) {
System.out.println("abstract class cannot be persisted");
throw new Exception();
}
}
}
效果:保留表結構生成,禁止直接持久化抽象類實例。
場景:資料庫 CHECK (score BETWEEN 0 AND 100),JPA 無標準註解表達。
// 定義
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MaximumValue { String value(); }
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MinimumValue { String value(); }
// 使用
@Entity
@EntityListeners(TestMaxMinListener.class)
public class TestMaxMin {
@Basic
@MinimumValue("1")
@MaximumValue("100")
public Integer value;
}
// 回調驗證
public class TestMaxMinListener {
@PrePersist
public void prePersist(Object obj) throws Exception {
TestMaxMin tmm = (TestMaxMin) obj;
Field f = tmm.getClass().getDeclaredField("value");
int max = Integer.parseInt(f.getAnnotation(MaximumValue.class).value());
int min = Integer.parseInt(f.getAnnotation(MinimumValue.class).value());
if (tmm.value > max || tmm.value < min) throw new Exception("Value out of range");
}
}
場景:資料庫 UNSIGNED INT,Java 無無符號類型,需應用層保證非負。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Unsigned {}
// 使用
@Unsigned @Basic private Integer value; // 回調中檢測 value >= 0
場景:OnlineGame.ServiceProvider NOT NULL,但 PCGame 繼承同表 (SINGLE_TABLE) 不需此欄。若資料庫層面設 NOT NULL,插入 PCGame 會失敗。
// 定義
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Required {}
// 使用:資料庫層面 nullable=true,應用層面 @Required 強制
@Column(name="", nullable=true)
@Basic @Required
private String ServiceProvider;
// 回調中檢測 @Required → 必須非空,僅對 OnlineGame 實例生效
優雅解耦:資料庫 DDL 寬鬆,業務約束集中在領域模型層,避免繼承策略衝突。
場景:Employee ↔ SoftwareLicense 多對多,關聯實體 Employee_owns_SoftwareLicense 攜帶屬性 RequestDate、ExpiredDate。標準 @ManyToMany 無法表達關聯實體與方向。
// 定義
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RelationshipMetaData {
boolean isSource(); // 是否為關聯來源端
boolean isManyToManyWithProperties(); // 關聯實體是否攜帶屬性
}
// 實體定義
@Entity @Table(name="Employee")
public class Employee {
@RelationshipMetaData(isSource=true, isManyToManyWithProperties=true)
@OneToMany(mappedBy="source")
private Collection owns;
}
@Entity @Table(name="SoftwareLicense")
public class SoftwareLicense {
@RelationshipMetaData(isSource=false, isManyToManyWithProperties=true)
@OneToMany(mappedBy="target")
private Collection ownedBy;
}
@Entity @Table(name="Employee_owns_SoftwareLicense")
public class Employee_owns_SoftwareLicense {
@ManyToOne private Employee source;
@ManyToOne private SoftwareLicense target;
// RequestDate, ExpiredDate...
}
OpenJPA 透過此註解識別關聯實體結構,正確生成關聯表 DDL 與查詢邏輯。
@EntityListeners + @PrePersist/@PreUpdate 實現橫切關注點分離,避免污染領域模型。RUNTIME 供反射檢測,@Inherited 保證繼承體系傳遞。OpenJPAPersistence、FetchPlan、QueryHints 等進階擴展機制。@NotNull, @Min, @Max) 替代部分場景。