프로젝트 중 MemberDTO에 Status 필드를 기존 String 타입에서 새로 만든 MemberStatus Enum 타입으로 변경하는 과정에 있어서 문제에 직면했습니다. 데이터베이스와 객체 간의 매핑에서 이넘 타입이 제대로 처리되지 않아, 애플리케이션의 안정성과 데이터의 일관성에 문제가 생길 수 있는 상황이었습니다. 이 글에서는 그 문제를 어떻게 진단하고 해결했는지 공유하고자 합니다.
MemberStatus 이넘 타입을 데이터베이스의 문자열 값과 매핑하는 과정에서 예외가 발생했습니다. 특히 MemberMapper에서 사용자의 상태를 INACTIVE로 초기 설정하는 과정에서 마이바티스가 이넘 값을 올바르게 해석하지 못해 데이터 삽입이 실패했습니다.
처음에는 마이바티스 설정에서 타입 핸들러를 명시적으로 선언하지 않고, 기본 제공되는 핸들러를 사용하여 문제를 해결하려 했습니다. 하지만 이 방법은 이넘 타입을 적절히 처리하지 못했고, 여러 테스트 후에도 문제가 계속 발생했습니다.
문제를 해결하기 위해 MemberStatusTypeHandler라는 커스텀 타입 핸들러를 구현하기로 결정했습니다. 이 핸들러는 PreparedStatement에 이넘 값을 설정하고, ResultSet에서 이넘 값을 추출할 수 있도록 구현되었습니다.
package com.shoppingmall.typeHandler;
import com.shoppingmall.domain.enums.MemberStatus;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@MappedTypes(MemberStatus.class)
public class MemberStatusTypeHandler extends BaseTypeHandler<MemberStatus> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, MemberStatus parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.name());
}
@Override
public MemberStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
String name = rs.getString(columnName);
return name == null ? null : MemberStatus.valueOf(name.toUpperCase());
}
@Override
public MemberStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String name = rs.getString(columnIndex);
return name == null ? null : MemberStatus.valueOf(name.toUpperCase());
}
@Override
public MemberStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String name = cs.getString(columnIndex);
return name == null ? null : MemberStatus.valueOf(name.toUpperCase());
}
}
MyBatisConfig 클래스를 통해 이 타입 핸들러를 시스템에 등록했습니다. 이는 마이바티스가 SQL 세션을 생성할 때 해당 핸들러를 인식하게 만들어, 모든 이넘 타입이 올바르게 처리될 수 있도록 보장합니다.
package com.shoppingmall.config;
import com.shoppingmall.typeHandler.MemberStatusTypeHandler;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
public
class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage("com.shoppingmall.domain.dto");
sessionFactory.setTypeHandlers(new TypeHandler<?>[]{ new MemberStatusTypeHandler() });
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml");
sessionFactory.setMapperLocations(resources);
return sessionFactory.getObject();
}
}
이 경험은 복잡한 데이터 유형을 다룰 때 커스텀 타입 핸들러의 중요성을 강조했습니다. 이를 통해 데이터베이스와의 상호작용이 원활해졌고, 시스템의 안정성과 확장성이 크게 향상되었습니다.