# 자바 직렬화

# 자바 직렬화란?

정의

자바 직렬화란 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터 변환하는 기술을 말합니다.

직렬화

  • 그리고 바이트로 변환된 데이터를 다시 객체로 변환하는 기술(역직렬화)을 포함해서 이야기합니다.
  • 시스템적으로는 JVM의 메모리에 상주(힙 또는 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술과, 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 형태를 같이 이야기합니다.

# 자바 직렬화의 조건

  • 자바 기본(primitive) 타입과 java.io.Serializable 인터페이스를 상속받은 객체는 직렬화/역직렬화 할 수 있는 기본 조건을 가집니다.



 





@Getter
@ToString
@AllArgsConstructor
public class Member implements Serializable {
    private String name;
    private String email;
    private int age;
}
  • 직렬화 대상 : 인터페이스 상속 받은 객체, Primitive 타입의 데이터
  • Primitive 타입이 아닌 Reference 타입처럼 주소값을 지닌 객체들은 바이트로 변환하기 위해 Serializable 인터페이스를 구현해야 합니다.

# 직렬화 하는 방법

  • 직렬화는 java.io.ObjectOutputStream 객체를 이용하여 수해할 수 있습니다.





 


 






Member member = new Member("무송", "songe08@gmail.com", 25);
byte[] serializedMember;

try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
    try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
        oos.writeObject(member);
        
        // serializedMember -> 직렬화된 member 객체 
        serializedMember = baos.toByteArray();
    }
}

// 바이트 배열로 생성된 직렬화 데이터를 base64로 변환
System.out.println(Base64.getEncoder().encodeToString(serializedMember));
  • 위의 과정을 통해 객체를 직렬화하여 바이트 배열(byte [])로 변환하였습니다.

# 자바 역직렬화의 조건

  • 자바 직렬화 대상 객체는 동일한 serialVersionUID 를 가지고 있어야 합니다.
private static final long serialVersionUID = 1L;
  • 위의 코드에서 serialVersionUID를 선언하지 않아도, 자동으로 해시값이 할당됩니다.
  • 직접 설정하는 이유는 기존의 클래스 멤버 변수가 변경되면 serialVersionUID가 달라지는데, 역직렬화 시 달라진 넘버로 Exception이 발생될 수 있습니다.
  • 따라서 직접 serialVersionUID을 관리해야 클래스의 변수가 변경되어도 직렬화에 문제가 발생하지 않게 됩니다.
  • serialVersionUID을 관리하더라도, 멤버 변수의 타입이 다르거나, 제거 혹은 변수명을 바꾸게 되면 Exception은 발생하지 않지만 데이터가 누락될 수 있습니다.

# 역직렬화 하는 방법






 

 





// 직렬화 예제에서 생성된 base64 데이터 
String base64Member = "...생략";
byte[] serializedMember = Base64.getDecoder().decode(base64Member);

try (ByteArrayInputStream bais = new ByteArrayInputStream(serializedMember)) {
    try (ObjectInputStream ois = new ObjectInputStream(bais)) {
        // 역직렬화된 Member 객체를 읽어온다.
        Object objectMember = ois.readObject();
        Member member = (Member) objectMember;
        System.out.println(member);
    }
}
  • ObjectInputStream로 역직렬화를 수행하며, Byte의 값을 다시 객체로 저장하는 과정입니다.

# 직렬화를 사용하는 이유

# 문자열 형태의 직렬화 방법

  • 직접 데이터를 문자열 형태로 확인 가능한 직렬화 방법입니다.
  • 범용적인 API나 데이터를 변환하여 추출할 때 많이 사용됩니다.

# CSV

  • 표형태의 다량의 데이터를 직렬화시 CSV가 많이 쓰입니다.
Member member = new Member("무송", "songe08@gmail.com", 25);
String csv = String.format("%s,%s,%d",member.getName(), member.getEmail(), member.getAge()); 

# JSON

  • 최근에 가장 많이 사용하는 포맷으로 자바스크립트에서 쉽게 사용 가능하고, 다른 데이터 포맷 방식에 비해 오버헤드가 적기 때문에 때문에 인기가 많습니다.
  • JSON 같은 경우는 구조적인 데이터를 전달하는 API 시스템 등에서 많이 사용하고 있습니다.
Member member = new Member("무송", "songe08@gmail.com", 25);
String json = String.format("", member.getName(), member.getEmail(), member.getAge());
  • 자바에서는 Jackson, GSON 등의 라이브러리를 이용해서 변환할 수 있습니다.

# 자바 직렬화의 장점 (자바 직렬화를 써야 하는 이유)

  • 자바 직렬화는 자바 시스템에서 개발에 최적화되어 있습니다.
  • 복잡한 데이터 구조의 클래스의 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화/역직렬화가 가능합니다.
  • 당연하게 보이는 장점 중에 하나지만 데이터 타입이 자동으로 맞춰지기 때문에 관련 부분을 큰 신경을 쓰지 않아도 됩니다.
  • 그렇게 역직렬화가 되면 기존 객체처럼 바로 사용할 수 있게 됩니다.

# 자바 직렬화는 언제 사용하나요?

  • JVM의 메모리에서만 상주되어있는 객체 데이터를 그대로 영속화(Persistence)가 필요할 때 사용됩니다.
  • 서블릿 세션 (Servlet Session)
    • 파일로 저장하거나 세션 클러스터링, DB를 저장하는 옵션 등을 선택하게 되면 세션 자체가 직렬화가 되어 저장되어 전달됩니다.
  • 캐시 (Cache)
    • 메모리, 외부 저장소, 파일 등을 저장소를 이용해서 데이터 객체를 저장한 후 동일한 요청이 오면 DB를 다시 요청하는 것이 아니라 저장된 객체를 찾아서 응답하게 하는 형태를 보통 캐시를 사용한다고 합니다.
  • 자바 RMI(Remote Method Invocation)
    • 자바 RMI를 간단하게 이야기하자면 원격 시스템 간의 메시지 교환을 위해서 사용하는 자바에서 지원하는 기술입니다.
    • 원격의 시스템의 메서드를 호출 시에 전달하는 메시지(보통 객체)를 자동으로 직렬화시켜 사용됩니다.

# 결론

  1. 외부 저장소로 저장되는 데이터는 짧은 만료시간의 데이터를 제외하고 자바 직렬화를 사용을 지양합니다.
  2. 역직렬화시 반드시 예외가 생긴다는 것을 생각하고 개발합니다.
  3. 자주 변경되는 비즈니스적인 데이터를 자바 직렬화을 사용하지 않습니다.
  4. 긴 만료 시간을 가지는 데이터는 JSON 등 다른 포맷을 사용하여 저장합니다.

# 참고자료