JAVA8 Consumer 사용기 & JPA Side Effect 발생

또리·2022년 7월 26일

JAVA

목록 보기
1/3

서론

회사의 AS-IS 소스 중에 1000줄에 달하는 반복적인 소스가 있었다.
이 소스를 개선하기 위해 JAVA 8의 Consumer를 사용한 적이 있는데 그 과정과 발생했던 Side Effect에 대해 설명하고자 한다.

문제점

기존 AS-IS 소스는 다음과 같다.

if (!StringUtils.equal(afEntity.getName(), bfEntity.getName()) {
	history.beforValue(bfEntity.getName());
    history.afterValue(afEntity.getName());
    bfEntity.setName(afEntity.getName());
    historyRepository.insert(history);
}

... X100개

위 코드는 afEntity 테이블의 name과 bfEntity 테이블의 name을 비교해서 다르다면 history 테이블에 적재를 하고 bfEntity의 name을 afEntity의 name으로 변경하는 로직이다.

문제는 이런 코드가 id, phoneNo, email 등등으로 해서 X100개가 있었다는 사실...!!!

저 테이블은 고객정보 테이블이었고 관리하는 고객항목만 100개가 넘어갔다...

물론 getter 메소드만 사용한다면 Consumer를 사용하지 않고 별도로 공통된 부분만 메소드로 빼서 getName() 값을 파라미터로 던질 수 있었다.

하지만 문제는 bfEntity의 name을 afEntity의 name으로 set 하는 부분을 어떻게 변경할 것인가였다. 고민하다 생각한 방법은 set 하는 부분을 Consumer로 넣어주는 것이었다.

해결방법

그래서 변경된 TO-BE 소스는 아래와 같다.

BfEntity bfEntity = bfRepository.findById(); // BfEntity 조회
AfEntity afEntity = afRepository.findById(); // AfEntity 조회

setAfterValue(bfEntity.getName(), afEntity.getName(), bfEntity, (BfEntity x) -> x.setName(afEntity.getName()));
setAfterValue(bfEntity.getId(), afEntity.getId(), bfEntity, (BfEntity x) -> x.setName(afEntity.getId()));
... X100개

// 새로운 공통 함수를 생성
private void setAfterValue(String bfVal, String afVal, BfEntity bfEntity, Consumer<BfEntity> consumer) {
	if (!StringUtils.equal(bfVal, afVal) {
    	history.beforValue(bfVal);
    	history.afterValue(afVal);
        consumer.accept(bfEntity);
        historyRepository.save(history);
    }
}

setAfterValue() 라는 새로운 공통 함수를 작성하였고 setter 부분은 Consumer의 accept를 호출하여 처리하도록 변경하였다.

Consumer는 1개의 Type T 인자를 받고 리턴 값이 없는 함수형 인터페이스이다.

물론 Consumer를 이렇게 사용하는 것이 맞는지는 모르지만 반복작인 작업에서 함수형 인터페이스는 꽤 편리한 기능인 것 같다. 더 나은 방법이 있다면..알려주세요...ㅠㅠ

또 다시 문제점...

문제는 로그에 history 테이블의 insert 쿼리와 bfEntity 테이블의 update 쿼리가 반복적으로 출력된다는 것이었다.

DEBUG INSERT INTO HISTORY ~
DEBUG UPDATE BF_ENTITY ~
DEBUG INSERT INTO HISTORY ~
DEBUG UPDATE BF_ENTITY ~

원래대로라면 history 테이블의 insert만 실행되어야 하고 최종적으로 단 한번 트랜잭션이 종료되는 시점에 DirtyChecking으로 bfEntity 테이블의 update 쿼리가 실행되어야 하는데
setAfterValue() 메소드가 실행이 끝나면 자동으로 DirtyChecking이 되는 것이었다.

일단 원인은 찾았으니 해당 로직은 변경해서 더티체킹이 실행되지 않는 방식으로 변경하였다.

예상으로 함수형 인터페이스를 실행시키면 내부적으로 별도의 트랜잭션이 생기는 것 같고 트랜잭션이 consumer.accept 가 종료되는 시점에 update가 되는 것 같다.
정확한 이유는 더 확인해봐야할 것 같다!

profile
공인중개사를 공부하는 금융 개발자

0개의 댓글