Hibernate 与所有其他对象/关系映射工具一样,需要元数据来管理数据从一种表示形式到另一种表示形式的转换。Hibernate 注解提供基于注解的映射元数据。
此版本 Hibernate 注解 基于 JPA 2 规范的最终版本(也称为 JSR-317)并支持其所有功能(包括可选功能)。通过非标准化的 Hibernate 特定注解,还可以使用 Hibernate 特定功能和扩展。
如果您要从以前的 Hibernate 注解版本迁移,请查看 Java 持久性迁移指南.
从 Hibernate 网站下载并解压缩 Hibernate Core 发行版。Hibernate 3.5 及更高版本包含 Hibernate 注解。
或者,在您的依赖项管理器(如 Maven 或 Ivy)中添加以下依赖项。以下是一个示例
<project ...>
...
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>${hibernate-core-version}</version>
</dependency>
</dependencies>
</project>
首先,设置您的类路径(在您在您喜欢的 IDE 中创建新项目后)
或者,将您的 pom.xml 导入到您喜欢的 IDE 中,并让依赖项自动解析,
这是一个包含 JPA 2.0 API 的 JAR,它完全符合规范并通过了 TCK 签名测试。当您将应用程序部署在 Java EE 6 应用程序服务器(例如 JBoss AS 6)中时,通常不需要它。
建议您使用 Hibernate Validator 和 Bean 验证规范功能,因为它与 Java Persistence 2 的集成已标准化。从 Hibernate 网站下载 Hibernate Validator 4 或更高版本,并将 hibernate-validator.jar
和 validation-api.jar
添加到您的类路径中。或者,在您的 pom.xml
中添加以下依赖项。
<project>
...
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator-version}</version>
</dependency>
...
</dependencies>
...
</project>
如果您想使用 Hibernate Search,请从 Hibernate 网站下载它,并将 hibernate-search.jar
及其依赖项添加到您的类路径中。或者,在您的 pom.xml
中添加以下依赖项。
<project>
...
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search</artifactId>
<version>${hibernate-search-version}</version>
</dependency>
...
</dependencies>
...
</project>
建议您使用 JPA 2 API 启动 Hibernate(有关详细信息,请参阅 Hibernate EntityManager 文档)。如果您使用 Hibernate Core 及其本地 API,请继续阅读。
如果您自己启动 Hibernate,请确保使用 AnnotationConfiguration
类,而不是 Configuration
类。以下是用(传统)HibernateUtil
方法的示例
package hello;
import org.hibernate.*;
import org.hibernate.cfg.*;
import test.*;
import test.animals.Dog;
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
sessionFactory = new AnnotationConfiguration()
.configure().buildSessionFactory();
} catch (Throwable ex) {
// Log exception!
throw new ExceptionInInitializerError(ex);
}
}
public static Session getSession()
throws HibernateException {
return sessionFactory.openSession();
}
}
这里有趣的是使用 AnnotationConfiguration
。包和带注解的类在您的常规 XML 配置文件(通常为 hibernate.cfg.xml
)中声明。以下是上面声明的等效项
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<mapping package="test.animals"/>
<mapping class="test.Flight"/>
<mapping class="test.Sky"/>
<mapping class="test.Person"/>
<mapping class="test.animals.Dog"/>
<mapping resource="test/animals/orm.xml"/>
</session-factory>
</hibernate-configuration>
请注意,您可以混合使用传统 hbm.xml 和注解方法。资源元素可以是 hbm 文件或 EJB3 XML 部署描述符。对于您的配置过程,这种区别是透明的。
或者,您可以使用程序化 API 定义带注解的类和包
sessionFactory = new AnnotationConfiguration() .addPackage("test.animals") //the fully qualified package name .addAnnotatedClass(Flight.class) .addAnnotatedClass(Sky.class) .addAnnotatedClass(Person.class) .addAnnotatedClass(Dog.class) .addResource("test/animals/orm.xml")
.configure()
.buildSessionFactory();
除了这种启动例程更改或配置文件更改之外,使用注解时使用 Hibernate API 的方式没有其他区别。您可以使用您喜欢的配置方法来设置其他属性(hibernate.properties
、hibernate.cfg.xml
、程序化 API 等)。
您可以将带注解的持久类和经典 hbm.cfg.xml
声明与同一个 SessionFactory
混合使用。但是,您不能多次声明一个类(无论是否带注解或通过 hbm.xml)。您也不能在实体层次结构中混合配置策略(hbm 与注解)。
为了简化从 hbm 文件到注解的迁移过程,配置机制会检测注解和 hbm 文件之间的映射重复。然后,HBM 文件在类到类基础上优先于带注解的元数据。您可以使用 hibernate.mapping.precedence
属性更改优先级。默认值为 hbm, class
,将其更改为 class, hbm
将在发生冲突时优先考虑带注解的类,而不是 hbm 文件。
Hibernate 注解使用 Java 简单日志门面 (SLF4J) 来记录各种系统事件。SLF4J 可以将您的日志输出定向到多个日志框架(NOP、Simple、log4j 版本 1.2、JDK 1.4 日志记录、JCL 或 logback),具体取决于您选择的绑定。为了正确设置日志记录,您需要在类路径中放置 slf4j-api.jar
以及您喜欢的绑定的 JAR 文件(对于 Log4J 而言,为 slf4j-log4j12.jar
)。有关更多详细信息,请参阅 SLF4J 文档。
Hibernate 注解感兴趣的日志记录类别为
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 列作为其主键列。
@Table
设置在类级别;它允许您为实体映射定义表、目录和模式名称。如果未定义 @Table
,则使用默认值:实体的非限定类名。
@Entity
@Table(name="tbl_sky")
public class Sky implements Serializable {
...
}
@Table(name="tbl_sky",
uniqueConstraints = {@UniqueConstraint(columnNames={"month", "day"})}
)
实体的每个非静态非瞬态属性(根据访问类型为字段或方法)都被认为是持久的,除非您将其注解为 @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
推荐的替代方案是使用 JP-QL(Java 持久化查询语言)或 Criteria 查询的投影功能。
@Lob
public String getFullText() {
return fullText;
}
@Lob
public byte[] getFullCode() {
return fullCode;
}
如果属性类型实现 java.io.Serializable
并且不是基本类型,并且如果属性没有用 @Lob
注解,那么将使用 Hibernate serializable
类型。
最好的用例是多个实体使用的可嵌入类,这些实体可能不使用相同的访问类型。在这种情况下,最好在可嵌入类级别强制访问类型。
要强制在给定类上使用访问类型,请使用 @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
属性除外。请注意,相应的字段(如果有)必须标记为 @Transient
或 transient
。
可以使用 @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="columnName"; boolean un
ique() default false; boolean nu
llable() default true; boolean in
sertable() default true; boolean up
datable() default true; String col
umnDefinition() default ""; String tab
le() default ""; int length
() default 255; int precis
ion() default 0; // decimal precision int scale(
) default 0; // decimal scale
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
@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
注解覆盖它)。
@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;
@Id
注解允许您定义哪个属性是实体的标识符。此属性可以由应用程序本身设置,也可以由 Hibernate 生成(首选)。您可以通过 @GeneratedValue
注解来定义标识符生成策略。
Hibernate 提供了比基本 JPA 更多的 id 生成器。有关更多信息,请查看 第 2.4 节“Hibernate 注解扩展”。
以下示例演示了使用 SEQ_STORE 配置的序列生成器(见下文)
@Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_STORE")
public Integer getId() { ... }
下一个示例使用身份生成器
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
public Long getId() { ... }
AUTO
生成器是可移植应用程序(跨多个数据库供应商)的首选类型。标识符生成配置可以通过生成器属性为多个 @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_GEN
和 SEQ_GEN
是应用程序级生成器。 EMP_GEN
定义了一个基于表的 id 生成器,使用 hilo 算法,max_lo
为 20。hi 值保存在 table
"GENERATOR_TABLE
" 中。信息保存在一个 pkColumnName
"key" 等于 pkColumnValue
"EMP
" 的行中,列 valueColumnName
"hi
" 包含下一个使用的 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 的黑暗时代,用于向后兼容性,我们建议您不要使用它(为了简单起见)。
@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
。
@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;
}
在实践中,您的代码只设置 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;
}
另一种可能更自然的方法是在我的实体的多个属性上放置 @Id
。这种方法只受 Hibernate 支持,但不需要额外的嵌入式组件。
@Entity
class Customer implements Serializable {
@Id @OneToOne
@JoinColumns({
@JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
@JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
@Id String customerNumber;
boolean preferredCustomer;
}
@Entity
class User {
@EmbeddedId UserId id;
Integer age;
}
@Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
}
@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;
}
Customer
和 CustomerId
具有相同的属性 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;
}
Hibernate 支持自动生成一些标识符属性。只需在一些 id 属性上使用 @GeneratedValue
注解即可。
@Entity
public class CustomerInventory implements Serializable {
@Id
@TableGenerator(name = "inventory",
table = "U_SEQUENCES",
pkColumnName = "S_ID",
valueColumnName = "S_NEXTNUM",
pkColumnValue = "inventory",
allocationSize = 1000)
@GeneratedValue(strategy = GenerationType.TABLE, generator = "inventory")
Integer id;
@Id @ManyToOne(cascade = CascadeType.MERGE)
Customer customer;
}
@Entity
public class Customer implements Serializable {
@Id
private int id;
}
所选策略是在层次结构中顶级实体的类级别使用 @Inheritance
注解声明的。
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Flight implements Serializable { ... }
这种策略支持一对多关联,前提是它们是双向的。这种策略不支持 IDENTITY
生成器策略:id 必须跨多个表共享。因此,在使用这种策略时,您不应该使用 AUTO
或 IDENTITY
。
所有超类和子类的所有属性都映射到同一个表中,实例通过一个特殊的鉴别器列来区分
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name="planetype",
discriminatorType=DiscriminatorType.STRING
)
@DiscriminatorValue("Plane")
public class Plane { ... }
@Entity
@DiscriminatorValue("A320")
public class A320 extends Plane { ... }
@PrimaryKeyJoinColumn
和 @PrimaryKeyJoinColumns
注解定义了连接子类表的 主键。
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public class Boat implements Serializable { ... }
@Entity
public class Ferry extends Boat { ... }
@Entity
@PrimaryKeyJoinColumn(name="BOAT_ID")
public class AmericaCupClass extends Boat { ... }
这有时很有用,可以 通过技术或业务超类共享公共属性,而无需将其作为常规映射实体(即此实体没有特定的表)包含在内。为此,您可以将它们映射为 @MappedSuperclass
。
@MappedSuperclass
public class BaseEntity {
@Basic
@Temporal(TemporalType.TIMESTAMP)
public Date getLastUpdate() { ... }
public String getLastUpdater() { ... }
...
}
@Entity class Order extends BaseEntity {
@Id public Integer getId() { ... }
...
}
您可以使用 @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
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() {
...
}
@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() {
...
}
多对一关联是在属性级别使用 @ManyToOne
注解声明的。
@Entity()
public class Flight implements Serializable {
@ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@JoinColumn(name="COMP_ID")
public Company getCompany() {
return company;
}
...
}
@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 {
...
}
@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
注解声明的。一对多关联可以是双向的。
由于多对一(几乎)始终是 JPA 规范中双向关系的所有者侧,因此一对多关联通过 @OneToMany(mappedBy=...)
进行注释。
@Entity
public class Troop {
@OneToMany(mappedBy="troop")
public Set<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk")
public Troop getTroop() {
...
}
Troop
通过 troop
属性与 Soldier
具有双向一对多关系。您不必(也不应该)在 mappedBy
侧定义任何物理映射。
@Entity
public class Troop {
@OneToMany
@JoinColumn(name="troop_fk") //we need to duplicate the physical information
public Set<Soldier> getSoldiers() {
...
}
@Entity
public class Soldier {
@ManyToOne
@JoinColumn(name="troop_fk", insertable=false, updatable=false)
public Troop getTroop() {
...
}
使用被拥有实体中的外键列的单向一对多并不常见,也不推荐。我们强烈建议您对此类关联使用连接表(如下一节所述)。此类关联通过 @JoinColumn
进行描述。
@Entity
public class Customer implements Serializable {
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="CUST_ID")
public Set<Ticket> getTickets() {
...
}
@Entity
public class Ticket implements Serializable {
... //no bidir
}
使用连接表的单向一对多是首选。此关联通过 @JoinTable
进行描述。
@Entity
public class Trainer {
@OneToMany
@JoinTable(
name="TrainedMonkeys",
joinColumns = @JoinColumn( name="trainer_id"),
inverseJoinColumns = @JoinColumn( name="monkey_id")
)
public Set<Monkey> getTrainedMonkeys() {
...
}
@Entity
public class Monkey {
... //no bidir
}
@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;
}
}
@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() { ... }
}
使用 @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() {...}
[...]
}
在 @AttributeOverride
中,必须使用 value.
前缀来覆盖映射值中使用的嵌入式对象的属性,并使用 key.
前缀来覆盖映射键中使用的嵌入式对象的属性。
@Entity
public class User {
@ElementCollection
@AttributeOverrides({
@AttributeOverride(name="key.street1", column=@Column(name="fld_street")),
@AttributeOverride(name="value.stars", column=@Column(name="fld_note"))
})
public Map<Address,Rating> getFavHomes() { ... }
@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 |
|-------------|
@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 |
|--------------|
同样,映射可以从关联实体属性之一借用其键,也可以有专用的列来存储显式键。
@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 |
|-------------| |---------------|
我们建议您从 @org.hibernate.annotations.MapKey
/ @org.hibernate.annotation.MapKeyManyToMany
迁移到上面描述的新标准方法。
具体来说,没有 @OrderColumn 或 @IndexColumn 的 java.util.List 集合将被视为包。
通过 Hibernate 特定的扩展可以获得更多对集合的支持(请参阅 第 2.4 节,“Hibernate 注解扩展”)。
您可能已经注意到 cascade
属性接受 CascadeType
数组作为值。JPA 中的级联概念与 Hibernate 中的操作的传递持久性和级联非常相似,但语义和级联类型略有不同
CascadeType.PERSIST
:将 persist(创建)操作级联到关联的实体上,如果调用 persist() 或实体处于托管状态,则会执行此操作
CascadeType.MERGE
:将 merge 操作级联到关联的实体上,如果调用 merge() 或实体处于托管状态,则会执行此操作
CascadeType.REMOVE
:将 remove 操作级联到关联的实体上,如果调用 delete(),则会执行此操作
CascadeType.REFRESH:
将 refresh 操作级联到关联的实体上,如果调用 refresh(),则会执行此操作
CascadeType.DETACH:
将 detach 操作级联到关联的实体上,如果调用 detach(),则会执行此操作
有关级联和创建/合并语义的更多信息,请参阅 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
您可以选择以急切或延迟方式获取关联实体。 fetch
参数可以设置为 FetchType.LAZY
或 FetchType.EAGER
。 EAGER
将尝试使用外部连接选择来检索关联对象,而 LAZY
仅在第一次访问关联对象时触发选择。 @OneToMany
和 @ManyToMany
关联默认设置为 LAZY
,而 @OneToOne
和 @ManyToOne
默认设置为 EAGER
。有关静态获取的更多信息,请参阅 第 2.4.5.1 节,“延迟选项和获取模式”。
建议的做法是在所有静态获取定义上使用 LAZY
,并通过 JP-QL 动态覆盖此选择。JP-QL 有一个 fetch
关键字,允许你在执行特定查询时覆盖延迟加载。这对于提高性能非常有用,需要根据具体情况进行决定。
复合主键使用嵌入式类作为主键表示,因此您需要使用 @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.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( CacheConcurrencyStrategy usage(); String reg
ion() default ""; String inc
lude() default "all"; )
用法:给定的缓存并发策略(NONE、READ_ONLY、NONSTRICT_READ_WRITE、READ_WRITE、TRANSACTIONAL) | |
区域(可选):缓存区域(默认为类的全限定名或集合的全限定名角色名)。 | |
|
不幸的是,您将失去使用 Criteria API 编写的查询的类型安全性。
<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
数组。
您还可以使用 lockMode
属性定义返回的实体应以何种锁定模式锁定。这等效于实体管理器查找操作的可选锁定模式。
@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")
})
}
)
@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;
}
}
@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(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
中定义的内容的额外元数据
@org.hibernate.annotations.Where
定义在检索此类的实例时使用的可选 SQL WHERE 子句。
@org.hibernate.annotations.Check
定义在 DDL 语句中定义的可选检查约束。
@OnDelete(action=OnDeleteAction.CASCADE)
在连接的子类上:在删除时使用 SQL 级联删除,而不是常规的 Hibernate 机制。
@org.hibernate.annotations.Table
也可以用来定义二级表的以下元素
@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 持久性规范。
@Id @GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid")
public String getId() {
@Id @GeneratedValue(generator="hibseq")
@GenericGenerator(name="hibseq", strategy = "seqhilo",
parameters = {
@Parameter(name="max_lo", value = "5"),
@Parameter(name="sequence", value="heybabyhey")
}
)
public Integer getId() {
strategy
是 Hibernate3 生成器策略的简称,或者 IdentifierGenerator
实现的完全限定类名。您可以通过 parameters
属性添加一些参数。
与它们的标准对应项相反,@GenericGenerator
和 @GenericGenerators
可用于包级别注释,使其成为应用程序级别生成器(就像它们位于 JPA XML 文件中一样)。
@GenericGenerators(
{
@GenericGenerator(
name="hibseq",
strategy = "seqhilo",
parameters = {
@Parameter(name="max_lo", value = "5"),
@Parameter(name="sequence", value="heybabyhey")
}
),
@GenericGenerator(...)
}
)
package org.hibernate.test.model
有时,您希望数据库为您执行一些计算,而不是在 JVM 中执行,您还可以创建某种虚拟列。您可以使用 SQL 片段(也称为公式)而不是将属性映射到列。这种属性是只读的(其值由您的公式片段计算)。
@Formula("obj_length * obj_height * obj_width")
public long getObjectVolume()
@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;
...
}
您可以使用 @Index
注释在单列属性上定义对特定列的索引,此时将忽略 columnNames 属性
@Column(secondaryTable="Cat1")
@Index(name="story1index")
public String getStoryPart1() {
return storyPart1;
}
在可嵌入对象内部,您可以将其中一个属性定义为指向拥有元素的指针。
@Entity
public class Person {
@Embeddable public Address address;
...
}
@Embeddable
public class Address {
@Parent public Person owner;
...
}
person == person.address.owner
某些属性是在插入或更新时由您的数据库生成的。Hibernate 可以处理这些属性,并触发后续选择以读取这些属性。
@Entity
public class Antenna {
@Id public Integer id;
@Generated(GenerationTime.ALWAYS)
@Column(insertable = false, updatable = false)
public String longitude;
@Generated(GenerationTime.INSERT) @Column(insertable = false)
public String latitude;
}
@Version
属性不能按设计为 @Generated(INSERT)
,它必须是 NEVER
或 ALWAYS
之一。
@Entity
@DiscriminatorFormula("case when forest_type is null then 0 else forest_type end")
public class Forest { ... }
您可以定义 Hibernate 为 JOINED 继承策略中的子类表生成的外部键名称。
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class File { ... }
@Entity
@ForeignKey(name = "FK_DOCU_FILE")
public class Document extends File {
@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
注解 @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;
}
@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;
}
@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
在双向关联中,如果一端是索引集合(即以 @OrderColumn
或 Map 表示),则需要特殊考虑。如果关联类上的属性明确映射了索引值,则允许使用 mappedBy
。
@Entity
public class Parent {
@OneToMany(mappedBy="parent")
@OrderColumn(name="order")
private List<Child> children;
...
}
@Entity
public class Child {
...
//the index column is mapped as a property in the associated entity
@Column(name="order")
private int order;
@ManyToOne
@JoinColumn(name="parent_id", nullable=false)
private Parent parent;
...
}
但是,如果子类上没有这样的属性,我们不能将关联视为真正的双向关联(关联一端存在另一端没有的信息:索引)。在这种情况下,我们不能将集合映射为 mappedBy
。相反,我们可以使用以下映射
@Entity
public class Parent {
@OneToMany
@OrderColumn(name="order")
@JoinColumn(name="parent_id", nullable=false)
private List<Child> children;
...
}
@Entity
public class Child {
...
@ManyToOne
@JoinColumn(name="parent_id", insertable=false, updatable=false, nullable=false)
private Parent parent;
...
}
@ManyToAny(
metaColumn = @Column( name = "property_type" ) )
@AnyMetaDef(
idType = "integer",
metaType = "string",
metaValues = {
@MetaValue( value = "S", targetEntity = StringProperty.class ),
@MetaValue( value = "I", targetEntity = IntegerProperty.class ) } )
@Cascade( { org.hibernate.annotations.CascadeType.ALL } )
@JoinTable( name = "obj_properties", joinColumns = @JoinColumn( name = "obj_id" ),
inverseJoinColumns = @JoinColumn( name = "property_id" ) )
public List<Property> getGeneralProperties() {
与 @Any
一样,@ManyToAny
可以使用命名的 @AnyDef
,有关更多信息,请参见第 2.4.5.2 节,“@Any”
Hibernate 提供了比 Java Persistence 规范更多的操作。您可以使用 @Cascade
注解来级联以下操作
@OneToMany( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
@Cascade(org.hibernate.annotations.CascadeType.REPLICATE)
public Collection<Employer> getEmployers()
Hibernate 能够在您的数据之上应用任意过滤器。这些过滤器在运行时应用于给定的会话。首先,您需要定义它们。
现在,我们需要定义应用于实体加载或集合加载的 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 { ... }
@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>();
使用 @org.hibernate.annotations.Table
和属性 sqlInsert
、sqlUpdate
、sqlDelete
之一(或全部)也可以覆盖辅助表的 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 {
要在注解中定义 Tuplizer,只需在相应的元素上使用 @Tuplizer
注解即可
@Entity
@Tuplizer(impl = DynamicEntityTuplizer.class)
public interface Cuisine {
@Id
@GeneratedValue
public Long getId();
public void setId(Long id);
public String getName();
public void setName(String name);
@Tuplizer(impl = DynamicComponentTuplizer.class)
public Country getCountry();
public void setCountry(Country country);
}
在 第 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
...
}
在正常情况下,orders 关联将由 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 文档中关于获取配置文件的讨论。
EJB3 中元数据的主要目标是注解,但 EJB3 规范提供了一种通过 XML 部署描述符覆盖或替换注解定义的元数据的方法。 在当前版本中,只支持纯 EJB3 注解覆盖。 如果您希望在某些实体中使用 Hibernate 特定功能,您必须使用注解或回退到 hbm 文件。 当然,您可以混合使用注解实体和 hbm 文件中描述的实体。
单元测试套件显示了一些额外的 XML 文件示例。
XML 部署描述符结构旨在反映注解结构。 因此,如果您知道注解结构,那么使用 XML 架构将非常简单。
您可以定义一个或多个描述元数据的 XML 文件,这些文件将被覆盖引擎合并。
您可以定义对所有 XML 文件都可用的全局级别元数据。 您不得对每个部署定义这些元数据多次。
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings
xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd"
version="2.0">
<persistence-unit-metadata>
<xml-mapping-metadata-complete/>
<persistence-unit-defaults>
<schema>myschema</schema>
<catalog>mycatalog</catalog>
<cascade-persist/>
</persistence-unit-defaults>
</persistence-unit-metadata>
xml-mapping-metadata-complete
表示所有实体、映射的超类和可嵌入元数据都应从 XML 中获取(即忽略注解)。
<?xml version="1.0" encoding="UTF-8"?> <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd" version="2.0"> <package>o
rg.hibernate.test.annotations.reflection</package> <entity cl
ass="Administration" access="PROPERTY" metadata-complete="true"> <table
name="tbl_admin"> <unique-constraint> <column-name>firstname</column-name> <column-name>lastname</column-name> </unique-constraint> </table> <secon
dary-table name="admin2"> <primary-key-join-column name="admin_id" referenced-column-name="id"/> <unique-constraint> <column-name>address</column-name> </unique-constraint> </secondary-table> <id-cl
ass class="SocialSecurityNumber"/> <inher
itance strategy="JOINED"/> <seque
nce-generator name="seqhilo" sequence-name="seqhilo"/> <table
-generator name="table" table="tablehilo"/> ... </entity> <entity class="PostalAdministration"> <prima
ry-key-join-column name="id"/> ... </entity> </entity-mappings>
| |
| |
实体必须有一个 您可以通过 对于元数据完整(见下文)元素,您可以定义一个 | |
您可以定义一个或多个唯一约束,如示例所示 | |
| |
| |
| |
| |
| |
|
<?xml version="1.0" encoding="UTF-8"?> <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd" version="2.0"> <package>org.hibernate.test.annotations.reflection</package> <entity class="Music" access="PROPERTY" metadata-complete="true"> <discriminator-value>Generic</discriminator-value> <discriminator-column length="34"/> ... </entity> <entity class="PostalAdministration"> <primary-key-join-column name="id"/> <named
-query name="adminById"> <query>select m from Administration m where m.id = :id</query> <hint name="org.hibernate.timeout" value="200"/> </named-query> <named
-native-query name="allAdmin" result-set-mapping="adminrs"> <query>select *, count(taxpayer_id) as taxPayerNumber from Administration, TaxPayer where taxpayer_admin_id = admin_id group by ...</query> <hint name="org.hibernate.timeout" value="200"/> </named-native-query> <sql-r
esult-set-mapping name="adminrs"> <entity-result entity-class="Administration"> <field-result name="name" column="fld_name"/> </entity-result> <column-result name="taxPayerNumber"/> </sql-result-set-mapping> <attri
bute-override name="ground"> <column name="fld_ground" unique="true" scale="2"/> </attribute-override> <association-override name="referer"> <join-column name="referer_id" referenced-column-name="id"/> </association-override> ... </entity> </entity-mappings>
| |
| |
| |
| |
|
Hibernate 注解主要关注持久化元数据。 该项目还与一些外部模块有很好的集成。
Default
组在实体插入和更新时进行验证,并且数据库模型也根据 Default
组进行相应更新。
您可以通过将属性设置为 callback, dll
来同时使用 callback
和 ddl
<persistence ...>
<persistence-unit ...>
...
<properties>
<property name="javax.persistence.validation.mode"
value="callback, ddl"/>
</properties>
</persistence-unit>
</persistence>
您可以在 hibernate.cfg.xml
、hibernate.properties
或以编程方式设置这些属性。
版权所有 © 2004 Red Hat Inc. 及其他作者