본 글은 글쓴이의 개인적인 생각이 담겨있을 수 있습니다.
PICK 프로젝트 - pick-server-Saturn
https://github.com/DSM-PICK/pick-server-Saturn
PICK 프로젝트를 유지보수하다가 새로운 기능을 추가할 일이 생겼다.
새로운 기능은 학년별로 출결 현황을 간략하게 보여주는 기능이었다.
기능 자체는 검색해서 보여주기만 하면 되기 때문에 간단한 기능이었는데,
이 기능을 구현하고 테스트 코드를 작성하는 과정에서 문제가 발생하였다.
새로 구현한 기능을 테스트하는 코드의 종류는 다음과 같다.
여기서 통합 테스트는 테스트에 성공하고 Service와 Repository 레이어 단위 테스트는 실패하게 된다.
Service와 Repository 단위 테스트에서는 다음과 같은 에러 메시지를 뱉어냈다.
Error attempting to apply AttributeConverter; nested exception is javax.persistence.PersistenceException: Error attempting to apply AttributeConverter
org.springframework.orm.jpa.JpaSystemException: Error attempting to apply AttributeConverter; nested exception is javax.persistence.PersistenceException: Error attempting to apply AttributeConverter
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:408)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:235)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:145)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at com.sun.proxy.$Proxy118.findByActivityDateAndStudentNumberStartsWith(Unknown Source)
at com.dsm.pick.repository.AttendanceRepositoryTest.학년과 날짜로 출석부 조회 OK(AttendanceRepositoryTest.kt:105)
...
에러 메시지를 읽어보면 AttributeConverter를 사용하다가 에러가 발생한 것으로 추측할 수 있다.
나는 AttributeConverter 인터페이스를 이용하여 Entity <-> Database 컨버터를 만들었었다.
그러므로 내가 만든 AttributeConverter에 문제가 있을 것이라고 생각했다.
하지만 이번 기능을 구현할 때는 Request Converter를 추가했지,
AttributeConverter에는 아무 것도 건들지 않았다.
문제의 원인이 흐려지자 Google에 JpaSystemException
에 대해서 찾아보니
관련 StackOverflow 문서를 찾을 수 있었다.
하지만 본 문서는 자바로 만들었는데 null 체크를 제대로 하지 않아 생긴 문제였다.
이번에 만든 Request Converter인 GradeConverter에 의심을 가지고
어노테이션은 잘 붙였는지, Converter 인터페이스를 정확히 숙지하고 구현하였는지 등을 찾아보았지만
애초에 Request Converter 관계 없이 Service 레이어에서도 에러가 발생하였기에 말도 안 되는 짓이었다.
그래서 에러 메시지를 정확히 다시 읽어보기로 하였고 문제는 그곳에서 해결할 수 있었다.
에러 메시지는 문단 단위로 봤을 때 아래로 내려갈 수록 더 근본적인 문제를 알려준다.
그러므로 가장 밑에 있는 에러 메시지를 보면 문제를 찾을 수 있다.
당연히 일반적인 상황에서는 밑에 있는 메시지까지 읽어보는 것이 정상이나,
정신없이 버그 픽스를 하고 꼼꼼히 살펴보지 않아 그것을 놓친 것 같다.
가장 밑 에러 메시지는 다음과 같다.
Caused by: com.dsm.pick.exception.NonExistStateException: 허용하지 않는 출석상태입니다. [state = 異쒖꽍]
at com.dsm.pick.domain.converter.StateConverter.convertToEntityAttribute(StateConverter.kt:14)
at com.dsm.pick.domain.converter.StateConverter.convertToEntityAttribute(StateConverter.kt:9)
at org.hibernate.metamodel.model.convert.internal.JpaAttributeConverterImpl.toDomainValue(JpaAttributeConverterImpl.java:45)
at org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter$2.doConversion(AttributeConverterSqlTypeDescriptorAdapter.java:140)
... 141 more
하.. NonExistStateException은 내가 만든 커스텀 에러고,
뒤의 에러 메시지는 에러 원인을 쉽게 알아내기 위해 만들어 놓은 것이다.
출석 상태가 異쒖꽍인 걸 보아하니 진짜 저런 문자를 넣었을리는 없고
IntelliJ 터미널에서 한글이 인코딩이 깨지는 것 같았다.
Windows 기준 [Help] -> [Edit Custom VM Options] -> idea64.exe.vmoptions 파일에
-Dfile.encoding=UTF-8
을 추가해주고 IntelliJ를 완전히 껐다키면
터미널에 정상적으로 한글 인코딩이 적용된다.
예전에도 에러 메시지를 끝까지 안 읽어서 생긴 문제가 더러 있엇는데
이제부터라도 정확히 읽는 습관을 들여야겠다.