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
자바 직렬화, 그것이 알고싶다. 실무편 - 우아한형제들 기술 블로그
자바 직렬화, 그것이 알고싶다. 훑어보기편 - 우아한형제들 기술 블로그
자바의신 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 |