1. 소개
이 사용방법(예제)에서는 JPA 엔터티와 Java 직렬화 가능 인터페이스가 혼합되는 방식에 대해 설명합니다. 먼저 java.io.Serializable 인터페이스와 왜 필요한지 살펴보겠습니다. 그런 다음 JPA 사양과 가장 널리 사용되는 구현으로 Hibernate를 살펴보겠습니다.
2. 직렬화 가능한 인터페이스란 무엇입니까?
Serializable 은 핵심 Java에서 발견되는 몇 안되는 마커 인터페이스 중 하나입니다. 마커 인터페이스 는 메서드나 상수가 없는 특수한 경우의 인터페이스입니다.
객체 직렬화는 Java 객체를 바이트 스트림으로 변환하는 프로세스입니다 . 그런 다음 이러한 바이트 스트림을 유선으로 전송하거나 영구 메모리에 저장할 수 있습니다. 역직렬화는 바이트 스트림을 가져와 Java 객체로 다시 변환하는 역 프로세스입니다. 객체 직렬화(또는 역직렬화)를 허용하려면 클래스에서 직렬화 가능 인터페이스를 구현해야 합니다. 그렇지 않으면 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 를 복합 키로 사용하는 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, "johndoe@gmail.com");
entityManager.persist(user);
User userDb = entityManager.find(User.class, userId);
assertEquals(userDb.email, "johndoe@gmail.com");
}
UserId 클래스가 Serializable 인터페이스를 구현하지 않으면 복합 키가 인터페이스를 구현해야 한다는 구체적인 메시지와 함께 MappingException 이 발생합니다.
3.2. 최대 절전 모드 @JoinColumn 어노테이션
Hibernate 공식 문서 는 Hibernate에서 매핑을 설명할 때 @JoinColumn 어노테이션 에서 referencedColumnName 을 사용할 때 참조된 필드가 직렬화 가능해야 한다고 언급합니다 . 일반적으로 이 필드는 다른 엔터티의 기본 키입니다. 복잡한 엔티티 클래스의 드문 경우지만 참조는 직렬화 가능해야 합니다.
이메일 필드가 더 이상 문자열 이 아니라 독립적인 엔티티인 이전 사용자 클래스를 확장해 보겠습니다. 또한 사용자를 참조하고 필드 유형 이 있는 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에서 어떻게 작동하는지 살펴보았습니다. 먼저 Serializable 에 대한 JPA 사양의 요구 사항을 살펴보았습니다 . 그 후, 우리는 JPA의 가장 인기 있는 구현으로 Hibernate를 조사했습니다. 마지막으로 JPA 엔터티가 웹 서버에서 작동하는 방식을 다루었습니다.
평소와 같이 이 기사에 제공된 모든 코드는 GitHub 에서 찾을 수 있습니다 .