개발 과정에서 MyBatis를 이용해 데이터베이스의 데이터를 조회할 때, 특정 컬럼의 데이터가 지속적으로 null로 매핑되는 현상이 발생
해당 컬럼은 PostgreSQL의 jsonb 타입으로 배열 데이터를 저장하고 있었으며, 데이터베이스 클라이언트를 통해 직접 조회했을 때는 데이터가 정상적으로 저장되어 있는 것을 확인
쿼리 문법이나 매퍼(Mapper) XML의 오타 문제가 아님을 확인한 후, 데이터 매핑 과정 자체의 원인을 파악
문제의 원인은 MyBatis의 기본 타입 변환 지원 범위에 있었다
MyBatis는 String, Integer, Date와 같은 자바의 기본 타입들에 대해서는 내장된 TypeHandler를 통해 DB 타입과의 변환을 자동으로 처리
jsonb와 같은 특수한 데이터 타입이나, 조회된 JSON 문자열을 Java의 List 컬렉션으로 변환(역직렬화)하는 방법은 기본적으로 제공하지 않는다
결과적으로 DB에서는 데이터를 정상적으로 반환했지만, MyBatis가 이를 Java 객체로 변환하는 방법을 알지 못해 최종적으로 null을 할당한 것
DB의 JSON 문자열과 Java의 List 간의 변환 로직을 직접 정의하는 커스텀 타입 핸들러를 구현
MyBatis에서 제공하는 BaseTypeHandler를 상속받아 JsonListTypeHandler 클래스를 작성
JSON 데이터의 직렬화 및 역직렬화 처리는 Jackson 라이브러리의 ObjectMapper를 활용
public class JsonListTypeHandler extends BaseTypeHandler<List<?>> {
private static final ObjectMapper mapper = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<?> parameter, JdbcType jdbcType) throws SQLException {
try {
// Java List -> JSON String 변환 후 DB에 저장
ps.setString(i, mapper.writeValueAsString(parameter));
} catch (JsonProcessingException e) {
throw new SQLException("Error converting List to JSON string", e);
}
}
@Override
public List<?> getNullableResult(ResultSet rs, String columnName) throws SQLException {
return parseJson(rs.getString(columnName));
}
@Override
public List<?> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return parseJson(rs.getString(columnIndex));
}
@Override
public List<?> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return parseJson(cs.getString(columnIndex));
}
private List<?> parseJson(String json) throws SQLException {
if (json == null || json.isEmpty()) {
return new ArrayList<>();
}
try {
return mapper.readValue(json, List.class);
} catch (JsonProcessingException e) {
throw new SQLException("Error converting JSON string to List", e);
}
}
}
구현한 핸들러가 동작할 수 있도록 Mapper XML에 해당 TypeHandler의 경로 명시
[조회 시 (ResultMap)]
을 사용하여 특정 컬럼을 읽을 때 작성한 핸들러를 거치도록 설정
<resultMap id="myResultMap" type="MyDto">
<id property="id" column="id"/>
<result property="myList" column="jsonb_column"
typeHandler="com.example.project.handler.JsonListTypeHandler"/>
</resultMap>
[저장 시 (Insert/Update)]
데이터를 삽입하거나 수정할 때도 파라미터 속성에 typeHandler를 지정하여 List 객체가 JSON 문자열로 정상 변환되도록 처리
<insert id="insertData">
INSERT INTO my_table (jsonb_column)
VALUES (
#{myList, typeHandler=com.example.project.handler.JsonListTypeHandler}
)
</insert>
커스텀 타입 핸들러를 적용한 결과, jsonb 데이터가 null 대신 의도한 Java List 객체로 정상 매핑되는 것을 확인
이 방식을 통해 서비스(Service) 계층에서 별도의 문자열 파싱 로직을 구현할 필요 없이, 데이터 접근 계층(Mapper)에서 타입 변환의 책임을 분리하고 캡슐화할 수 있었다~