본문 바로가기

프로그래밍/Java

Serializable(2)

반응형

Serializable(2)

날짜: 2021년 6월 13일

학습목표

저번 포스팅에서 간단하게 Serializable의 정의와 용도에 대해서 알아봤다. 그럼 실제로 이런 직렬화는 어떻게 쓸수있는지 코드로 실습해보고, 또 어떤 특징들이 있는지 파악해보자.

  • 객체를 파일에 쓰고 읽는법
  • DMP Serializable 사례 분석

객체를 저장해보자.

자바에서는 ObjectOutputStream클래스를 사용하면 객체를 저장할 수 있다. 또 반대로 ObjectInputStream클래스를 사용하면 객체를 읽어드릴수 있다. 먼저 객체를 저장하는 법을 알아보자. 먼저 예제 코드에 사용될(저장되고 읽힐) DTO클래스 코드이다.

package serializable;

public class SerialDTO {
    private String bookName;
    private int bookOrder;
    private boolean bestSeller;
    private long soldPerday;
    public SerialDTO(String bookName, int bookOrder, boolean bestSeller, long soldPerday) {
        super();
        this.bookName = bookName;
        this.bookOrder = bookOrder;
        this.bestSeller = bestSeller;
        this.soldPerday = soldPerday;
    }
    @Override
    public String toString() {
        return "SerialDTO [bookName=" + bookName + ", bookOrder=" + bookOrder + ", bestSeller=" + bestSeller
                + ", soldPerday=" + soldPerday + "]";
    }
}

이제 실제로 객체를 저장하는 코드를 작성해보자.

package serializable;

import static java.io.File.separator;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class ManageObject {

    public static void main(String[] args) {
        ManageObject manager = new ManageObject();
        String fullPath = separator+"Serial.obj";
        SerialDTO dto = new SerialDTO("goj", 1, true, 100);
        manager.saveObject(fullPath, dto);
    }
    public void saveObject(String fullPath, SerialDTO dto) {
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        try {
            fos = new FileOutputStream(fullPath);
            oos = new ObjectOutputStream(fos);
            oos.writeObject(dto);
            System.out.println("success");
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            if(oos != null) {
                try {
                    oos.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if(fos != null) {
                try {
                    fos.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

코드가 복잡해보이지만, try-catch구문 때문이니, 잘 살펴보자. SerialDTO객체를 생성하고, 그 객체를 ObjectOutputStream객체에게 넘겨 파일로 저장하는 코드이다. 그럼 실행 후 결과는 어떻게 되었을까??

java.io.NotSerializableException: serializable.SerialDTO
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at serializable.ManageObject.saveObject(ManageObject.java:21)
    at serializable.ManageObject.main(ManageObject.java:13)

직렬화가 되어있지 않다는 오류가 출력되었다. SerialDTO클래스를 잘 살펴보았다면, Serializable을 구현하지 않을것을 볼수 있다. 그럼, Serializable을 다시 제대로 선언하고 실행해보자.

자바의 기본타입들은 기본적으로 직렬화가 된다.

public class SerialDTO implements Serializable {
    ... 생략
}

실행을 해보면, success가 콘솔 출력이 되는것을 볼수 있다. 그럼, 지정경로에 제대로 파일이 생겼는지도 확인해보자!

파일도 잘 저장된것을 확인할수 있다. 이 파일을 텍스트 편집기로 읽기는 힘들다. 지난번에 알아봤듯이, 바이트코드로 변환해 저장하기 때문이다. 그럼 이제 저장한 객체를 읽어보는 법을 알아보자!!

객체를 읽어보자!!

위의 코드에서 객체를 읽어오는 메소드를 작성해보자!

public void loadObject(String fullPath) {
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        try {
            fis = new FileInputStream(fullPath);
            ois = new ObjectInputStream(fis);
            Object obj = ois.readObject();
            SerialDTO dto = (SerialDTO)obj;
            System.out.println(dto);
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            if(ois != null) {
                try {
                    ois.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if(fis != null) {
                try {
                    fis.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

Output → Input으로 바뀌었고 대부분 코드 흐름이 비슷하니, 이해하기 힘들지 않을 것이다.

실행결과를 확인 해보자!

SerialDTO [bookName=goj, bookOrder=1, bestSeller=true, soldPerday=100]

SerialDTO클래스에 toString()을 구현했기 때문에, 객체를 잘 읽어왔음을 확인할 수있다.

그럼, 이 Serializable 객체가 변경되면 어떻게 될까?..... 이 상태에서 SerialDTO 클래스에 변수하나만 더 추가하도록 하자.

public class SerialDTO implements Serializable {
    private String bookName;
    private int bookOrder;
    private boolean bestSeller;
    private long soldPerday;
    private String booktype ="IT"; //추가된 변수
    ... 생략
}

실행을 하면 어떻게 될까??

java.io.InvalidClassException: serializable.SerialDTO; local class incompatible: stream classdesc serialVersionUID = -1945794971039993463, local class serialVersionUID = -9105029085510026625
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
    at serializable.ManageObject.loadObject(ManageObject.java:52)
    at serializable.ManageObject.main(ManageObject.java:16)

지난번 포스팅에서 알아본 것처럼 serialVersionUID가 다르다는 오류가 나오고 있다. 이런 오류를 해결하기 위해서는 명시적으로 serialVersionUID를 지정하면 된다. serialVersionUID를 명시적으로 지정, 다시 저장 후 읽어보자!

public class SerialDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    ... 생략
}

여기서 기존의 bookType 변수명을 bookTypes로 바꾸고, toString()메소드에도 내용을 변경했다.

객체의 정보가 바뀌었지만, serialVersionUID가 같으면 어떻게 될까??

SerialDTO [bookName=goj, bookOrder=1, bestSeller=true, soldPerday=100, booktypes=null]

잘나온다! 그러나, 객체의 정보가 바뀌었는데도 오류가 나지 않는다면 운영상황에서 데이터가 꼬일 수 있기 때문에 권장하지 않는다.

transient와 Serializable은 땔 수 없는 관계

한가지만 더 확인 해보자. SerialDTO의 bookOrder 선언문 앞에 transient 예약어를 선언해보자.

public class SerialDTO implements Serializable {
    private static final long serialVersionUID = 1L;
    private String bookName;
    transient private int bookOrder;
    ... 생략
}

이제, 다시 객체를 저장하고 읽어보자!

SerialDTO [bookName=goj, **bookOrder=0**, bestSeller=true, soldPerday=100, booktypes=IT]

bookOrder의 값이 0이다. 우리는 분명 1로 지정했으나, 0으로 나왔다. 왜일까??

객체를 저장하거나, 다른 JVM으로 객체를 보낼 때, transient 키워드가 선언된 변수는 직렬화 대상에서 제외된다.

  • 패스워드 등의 보안상 중요한 변수
  • 꼭 저장할 필요하 없는 변수

일 경우에는 transient 키워드로, 직렬화에서 제외할 수 있다.

Feedback

자바의신의 교재내용에 나온 내용을 공부했다. 정말 간단하게 코드를 사용하는 법에 대해서만 공부를 한듯 하다. 교재를 읽고나면, 직렬화는 단순히 저장하거나, 타 시스템으로 보낼때 오류가 나니까 직렬화를 쓴다. 정도로만 다가온다. 교재는 단순하게 문법을 익히는 위주로 봐야할 것 같다.

다음 포스팅에서는 직렬화를 왜 써야하고, 직렬화의 종류와 특징들에 대해서 공부해 봐야겠다.

알게된 점

  • 객체를 파일에 쓰고 읽는법

알아야할 점

  • 직렬화는 왜 쓰는가?
  • 직렬화의 종류
  • 직렬화는 어디에 쓰이는가?
  • 직렬화의 단점
  • DMP Serializable 사례 분석

References

자바 직렬화, 그것이 알고싶다. 실무편 - 우아한형제들 기술 블로그

자바 직렬화, 그것이 알고싶다. 훑어보기편 - 우아한형제들 기술 블로그

Java의 직렬화(Serialize)란?

자바의신 VOL2

반응형

'프로그래밍 > Java' 카테고리의 다른 글

NIO(1)  (0) 2021.06.14
Serializable(3)  (0) 2021.06.13
Serializable(1)  (0) 2021.06.11
Java I/O  (0) 2021.06.11
람다와 스트림의 사용법(2)  (0) 2021.06.10