一、简介
在本教程中,我们将讨论 JPA 实体和 Java Serialized 接口如何混合。首先,我们将了解 java.io.Serialized 接口以及为什么需要它。之后,我们将了解 JPA 规范和 Hibernate 作为其最流行的实现。
2.什么是 可串行化 接口?
Serialized 是核心 Java 中为数不多的标记接口之一。标记接口是没有方法或常量的特殊接口。
对象序列化是将 Java 对象转换为字节流的过程 。然后我们可以通过线路传输这些字节流或将它们存储在持久内存中。 反序列化是相反的过程 ,我们获取字节流并将它们转换回 Java 对象。要允许对象序列化(或反序列化),类必须实现 Serialized 接口。否则,我们将遇到 java.io.NotSerializedException 。 序列化广泛应用于RMI、JPA、EJB等技术中 。
3.JPA和 可序列化
让我们看看 JPA 规范对 Serialized 有何 规定,以及它与 Hibernate 有何关系。
3.1. JPA规范
JPA 的核心部分之一是实体类。我们将此类类标记为实体(使用 @Entity 注释或 XML 描述符)。我们的实体类必须满足几个要求, 根据 JPA 规范,我们最关心的一个要求是:
如果实体实例要作为分离对象按值传递(例如,通过远程接口),则实体类必须实现 Serialized 接口。
在实践中, 如果我们的对象要离开 JVM 的域,就需要序列化 。
每个实体类都由持久字段和属性组成。该规范要求实体的字段可以是 Java 原语、Java 可序列化类型或用户定义的可序列化类型。
实体类还必须有主键。主键可以是原始键(单个持久字段)或复合键。多个规则适用于组合键,其中之一是 组合键需要可序列化 。
让我们使用 Hibernate、H2 内存数据库和以 UserId 作为组合键的 User 域对象创建一个简单的示例:
@Entity
public class User {
@EmbeddedId UserId userId;
String email;
// constructors, getters and setters
}
@Embeddable
public class UserId implements Serializable{
private String name;
private String lastName;
// getters and setters
}
我们可以使用集成测试来测试我们的域定义:
@Test
public void givenUser_whenPersisted_thenOperationSuccessful() {
UserId userId = new UserId();
userId.setName("John");
userId.setLastName("Doe");
User user = new User(userId, "[email protected]");
entityManager.persist(user);
User userDb = entityManager.find(User.class, userId);
assertEquals(userDb.email, "[email protected]");
}
如果我们的 UserId 类没有实现 Serialized 接口,我们将得到一个 MappingException ,其中包含一条具体消息,表明我们的组合键必须实现该接口。
3.2. Hibernate @JoinColumn 注解
Hibernate官方文档在描述Hibernate中的映射时指出, 当我们使用 @JoinColumn 注释中的 referencedColumnName 时,引用的字段必须是可序列化的 。通常,该字段是另一个实体中的主键。在复杂实体类的极少数情况下,我们的引用必须是可序列化的。
让我们扩展之前的 User 类,其中 电子邮件 字段不再是 String 而是一个独立的实体。此外,我们将添加 一个 Account 类,该类将引用用户并具有字段 类型。 每个 用户 可以拥有多个不同类型的帐户。我们将通过 电子邮件 映射 帐户 ,因为通过电子邮件地址搜索更自然:
@Entity
public class User {
@EmbeddedId private UserId userId;
private Email email;
}
@Entity
public class Email implements Serializable {
@Id
private long id;
private String name;
private String domain;
}
@Entity
public class Account {
@Id
private long id;
private String type;
@ManyToOne
@JoinColumn(referencedColumnName = "email")
private User user;
}
为了测试我们的模型,我们将编写一个测试,为用户创建两个帐户并通过电子邮件对象进行查询:
@Test
public void givenAssociation_whenPersisted_thenMultipleAccountsWillBeFoundByEmail() {
// object creation
entityManager.persist(user);
entityManager.persist(account);
entityManager.persist(account2);
List<Account> userAccounts = entityManager.createQuery("select a from Account a join fetch a.user where a.user.email = :email", Account.class)
.setParameter("email", email)
.getResultList();
assertEquals(userAccounts.size(), 2);
}
注意:user 是H2数据库中的保留字,不能用于实体的名称。
如果 Email 类没有实现 Serialized 接口,我们将再次收到 MappingException ,但这次会出现一条有点神秘的消息:“无法确定类型”。
3.3.将实体暴露给表示层
当使用 HTTP 通过网络发送对象时,我们通常会为此目的创建特定的 DTO(数据传输对象)。通过创建 DTO,我们将内部域对象与外部服务解耦。 如果我们想在没有 DTO 的情况下将实体直接暴露给表示层,那么实体必须是可序列化的 。
我们使用 HttpSession 对象来存储相关数据,帮助我们在多个页面访问我们的网站时识别用户。 Web 服务器可以在正常关闭时将会话数据存储在磁盘上,或者将会话数据传输到集群环境中的另一个 Web 服务器。如果实体是此过程的一部分,那么它必须是可序列化的。否则,我们将遇到 NotSerializedException 。
4。结论
在本文中,我们介绍了 Java 序列化的基础知识,并了解了它如何在 JPA 中发挥作用。首先,我们回顾了 JPA 规范中有关 Serialized 的 要求。之后,我们将 Hibernate 视为最流行的 JPA 实现。最后,我们介绍了 JPA 实体如何与 Web 服务器配合使用。
与往常一样,本文中提供的所有代码都可以在 GitHub 上找到。