"데이터 가공이 필요해요."
공통 기능(엑셀 다운로드)에서 메모리를 절약하기 위해 대용량 데이터를 조회할 경우 마이바티스 `Cursor` 타입을 매개변수로 받도록 설계했다. 여기서 문제를 명확히 하기 위해 커서 데이터가 전부 소비됐으면, 예외를 던져주기로 했다.
class ExcelFile {
public void write(Cursor<?> cursor) {
if (cursor.isConsumed()) throw new IllegalArgumentException("데이터가 없어요.");
try (cursor) {
// cursor 데이터를 엑셀 파일에 작성하는 로직
}
}
}
공통 기능을 사용하는 개발자는 커서 데이터 타입을 인자로 넘겨줘야 하는데, 흔히 개발자들이 DB에서 조회된 이 커서 데이터를 인자로 넘겨주기 전에 가공해야 하는 경우가 생긴다. 흔한 컬렉션(List, Set, Map) 타입은 개발자들이 인자로 넘겨주기 전에 가공하면 되지만, 문제는 이 커서 타입 데이터는 스트림이라는 것이다. 커서 데이터를 한번 읽고 넘겨준다면, 공통 기능이 제대로 동작하지 않을 것이다.
Cursor<Object> objects = repository.findAll(); // db 데이터 조회
for (obj : objects) {
// 객체 수정
}
ExcelFile file = new ExcelFile();
file.write(objects); // **예외 발생** "데이터가 없어요."
개발자들이 공통 기능을 사용하기 전에 데이터를 수정할 수 있으면서, (메모리 절약을 위해) 커서 데이터는 유지해야 한다. 이를 해결하기 위해 데코레이터 패턴을 사용한 경험을 작성하고자 한다.
Mybatis Cursor Stream
마이바티스에서 제공하는 데이터 타입인데, 대용량 쿼리 조회 시 발생할 수 있는 메모리 부족 문제를 해결해 준다. 쿼리문 실행 결과를 마치 페이징처리된 것처럼 조금씩 어플리케이션 메모리에 스트림 방식으로 조금씩 로드하는 것이다.
하지만 스트림방식의 특성상, 한 번 소비되면 재사용 될 수 없기 때문에 데이터를 여러 번 읽을 수 없다.
데코레이터(Decorator) 패턴
패턴에 대한 것은 잘 설명된 글이 많아 간단히만 설명하겠다.
먼저 데코레이터 패턴에 대해 한 단어로 설명하자면 객체꾸미기라고 말할 수 있다. 객체를 같은 타입으로 계속해서 겉에 꾸며나가는 것이다.
흔히 예로 많이 드는 카페에서 주문을 받는 상황에서, 카페라떼에 휘핑크림추가 + 두유변경 + 사이즈업과 같은 여러 개의 옵션을 추가하는 기능을 구현할 때 적합한 패턴이다.
java.io 패키지를 보면 대부분이 데코레이터 패턴을 사용했다. 같은 타입의 데코레이터를 원하는 만큼 감싸서 기능을 추가하도록 설계되었다. 아래 코드는백준에서 습관적으로 작성하는읽기∙쓰기 작업 속도를 향상시키기 위해 버퍼 기능을 추가한 것이다.
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out))) {}
Cursor Decorator
커서 데이터를 읽을 때, 특정 로직을 수행할 수 있도록 하는 추가 기능 데코레이터를 제공했다.
class T CursorDecorator<T> implements Cursor {
private final Cursor<T> cursor;
private final Consumer<T> consumer;
public CursorDecorator(Cursor<T> cursor, Consumer<T> consumer) {
this.cursor = cursor;
this.consumer = consumer;
}
@Override
public void forEach(Consumer<? super T> action) {
for (c : cursor) {
this.consumer.accept(c);
action.accept(c);
}
}
}
이제 데이터 가공이 필요한 개발자들은 아래와 같이 사용할 수 있다.
Cursor<Object> objects = repository.findAll(); // db 데이터 조회
Cursor<Object> modifiedObjects = new CursorDecorator(objects, (obj) -> {
// 객체 수정
});
ExcelFile file = new ExcelFile();
file.write(modifiedObjects); // 성공
남들이 보기엔 별 문제도 아닌 간단한 상황일 수도 있지만, 개인적으로 예전에 공부했던 디자인패턴을 실제 문제 상황에 적용해 해결했다는 것이 뿌듯하기도 하고 재밌었다.
'Backend > Java' 카테고리의 다른 글
Java에서 Null을 다루는 방식 (0) | 2023.05.22 |
---|---|
공통 lib -> spring-boot-starter로 바꾸기(3) (0) | 2023.04.23 |
공통 lib -> spring-boot-starter로 바꾸기(2) (0) | 2023.03.20 |
공통 lib -> spring-boot-starter로 바꾸기(1) (0) | 2023.02.13 |
Session은 Controller까지만 (0) | 2022.10.09 |