마이바티스타입핸들러 (1)

MyBatis TypeHandler를 이용한 객체리스트 핸들링

이번에 특정 서비스를 개발하다가 JSON 형태의 스트링을 그대로 데이터베이스에 저장했다가 꺼내써야하는 상황이 생겼다.

그것도 RBD인 mysql 데이터베이스에 말이다. mysql이 json 컬럼을 지원하기는 하지만 json 함수를 쿼리에 쓸 필요까지는 없는 상황이다보니 그냥 varchar타입으로 컬럼을 정의했고 여기에 json포맷의 스트링을 그대로 저장했다가 꺼내쓸 수 있도록 해야했다.

일단 타입핸들러를 이용하여 이를 처리하기로 했고 어떻게 사용하는 건지 검색을 좀 해봤다. 구글에서 "Type handler for ArrayList in myBatis" 라고 검색을 하니 많은 포스팅이 검색이 되었고 내 프로젝트에 맞게 가져다가 쓸 수 있었다.

작업 내용을 정리하자면 다음과 같다. 

 

우선 dto의 구조를 보면 MyJsonDataWrapperClass 가 myJsonData 리스트를 들고 있는데 이때 MyJsonData 클래스가 바로 json 형태로 데이터베이스에 저장될 정보이다.

@Getter
@Setter
@JsonInclude(Include.NON_NULL)
public class MyJsonDataWrapperClass {

    private Integer someIntData;
    private List<MyJsonData> myJsonData = new ArrayList<>();
    
    @Override
    public String toString() {
        return new Gson().toJson(this);
    }
}
@Getter
@Setter
@EqualsAndHashCode
public class MyJsonData {
    private String name;
    private int age;

    @Override
    public String toString() {
        return new Gson().toJson(this);
    }
}

 

데이터베이스에는 myJsonData 라는 이름으로 text타입의 컬럼을 만들었고 mybatis의 insert문에 사용할 타입핸들러를 아래와 같이 명시해주었다.

, myJsonData = #{myJsonData, typeHandler=MyJsonDataTypeHandler}

 

그리고 타입핸들러는 아래와 같이 정의하였다.

@Slf4j
public class MyJsonDataTypeHandler extends BaseTypeHandler<List<MyJsonData>> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<MyJsonData> parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, new Gson().toJson(parameter));
    }

    @Override
    public List<MyJsonData> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return convertToList(rs.getString(columnName));
    }

    @Override
    public List<MyJsonData> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return convertToList(rs.getString(columnIndex));
    }

    @Override
    public List<MyJsonData> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return convertToList(cs.getString(columnIndex));
    }

    private List<MyJsonData> convertToList(String myJsonDataListAsString) {
        try {
            return new ObjectMapper().readValue(myJsonDataListAsString, new TypeReference<List<MyJsonData>>() {
            });
        } catch (IOException e) {
            log.error("MyJsonDataTypeHandler failed to convert text to list, myJsonDataListAsString:{}", myJsonDataListAsString, e);
        }
        return Collections.emptyList();
    }
}

 

BaseTypeHandler를 상속하면서 List<MyJsonData> 타입에 대한 핸들러임을 명시해주었고 setter에는 리스트 형태를 json 스트링으로 만들어주도록 Gson.toJson() 메서드를 이용하였고, getter에는 조회한 json 스트링을 다시 리스트 형태로 변환해주도록 하였는데 이때는 ObjectMapper.readValue()를 이용하였다.

 

조회시에는 mybatis 매핑파일에 아래와 같이 resultMap을 정의하여 MyJsonDataTypeHandler를 이용해서 myJsonData필드의 값을 ArrayList로 변환하도록 하였으며, 이렇게 정의한 resultMap을 select 문의 resultMap으로 선언해주었다.

<resultMap id="myJsonDataClassMap" type="MyJsonDataWrapperClass">
<result property="myJsonData" column="myJsonData" javaType="java.util.ArrayList"
jdbcType="VARCHAR" typeHandler="MyJsonDataTypeHandler" />
</resultMap>

....중략....

<select id="findMyJsonDataType"
resultMap="myJsonDataClassMap">
SELECT * FROM findMyJsonDataWrapperClass
</select>

 

이렇게 해주면 소스레벨에서는 특정 객체의 리스트 형태로 핸들링을 하면서 데이터베이스에는 json 스트링으로 저장하여 사용할 수 있다.

 

컬럼단위로 데이터를 쪼개서 저장하기 애매한 상황에서 json 스트링을 varchar(text) 타입으로 통으로 저장하고 소스레벨에서는 객체타입으로 핸들링할 수 있도록 해주는 건 정말 멋진 기능인것 같다.