JPA Entities and the Serializable Interface – JPA实体和可序列化的接口

最后修改: 2021年 7月 3日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’ll discuss how JPA entities and the Java Serializable interface blend. First, we’ll take a look at the java.io.Serializable interface and why we need it. After that, we’ll take a look at the JPA specification and Hibernate as its most popular implementation.

在本教程中,我们将讨论JPA实体和Java Serializable接口如何融合。首先,我们将看看java.io.Serializable接口以及我们为什么需要它。之后,我们将看看JPA规范和作为其最流行实现的Hibernate。

2. What Is the Serializable Interface?

2.什么是可序列化的接口?

Serializable is one of the few marker interfaces found in core Java. Marker interfaces are special case interfaces with no methods or constants.

Serializable是在核心Java中发现的少数标记接口之一。标记接口是没有方法或常量的特例接口。

Object serialization is the process of converting Java objects into byte streams. We can then transfer these byte streams over the wire or store them in persistent memory. Deserialization is the reverse process, where we take byte streams and convert them back into Java objects. To allow object serialization (or deserialization), a class must implement the Serializable interface. Otherwise, we’ll run into java.io.NotSerializableException. Serialization is widely used in technologies such as RMI, JPA, and EJB.

对象序列化是将Java对象转换为字节流的过程。然后我们可以通过电线传输这些字节流或将其存储在持久性内存中。反序列化是一个相反的过程,我们将字节流转换回Java对象。为了允许对象序列化(或反序列化),一个类必须实现Serializable接口。否则,我们将遇到java.io.NotSerializableException序列化被广泛用于RMI、JPA和EJB等技术中

3. JPA and Serializable

3.JPA和Serializable

Let’s see what the JPA specification says about Serializable and how it pertains to Hibernate.

让我们看看JPA规范中关于Serializable 的内容,以及它与Hibernate的关系。

3.1. JPA Specification

3.1.JPA规范

One of the core parts of JPA is an entity class. We mark such classes as entities (either with the @Entity annotation or an XML descriptor). There are several requirements that our entity class must fulfill, and the one we’re most concerned with, according to the JPA specification, is:

JPA的核心部分之一是实体类。我们将这样的类标记为实体(可以使用@Entity注解或XML描述符)。我们的实体类必须满足几个要求,根据JPA规范,我们最关心的一个要求是:

If an entity instance is to be passed by value as a detached object (e.g., through a remote interface), the entity class must implement the Serializable interface.

如果一个实体实例要作为一个分离的对象(例如,通过一个远程接口)按值传递,那么实体类必须实现Serializable 接口

In practice, if our object is to leave the domain of the JVM, it’ll require serialization.

在实践中,如果我们的对象要离开JVM的领域,它就需要进行序列化

Each entity class consists of persistent fields and properties. The specification requires that fields of an entity may be Java primitives, Java serializable types, or user-defined serializable types.

每个实体类由持久的字段和属性组成。该规范要求实体的字段可以是Java原语、Java可序列化类型或用户定义的可序列化类型。

An entity class must also have a primary key. Primary keys can be primitive (single persistent field) or composite. Multiple rules apply to a composite key, one of which is that a composite key is required to be serializable.

一个实体类也必须有一个主键。主键可以是原始的(单个持久化字段),也可以是复合的。多个规则适用于复合键,其中之一是复合键必须是可序列化的

Let’s create a simple example using Hibernate, H2 in-memory database, and a User domain object with UserId as a composite key:

让我们使用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
}

We can test our domain definition using the integration test:

我们可以使用集成测试来测试我们的领域定义。

@Test
public void givenUser_whenPersisted_thenOperationSuccessful() {
    UserId userId = new UserId();
    userId.setName("John");
    userId.setLastName("Doe");
    User user = new User(userId, "johndoe@gmail.com");

    entityManager.persist(user);

    User userDb = entityManager.find(User.class, userId);
    assertEquals(userDb.email, "johndoe@gmail.com");
}

If our UserId class does not implement the Serializable interface, we’ll get a MappingException with a concrete message that our composite key must implement the interface.

如果我们的UserId类没有实现Serializable接口,我们将得到一个MappingException的具体信息,即我们的复合键必须实现该接口。

3.2. Hibernate @JoinColumn Annotation

3.2.Hibernate @JoinColumn注释

Hibernate official documentation, when describing mapping in Hibernate, notes that the referenced field must be serializable when we use referencedColumnName from the @JoinColumn annotation. Usually, this field is a primary key in another entity. In rare cases of complex entity classes, our reference must be serializable.

Hibernate官方文档在描述Hibernate中的映射时指出,当我们使用@JoinColumn /a>注释中的referencedColumnName 时,引用的字段必须是可序列的。通常,这个字段是另一个实体中的一个主键。在复杂的实体类的罕见情况下,我们的引用必须是可序列化的。

Let’s extend the previous User class where the email field is no longer a String but an independent entity. Also, we’ll add an Account class that will reference a user and has a field type. Each User can have multiple accounts of different types. We’ll map Account by email since it’s more natural to search by email address:

让我们扩展之前的User 类,其中email 字段不再是一个String,而是一个独立的实体。同时,我们将添加一个账户类,它将引用一个用户,并有一个字段类型。每个用户可以有多个不同类型的账户。我们将通过email来映射Account,因为通过email地址来搜索更自然。

@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;
}

To test our model, we’ll write a test where we create two accounts for a user and query by an email object:

为了测试我们的模型,我们将写一个测试,为一个用户创建两个账户并通过电子邮件对象进行查询。

@Test
public void givenAssociation_whenPersisted_thenMultipleAccountsWillBeFoundByEmail() {
    // object creation 

    entityManager.persist(user);
    entityManager.persist(account);
    entityManager.persist(account2);

    List userAccounts = entityManager.createQuery("select a from Account a join fetch a.user where a.user.email = :email")
      .setParameter("email", email)
      .getResultList();
    
    assertEquals(userAccounts.size(), 2);
}

If the Email class does not implement the Serializable interface, we’ll get MappingException again, but this time with a somewhat cryptic message: “Could not determine type”.

如果Email类没有实现Serializable接口,我们将再次得到MappingException,但这次有一个有点神秘的消息。”无法确定类型”。

3.3. Exposing Entities to the Presentation Layer

3.3.向表现层暴露实体

When sending objects over the wire using HTTP, we usually create specific DTOs (data transfer objects) for this purpose. By creating DTOs, we decouple internal domain objects from external services. If we want to expose our entities directly to the presentation layer without DTOs, then entities must be serializable.

当使用HTTP在电线上发送对象时,我们通常为此目的创建特定的DTOs(数据传输对象)。通过创建DTO,我们将内部域对象与外部服务解耦。如果我们想在没有DTO的情况下将我们的实体直接暴露给呈现层,那么实体必须是可序列化的

We use the HttpSession object to store relevant data that help us identify users across multiple page visits to our website. The web server can store session data on a disk when shutting down gracefully or transfer session data to another web server in clustered environments. If an entity is part of this process, then it must be serializable. Otherwise, we’ll run into NotSerializableException.

我们使用HttpSession对象来存储相关数据,以帮助我们在多次访问网站的页面中识别用户。在优雅地关闭时,Web服务器可以将会话数据存储在磁盘上,或者在集群环境中将会话数据转移到另一个Web服务器上。如果一个实体是这个过程的一部分,那么它必须是可序列化的。否则,我们会遇到NotSerializableException

4. Conclusion

4.总结

In this article, we covered the basics of Java serialization and saw how it comes into play in JPA. First, we went over the JPA specification’s requirements regarding Serializable. After that, we looked into Hibernate as the most popular implementation of JPA. In the end, we covered how JPA entities work with web servers.

在这篇文章中,我们介绍了Java序列化的基础知识,并看到它是如何在JPA中发挥作用的。首先,我们回顾了JPA规范中关于Serializable的要求。之后,我们研究了Hibernate作为JPA的最流行的实现。最后,我们介绍了JPA实体如何与Web服务器协同工作。

As usual, all code presented in this article can be found over on GitHub.

像往常一样,本文中介绍的所有代码都可以在GitHub上找到