본문 바로가기
Java

[Java] 자바 직렬화 (Serialize)

by 코리늬 2019. 7. 15.

자바 transient 를 찾아보려 했는데 transient를 알기 위해서는 Serialize에 대한 이해가 필요하다고 한다.

그 이유는 transient는 Serialize 하는 과정에서 제외하고 싶은 경우 선언하는 키워드이기 때문이다.

직렬화(Serialize)

  • 자바 시스템 내부에서 사용되는 Object 또는 Data를 외부의 자바 시스템에서도 사용할 수 있도록 byte 형태로 데이터를 변환하는 기술.
  • JVM의 메모리에 상주(힙 또는 스택) 되어 있는 객체 데이터를 바이트 형태로 변환하는 기술

역직렬화(Deserialize)

  • byte로 변환된 Data를 원래대로 Object나 Data로 변환하는 기술을 역직렬화(Deserialize)라고 부른다.
  • 직렬화된 바이트 형태의 데이터를 객체로 변환해 JVM으로 상주시키는 형태

직렬화 조건

  • java.io.Serializable 인터페이스를 상속받은 객체는 직렬화 할 수 있는 기본 조건이다.
public class Member implements Serializable {
    private String name;
    private String email;
    private int age;

    public Member(String name, String email, int age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }
    @Override
    public String toString() {
        return String.format("Member{name='%s', email='%s', age='%s'}", name, email, age);
    }
}

직렬화 방법

  • java.io.ObjectOutputStream 을 사용해 직렬화를 진행한다.
public static void main(String[] args){
    Member member = new Member("코린이", "corin2@tistory.com",27);
    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));
}

역직렬화 조건

  • 직렬화 대상이 된 객체의 클래스가 클래스 패스에 존재해야 하며, import 되어 있어야 한다.
  • 직렬화와 역직렬화를 진행하는 시스템이 서로 다를 수 있다는 점을 고려해야 한다.
  • 자바 직렬화 대상 객체는 동일한 serialVersionUID를 가지고 있어야 한다.
    • private static final long serialVersionUID = 1L;

역직렬화 방법

  • java.io.ObjectInputStream을 사용해 진행한다.
public static void main(String[] args){
    //직렬화 예제에서 생성된 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);
        }
    }
}

예외 처리를 왜 저런식으로 했는지 궁금하다면 [Effective java]try with resources 사용 링크를 참조하자.

직렬화 방법에는 여러 Format이 존재한다.

  • 표 형태의 다량의 데이터는 CSV 형태
  • 구조적인 데이터는 XML, JSON 형태

CSV 파일 (comma-separated values)

몇가지 필드를 ' , ' 로 구분한 텍스트 데이터 및 파일이다. 확장자는 .csv 이고 스프레드시트나 데이터베이스 소프트웨어에서 많이 쓰인다.

CSV

  • 데이터를 표현하는 방법 중 가장 많이 사용하는 방법으로 콤마(,)를 기준으로 데이터를 구분한다.
Member member = new Member("코린이", "corin2@tistory.com",27);
//member 객체를 csv로 변환
String csv = String.format("%s,%s,%d", member.getName(), member.getEmail(), member.getAge());
System.out.println(csv);

JSON

Member member = new Member("코린이", "corin2@tistory.com",27);
//member 객체를 json로 변환
String json = String.format("{\"name\":\"%s"\",\"email\":\"%s\",\"age\":%d}",member.getName(), member.getEmail(), member.getAge());
System.out.println(json);

사용하는 이유

  • 복잡한 데이터 구조의 클래스 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화가 가능하다.
  • 데이터 타입이 자동으로 맞춰지기 때문에 관련 부분을 큰 신경 쓰지 않아도 된다.

사용되는 곳

서블릿 세션

  • 세션을 서블릿 메모리 위에서 운용하면 직렬화가 필요하지 않지만, 파일로 저장하거나 세션 클러스터링, DB를 저장하는 옵션 등을 선택하면 세션 자체가 직렬화가 되어 저장된다.

캐시

  • Encache, Redis, Memcached 라이브러리 시스템에 많이 사용된다.

자바 RMI(Remote Method Invocation)

  • 원격시스템 간의 메시지 교환을 위해서 사용하는 자바에서 지원하는 기술.

자바 직렬화의 단점

역직렬화시 클래스 구조 변경 문제

기존 멤버 클래스

 public class Member implements Serializable {
      private String name;
      private String email;
      private int age;
    // 생략
  }

직렬화한 Data

rO0ABXNyABp3b293YWhhbi5ibG9nLmV4YW0xLk1lbWJlcgAAAAAAAAABAgAESQADYWdlSQAEYWdlMkwABWVtYWlsdAASTGphdmEvbGFuZy9TdHJpbmc7TAAEbmFtZXEAfgABeHAAAAAZAAAAAHQAFmRlbGl2ZXJ5a2ltQGJhZW1pbi5jb210AAnquYDrsLDrr7w=

멤버 클래스 속성이 추가된 상황

 public class Member implements Serializable {
      private String name;
      private String email;
      private int age;
     //phone 추가
     private String phone;

  }

속성이 추가된 상황에서 직렬화한 Data를 역직렬화 한다면??

  • java.io.InvalidClassException 발생

  • 직렬화하는 시스템과 역직렬화하는 시스템이 다른 경우 발생

  • 각 시스템에서 사용하고 있는 모델 버젼 차이 발생시 생기는 문제

그렇다면 ??

  • 위에서 언급했던 SUID(serialVersionUID)를 정의해야 한다.
  • Default는 클래스의 기본 해쉬값을 사용한다.

다른문제

  • String -> StringBuilder, int -> long으로 변경해도 역직렬화에서 Exception 발생한다.
  • 멤버 변수가 빠지면 Exception 대신 nul값이 들어간다.

직렬화 Data Size 문제

{"name" : "코린이", "email" :"corin2@tistory.com","age":27 }
serializedMember (byte size = 146)
json (byte size = 62)
  • 위 처럼 간단한 객체의 내용도 크기 차이가 많이 나는것을 볼 수 있다.
  • 일반적인 메모리 기반의 Cache에서는 Data를 저장할 수 있는 용량의 한계가 있기 때문에 Json 형태와 같은 경량화된 형태로 직렬화하는 것도 좋은 방법이다.

요약

  • 외부 저장소로 저장되는 데이터는 짧은 만료시간의 데이터를 제외하고 자바 직렬화의 사용을 지양한다.
  • 역직렬화는 반드시 예외가 생긴다는 점을 고려한다.
  • 자주 변경되는 비즈니스적인 데이터는 직렬화를 하지 않는다.
  • 긴 만료 시간을 가지는 데이터는 JSON 등 다른 포맷을 사용해 저장한다.

 

궁금했던 부분

  1. transient 키워드를 적용시킨 name필드의 역직렬화 결과 데이터가 null 인데, 애초에 직렬화를 할 때부터 name 필드의 Data는 직렬화 대상에서 제외되었기 때문에 직렬화가 되지 않았기 때문에 역직렬화 결과가 null 값인 건가요??

  2. transient 를 적용시키면 혹시 getter/setter에도 영향을 주게 되는건가요??

 

nesoy님의 답변

안녕하세요 @n1tjrgns님.

  1. 저도 궁금해서 조금 더 찾아보니 실제로 직렬화 대상에서 제외하여 직렬화하기 때문에 역직렬화 결과가 null로 나오는 거 같습니다.
    Variables may be marked transient to indicate that they are not part of the persistent state of an object.
  1. Java에서 Method Level에 transient 키워드를 적용시키는 방법은 없어보입니다. ㅠㅠ 실제로 Method는 존재에는 영향이 없지만 값에 대해서는 영향이 있어보입니다. :)

감사합니다.

 

너무 친절하게 알려주셔서 감사했다.

 

참고

https://nesoy.github.io/articles/2018-04/Java-Serialize

'Java' 카테고리의 다른 글

[Effective Java] 28. 배열보다는 리스트를 사용해라  (0) 2019.07.17
[Java] 자바 transient  (0) 2019.07.16
split 세부적으로 사용하기  (0) 2019.04.22
[Effective Java] try with resources  (0) 2019.03.29
자바 valueOf()  (0) 2019.03.28

댓글