☕【Java技术指南】「JPA编程专题」让你不再对JPA技术中的“持久化型注解”感到陌生了!
阅读原文时间:2021年09月25日阅读:2

JPA的介绍分析

  • Java持久化API (JPA) 显著简化了Java Bean的持久性并提供了一个对象关系映射方法,该方法使您可以采用声明方式定义如何通过一种标准的可移植方式,将Java 对象映射到关系数据库表以及后续的一系列数据持久化行为。

  • JPA可以将任何普通的Java 对象 (POJO) 类指定为 JPA 实体。

    • JPA实体:一个应使用JPA实现程序的服务将其非临时字段持久保存到关系数据库(在 Java EE EJB 容器的内部或在简单 Java SE 应用程序中的 EJB 容器的外部)的 Java 对象。
    • 可以使用注解配置实体的JPA行为,注解是一种使用元数据修饰 Java 源代码的简单表达方法,它编译为相应的 Java 类文件,以便在运行时由 JPA 持久化机制提供程序解释以管理 JPA 行为。
  • 例如,要将Java类指定为JPA实体,请使用@Entity注解:

JPA注解总览




JPA实体型注解

@Entity

使用 @Entity 批注将普通的旧式 Java 对象 (POJO) 类指定为实体,并使其可用于 JPA 服务。必须将 POJO 类指定为实体,然后才可以使用任何其他 JPA 批注。

@Table

默认情况下,JPA持续性提供程序假设实体的所有持久字段均存储到一个名称为实体名称的数据库表中(请参阅 @Entity )。 在以下条件下,使用 @Table注解可以指定与实体关联的主表:

实体名称难于处理、是一个保留字、与预先存在的数据模型不兼容或作为数据库中的表名无效需要控制表所属的目录或模式。

如果希望 JPA 将某些字段持久保存到主表,而将其他字段持久保存到一个或多个辅助表,请参阅@SecondaryTable 。 下表列出了此批注的属性。有关更多详细信息,请参阅 API 。

@Entity
@Table(name="Model")
public class JavaModel implements Serializable {
    ...
}

@TableGenerator

  • 如果使用 @GeneratedValue 批注指定一个 TABLE 类型的主键生成器,可以使用 @TableGenerator 批注微调该主键生成器以:

    • 由于名称难于处理、是一个保留字、与预先存在的数据模型不兼容或作为数据库中的表名无效而更改主键生成器的表名称
    • 更改分配大小以匹配应用程序要求或数据库性能参数
    • 更改初始值以匹配现有的数据模型(例如,如果基于已经为其分配或保留了一组主键值的现有数据集构建)
    • 使用特定目录或模式配置主键生成器的表
    • 在主键生成器表的一列或多列商配置一个唯一的约束
@TableGenerator 属性

显示了如何使用此注解为名为 empGen 的 TABLE 主键生成器指定分配大小。

@TableGenerator
@Entity
public class Employee implements Serializable {  
    @Id
    @TableGenerator(
        name="empGen",
        allocationSize=1
    )
  @GeneratedValue(strategy=TABLE, generator="empGen")
  @Column(name="CUST_ID")
   public Long getId() {
        return id;
    }
    ...

@Temporal

使用 @Temporal 注解指定 JPA 的提供程序应只为 java.util.Date 和 java.util.Calendar 类型的字段或属性持久保存的数据库类型,可以与 @Basic 一起使用。

示例,显示了如何使用此批注指定 JPA 持续性提供程序应将 java.util.Date 字段 startDate 持久保存为 DATE ( java.sql.Date ) 数据库类型。

@Entity
public class Employee {
    @Temporal(DATE)
    protected java.util.Date startDate;
    ...
}

@Transient

  • 默认情况下,JPA 持久化提供程序假设实体的所有字段均为持久字段。

  • 使用 @Transient 注解指定实体的非持久字段或属性,例如,一个在运行时使用但并非实体状态一部分的字段或属性。

  • JPA 提供程序不会对批注为 @Transient 的属性或字段持久保存(或创建数据库模式)。

  • 该注解可以与 @Entity 、@MappedSuperclass 和 @Embeddable 一起使用。

  • 该注解没有属性。

    @Entity
    public class Employee {   
    @Id
    int id;   
    @Transient
    Session currentSession;   
    …}

@Column

默认情况下,JPA 持续性提供程序假设每个实体的持久字段存储在其名称与持久字段的名称相匹配的数据库表列中。

使用 @Column 批注:

将持久字段与其他名称关联(如果默认列名难于处理、与事先存在的数据模型不兼容或作为数据库中的列名无效)

将持久字段与辅助表中的列关联(请参阅 @SecondaryTable

微调数据库中列的特征

@Column 属性

如何使用此批注使 JPA 将 empId 持久保存到辅助表 EMP_HR 中的列 EMP_NUM 。默认情况下,JPA 将 empName 持久保存到主表 Employee 中的列 empName 。

@Column

@Entity
@SecondaryTable(name="EMP_HR")
public class Employee implements Serializable {  

    @Column(name="EMP_NUM", table="EMP_HR")
    private Long empId;
    private String empName;
}

@UniqueConstraint

默认情况下,JPA持久化提供程序假设所有列均可以包含重复值。

使用@UniqueConstraint注解指定将在为主表或辅助表生成的DDL中包含一个唯一约束,或者,您可以在列级别指定唯一约束。

属性状态

显示了如何使用此注解对主表 EMP 中的列 EMP_ID 和 EMP_NAME 指定一个唯一约束,使用唯一约束的 @Table

@Entity
@Table(
name="EMP",
uniqueConstraints={@UniqueConstraint(columnNames={"EMP_ID", "EMP_NAME"})}
)
public class Employee implements Serializable {
    ...
}

@Version

默认情况下,JPA持久化提供程序假设应用程序负责数据一致性。

使用@Version注解通过指定用作其乐观锁定值的实体类的版本字段或属性来启用 JPA 管理的乐观锁定(推荐做法)。

  • 选择版本字段或属性时,确保:

    • 每个实体只有一个版本字段或属性
    • 选择一个持久保存到主表的属性或字段(请参阅 @Table
    • 您的应用程序不修改版本属性或字段

如何使用此注解将属性getVersionNum指定为乐观锁定值。在该示例中,该属性的列名设置为OPTLOCK(请参阅 @Column ),而非属性的默认列名。

@Version

@Entity
public class Employee implements Serializable {
    ...
    @Version
    @Column(name="OPTLOCK")
    protected int getVersionNum() {
        return versionNum;
    }
    ...
}

@Embeddable@Embedded

  • 使用 @Embeddable 批注指定一个类,该类的实例存储为拥有实体的固有部分并共享该实体的身份。嵌入对象的每个持久属性或字段都将映射到实体的数据库表。

  • 类 EmploymentPeriod 在用作为 @Embedded 的持久字段的类型时可以嵌套到实体中.

    @Embeddable
    public class EmploymentPeriod {
    java.util.Date startDate;
    java.util.Date endDate;

    }

@Embedded

  • 使用 @Embedded 批注指定一个持久字段,该字段的 @Embeddable 类型可以存储为拥有实体的固有部分,并共享该实体的身份。

  • 嵌入对象的每个持久属性或字段均映射到拥有实体的数据库表。

  • 可以结合使用 @Embedded@Embeddable 以建立严格所有权关系的模型,以便在删除了拥有对象的情况下还将删除被拥有的对象。嵌入的对象不应映射到多个表。

  • @Embeddable 类中指定的列定义适用于 @Embedded 类。

  • 如果要覆盖这些列定义,请使用 @AttributeOverride

  • @Embeddable 类 EmploymentPeriod可以使用指定的属性覆盖嵌入到实体类中。

    @Entity
    public class Employee implements Serializable{…
    @Embedded
    @AttributeOverrides({
    @AttributeOverride(name="startDate", column=@Column("EMP_START")),
    @AttributeOverride(name="endDate", column=@Column("EMP_END"))
    )
    public EmploymentPeriod getEmploymentPeriod() {}}

@EmbeddedId

使用 @EmbeddedId 批注指定一个由实体拥有的可嵌入复合主键类(通常由两个或更多基元类型或 JDK 对象类型组成)。从原有数据库映射时(此时数据库键由多列组成),通常将出现复合主键。

复合主键类具有下列特征:

  • 它是一个普通的旧式 Java 对象 (POJO) 类。

  • 它必须为 public ,并且必须有一个 public 无参数构造函数。

  • 如果使用基于属性的访问,则主键类的属性必须为 public 或 protected 。

  • 它必须是可序列化的。

  • 它必须定义 equals 和 hashCode 方法。

  • 这些方法的值相等性的语义必须与键映射到的数据库类型的数据库相等性一致。

    @Embeddable
    public class EmployeePK implements Serializable{
    private String name;
    private long id;
    public EmployeePK()  {    }
    //setter and getter
    public int hashCode() {
    return (int) name.hashCode() + id;
    }
    public boolean equals(Object obj) {
    if (obj == this) return true;
    if (!(obj instanceof EmployeePK)) return false;
    if (obj == null) return false;
    EmployeePK pk = (EmployeePK) obj;
    return pk.id == id && pk.name.equals(name);
    }
    }

    @Entity
    public class Employee implements Serializable{
    EmployeePK primaryKey;
    public Employee(){}
    @EmbeddedId
    public EmployeePK getPrimaryKey()  {
    return primaryKey;
    }
    public void setPrimaryKey(EmployeePK pk){
    primaryKey = pk;
    }

    }

@MappedSuperclass

  • 使用 @MappedSuperclass 批注指定一个实体类从中继承持久字段的超类。当多个实体类共享通用的持久字段或属性时,这将是一个方便的模式。

  • 您可以像对实体那样使用任何直接和关系映射批注(如 @Basic@ManyToMany)对该超类的字段和属性进行批注,但由于没有针对该超类本身的表存在,因此这些映射只适用于它的子类。继承的持久字段或属性属于子类的表。

  • 可以在子类中使用 @AttributeOverride@AssociationOverride 批注来覆盖超类的映射配置。

如何使用此批注将 Employee 指定为映射超类。如何扩展实体中的此超类,以及如何在实体类中使用 @AttributeOverride 以覆盖超类中设置的配置。

@MappedSuperclass
public class Employee {
@Id
protected Integer empId;

@Version
protected Integer version;

@ManyToOne
@JoinColumn(name="ADDR")
protected Address address;

public Integer getEmpId() {
        ...
    }

public void setEmpId(Integer id) {
        ...
    }

public Address getAddress() {
        ...
    }

public void setAddress(Address addr) {
        ...
    }
}

@MappedSuperclass
@Entity
@AttributeOverride(name="address", column=@Column(name="ADDR_ID"))
public class PartTimeEmployee extends Employee {

@Column(name="WAGE")
protected Float hourlyWage;

public PartTimeEmployee() {
        ...
    }

public Float getHourlyWage() {
        ...
    }

public void setHourlyWage(Float wage) {
        ...
    }
}

@EntityListeners

@EntityListeners将一个或多个实体监听程序类与 @Entity@MappedSuperclass 关联,条件是您需要在指定的生命周期事件发生时执行逻辑。

  • 不希望在实体 API 中公开生命周期监听程序方法。
  • 要在不同的实体类型之间共享生命周期监听程序逻辑。
  • 当实体或子类上发生生命周期事件时,JPA 持续性提供程序将按监听程序定义的顺序通知每个实体监听程序,并调用使用相应的生命周期事件类型进行批注的实体监听程序方法(如果有)。

实体监听程序类具有以下特征:

  • 它是一个普通的旧式 Java 对象 (POJO) 类
  • 它有一个或多个具有以下签名的回调方法:
  • public void (Object)
  • 可以指定参数类型 Object ,或实体监听程序将与其关联的实体类的类型。
  • 它用一个或多个生命周期事件批注对每个回调方法进行批注。
  • 一个生命周期事件只能与一个回调监听程序方法关联,但某个给定的回调监听程序方法可以与多个生命周期事件关联。
  • 如果使用实体监听程序,则可以管理哪些实体监听程序使用

@EntityListeners 属性

显示了您可以将多个生命周期事件与给定的实体监听程序类方法关联,但任何给定的生命周期事件只能在实体监听程序类中出现一次。

@Entity
@EntityListeners(value={EmployeePersistListner.class,
EmployeeRemoveListener.class})
public class Employee implements Serializable {
    …
}

    public class EmployeePersistListener {
    @PrePersist
    public void employee PrePersist(Object employee) {}};

public class EmployeeRemoveListener {
    @PreRemove
    @PostRemove
    public void employeePreRemove(Object employee) { } ...}

@ExcludeDefaultListeners

如果默认监听程序行为不适用,请使用 @ExcludeDefaultListeners 批注覆盖(并阻止)针对给定 @Entity@MappedSuperclass 执行的默认监听程序。

@Entity
@ExcludeDefaultListeners
public class Employee implements Serializable {
    …
}