Hibernate.org社区文档

第 2 章 实体映射

2.1. 简介
2.2. 使用 JPA (Java 持久性注解) 进行映射
2.2.1. 将 POJO 标记为持久实体
2.2.2. 映射简单属性
2.2.3. 映射标识属性
2.2.4. 映射继承
2.2.5. 映射实体关联/关系
2.2.6. 映射复合主键和指向复合主键的外键
2.2.7. 映射辅助表
2.2.8. 缓存实体
2.3. 映射查询
2.3.1. 映射 JP-QL/HQL 查询
2.3.2. 映射原生查询
2.4. Hibernate 注解扩展
2.4.1. 实体
2.4.2. 标识符
2.4.3. 属性
2.4.4. 继承
2.4.5. 单一关联相关注解
2.4.6. 集合相关注解
2.4.7. 级联
2.4.8. 过滤器
2.4.9. 查询
2.4.10. 为 CRUD 操作定制 SQL
2.4.11. 元组化器
2.4.12. 获取配置文件

本节介绍如何使用 Java 持久性 2.0 注解以及 Hibernate 特定的注解扩展来描述持久性映射。

JPA 实体是普通的 POJO。实际上,它们是 Hibernate 持久实体。它们的映射通过 JDK 5.0 注解而不是 hbm.xml 文件来定义。JPA 2 XML 描述符语法也定义了用于覆盖。

JPA 注解位于 javax.persistence.* 包中。你最喜欢的 IDE 可以为你自动完成注解及其属性 (即使没有特定的 "JPA" 模块,因为 JPA 注解是普通的 JDK 5 注解)。

在 Hibernate 注解测试套件本身中可以找到一套完整的、可工作的示例:大多数单元测试旨在代表一个具体示例,并成为你的灵感来源。你可以在分发版中获取测试套件源代码。

每个持久 POJO 类都是一个实体,并且使用 @Entity 注解 (在类级别) 声明。

@Entity

public class Flight implements Serializable {
    Long id;
    @Id
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
}         

@Entity 将类声明为一个实体 (即一个持久 POJO 类),@Id 声明了该实体的标识属性。其他映射声明是隐式的。Flight 类映射到 Flight 表,使用 id 列作为其主键列。

根据你是注解字段还是方法,Hibernate 使用的访问类型将是 fieldproperty。EJB3 规范要求你对将被访问的元素类型声明注解,即如果你使用 property 访问,则为 getter 方法;如果你使用 field 访问,则为字段。应避免在字段和方法中混合注解。Hibernate 将根据 @Id@EmbeddedId 的位置推断访问类型。

实体的每个非静态非瞬态属性 (根据访问类型而定为字段或方法) 都被视为持久,除非你将其注解为 @Transient。为你的属性没有注解等同于相应的 @Basic 注解。@Basic 注解允许你声明属性的获取策略

public transient int counter; //transient property


private String firstname; //persistent property
@Transient
String getLengthInMeter() { ... } //transient property
String getName() {... } // persistent property
@Basic
int getLength() { ... } // persistent property
@Basic(fetch = FetchType.LAZY)
String getDetailedComment() { ... } // persistent property
@Temporal(TemporalType.TIME)
java.util.Date getDepartureTime() { ... } // persistent property           
@Enumerated(EnumType.STRING)
Starred getNote() { ... } //enum persisted as String in database

counter 是一个瞬态字段,lengthInMeter 是一个注解为 @Transient 的方法,并且将被实体管理器忽略。namelengthfirstname 属性被映射为持久且急切获取 (简单属性的默认值)。detailedComment 属性值将延迟从数据库中获取,直到第一次访问实体的延迟属性时才获取。通常,你不必延迟简单属性 (不要与延迟关联获取混淆)。

推荐的替代方法是使用 JP-QL (Java 持久性查询语言) 或 Criteria 查询的投影功能。

JPA 支持 Hibernate 支持的所有基本类型的属性映射 (所有基本 Java 类型、它们各自的包装器和可序列化类)。Hibernate 注解开箱即用地支持枚举类型映射,无论是映射到序数列 (保存枚举序数) 还是基于字符串的列 (保存枚举字符串表示):持久性表示 (默认为序数) 可以通过 @Enumerated 注解进行覆盖,如 note 属性示例所示。

在普通 Java API 中,时间的精度未定义。处理时间数据时,你可能想要描述数据库中预期的精度。时间数据可以具有 DATETIMETIMESTAMP 精度 (即实际日期、仅时间或两者)。使用 @Temporal 注解来微调这一点。

@Lob 指示属性应保存在 Blob 或 Clob 中,具体取决于属性类型:java.sql.ClobCharacter[]char[] 和 java.lang.String 将保存在 Clob 中。java.sql.BlobByte[]byte[] 和可序列化类型将保存在 Blob 中。



@Lob
public String getFullText() {
    return fullText;
}
@Lob 
public byte[] getFullCode() {
    return fullCode;
}
 

如果属性类型实现 java.io.Serializable 并且不是基本类型,并且如果属性未用 @Lob 注解,则使用 Hibernate serializable 类型。

默认情况下,类层次结构的访问类型由 @Id@EmbeddedId 注解的位置定义。如果这些注解位于字段上,则仅字段被视为持久,并且状态通过字段进行访问。如果这些注解位于 getter 上,则仅 getter 被视为持久,并且状态通过 getter/setter 进行访问。这在实践中运行良好,并且是推荐的方法。

但是,在某些情况下,你需要

最佳用例是由多个实体使用的嵌入式类,这些实体可能不使用相同的访问类型。在这种情况下,最好在嵌入式类级别强制访问类型。

若要在给定类上强制访问类型,请使用 @Access 注解,如下所示

@Entity

public class Order {
   @Id private Long id;
   public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }
   @Embedded private Address address;
   public Address getAddress() { return address; }
   public void setAddress() { this.address = address; }
}
@Entity
public class User {
   private Long id;
   @Id public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }
   private Address address;
   @Embedded public Address getAddress() { return address; }
   public void setAddress() { this.address = address; }
}
@Embeddable
@Access(AcessType.PROPERTY)
public class Address {
   private String street1;
   public String getStreet1() { return street1; }
   public void setStreet1() { this.street1 = street1; }
   private hashCode; //not persistent
}

你还可以覆盖单个属性的访问类型,同时保留其他属性的标准。

@Entity

public class Order {
   @Id private Long id;
   public Long getId() { return id; }
   public void setId(Long id) { this.id = id; }
   @Transient private String userId;
   @Transient private String orderId;
   @Access(AccessType.PROPERTY)
   public String getOrderNumber() { return userId + ":" + orderId; }
   public void setOrderNumber() { this.userId = ...; this.orderId = ...; }
}

在此示例中,默认访问类型为 FIELD,除了 orderNumber 属性。请注意,相应的字段 (如果有) 必须标记为 @Transienttransient

用于属性映射的列可以使用 @Column 注解来定义。使用它来覆盖默认值 (有关默认值的更多信息,请参阅 EJB3 规范)。你可以将此注解应用于属性级别,适用于以下属性:



@Entity
public class Flight implements Serializable {
...
@Column(updatable = false, name = "flight_name", nullable = false, length=50)
public String getName() { ... }
            

name 属性映射到 flight_name 列,该列不可为空,长度为 50 且不可更新 (使属性不可变)。

此注解可以应用于普通属性以及 @Id@Version 属性。

@Column(
    name="colu(1)mnName";
    boolean un(2)ique() default false;
    boolean nu(3)llable() default true;
    boolean in(4)sertable() default true;
    boolean up(5)datable() default true;
    String col(6)umnDefinition() default "";
    String tab(7)le() default "";
    int length(8)() default 255;
    int precis(9)ion() default 0; // decimal precision
    int scale((10)) default 0; // decimal scale

1

name (可选):列名 (默认为属性名)

2

unique (可选):是否在该列上设置唯一约束 (默认值为 false)

3

nullable (可选):将列设置为可为空 (默认值为 true)。

4

insertable (可选):列是否将成为 insert 语句的一部分 (默认值为 true)

5

updatable (可选):列是否将成为 update 语句的一部分 (默认值为 true)

6

columnDefinition (可选):覆盖此特定列的 SQL DDL 片段 (不可移植)

7

table (可选):定义目标表 (默认为主表)

8

length (可选):列长度 (默认值为 255)

8

precision (可选):列小数精度(默认值为 0)

10

scale (可选):如果需要,列小数位数(默认值为 0)

可以在实体中声明嵌入式组件,甚至覆盖其列映射。组件类必须在类级别使用 @Embeddable 注解进行注释。可以使用关联属性中的 @Embedded@AttributeOverride 注解,为特定实体覆盖嵌入式对象的列映射。

@Entity

public class Person implements Serializable {
    // Persistent component using defaults
    Address homeAddress;
    @Embedded
    @AttributeOverrides( {
            @AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),
            @AttributeOverride(name="name", column = @Column(name="bornCountryName") )
    } )
    Country bornIn;
    ...
}          
@Embeddable

public class Address implements Serializable {
    String city;
    Country nationality; //no overriding here
}            
@Embeddable

public class Country implements Serializable {
    private String iso2;
    @Column(name="countryName") private String name;
    public String getIso2() { return iso2; }
    public void setIso2(String iso2) { this.iso2 = iso2; }
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    ...
}            

嵌入式对象继承其拥有实体的访问类型(请注意,可以使用 @Access 注解覆盖此类型)。

Person 实体有两个组件属性,homeAddressbornInhomeAddress 属性未进行注释,但 Hibernate 会通过在 Address 类中查找 @Embeddable 注解来推断它是一个持久组件。我们还使用 @Embedded@AttributeOverride 注解,为 Country 的每个映射属性覆盖列名的映射(为 bornCountryName)。如您所见,Country 也是 Address 的嵌套组件,同样是通过 Hibernate 和 JPA 默认值进行自动检测。通过点表达式覆盖嵌入式对象的嵌入式对象的列。

    @Embedded

    @AttributeOverrides( {
            @AttributeOverride(name="city", column = @Column(name="fld_city") ),
            @AttributeOverride(name="nationality.iso2", column = @Column(name="nat_Iso2") ),
            @AttributeOverride(name="nationality.name", column = @Column(name="nat_CountryName") )
            //nationality columns in homeAddress are overridden
    } )
    Address homeAddress;

Hibernate 注解支持 JPA 规范未明确支持的某些内容。您可以使用 @MappedSuperclass 注解对嵌入式对象进行注释,以使超类属性持久化(有关更多信息,请参阅 @MappedSuperclass)。

您还可以在嵌入式对象中使用关联注解(即 @OneToOne@ManyToOne@OneToMany@ManyToMany)。若要覆盖关联列,可以使用 @AssociationOverride

如果您想在同一个实体中两次使用相同的嵌入式对象类型,则列名默认机制将不起作用,因为多个嵌入式对象将共享相同的列集。在纯 JPA 中,您需要覆盖至少一组列。但是,Hibernate 允许您通过 NamingStrategy 接口增强默认命名机制。您可以编写一个策略来防止这种情况下的名称冲突。DefaultComponentSafeNamingStrategy 就是一个示例。

@Id 注解允许您定义哪个属性是实体的标识符。此属性可以由应用程序本身设置,也可以由 Hibernate 生成(首选)。您可以使用 @GeneratedValue 注解定义标识符生成策略。

JPA 定义了五种类型的标识符生成策略

Hibernate 提供了比基本的 JPA 生成器更多的 id 生成器。有关更多信息,请参阅 第 2.4 节,“Hibernate Annotation 扩展”

以下示例显示了使用 SEQ_STORE 配置的序列生成器(见下文)

@Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_STORE")

public Integer getId() { ... }         

下一个示例使用标识生成器

@Id @GeneratedValue(strategy=GenerationType.IDENTITY)

public Long getId() { ... }         

对于可移植应用程序(跨多个数据库供应商),AUTO 生成器是首选类型。标识符生成配置可以通过 generator 属性在多个 @Id 映射中共享。有几种配置可以通过 @SequenceGenerator@TableGenerator 使用。生成器的范围可以是应用程序或类。类定义的生成器在类外部不可见,并且可以覆盖应用程序级生成器。应用程序级生成器在 XML 级别定义(请参阅 第 3 章,通过 XML 覆盖元数据

<table-generator name="EMP_GEN"

            table="GENERATOR_TABLE"
            pk-column-name="key"
            value-column-name="hi"
            pk-column-value="EMP"
            allocation-size="20"/>
//and the annotation equivalent
@javax.persistence.TableGenerator(
    name="EMP_GEN",
    table="GENERATOR_TABLE",
    pkColumnName = "key",
    valueColumnName = "hi"
    pkColumnValue="EMP",
    allocationSize=20
)
<sequence-generator name="SEQ_GEN" 
    sequence-name="my_sequence"
    allocation-size="20"/>
//and the annotation equivalent
@javax.persistence.SequenceGenerator(
    name="SEQ_GEN",
    sequenceName="my_sequence",
    allocationSize=20
)
         

如果使用 JPA XML(如 META-INF/orm.xml)定义生成器,则 EMP_GENSEQ_GEN 是应用程序级生成器。EMP_GEN 使用 hilo 算法定义基于表的 id 生成器,其 max_lo 为 20。hi 值保存在表 "GENERATOR_TABLE" 中。信息保存在一行中,其中 pkColumnName "key" 等于 pkColumnValue "EMP",而列 valueColumnName "hi" 包含使用的下一个高值。

SEQ_GEN 定义一个序列生成器,使用名为 my_sequence 的序列。此基于序列的 hilo 算法使用的分配大小为 20。请注意,此版本的 Hibernate 注解不会处理序列生成器中的 initialValue。默认分配大小为 50,因此如果您想使用序列并每次都获取值,则必须将分配大小设置为 1。

重要

我们建议所有新项目使用 hibernate.id.new_generator_mappings=true,因为新的生成器效率更高,更接近 JPA 2 规范语义。但是它们与现有数据库不向后兼容(如果使用序列或表进行 id 生成)。有关如何激活它们的更多信息,请参阅 第 1.3 节,“属性”

注意

JPA 规范不支持包级别定义。但是,您可以在包级别使用 @GenericGenerator(请参阅 第 2.4.2 节,“标识符”)。

以下示例显示了在类范围内定义序列生成器的示例

@Entity

@javax.persistence.SequenceGenerator(
    name="SEQ_STORE",
    sequenceName="my_sequence"
)
public class Store implements Serializable {
    private Long id;
    @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_STORE")
    public Long getId() { return id; }
}         

此类将使用名为 my_sequence 的序列,并且 SEQ_STORE 生成器在其他类中不可见。请注意,您可以查看 org.hibernate.test.annotations.id 包中的 Hibernate 注解测试,以获取更多示例。

最后,您可以要求 Hibernate 从另一个关联实体复制标识符。在 Hibernate 行话中,这称为外键生成器,但 JPA 映射更易读,因此建议使用。

@Entity

class MedicalHistory implements Serializable {
  @Id @OneToOne
  @JoinColumn(name = "person_id")
  Person patient;
}
@Entity
public class Person implements Serializable {
  @Id @GeneratedValue Integer id;
}

或者

@Entity

class MedicalHistory implements Serializable {
  @Id Integer id;
  @MapsId @OneToOne
  @JoinColumn(name = "patient_id")
  Person patient;
}
@Entity
class Person {
  @Id @GeneratedValue Integer id;
}

如果您有兴趣了解有关“派生标识”的更多示例,JPA 2 规范在第 2.4.1.3 章中提供了一组很好的示例。

但标识符不必是单个属性,它可以由多个属性组成。

您可以通过多种语法定义复合主键

如您所见,最后一种情况远非一目了然。它继承自 EJB 2 的黑暗时代,是为了向后兼容而设计的,我们建议您不要使用它(为了简单起见)。

让我们通过示例来探索所有三种情况。

以下是一个 @EmbeddedId 的简单示例。

@Entity

class User {
  @EmbeddedId
  @AttributeOverride(name="firstName", column=@Column(name="fld_firstname")
  UserId id;
  Integer age;
}
@Embeddable
class UserId implements Serializable {
  String firstName;
  String lastName;
}

您可以注意到 UserId 类是可序列化的。若要覆盖列映射,请使用 @AttributeOverride

嵌入式 id 本身可以包含关联实体的主键。

@Entity

class Customer {
  @EmbeddedId CustomerId id;
  boolean preferredCustomer;
  @MapsId("userId")
  @JoinColumns({
    @JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
    @JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
  })
  @OneToOne User user;
}
@Embeddable
class CustomerId implements Serializable {
  UserId userId;
  String customerNumber;
}
@Entity 
class User {
  @EmbeddedId UserId id;
  Integer age;
}
@Embeddable
class UserId implements Serializable {
  String firstName;
  String lastName;
}

在嵌入式 id 对象中,关联属性表示为关联实体的标识符。但是,您可以使用 @MapsId 注解将其值链接到实体中的常规关联属性。 @MapsId 值对应于包含关联实体标识符的嵌入式 id 对象的属性名称。在数据库中,这意味着 Customer.userCustomerId.userId 属性共享同一个底层列(在本例中为 user_fk)。

实际上,您的代码只设置 Customer.user 属性,而 Hibernate 会将用户 id 值复制到 CustomerId.userId 属性中。

虽然 JPA 不支持,但 Hibernate 允许您将关联属性直接放置在嵌入式 id 组件中(而不是必须使用 @MapsId 注解)。

@Entity

class Customer {
  @EmbeddedId CustomerId id;
  boolean preferredCustomer;
}
@Embeddable
class CustomerId implements Serializable {
  @OneToOne
  @JoinColumns({
    @JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
    @JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
  }) 
  User user;
  String customerNumber;
}
@Entity 
class User {
  @EmbeddedId UserId id;
  Integer age;
}
@Embeddable
class UserId implements Serializable {
  String firstName;
  String lastName;
}

@IdClass 在实体上指向代表类标识符的类(组件)。在实体上使用 @Id 标记的属性必须在 @IdClass 上有相应的属性。搜索孪生属性的返回类型必须是相同的(对于基本属性)或与关联实体的标识符类对应(对于关联属性)。

@Entity

class Customer {
  @Id @OneToOne
  @JoinColumns({
    @JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
    @JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
  }) 
  User user;
  
  @Id String customerNumber;
  boolean preferredCustomer;
}
class CustomerId implements Serializable {
  UserId user;
  String customerNumber;
}
@Entity 
class User {
  @EmbeddedId UserId id;
  Integer age;
}
@Embeddable
class UserId implements Serializable {
  String firstName;
  String lastName;
}

CustomerCustomerId 具有相同的属性 customerNumber 以及 user

虽然不是 JPA 标准,但 Hibernate 允许您在 @IdClass 中声明普通关联属性。

@Entity

class Customer {
  @Id @OneToOne
  @JoinColumns({
    @JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
    @JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
  }) 
  User user;
  
  @Id String customerNumber;
  boolean preferredCustomer;
}
class CustomerId implements Serializable {
  @OneToOne User user;
  String customerNumber;
}
@Entity 
class User {
  @EmbeddedId UserId id;
  Integer age;
}
@Embeddable
class UserId implements Serializable {
  String firstName;
  String lastName;
}

EJB3 支持三种类型的继承

使用 @Inheritance 注解在层次结构中顶级实体的类级别声明所选策略。

这在某些情况下很有用,可以将公共属性通过技术或业务超类共享,而无需将其包含为常规映射实体(即,此实体没有特定表)。为此,可以将它们映射为 @MappedSuperclass

@MappedSuperclass

public class BaseEntity {
    @Basic
    @Temporal(TemporalType.TIMESTAMP)
    public Date getLastUpdate() { ... }
    public String getLastUpdater() { ... }
    ...
}
@Entity class Order extends BaseEntity {
    @Id public Integer getId() { ... }
    ...
}

在数据库中,此层次结构将表示为一个 Order 表,其中包含 idlastUpdatelastUpdater 列。嵌入式超类属性映射被复制到它们的实体子类中。请记住,嵌入式超类虽然不是层次结构的根,但它不是层次结构的根。

可以使用 @AttributeOverride 注释在根实体级别覆盖在实体超类中定义的列。

@MappedSuperclass

public class FlyingObject implements Serializable {
    public int getAltitude() {
        return altitude;
    }
    @Transient
    public int getMetricAltitude() {
        return metricAltitude;
    }
    @ManyToOne
    public PropulsionType getPropulsion() {
        return metricAltitude;
    }
    ...
}
@Entity
@AttributeOverride( name="altitude", column = @Column(name="fld_altitude") )
@AssociationOverride( 
   name="propulsion", 
   joinColumns = @JoinColumn(name="fld_propulsion_fk") 
)
public class Plane extends FlyingObject {
    ...
}

altitude 属性将持久化到 Plane 表的 fld_altitude 列中,而推进关联将具体化为一个 fld_propulsion_fk 外键列。

可以在 @Entity 类、@MappedSuperclass 类和指向 @Embeddable 对象的属性上定义 @AttributeOverride@AssociationOverride

可以使用 @OneToOne 通过一对一关系关联实体。一对一关联有三种情况:关联实体共享相同的主键值、其中一个实体拥有一个外键(注意,数据库中的此 FK 列应受约束为唯一的,以模拟一对一的基数)、或使用关联表来存储两个实体之间的链接(必须在每个 fk 上定义唯一的约束,以确保一对一的基数)。

首先,我们使用共享主键映射真正的 一对一关联

@Entity

public class Body {
    @Id
    public Long getId() { return id; }
    @OneToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn
    public Heart getHeart() {
        return heart;
    }
    ...
}            
@Entity

public class Heart {
    @Id
    public Long getId() { ...}
}            

@PrimaryKeyJoinColumn 注释表明实体的主键用作关联实体的外键值。

在下面的示例中,关联实体通过显式外键列链接。

@Entity

public class Customer implements Serializable {
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="passport_fk")
    public Passport getPassport() {
        ...
    }
@Entity
public class Passport implements Serializable {
    @OneToOne(mappedBy = "passport")
    public Customer getOwner() {
    ...
}            

一个 Customer 与一个 Passport 链接,在 Customer 表中有一个名为 passport_fk 的外键列。连接列使用 @JoinColumn 注释声明,该注释类似于 @Column 注释。它还有一个名为 referencedColumnName 的参数。此参数声明目标实体中将用于连接的列。请注意,当使用 referencedColumnName 到非主键列时,关联类必须是 Serializable。还要注意,referencedColumnName 到非主键列必须映射到具有单个列的属性(其他情况可能无法正常工作)。

关联可能是双向的。在双向关系中,其中一方(且仅一方)必须是所有者:所有者负责关联列的更新。为了声明一方 负责关系,使用属性 mappedBymappedBy 指向所有者一方关联的属性名称。在本例中,即 passport。正如你所见,你无需(也不应该)声明连接列,因为该列已在所有者一方声明。

如果在所有者一方没有声明 @JoinColumn,则将应用默认值。一个或多个连接列将在所有者表中创建,其名称将是所有者一方关系的名称、_(下划线)以及被拥有方主键列的名称的组合。在本例中为 passport_id,因为属性名称为 passportPassport 的 id 列为 id

第三种可能性(使用关联表)非常奇特。

@Entity

public class Customer implements Serializable {
    @OneToOne(cascade = CascadeType.ALL)
    @JoinTable(name = "CustomerPassports",
        joinColumns = @JoinColumn(name="customer_fk"),
        inverseJoinColumns = @JoinColumn(name="passport_fk")
    )
    public Passport getPassport() {
        ...
    }
@Entity
public class Passport implements Serializable {
    @OneToOne(mappedBy = "passport")
    public Customer getOwner() {
    ...
}          

一个 Customer 通过名为 CustomerPassports 的关联表与一个 Passport 链接;该关联表有一个名为 passport_fk 的外键列,它指向 Passport 表(由 inverseJoinColumn 具体化),还有一个名为 customer_fk 的外键列,它指向 Customer 表(由 joinColumns 属性具体化)。

必须在 such 映射中显式声明连接表名称和连接列。

多对一关联在属性级别使用 @ManyToOne 注释声明。

@Entity()

public class Flight implements Serializable {
    @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
    @JoinColumn(name="COMP_ID")
    public Company getCompany() {
        return company;
    }
    ...
}            

@JoinColumn 属性是可选的,默认值与一对一中的相同,即所有者一方关系名称、_(下划线)以及被拥有方主键列名称的组合。在本例中为 company_id,因为属性名称为 company,Company 的 id 列为 id

@ManyToOne 有一个名为 targetEntity 的参数,它描述了目标实体名称。通常不需要此参数,因为默认值(存储关联的属性类型)在几乎所有情况下都适用。但是,当希望使用接口作为返回类型而不是常规实体时,这很有用。

@Entity

public class Flight implements Serializable {
    @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE}, targetEntity=CompanyImpl.class )
    @JoinColumn(name="COMP_ID")
    public Company getCompany() {
        return company;
    }
    ...
}
public interface Company {
    ...
}

还可以通过关联表映射多对一关联。此关联表由 @JoinTable 注释描述,它将包含一个引用回实体表的外部键(通过 @JoinTable.joinColumns)和一个引用目标实体表的外部键(通过 @JoinTable.inverseJoinColumns)。

@Entity

public class Flight implements Serializable {
    @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
    @JoinTable(name="Flight_Company",
        joinColumns = @JoinColumn(name="FLIGHT_ID"),
        inverseJoinColumns = @JoinColumn(name="COMP_ID")
    )
    public Company getCompany() {
        return company;
    }
    ...
}       

可以使用 @OneToMany@ManyToMany 注释分别将指向关联实体的 CollectionListMapSet 映射为一对多或多对多关联。如果集合是基本类型或嵌入式类型,请使用 @ElementCollection。我们将在后面的部分详细介绍这一点。

一对多关联在属性级别使用 @OneToMany 注释声明。一对多关联可能是双向的。

多对多关联使用 @ManyToMany 注解在逻辑上进行定义。您还必须使用 @JoinTable 注解来描述关联表和连接条件。如果关联是双向的,则一方必须是所有者,另一方必须是反向端(即,在更新关联表中的关系值时,它将被忽略)。

@Entity

public class Employer implements Serializable {
    @ManyToMany(
        targetEntity=org.hibernate.test.metadata.manytomany.Employee.class,
        cascade={CascadeType.PERSIST, CascadeType.MERGE}
    )
    @JoinTable(
        name="EMPLOYER_EMPLOYEE",
        joinColumns=@JoinColumn(name="EMPER_ID"),
        inverseJoinColumns=@JoinColumn(name="EMPEE_ID")
    )
    public Collection getEmployees() {
        return employees;
    }
    ...
}               
@Entity

public class Employee implements Serializable {
    @ManyToMany(
        cascade = {CascadeType.PERSIST, CascadeType.MERGE},
        mappedBy = "employees",
        targetEntity = Employer.class
    )
    public Collection getEmployers() {
        return employers;
    }
}               

我们已经展示了关联的许多声明和详细属性。我们将深入探讨 @JoinTable 描述,它定义了一个 name、一个连接列数组(注解中的数组使用 {A, B, C} 定义)和一个反向连接列数组。后者是关联表的列,它们引用 Employee 主键(“另一侧”)。

如前所述,另一侧不必(也不能)描述物理映射:一个简单的 mappedBy 参数,其中包含所有者端属性名称,将两者绑定在一起。

与任何其他注解一样,大多数值在多对多关系中都会被推测出来。如果没有在单向多对多关系中描述任何物理映射,则将应用以下规则。表名是所有者表名、_ 和另一侧表名的串联。引用所有者表的外部键名称是所有者表名、_ 和所有者主键列的串联。引用另一侧的外部键名称是所有者属性名、_ 和另一侧主键列的串联。这些规则与单向一对多关系中使用的规则相同。



@Entity
public class Store {
    @ManyToMany(cascade = CascadeType.PERSIST)
    public Set<City> getImplantedIn() {
        ...
    }
}
@Entity
public class City {
    ... //no bidirectional relationship
}
               

一个 Store_City 用作连接表。Store_id 列是 Store 表的外键。implantedIn_id 列是 City 表的外键。

如果没有在双向多对多关系中描述任何物理映射,则将应用以下规则。表名是所有者表名、_ 和另一侧表名的串联。引用所有者表的外部键名称是另一侧属性名、_ 和所有者主键列的串联。引用另一侧的外部键名称是所有者属性名、_ 和另一侧主键列的串联。这些规则与单向一对多关系中使用的规则相同。

@Entity

public class Store {
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    public Set<Customer> getCustomers() {
        ...
    }
}
@Entity
public class Customer {
    @ManyToMany(mappedBy="customers")
    public Set<Store> getStores() {
        ...
    }
}               

一个 Store_Customer 用作连接表。stores_id 列是 Store 表的外键。customers_id 列是 Customer 表的外键。

在某些简单情况下,不需要关联两个实体,而只是创建基本类型或可嵌入对象的集合。在这种情况下使用 @ElementCollection

@Entity

public class User {
   [...]
   public String getLastname() { ...}
   @ElementCollection
   @CollectionTable(name="Nicknames", joinColumns=@JoinColumn(name="user_id"))
   @Column(name="nickname")
   public Set<String> getNicknames() { ... } 
}

保存集合数据的集合表使用 @CollectionTable 注解设置。如果省略,集合表名将默认为包含实体的名称和集合属性的名称,以一个下划线分隔:在我们的示例中,它将是 User_nicknames

保存基本类型的列使用 @Column 注解设置。如果省略,列名默认为属性名称:在我们的示例中,它将是 nicknames

但您不限于基本类型,集合类型可以是任何可嵌入对象。要覆盖集合表中可嵌入对象的列,请使用 @AttributeOverride 注解。

@Entity

public class User {
   [...]
   public String getLastname() { ...}
   @ElementCollection
   @CollectionTable(name="Addresses", joinColumns=@JoinColumn(name="user_id"))
   @AttributeOverrides({
      @AttributeOverride(name="street1", column=@Column(name="fld_street"))
   })
   public Set<Address> getAddresses() { ... } 
}
@Embeddable
public class Address {
   public String getStreet1() {...}
   [...]
}

这样的可嵌入对象不能包含它自己的集合。

列表可以通过两种不同的方式进行映射

要在内存中对列表进行排序,请在您的属性中添加 @javax.persistence.OrderBy。此注解将逗号分隔的属性列表(目标实体的属性)作为参数,并根据此进行排序(例如 firstname asc, age desc),如果字符串为空,则该集合将按目标实体的主键进行排序。

@Entity

public class Customer {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
   @OneToMany(mappedBy="customer")
   @OrderBy("number")
   public List<Order> getOrders() { return orders; }
   public void setOrders(List<Order> orders) { this.orders = orders; }
   private List<Order> orders;
}
@Entity
public class Order {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
   public String getNumber() { return number; }
   public void setNumber(String number) { this.number = number; }
   private String number;
   @ManyToOne
   public Customer getCustomer() { return customer; }
   public void setCustomer(Customer customer) { this.customer = customer; }
   private Customer number;
}
-- Table schema
|-------------| |----------|
| Order       | | Customer |
|-------------| |----------|
| id          | | id       |
| number      | |----------| 
| customer_id |
|-------------|

要在专用列中存储索引值,请在您的属性上使用 @javax.persistence.OrderColumn 注解。此注解描述了保存索引值的列的列名和属性。该列托管在包含关联外键的表中。如果未指定列名,则默认情况下为引用属性的名称,后面跟着下划线,再跟着 ORDER(在以下示例中,它将是 orders_ORDER)。

@Entity

public class Customer {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
   @OneToMany(mappedBy="customer")
   @OrderColumn(name"orders_index")
   public List<Order> getOrders() { return orders; }
   public void setOrders(List<Order> orders) { this.orders = orders; }
   private List<Order> orders;
}
@Entity
public class Order {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
   public String getNumber() { return number; }
   public void setNumber(String number) { this.number = number; }
   private String number;
   @ManyToOne
   public Customer getCustomer() { return customer; }
   public void setCustomer(Customer customer) { this.customer = customer; }
   private Customer number;
}
-- Table schema
|--------------| |----------|
| Order        | | Customer |
|--------------| |----------|
| id           | | id       |
| number       | |----------| 
| customer_id  |
| orders_index |
|--------------|

同样,映射可以从关联实体属性之一借用其键,也可以拥有专用列来存储显式键。

要使用目标实体属性之一作为映射的键,请使用 @MapKey(name="myProperty")myProperty 是目标实体中的属性名称)。当使用 @MapKey(不带属性名称)时,将使用目标实体主键。映射键使用与指出的属性相同的列:没有定义额外的列来保存映射键,这很有意义,因为映射键实际上表示目标属性。请注意,加载后,键将不再与属性保持同步,换句话说,如果您更改属性值,则键不会在您的 Java 模型中自动更改。

@Entity

public class Customer {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
   @OneToMany(mappedBy="customer")
   @MapKey(name"number")
   public Map<String,Order> getOrders() { return orders; }
   public void setOrders(Map<String,Order> order) { this.orders = orders; }
   private Map<String,Order> orders;
}
@Entity
public class Order {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
   public String getNumber() { return number; }
   public void setNumber(String number) { this.number = number; }
   private String number;
   @ManyToOne
   public Customer getCustomer() { return customer; }
   public void setCustomer(Customer customer) { this.customer = customer; }
   private Customer number;
}
-- Table schema
|-------------| |----------|
| Order       | | Customer |
|-------------| |----------|
| id          | | id       |
| number      | |----------| 
| customer_id |
|-------------|

否则,映射键将映射到一个或多个专用列。要自定义内容,请使用以下注解之一

您还可以使用 @MapKeyClass 来定义键的类型,如果您没有使用泛型(此时,您应该想知道为什么在今天这个时代您不使用泛型)。

@Entity

public class Customer {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
   @OneToMany @JoinTable(name="Cust_Order")
   @MapKeyColumn(name"orders_number")
   public Map<String,Order> getOrders() { return orders; }
   public void setOrders(Map<String,Order> orders) { this.orders = orders; }
   private Map<String,Order> orders;
}
@Entity
public class Order {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;
   public String getNumber() { return number; }
   public void setNumber(String number) { this.number = number; }
   private String number;
   @ManyToOne
   public Customer getCustomer() { return customer; }
   public void setCustomer(Customer customer) { this.customer = customer; }
   private Customer number;
}
-- Table schema
|-------------| |----------| |---------------|
| Order       | | Customer | | Cust_Order    |
|-------------| |----------| |---------------|
| id          | | id       | | customer_id   |
| number      | |----------| | order_id      |
| customer_id |              | orders_number |
|-------------|              |---------------|

现在让我们探索基于您选择的映射的各种集合语义。


具体来说,没有 @OrderColumn 或 @IndexColumn 的 java.util.List 集合将被视为包。

有关集合的更多支持,请通过 Hibernate 特定扩展获得(请参阅 第 2.4 节,“Hibernate 注解扩展”)。

您可能已经注意到,cascade 属性接受一个 CascadeType 数组作为值。JPA 中的级联概念与 Hibernate 中的传递持久性和操作级联非常相似,但语义和级联类型略有不同

有关级联和创建/合并语义的更多信息,请参阅 JPA 规范的第 6.3 章。

您还可以启用孤儿删除语义。如果从 @OneToMany 集合中删除了实体,或者从 @OneToOne 关联中取消了关联实体的引用,则如果将 orphanRemoval 设置为 true,则可以将该关联实体标记为删除。从某种意义上说,这意味着关联实体的生命周期与拥有实体绑定在一起,就像可嵌入对象一样。

@Entity class Customer {

   @OneToMany(orphanRemoval=true) public Set<Order> getOrders() { return orders; }
   public void setOrders(Set<Order> orders) { this.orders = orders; }
   private Set<Order> orders;
   [...]
}
@Entity class Order { ... }
Customer customer = em.find(Customer.class, 1l);
Order order = em.find(Order.class, 1l);
customer.getOrders().remove(order); //order will be deleted by cascade

组合主键使用嵌入类作为主键表示,因此您将使用 @Id@Embeddable 注释。或者,您可以使用 @EmbeddedId 注释。请注意,依赖类必须是可序列化的并实现 equals()/hashCode()。您也可以使用 @IdClass。这些在 第 2.2.3 节,“映射标识符属性” 中有更详细的介绍。

@Entity

public class RegionalArticle implements Serializable {
    @Id
    public RegionalArticlePk getPk() { ... }
}
@Embeddable
public class RegionalArticlePk implements Serializable { ... }       

或者

@Entity

public class RegionalArticle implements Serializable {
    @EmbeddedId
    public RegionalArticlePk getPk() { ... }
}
public class RegionalArticlePk implements Serializable { ... }         

@Embeddable 继承其拥有实体的访问类型,除非使用了 @Access。组合外键(如果未使用默认敏感值)在使用 @JoinColumns 元素的关联上定义,它基本上是 @JoinColumn 的数组。建议明确表达 referencedColumnNames。否则,Hibernate 会假定您使用与主键声明中相同的列顺序。

@Entity

public class Parent implements Serializable {
    @Id
    public ParentPk id;
    public int age;
    @OneToMany(cascade=CascadeType.ALL)
    @JoinColumns ({
        @JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
        @JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
        @JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
    })
    public Set<Child> children; //unidirectional
    ...
} 
@Entity

public class Child implements Serializable {
    @Id @GeneratedValue
    public Integer id;
    @ManyToOne
    @JoinColumns ({
        @JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
        @JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
        @JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
    })
    public Parent parent; //unidirectional
}       
@Embeddable

public class ParentPk implements Serializable {
    String firstName;
    String lastName;
    ...
}        

请注意 referencedColumnName 的显式用法。

Hibernate 通过 Session 的概念,自然地为实体提供了一级缓存,称为持久性上下文。此缓存与手头的用例相关。但是,某些实体由许多不同的用例共享,并且几乎不会更改。您可以将这些实体缓存到称为二级缓存的内容中。

默认情况下,实体不属于二级缓存的一部分。虽然我们不推荐这样做,但您可以通过在 persistence.xml 文件中设置 shared-cache-mode 元素或使用 javax.persistence.sharedCache.mode 属性 来覆盖此设置。以下值是可能的

可以使用 hibernate.cache.default_cache_concurrency_strategy 属性设置默认使用的缓存并发策略

@Entity @Cacheable

@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Forest { ... }

Hibernate 还允许您缓存集合的内容或标识符(如果集合包含其他实体)。在集合属性上使用 @Cache 注释。

@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)

@JoinColumn(name="CUST_ID")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public SortedSet<Ticket> getTickets() {
    return tickets;
}

@org.hibernate.annotations.Cache 定义给定二级缓存的缓存策略和区域。

@Cache(
    CacheConcu(1)rrencyStrategy usage();
    String reg(2)ion() default "";
    String inc(3)lude() default "all";
)

1

用法:给定的缓存并发策略(NONE、READ_ONLY、NONSTRICT_READ_WRITE、READ_WRITE、TRANSACTIONAL)

2

区域(可选):缓存区域(默认为类的 fqcn 或集合的 fq 角色名称)

3

include(可选):all 表示包含所有属性,non-lazy 表示仅包含非延迟属性(默认值 all)。

虽然您可以在代码中编写查询,但建议将它们外部化

不幸的是,您将丢失使用 Criteria API 编写的查询的类型安全性。

您可以使用注释映射 JP-QL/HQL 查询。 @NamedQuery@NamedQueries 可以定义在类级别或 JPA XML 部署描述符中。但是,它们的定义对于会话工厂/实体管理器工厂范围是全局的。命名查询由其名称和实际查询字符串定义。

<entity-mappings>

    <named-query name="plane.getAll">
        <query>select p from Plane p</query>
    </named-query>
    ...
</entity-mappings>
...
@Entity
@NamedQuery(name="night.moreRecentThan", query="select n from Night n where n.date >= :date")
public class Night {
    ...
}
public class MyDao {
    doStuff() {
        Query q = s.getNamedQuery("night.moreRecentThan");
        q.setDate( "date", aMonthAgo );
        List results = q.list();
        ...
    }
    ...
}      

您还可以通过 hints 属性中的 QueryHint 数组为查询提供一些提示。

可用的 Hibernate 提示是


您还可以使用 lockMode 属性定义返回的实体应被锁定的锁定模式。这等效于实体管理器查找操作的可选锁定模式。

您还可以映射本机查询(即普通 SQL 查询)。要实现此目标,您需要使用 @SqlResultSetMapping(如果您计划定义多个结果集映射,则使用 @SqlResultSetMappings)来描述 SQL 结果集结构。与 @NamedQuery 类似,@SqlResultSetMapping 可以定义在类级别或 JPA XML 文件中。但是,其范围对于应用程序是全局的。

正如我们将看到的,resultSetMapping 参数定义在 @NamedNativeQuery 中,它表示定义的 @SqlResultSetMapping 的名称。结果集映射声明此本机查询检索的实体。实体的每个字段都绑定到 SQL 别名(或列名)。实体的所有字段(包括子类的字段和相关实体的外键列)都必须存在于 SQL 查询中。字段定义是可选的,前提是它们映射到与类属性上声明的字段相同的列名。

@NamedNativeQuery(name="night&area", query="select night.id nid, night.night_duration, "

    + " night.night_date, area.id aid, night.area_id, area.name "
    + "from Night night, Area area where night.area_id = area.id", 
                  resultSetMapping="joinMapping")
@SqlResultSetMapping(name="joinMapping", entities={
    @EntityResult(entityClass=Night.class, fields = {
        @FieldResult(name="id", column="nid"),
        @FieldResult(name="duration", column="night_duration"),
        @FieldResult(name="date", column="night_date"),
        @FieldResult(name="area", column="area_id"),
        discriminatorColumn="disc"
    }),
    @EntityResult(entityClass=org.hibernate.test.annotations.query.Area.class, fields = {
        @FieldResult(name="id", column="aid"),
        @FieldResult(name="name", column="name")
    })
    }
)

在上面的示例中,night&area 命名查询使用 joinMapping 结果集映射。此映射返回 2 个实体,NightArea,每个属性都被声明并关联到一个列名,实际上是查询检索的列名。现在让我们看看属性/列的隐式声明。

@Entity

@SqlResultSetMapping(name="implicit",
                     entities=@EntityResult(entityClass=SpaceShip.class))
@NamedNativeQuery(name="implicitSample", 
                  query="select * from SpaceShip", 
                  resultSetMapping="implicit")
public class SpaceShip {
    private String name;
    private String model;
    private double speed;
    @Id
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Column(name="model_txt")
    public String getModel() {
        return model;
    }
    public void setModel(String model) {
        this.model = model;
    }
    public double getSpeed() {
        return speed;
    }
    public void setSpeed(double speed) {
        this.speed = speed;
    }
}

在此示例中,我们只描述结果集映射的实体成员。属性/列映射使用实体映射值完成。在这种情况下,model 属性绑定到 model_txt 列。如果与相关实体的关联涉及组合主键,则应为每个外键列使用 @FieldResult 元素。 @FieldResult 名称由关系的属性名称、一个点(“.”)和主键的字段或属性的名称组成。

@Entity

@SqlResultSetMapping(name="compositekey",
        entities=@EntityResult(entityClass=SpaceShip.class,
            fields = {
                    @FieldResult(name="name", column = "name"),
                    @FieldResult(name="model", column = "model"),
                    @FieldResult(name="speed", column = "speed"),
                    @FieldResult(name="captain.firstname", column = "firstn"),
                    @FieldResult(name="captain.lastname", column = "lastn"),
                    @FieldResult(name="dimensions.length", column = "length"),
                    @FieldResult(name="dimensions.width", column = "width")
                    }),
        columns = { @ColumnResult(name = "surface"),
                    @ColumnResult(name = "volume") } )
@NamedNativeQuery(name="compositekey",
    query="select name, model, speed, lname as lastn, fname as firstn, length, width, length * width as surface from SpaceShip", 
    resultSetMapping="compositekey")
} )
public class SpaceShip {
    private String name;
    private String model;
    private double speed;
    private Captain captain;
    private Dimensions dimensions;
    @Id
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @ManyToOne(fetch= FetchType.LAZY)
    @JoinColumns( {
            @JoinColumn(name="fname", referencedColumnName = "firstname"),
            @JoinColumn(name="lname", referencedColumnName = "lastname")
            } )
    public Captain getCaptain() {
        return captain;
    }
    public void setCaptain(Captain captain) {
        this.captain = captain;
    }
    public String getModel() {
        return model;
    }
    public void setModel(String model) {
        this.model = model;
    }
    public double getSpeed() {
        return speed;
    }
    public void setSpeed(double speed) {
        this.speed = speed;
    }
    public Dimensions getDimensions() {
        return dimensions;
    }
    public void setDimensions(Dimensions dimensions) {
        this.dimensions = dimensions;
    }
}
@Entity
@IdClass(Identity.class)
public class Captain implements Serializable {
    private String firstname;
    private String lastname;
    @Id
    public String getFirstname() {
        return firstname;
    }
    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }
    @Id
    public String getLastname() {
        return lastname;
    }
    public void setLastname(String lastname) {
        this.lastname = lastname;
    }
}

如果您检索单个实体并使用默认映射,则可以使用 resultClass 属性而不是 resultSetMapping

@NamedNativeQuery(name="implicitSample", query="select * from SpaceShip",

    resultClass=SpaceShip.class)
public class SpaceShip {

在某些本机查询中,您需要返回标量值,例如在构建报表查询时。您可以在 @SqlResultsetMapping 中通过 @ColumnResult 映射它们。您实际上甚至可以在同一个本机查询中混合实体和标量返回值(但这可能并不常见)。

@SqlResultSetMapping(name="scalar", columns=@ColumnResult(name="dimension"))

@NamedNativeQuery(name="scalar", query="select length*width as dimension from SpaceShip", resultSetMapping="scalar")

引入了另一个特定于本机查询的查询提示:org.hibernate.callable,它可以根据查询是否是存储过程而为 true 或 false。

Hibernate 3.1 提供了各种其他注释,您可以将这些注释与 EJB 3 实体混合使用。它们被设计为 EJB3 注释的自然扩展。

为了增强 EJB3 功能,Hibernate 提供了与 Hibernate 特性匹配的特定注释。 org.hibernate.annotations 包包含所有这些注释扩展。

您可以在实体上微调 Hibernate 执行的一些操作,超出了 EJB3 规范提供的范围。

@org.hibernate.annotations.Entity 添加了可能超出标准 @Entity 中定义的范围所需的额外元数据

以下是一些额外的 Hibernate 注释扩展

@org.hibernate.annotations.BatchSize 允许您定义获取此实体实例时的批处理大小(例如 @BatchSize(size=4))。加载给定实体时,Hibernate 然后将在持久性上下文中加载相同类型的所有未初始化实体,直到达到批处理大小为止。

@org.hibernate.annotations.Proxy 定义实体的延迟属性。lazy(默认为 true)定义类是否延迟。proxyClassName 是用于生成代理的接口(默认值是类本身)。

@org.hibernate.annotations.Where 定义检索此类实例时使用的可选 SQL WHERE 子句。

@org.hibernate.annotations.Check 定义在 DDL 语句中定义的可选检查约束。

@OnDelete(action=OnDeleteAction.CASCADE) 在联接子类上:在删除时使用 SQL 级联删除,而不是常规的 Hibernate 机制。

@Table(appliesTo="tableName", indexes = { @Index(name="index1", columnNames={"column1", "column2"} ) } ) 在表 tableName 的列上创建定义的索引。这可以应用于主表或任何辅助表。 @Tables 注释允许您在不同的表上应用索引。 预计此注释将出现在 @javax.persistence.Table@javax.persistence.SecondaryTable 出现的地方。

@org.hibernate.annotations.Table 也可以用于定义辅助表的以下元素

@Immutable 将实体或集合标记为不可变。 应用程序不能更新不可变实体。 这允许 Hibernate 进行一些轻微的性能优化。 对不可变实体的更新将被忽略,但不会抛出异常。 @Immutable 必须仅用于根实体。 放置在集合上的 @Immutable 使集合不可变,这意味着不允许对集合进行添加和删除操作。 在这种情况下,将抛出 HibernateException

@Persister 允许您定义自己的自定义持久策略。 例如,您可以指定 org.hibernate.persister.EntityPersister 的子类,甚至可以提供接口 org.hibernate.persister.ClassPersister 的完全新实现,该实现通过例如存储过程调用、序列化到平面文件或 LDAP 来实现持久化。

@Entity

@BatchSize(size=5)
@org.hibernate.annotations.Entity(
        selectBeforeUpdate = true,
        dynamicInsert = true, dynamicUpdate = true,
        optimisticLock = OptimisticLockType.ALL,
        polymorphism = PolymorphismType.EXPLICIT)
@Where(clause="1=1")
@org.hibernate.annotations.Table(name="Forest", indexes = { @Index(name="idx", columnNames = { "name", "length" } ) } )
@Persister(impl=MyEntityPersister.class)
public class Forest { ... }
@Entity

@Inheritance(
    strategy=InheritanceType.JOINED
)
public class Vegetable { ... }
@Entity
@OnDelete(action=OnDeleteAction.CASCADE)
public class Carrot extends Vegetable { ... }

Hibernate 注释在定义标识符时超越了 Java 持久性规范。

@org.hibernate.annotations.Type 覆盖了使用的默认 hibernate 类型:这通常是不必要的,因为 Hibernate 正确地推断出类型。 请参阅 Hibernate 参考指南以获取有关 Hibernate 类型的更多信息。

@org.hibernate.annotations.TypeDef@org.hibernate.annotations.TypeDefs 允许您声明类型定义。 这些注释可以放置在类级或包级。 请注意,这些定义对于会话工厂是全局的(即使在类级定义时也是如此)。 如果类型用于单个实体,则可以将定义放在实体本身。 否则,建议将定义放在包级别。 在下面的示例中,当 Hibernate 遇到 PhoneNumer 类的属性时,它将持久策略委托给自定义映射类型 PhoneNumberType。 但是,属于其他类的属性也可以通过显式使用 @Type 注释将它们的持久策略委托给 PhoneNumberType

@TypeDef(

   name = "phoneNumber",
   defaultForType = PhoneNumber.class,
   typeClass = PhoneNumberType.class
)
@Entity
public class ContactDetails {
   [...]
   private PhoneNumber localPhoneNumber;
   @Type(type="phoneNumber")
   private OverseasPhoneNumber overseasPhoneNumber;
   [...]
}

以下示例显示了 parameters 属性的使用以自定义 TypeDef。

//in org/hibernate/test/annotations/entity/package-info.java

@TypeDefs(
    {
    @TypeDef(
        name="caster",
        typeClass = CasterStringType.class,
        parameters = {
            @Parameter(name="cast", value="lower")
        }
    )
    }
)
package org.hibernate.test.annotations.entity;
//in org/hibernate/test/annotations/entity/Forest.java
public class Forest {
    @Type(type="caster")
    public String getSmallText() {
    ...
}      

当使用复合用户类型时,您必须表达列定义。 @Columns 已为此目的引入。

@Type(type="org.hibernate.test.annotations.entity.MonetaryAmountUserType")

@Columns(columns = {
    @Column(name="r_amount"),
    @Column(name="r_currency")
})
public MonetaryAmount getAmount() {
    return amount;
}
public class MonetaryAmount implements Serializable {
    private BigDecimal amount;
    private Currency currency;
    ...
}

默认情况下,当 Hibernate 无法解析关联(因为预期的关联元素不在数据库中(关联列上的 ID 错误))时,Hibernate 会引发异常。 这对于遗留和维护不善的模式来说可能很不方便。 您可以要求 Hibernate 忽略此类元素,而不是使用 @NotFound 注释引发异常。 此注释可用于 @OneToOne(带 FK)、@ManyToOne@OneToMany@ManyToMany 关联。

@Entity

public class Child {
    ...
    @ManyToOne
    @NotFound(action=NotFoundAction.IGNORE)
    public Parent getParent() { ... }
    ...
}

有时您希望将级联删除委托给您的数据库。

@Entity

public class Child {
    ...
    @ManyToOne
    @OnDelete(action=OnDeleteAction.CASCADE)
    public Parent getParent() { ... }
    ...
}

在这种情况下,Hibernate 在数据库级别生成级联删除约束。

外部键约束虽然由 Hibernate 生成,但名称难以阅读。 您可以使用 @ForeignKey 覆盖约束名称。

@Entity

public class Child {
    ...
    @ManyToOne
    @ForeignKey(name="FK_PARENT")
    public Parent getParent() { ... }
    ...
}
alter table Child add constraint FK_PARENT foreign key (parent_id) references Parent

JPA 带有 fetch 选项来定义延迟加载和获取模式,但是 Hibernate 在此领域有更多选项集。 要微调延迟加载和获取策略,已引入了一些附加注释

Hibernate 注释覆盖了 EJB3 获取选项。


@Any 注解定义了对来自多个表的类的多态关联。这种类型的映射始终需要多个列。第一列保存关联实体的类型。其余列保存标识符。无法为这种类型的关联指定外键约束,因此它绝对不适合作为映射(多态)关联的常规方法。您应该仅在非常特殊的情况下使用它(例如,审计日志、用户会话数据等)。

@Any 注解描述了保存元数据信息的列。要将元数据信息的价值和实际实体类型联系起来,使用 @AnyDef@AnyDefs 注解。

    @Any( metaColumn = @Column( name = "property_type" ), fetch=FetchType.EAGER )

    @AnyMetaDef( 
        idType = "integer", 
        metaType = "string", 
        metaValues = {
            @MetaValue( value = "S", targetEntity = StringProperty.class ),
            @MetaValue( value = "I", targetEntity = IntegerProperty.class )
        } )
    @JoinColumn( name = "property_id" )
    public Property getMainProperty() {
        return mainProperty;
    }

idType 代表目标实体的标识符属性类型,而 metaType 代表元数据类型(通常为 String)。

注意,@AnyDef 可以相互使用并重复使用。建议在这种情况下将其作为包元数据放置。

//on a package

@AnyMetaDef( name="property" 
    idType = "integer", 
    metaType = "string", 
    metaValues = {
        @MetaValue( value = "S", targetEntity = StringProperty.class ),
        @MetaValue( value = "I", targetEntity = IntegerProperty.class )
    } )
package org.hibernate.test.annotations.any;
//in a class
    @Any( metaDef="property", metaColumn = @Column( name = "property_type" ), fetch=FetchType.EAGER )
    @JoinColumn( name = "property_id" )
    public Property getMainProperty() {
        return mainProperty;
    }

可以使用以下方式设置集合属性:

您还可以声明排序比较器。使用 @Sort 注解。表示您想要在未排序、自然排序或自定义比较器之间使用的比较器类型。如果您想使用自己的比较器实现,您还需要使用 comparator 属性来表达实现类。请注意,您需要使用 SortedSetSortedMap 接口。

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)

    @JoinColumn(name="CUST_ID")
    @Sort(type = SortType.COMPARATOR, comparator = TicketComparator.class)
    @Where(clause="1=1")
    @OnDelete(action=OnDeleteAction.CASCADE)
    public SortedSet<Ticket> getTickets() {
        return tickets;
    }

有关更多信息,请参阅这些注解的先前描述。

外键约束虽然由 Hibernate 生成,但名称却难以理解。您可以使用 @ForeignKey 覆盖约束名称。请注意,此注解必须放在关系的拥有方,inverseName 指向另一方的约束。

@Entity

public class Woman {
    ...
    @ManyToMany(cascade = {CascadeType.ALL})
    @ForeignKey(name = "TO_WOMAN_FK", inverseName = "TO_MAN_FK")
    public Set<Man> getMens() {
        return mens;
    }
}
alter table Man_Woman add constraint TO_WOMAN_FK foreign key (woman_id) references Woman
alter table Man_Woman add constraint TO_MAN_FK foreign key (man_id) references Man

Hibernate 能够对您的数据应用任意过滤器。这些过滤器在运行时应用于给定会话。首先,您需要定义它们。

@org.hibernate.annotations.FilterDef@FilterDefs 定义由使用相同名称的过滤器使用的过滤器定义。过滤器定义具有 name() 和参数数组。参数将允许您在运行时调整过滤器的行为。每个参数由 @ParamDef 定义,该参数具有名称和类型。您还可以为给定的 @FilterDef 定义 defaultCondition() 参数,以设置在每个单独的 @Filter 中未定义任何条件时要使用的默认条件。可以在类级别或包级别定义 @FilterDef

现在我们需要定义应用于实体加载或集合加载的 SQL 过滤器子句。 @Filter 用于放置在实体或集合元素上

@Entity

@FilterDef(name="minLength", parameters=@ParamDef( name="minLength", type="integer" ) )
@Filters( {
    @Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length"),
    @Filter(name="minLength", condition=":minLength <= length")
} )
public class Forest { ... }

当集合使用关联表作为关系表示时,您可能希望将过滤器条件应用于关联表本身或目标实体表。要将约束应用于目标实体,请使用常规的 @Filter 注解。但是,如果您想定位关联表,请使用 @FilterJoinTable 注解。

    @OneToMany

    @JoinTable
    //filter on the target entity table
    @Filter(name="betweenLength", condition=":minLength <= length and :maxLength >= length")
    //filter on the association table
    @FilterJoinTable(name="security", condition=":userlevel >= requredLevel")
    public Set<Forest> getForests() { ... }

Hibernate 使您能够覆盖生成的每个 SQL 语句。我们已经看到了本机 SQL 查询的使用,但您也可以覆盖用于加载或更改实体状态的 SQL 语句。

@Entity

@Table(name="CHAOS")
@SQLInsert( sql="INSERT INTO CHAOS(size, name, nickname, id) VALUES(?,upper(?),?,?)")
@SQLUpdate( sql="UPDATE CHAOS SET size = ?, name = upper(?), nickname = ? WHERE id = ?")
@SQLDelete( sql="DELETE CHAOS WHERE id = ?")
@SQLDeleteAll( sql="DELETE CHAOS")
@Loader(namedQuery = "chaos")
@NamedNativeQuery(name="chaos", query="select id, size, name, lower( nickname ) as nickname from CHAOS where id= ?", resultClass = Chaos.class)
public class Chaos {
    @Id
    private Long id;
    private Long size;
    private String name;
    private String nickname;

@SQLInsert@SQLUpdate@SQLDelete@SQLDeleteAll 分别覆盖 INSERT 语句、UPDATE 语句、DELETE 语句、删除所有实体的 DELETE 语句。

如果您希望调用存储过程,请确保将 callable 属性设置为 true (@SQLInsert(callable=true, ...))。

要检查执行是否正确,Hibernate 允许您定义以下三种策略之一

要定义结果检查样式,请使用 check 参数 (@SQLUpdate(check=ResultCheckStyle.COUNT, ...))。

您还可以使用本机 SQL 查询或 HQL 查询覆盖 SQL 加载语句。您只需使用 @Loader 注解引用命名查询。

您可以使用完全相同的注解集来覆盖与集合相关的语句。

@OneToMany

@JoinColumn(name="chaos_fk")
@SQLInsert( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = ? where id = ?")
@SQLDelete( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = null where id = ?")
private Set<CasimirParticle> particles = new HashSet<CasimirParticle>();

参数顺序很重要,由 Hibernate 处理属性的顺序定义。您可以通过启用 org.hibernate.persister.entity 级别的调试日志来查看预期顺序。启用此级别后,Hibernate 将打印出用于创建、更新、删除等实体的静态 SQL。(要查看预期序列,请记住不要通过注解包含您的自定义 SQL,因为这会覆盖 Hibernate 生成的静态 SQL。)

还可以使用 @org.hibernate.annotations.Table 以及 (或全部) 属性 sqlInsertsqlUpdatesqlDelete 来覆盖辅助表的 SQL 语句。

@Entity

@SecondaryTables({
    @SecondaryTable(name = "`Cat nbr1`"),
    @SecondaryTable(name = "Cat2"})
@org.hibernate.annotations.Tables( {
    @Table(appliesTo = "Cat", comment = "My cat table" ),
    @Table(appliesTo = "Cat2", foreignKey = @ForeignKey(name="FK_CAT2_CAT"), fetch = FetchMode.SELECT,
        sqlInsert=@SQLInsert(sql="insert into Cat2(storyPart2, id) values(upper(?), ?)") )
} )
public class Cat implements Serializable {

前面的示例还表明,您可以为给定表(主表或辅助表)提供注释:此注释将用于 DDL 生成。

第 2.4.5.1 节,“延迟选项和获取模式”中,我们已经了解了如何使用 @Fetch 注解来影响关联对象的获取策略。另一种方法是所谓的获取配置文件。获取配置文件是与 org.hibernate.SessionFactory 关联的命名配置,它在 org.hibernate.Session. 上启用。一旦在 org.hibernate.Session 上启用,获取配置文件将对该会话生效,直到它被显式禁用。让我们看一个例子

@Entity

@FetchProfile(name = "customer-with-orders", fetchOverrides = {
   @FetchProfile.FetchOverride(entity = Customer.class, association = "orders", mode = FetchMode.JOIN)
})
public class Customer {
   @Id
   @GeneratedValue
   private long id;
   private String name;
   private long customerNumber;
   @OneToMany
   private Set<Order> orders;
   // standard getter/setter
   ...
}

在正常情况下,订单关联将由 Hibernate 延迟加载,但在用例中,如果将客户及其订单一起加载更有效,您可以执行以下操作

Session session = ...;

session.enableFetchProfile( "customer-with-orders" );  // name matches @FetchProfile name
Customer customer = (Customer) session.get( Customer.class, customerId );
session.disableFetchProfile( "customer-with-orders" ); // or just close the session
...

注意

获取配置文件定义是全局的,它与您将它们放置在哪个类上无关。您可以将 @FetchProfile 注解放置在类或包(package-info.java)上。为了为同一个类或包定义多个获取配置文件,可以使用 @FetchProfiles

目前只支持连接样式的获取配置文件,但计划是支持其他样式。有关详细信息,请参阅 HHH-3414。另请参阅 Hibernate Core 文档中关于获取配置文件的讨论。