Java 持久化/映射
在 Java 中持久化某个东西,您需要做的第一件事是定义它如何持久化。这称为映射过程 (详细信息)。多年来,映射过程出现了许多不同的解决方案,包括一些不需要您进行任何映射,而是允许您直接持久化任何东西的对象数据库。对象关系映射工具会为数据模型生成对象模型,其中包含映射和持久化逻辑。ORM 产品提供了映射工具,允许将现有对象模型映射到现有数据模型,并将此映射元数据存储在平面文件、数据库表、XML 和最终注释中。
在 JPA 中,映射可以存储在 Java 注释中,也可以存储在 XML 文件中。JPA 的一个重要方面是,只需要最少的映射量。JPA 实现需要为映射对象的大多数方面提供默认值。
在 JPA 中映射对象的最低要求是定义哪些对象可以持久化。这可以通过用@Entity
注释标记类,或在持久化单元的 ORM XML 文件中为类添加<entity>
标签来实现。此外,必须为类定义主键或唯一标识符属性。这可以通过用@Id
注释标记类的一个字段或属性(get 方法),或在ORM XML 文件中为类的属性添加<id>
标签来实现。
JPA 实现将默认所有其他映射信息,包括默认表名、所有定义字段或属性的列名、关系的基数和映射、访问对象的全部 SQL 和持久化逻辑。大多数 JPA 实现还提供在运行时生成数据库表的选择,因此开发人员只需很少的工作量就可以快速开发持久性 JPA 应用程序。
import javax.persistence.*;
...
@Entity
public class Employee {
@Id
private long id;
private String firstName;
private String lastName;
private Address address;
private List<Phone> phones;
private Employee manager;
private List<Employee> managedEmployees;
...
...
}
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="1.0"
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_1_0.xsd">
<description>The minimal mappings for a persistent entity in XML.</description>
<entity name="Employee" class="org.acme.Employee" access="FIELD">
<attributes>
<id name="id"/>
</attributes>
</entity>
</entity-mappings>
JPA 允许将注释放置在类字段或属性的get
方法上。这定义了类的访问类型,即FIELD
或PROPERTY
。所有注释必须都放在字段上,或者所有注释都放在get
方法上,但不能两者兼而有之(除非使用@Access
注释)。JPA 没有定义默认访问类型(奇怪的是),因此默认值可能取决于 JPA 提供程序。默认值被认为是根据@Id
注释的位置发生的,如果放在字段上,则类使用FIELD
访问,如果放在get
方法上,则类使用PROPERTY
访问。访问类型也可以通过 XML 在<entity>
元素中定义。可以使用@Access
注释或access-type
XML 属性配置访问类型。
对于FIELD
访问,将直接访问类字段值以从数据库中存储和加载值。这通常通过反射或生成的字节码来完成,但这取决于 JPA 提供程序和配置。字段可以是private
或任何其他访问类型。FIELD
通常更安全,因为它避免了应用程序get
/set
方法中可能发生的任何意外副作用代码。
对于PROPERTY
访问,将使用类get
和set
方法从数据库中存储和加载值。这通常通过反射或生成的字节码来完成,但这取决于 JPA 提供程序和配置。PROPERTY
的优势在于允许应用程序在将数据库值存储在对象中时执行转换。用户应该注意不要在get
/set
方法中放置任何可能干扰持久化的副作用。
JPA 2.0 允许在特定字段或get
方法上设置访问类型。这允许类使用一个默认访问机制,但对于一个属性来说,可以使用不同的访问类型。这可以用于需要通过一组数据库特定get
、set
进行转换的属性,或者允许特定属性避免其get
、set
方法中的副作用。这是通过在映射属性上指定@Access
注释或 XML 属性来实现的。如果字段/属性的属性名称与映射属性的属性名称不同,您可能还需要将其标记为@Transient
。
JPA 允许在persistence-unit-defaults
或entity-mappings
中的实体默认值中设置持久化单元默认<access-type>
元素。
- TopLink / EclipseLink : 默认使用
FIELD
访问。如果启用了编织,并且使用字段访问,则使用生成的字节码直接访问字段。如果不使用编织,或使用属性访问,则使用反射。EclipseLink 还支持第三种访问类型VIRTUAL
,它可以从 ORM XML 中用于映射存储在属性 Map 中的动态属性。
@Entity
@Access(AccessType.FIELD)
public class Employee {
@Id
private long id;
private String firstName;
private String lastName;
@Transient
private Money salary;
@ManyToOne(fetch=FetchType.LAZY)
private Employee manager;
@Access(AccessType.PROPERTY)
private BigDecimal getBDSalary() {
return this.salary.toNumber();
}
private void setBDSalary(BigDecimal salary) {
this.salary = new Money(salary);
}
private Money getSalary() {
return this.salary;
}
private void setSalary(Money salary) {
this.salary = salary;
}
...
}
- 这通常发生在您对类的字段和方法(属性)都进行注释时。您必须选择字段访问或属性访问,并且保持一致。此外,在注释属性时,必须将注释放在 get 方法上,而不是 set 方法上。还要确保您没有在 XML 中定义相同的映射,因为这可能会覆盖注释。您也可能存在类路径问题,例如类路径上可能存在旧版本的类。
- 持久化出现奇怪行为有很多原因。一个可能导致奇怪行为的常见问题是在使用属性访问时,在 get 或 set 方法中放置副作用。因此,通常建议在映射中使用字段访问,即在变量上而不是在 get 方法上放置注释。
- 例如,考虑
public void setPhones(List<Phone> phones) {
for (Phone phone : phones) {
phone.setOwner(this);
}
this.phones = phones;
}
- 这看起来可能是无害的,但这些副作用可能会产生意想不到的后果。例如,如果关系是
lazy
,这将具有在从数据库中设置时始终实例化集合的效果。它还可能对某些 JPA 实现的持久化、合并和其他操作产生影响,从而导致重复插入、错过更新或对象模型损坏。
- 我也见过一些明显错误的属性方法,例如总是返回新对象或副本的 get 方法,或者实际上没有设置值的 set 方法。
- 一般来说,如果您要使用属性访问,请确保您的属性方法没有副作用。您甚至可以使用与应用程序不同的属性方法。