
PostgreSQL의 Array 데이터 타입을 사용하여 프로젝트 진행 도중 Mybatis 조회 시에 에러가 발생하는 것을 확인했다. 기본적으로 Mybatis 가 _지원하지 않는 데이터 타입을 읽고자 할 때는 사용자 정의 타입 핸들러 클래스를 만들고 등록하는 것이 필요하다.
마이바티스가 PreparedStatement에 파라미터를 설정하고 ResultSet에서 값을 가져올때마다 TypeHandler는 적절한 자바 타입의 값을 가져오기 위해 사용된다.
-> 디폴트 TypeHandlers 안에는 Array 타입이 없으므로 사용자가 정의해서 사용한다.
package com.t4e1.minihub.config;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
@MappedTypes(java.util.List.class)
@MappedJdbcTypes(JdbcType.ARRAY)
public class ArrayTypeHandler extends BaseTypeHandler<List<String>> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
Array array = ps.getConnection().createArrayOf("varchar", parameter.toArray());
ps.setArray(i, array);
}
@Override
public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
return toList(rs.getArray(columnName));
}
@Override
public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return toList(rs.getArray(columnIndex));
}
@Override
public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return toList(cs.getArray(columnIndex));
}
private List<String> toList(Array pgArray) throws SQLException {
if (pgArray == null)
return new ArrayList<>();
String[] strings = (String[]) pgArray.getArray();
return containsOnlyNulls(strings) ? new ArrayList<>() : new ArrayList<>(List.of(strings));
}
private boolean containsOnlyNulls(String[] strings) {
for (String s : strings) {
if (s != null) {
return false;
}
}
return true;
}
}

createArrayOf에는 테이블에서 사용하는 배열의 자료형을 입력한다. 여기서는 Varchar()[]로 컬럼을 만들었기 때문에 "varchar"를 넣어주었다.
작성한 핸들러를 Mybatis에 등록해준다. mybatis-config.xml 파일을 사용하는 경우도 있지만, 여기서는 @Configuration 클래스를 사용해서 추가해본다.

package com.t4e1.minihub.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@MapperScan(basePackages = "com.t4e1.minihub", annotationClass = Mapper.class)
public class MybatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
Properties properties = new Properties();
properties.setProperty("typeHandlers", "com.t4e1.minihub.config.ArrayTypeHandler");
sessionFactory.setConfigurationProperties(properties);
return sessionFactory.getObject();
}
}
마지막으로 Mapper.xml의 resultmap에 등록해준다.
<resultMap id="recordList" type="com.t4e1.minihub.query.history.dto.RecordDTO">
<id property="id" column="id"/>
<result property="title" column="title"/>
<result property="content" column="content"/>
<result property="path" column="img_path"/>
<result property="tags" column="tags" typeHandler="com.t4e1.minihub.config.ArrayTypeHandler"/>
<result property="pwd" column="pwd"/>
</resultMap>
typeHandler="" 를 등록할 때, 핸들러의 클래스명 만을 입력하니 핸들러를 찾을 수 없다는 에러가 발생했다. 전체 경로를 적어주자.

성공적으로 테스트 코드가 동작하며, 로깅용 출력에 tags가 배열로 나오는 것과 특정 배열에서 인덱스를 지정해서 값을 확인할 수 있는 것을 확인할 수 있다.
원했던 결과를 얻었지만 아직 Mybatis에 대해 잘 이해하지 못하고 있다는 것을 느낄 수 있었다. SqlSessionFactory 나 Mybatis의 기본 동작에 대해서 별도로 정리할 필요가 있고, 사용자 정의 Handler의 각 메소드에 대해서도 한번 정리해 볼 것