Перейти к основному содержимому

Сущности JPA и сериализуемый интерфейс

· 5 мин. чтения

1. Введение

В этом руководстве мы обсудим, как сочетаются сущности JPA и интерфейс Java Serializable . Во-первых, мы рассмотрим интерфейс java.io.Serializable и зачем он нам нужен. После этого мы рассмотрим спецификацию JPA и Hibernate как наиболее популярную ее реализацию.

2. Что такое сериализуемый интерфейс?

Serializable — один из немногих интерфейсов маркеров, которые можно найти в ядре Java. Маркерные интерфейсы — это специальные интерфейсы без методов или констант.

Сериализация объектов — это процесс преобразования объектов Java в потоки байтов . Затем мы можем передавать эти потоки байтов по сети или хранить их в постоянной памяти. Десериализация — это обратный процесс , когда мы берем потоки байтов и преобразовываем их обратно в объекты Java. Чтобы разрешить сериализацию (или десериализацию) объектов, класс должен реализовать интерфейс Serializable . В противном случае мы столкнемся с java.io.NotSerializableException . Сериализация широко используется в таких технологиях, как RMI, JPA и EJB .

3. JPA и сериализуемые

Давайте посмотрим, что спецификация JPA говорит о Serializable и как это относится к Hibernate.

3.1. Спецификация JPA

Одной из основных частей JPA является класс сущности. Мы помечаем такие классы как сущности (либо аннотацией @Entity , либо дескриптором XML). Есть несколько требований, которым должен соответствовать наш класс сущностей, и, согласно спецификации JPA , нас больше всего интересует следующее:

  > Если экземпляр сущности должен быть передан по значению как отдельный объект (например, через удаленный интерфейс),  класс сущности должен реализовать  интерфейс  `Serializable`  .  

На практике, если наша цель — покинуть домен JVM, потребуется сериализация .

Каждый класс сущностей состоит из постоянных полей и свойств. Спецификация требует, чтобы поля объекта могли быть примитивами Java, сериализуемыми типами Java или сериализуемыми типами, определяемыми пользователем.

Класс сущностей также должен иметь первичный ключ. Первичные ключи могут быть примитивными (одно постоянное поле) или составными. К составному ключу применяются несколько правил, одно из которых заключается в том, что составной ключ должен быть сериализуемым .

Давайте создадим простой пример, используя Hibernate, базу данных H2 в памяти и объект домена пользователя с UserId в качестве составного ключа:

@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, "johndoe@gmail.com");

entityManager.persist(user);

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

Если наш класс UserId не реализует интерфейс Serializable , мы получим исключение MappingException с конкретным сообщением о том, что наш составной ключ должен реализовать интерфейс.

3.2. Hibernate @JoinColumn Аннотация

Официальная документация Hibernate при описании сопоставления в Hibernate отмечает, что ссылочное поле должно быть сериализуемым, когда мы используем referencedColumnName из аннотации @JoinColumn . Обычно это поле является первичным ключом в другом объекте. В редких случаях сложных классов сущностей наша ссылка должна быть сериализуемой.

Давайте расширим предыдущий класс User , где поле электронной почты больше не является строкой , а является независимым объектом. Кроме того, мы добавим класс 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 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);
}

Если класс Email не реализует интерфейс Serializable , мы снова получим MappingException , но на этот раз с несколько загадочным сообщением: «Не удалось определить тип».

3.3. Отображение сущностей на уровне представления

При отправке объектов по сети с использованием HTTP мы обычно создаем для этой цели специальные DTO (объекты передачи данных). Создавая DTO, мы отделяем внутренние объекты домена от внешних служб. Если мы хотим выставлять наши сущности непосредственно на уровень представления без DTO, то сущности должны быть сериализуемыми .

Мы используем объект HttpSession для хранения релевантных данных, которые помогают нам идентифицировать пользователей при посещении нескольких страниц нашего веб-сайта. Веб-сервер может сохранять данные сеанса на диске при корректном завершении работы или передавать данные сеанса на другой веб-сервер в кластерных средах. Если объект является частью этого процесса, он должен быть сериализуемым. В противном случае мы столкнемся с NotSerializableException .

4. Вывод

В этой статье мы рассмотрели основы сериализации Java и увидели, как она проявляется в JPA. Во-первых, мы рассмотрели требования спецификации JPA относительно Serializable . После этого мы рассмотрели Hibernate как самую популярную реализацию JPA. В конце мы рассмотрели, как объекты JPA работают с веб-серверами.

Как обычно, весь код, представленный в этой статье, можно найти на GitHub .