跳转到内容

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 中持久化实体映射的示例

[编辑 | 编辑源代码]
<?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 方法上。这定义了类的访问类型,即FIELDPROPERTY。所有注释必须都放在字段上,或者所有注释都放在get 方法上,但不能两者兼而有之(除非使用@Access 注释)。JPA 没有定义默认访问类型(奇怪的是),因此默认值可能取决于 JPA 提供程序。默认值被认为是根据@Id 注释的位置发生的,如果放在字段上,则类使用FIELD 访问,如果放在get 方法上,则类使用PROPERTY 访问。访问类型也可以通过 XML 在<entity> 元素中定义。可以使用@Access 注释或access-type XML 属性配置访问类型。

对于FIELD 访问,将直接访问类字段值以从数据库中存储和加载值。这通常通过反射或生成的字节码来完成,但这取决于 JPA 提供程序和配置。字段可以是private 或任何其他访问类型。FIELD 通常更安全,因为它避免了应用程序get/set 方法中可能发生的任何意外副作用代码。

对于PROPERTY 访问,将使用类getset 方法从数据库中存储和加载值。这通常通过反射或生成的字节码来完成,但这取决于 JPA 提供程序和配置。PROPERTY 的优势在于允许应用程序在将数据库值存储在对象中时执行转换。用户应该注意不要在get/set 方法中放置任何可能干扰持久化的副作用。

JPA 2.0 允许在特定字段或get 方法上设置访问类型。这允许类使用一个默认访问机制,但对于一个属性来说,可以使用不同的访问类型。这可以用于需要通过一组数据库特定getset 进行转换的属性,或者允许特定属性避免其getset 方法中的副作用。这是通过在映射属性上指定@Access 注释或 XML 属性来实现的。如果字段/属性的属性名称与映射属性的属性名称不同,您可能还需要将其标记为@Transient

JPA 允许在persistence-unit-defaultsentity-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 方法。
一般来说,如果您要使用属性访问,请确保您的属性方法没有副作用。您甚至可以使用与应用程序不同的属性方法。
华夏公益教科书