Legacy DB의 JPA Entity Mapping (Enum Converter 편)
안녕하세요.
저는 우아한형제들 비즈상품개발팀의 이은경입니다.
Legacy DB의 JPA Entity Mapping (복합키 매핑 편)에 이어
저는 DB의 코드값과 Java Enum을 연결해주는 과정에서 유용하게 사용한 @Convert
에 대해서 설명하려고 합니다.
기
그동안 광고 시스템에서는 모든 상태값에 대해서 아래와 같이 DB 코드 테이블에 정의된 코드값을 사용하고 있었습니다.
단순히 status code만 보고는 실제로 무슨 상태인지 value를 알기가 힘들었고,
어느 정도 개발/운영을 하다보면 자주 사용하는 상태값은 머리 속에 있지만
새로운 사람이 오는 경우, 혹은 자주 안보던 테이블을 오랫만에 보는 경우에는 또 다시 코드 조회 페이지에 들어가
‘0’이 무슨 값인지, 혹은 ‘결제중’ 값을 가진 코드가 뭔지 확인해야봐야 하는 경우가 생기고는 했더랬습니다.
승
하지만 이제 우리에게는 Enum이 있으니 굳이 코드값을 외울 필요가 없어졌습니다!
(enum의 유용성은 이동욱님 Java Enum 활용기를 참고해주세요.)
그렇다고 모든 소스와 DB를 뒤져 기존에 사용중인 ‘0’, ’1’, ’2’ 코드를
‘REQUEST’, ‘PROGRESS’, ‘CANCELED’로 바꿀 수는 없었습니다.
DB에는 계속 기존 코드값으로 저장하되 java에서의 상태값은 Enum으로 변경해서 매핑하자!
그런데 편리하게도! 이런 매핑을 너무나 심플하게 해결해주는 AttributeConverter
라는 녀석이 있었습니다.
전
사용법은 매우 심플합니다.
엔티티 필드를 Enum으로 정의하고 상단에 @Convert(convert = {컨버터클래스.class}) 를 붙여줍니다.
각 컨버터는 AttributeConverter의 convertToDatabaseColumn
(Enum -> db데이터)과
convertToEntityAttribute
(db데이터 -> Enum)를 구현합니다.
그리고 Enum에서 레거시코드를 통해 해당 Enum을 찾는 메소드를 추가해줍니다.
이렇게 세부분을 작성하면 db에서 데이터를 읽어 JPA entity로 매핑할 때 Enum <-> db값 이 자동으로 컨버팅됩니다.
Enum에 기존 레거시 코드값만 셋팅해놓으면 컨버터가 알아서 매핑해주니
넘나 세상 편함.
그런데 테이블에 이런 코드값이 많다보니 컨버터가 하나 둘 셋 넷 다섯.. 마흔열둘..
이 많은 컨버터가 다 동일한 코드를 반복하고 있었고, 많은 Enum에서 항상 ofLegacycode 메소드가 반복되어야 했습니다.
결
따라라라라~
우리 코드가 이렇!게 달라졌습니다~
각 Enum은 LegacyCommonType를 구현하고
LegacyCommonType는 공통으로 필요한 getter메소드가 있습니다.
그리고 기존 레거시 코드로 Enum을 찾아오는 부분은 util 클래스를 만들어 static method로 사용합니다.
모든 컨버터는 AbstractLegacyEnumAttributeConverter 컨버터를 상속만 하고,
생성자에서 null이 들어올 수 있는지(기존 DB에 null이 아닌 blank값 처리를 위한 용도)와 각 Enum의 이름만 지정해줍니다.
공통 컨버터는 Enum클래스, 로그 및 에러메세지를 위한 Enum name, 그리고 null 허용여부를 필드로 가지고 있습니다.
그리고 아까 각 컨버터에서 반복하던 부분을 구현합니다.
물론 코드수 만큼 Enum + Converter 클래스를 만들어야하는 수고로움은 있으나
java에서 string 코드값으로 헷갈릴 필요가 없다 + 기존 db 값과 사용하는 레거시 코드까지 모두 변경할 필요가 없다 는
장점으로 이 정도의 귀찮음은 극뽁~
외
추가로 컨버터 홀릭이 되어 아무 생각 없이 저지른 실수가 하나 있는데요
저희가 가지고 있는 정보 중에 db에 암호화가 되어 저장하는 항목이 있었습니다.
암호화된 값은 자바 코드에서는 알 필요가 없고,
어차피 모든 로직과 화면단에서는 복호화해야하기 때문에
컨버터를 써서 자바에서는 복호화된 값을 쓰고 저장할 때만 암호화하면 되겠네? 라고 단순하게 생각해서
암복호화 로직을 컨버터에 넣었습니다.
테스트로 한두개 뽑아 볼 때는 아주 유용했습니다.
구론데.. 구로나..
서버에 올려서 테스트 하는데 해당 엔티티와 연관된 리스트 페이지가 넘나 느리네…
왜 때문에? 베타라 그런가? ('')a…
컨버터는 해당 엔티티를 db에서 꺼내와 매핑할 때마다 무조건 컨버팅하기 때문에,
리스트에서는 그 암호화된 필드를 쓰지 않더라도 무조건 암/복호화 로직을 거치고 있었고,
여기서 함정은..
그 암/복호화 로직이 api로 통신하는 모듈이었습니다.
그래서 20건씩 리스트 뿌려줄 때마다 무조건 복호화 api 호출 x20 을 하고 있었고..
행여라도 api 통신 실패가 되면 아예 엔티티 매핑 실패가 되서 리스트가 나오지 않..
util성 클래스와 컨버터 같은 로직에서는 외부와 통신하거나 외부에 의해 영향받는 부분이 없는
순수 나만의 것이어야 합니다.
당연한 얘긴데? 할 수도 있지만..
세상 어딘가엔 저 같은 실수를 하는 사람도 또 있지 않겠습니까?
수만 수십만 개발자 중에 설마 저 하나 그랬겠습니까. (사실 그랬…)
세상에 존재할지 어떨지 모르는 나 같을 또 한명의 개발자를 위해 listen and repeat.
That must have absolutely only thats logic in the util class or converter class, not using api communicating or not being affected by the outside (나니?)
welcome feedback! thank you!