2.5. 세션 객체 직렬화
이 장에서 애플리케이션 코드에 요구하는 조건은 이것 하나입니다.
세션이 WAS 메모리에 있을 때는 객체를 그대로 두면 됐지만, 데이터그리드로 보내려면 객체를 바이트로 바꿔야 합니다. 그래서 세션에 담는 객체는 Serializable 을 구현해야 합니다. 구현하지 않은 객체를 담으면 저장하는 시점에 오류가 납니다.
아래와 같이 User 객체를 세션에 담으려면 User 클래스가 Serializable 을 구현해야 합니다.
User user = new User();
...
session.setAttribute("key", user);
public class User implements Serializable {
}
확인할 것 네 가지
첫째, 안에 담긴 객체까지 모두 직렬화되어야 합니다. User 가 Address 를 필드로 갖고 있다면 Address 도 Serializable 이어야 합니다. 하나라도 빠지면 저장할 때 java.io.NotSerializableException 이 나고, 예외 메시지에 문제가 된 클래스 이름이 나옵니다.
둘째, 저장할 필요가 없는 필드는 transient 로 표시합니다. 데이터베이스 연결이나 파일 핸들처럼 직렬화할 수 없는 것, 그리고 다시 만들 수 있는 값은 transient 로 두어 저장 대상에서 뺍니다.
public class User implements Serializable {
private String userId;
private transient Connection conn; // 저장하지 않는다
}
셋째, serialVersionUID 를 선언합니다. 아래 "업그레이드할 때 세션이 깨지지 않게 하려면" 을 참고하십시오.
넷째, 세션에 큰 객체를 담지 않습니다. 세션은 요청마다 저장소를 오가므로, 담긴 객체가 크면 그만큼 네트워크와 변환 시간이 늘어납니다. 조회 결과 목록 같은 큰 데이터는 세션에 담지 말고 필요할 때 다시 조회하는 편이 낫습니다. 지금 세션이 얼마나 큰지는 "세션 메모리 사용량 보기" 로 확인할 수 있습니다.
업그레이드할 때 세션이 깨지지 않게 하려면
세션에 담는 클래스에는 serialVersionUID 를 직접 선언하십시오. 선언하지 않으면 애플리케이션을 새 버전으로 올릴 때 로그인한 사용자들이 한꺼번에 오류를 겪을 수 있습니다.
왜 문제가 되나
serialVersionUID 는 직렬화된 데이터와 클래스가 서로 맞는지 확인하는 값입니다. 선언하지 않으면 자바가 클래스 구조에서 자동으로 계산합니다 — 필드 이름과 타입, 메소드, 구현한 인터페이스가 모두 계산에 들어갑니다.
그래서 클래스를 조금만 고쳐도 값이 달라집니다. 필드를 하 나 추가하거나 이름을 바꾸는 것만으로 새 값이 나옵니다.
세션은 WAS 밖의 데이터그리드에 남아 있습니다. 그러니 애플리케이션을 새로 배포해도 저장소에는 이전 버전으로 저장된 세션이 그대로 있습니다. 새 코드가 그 세션을 읽으려 하면 값이 맞지 않아 실패합니다.
java.io.InvalidClassException: com.example.User; local class incompatible:
stream classdesc serialVersionUID = 8721905643012, local class serialVersionUID = 3390274612885
언제 드러나나
| 상황 | 무슨 일이 생기나 |
|---|---|
| 무중단 배포 중 | 옛 버전과 새 버전 인스턴스가 함께 도는 동안, 한쪽이 저장한 세션을 다른 쪽이 읽지 못합니다 |
| 배포를 마친 뒤 | 저장소에 남아 있던 이전 세션들이 타임아웃으로 없어질 때까지 오류가 이어집니다 |
| 되돌릴 때 | 새 버전에서 저장된 세션을 옛 버전이 읽지 못해 같은 문제가 반복됩니다 |
WAS 메모리만 쓰던 때는 드러나지 않던 문제입니다. 재시작하면 세 션이 함께 없어져 옛 데이터가 남지 않았기 때문입니다. 세션을 밖에 저장하면서 새로 생기는 조건입니다.
어떻게 하나
세션에 담는 모든 클래스에 값을 직접 적습니다. 값 자체는 무엇이든 되며, 클래스를 고쳐도 그 값을 바꾸지 않는 것이 핵심입니다.
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String userId;
private String name;
}
안에 담긴 객체도 마찬가지입니다. User 가 Address 를 갖고 있다면 Address 에도 선언합니다.
값을 고정해도 안전하지 않은 변경
serialVersionUID 를 고정하면 필드를 추가하는 변경은 안전 합니다. 이전 세션에는 그 필드가 없으므로 기본값(null, 0, false)으로 읽힙니다.
반면 아래는 값을 고정해도 오류가 나거나 값이 잘못 읽힙니다.
- 필드의 타입을 바꾸는 것 (
String→Long등) - 필드를 삭제하는 것 (읽을 때 무시되지만, 그 값을 쓰던 코드가 있으면 동작이 달라집니다)
- 클래스 이름이나 패키지를 바꾸는 것
이런 변경이 필요하면 배포 전에 저장소의 세션을 비우거나, 사용자가 적은 시간에 배포해 재로그인 영향을 줄이십시오.