mybatis中typeHandler自定义实现typeHandler与数据库映射JSON读取

mybatis中typeHandler自定义实现typeHandler与数据库映射JSON读取


背景

mysql从5.7.版本开始支持json列,但是本质上仍然存储的是一个字符串,比起直接用varchar来说,它有专门对于json的的检索方式和修改方法。
在jdbc规范中,还没json类型的定义。所以对象一般都是用String属性,映射数据库的json列。在存储和读取的时候,需要自己完成json的序列化和反序列化。
在使用MyBatis的框架,可以通过定义TypeHandler来自动完成Json属性的序列化和反序列化。

环境

mybatis-spring-boot-starter:1.3.2
mysql:5.7.35
org.json:20210307

自定义 typeHandler 实现json从数据库中读取

将 mysql 中 json 格式的数据与 mybais 转化

步骤

1、继承BaseTypeHandler类

BaseTypeHandler有四个方法,分别是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Object o, JdbcType jdbcType) throws SQLException {

}

@Override
public Object getNullableResult(ResultSet resultSet, String s) throws SQLException {
return null;
}

@Override
public Object getNullableResult(ResultSet resultSet, int i) throws SQLException {
return null;
}

@Override
public Object getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return null;
}

我这里重写了JsonArrayHandler和JsonObjectHandler两个对象。

JsonArrayHandler.java示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package cn.blingsec.engine.utils;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.json.JSONArray;
import org.json.JSONException;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JsonArrayHandler extends BaseTypeHandler<JSONArray> {

public JSONArray delResult(String jsonSource) throws SQLException {
if (jsonSource != null) {
JSONArray jsonArray;
try {
jsonArray = new JSONArray(jsonSource);
} catch (JSONException ex) {
throw new SQLException("There is an error converting JSONArray to json format for the content:" + jsonSource);
}
return jsonArray;
}
return null;
}

@Override
public void setNonNullParameter(PreparedStatement ps,
int i,
JSONArray parameter, //需要转换的类型,JSON类型
JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.toString());
}

@Override
public JSONArray getNullableResult(ResultSet rs, String columnName) throws SQLException {
return delResult(rs.getString(columnName));
}

@Override
public JSONArray getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return delResult(rs.getString(columnIndex));
}

@Override
public JSONArray getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return delResult(cs.getString(columnIndex));
}
}

JsonObjectHandler.java 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package cn.blingsec.engine.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JsonTypeHandler<T extends Object> extends BaseTypeHandler<T> {
private static final ObjectMapper mapper = new ObjectMapper();
private Class<T> clazz;
public JsonTypeHandler(Class<T> clazz) {
if (clazz == null) throw new IllegalArgumentException("Type argument cannot be null");
this.clazz = clazz;
}

@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, T t, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, this.toJson(t));
}
@Override
public T getNullableResult(ResultSet resultSet, String s) throws SQLException {
return this.toObject(resultSet.getString(s), clazz);
}

@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return this.toObject(rs.getString(columnIndex), clazz);
}

@Override
public T getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return this.toObject(callableStatement.getString(i), clazz);
}

private String toJson(T object) {
try {
return mapper.writeValueAsString(object);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private T toObject(String content, Class<?> clazz) {
if (content != null && !content.isEmpty()) {
try {
return (T) mapper.readValue(content, clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
}

2、配置

重写TypeHandler这四个方法,在配置文件中需要自行配置,我在application.properties中配置,也可以在spring-mybatis.xml (数据源配置的文件)中配置。
application.properties相关配置如下:

1
mybatis.type-handlers-package=cn.blingsec.engine.utils.JsonArrayHandler,cn.blingsec.engine.utils.JsonObjectHandler

spring-mybatis.xml配置可以参考以下内容,来源:https://www.cnblogs.com/han-guang-xue/p/12832074.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 数据源配置 -->
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:mapper/*/*.xml"></property>
<!-- 加入自定义typeHandler -->
<property name="typeHandlers">
<!-- 示例加入多个 typeHandler -->
<list>
<bean class="com.xingshu.utils.typehandler.JSONArrayHandler"></bean>
<bean class="com.xingshu.utils.typehandler.JSONObjectHandler"></bean>
</list>
</property>
></bean>

3、mapper xml文件中配置

1
2
3
4
5
6
7
8
9
<insert id="updateComponentAnalysisCache">
INSERT INTO
component_analysis_cache(cache_type, last_occurrence, result, target,target_host,target_type,uuid)
VALUES
(#{ComponentAnalysisCache.cacheType},#{ComponentAnalysisCache.lastOccurrence},
#{ComponentAnalysisCache.result,typeHandler=cn.blingsec.engine.utils.JsonObjectHandler},#{ComponentAnalysisCache.target},
#{ComponentAnalysisCache.targetHost},#{ComponentAnalysisCache.targetType},
#{ComponentAnalysisCache.uuid})
</insert>

我这里需要存储json数据的参数是:result

参考资料中有这样一个描述:
属性名后面 typeHandler 可以直接去掉, Mybatis 会自动映射,mybatis对jdbctype 和 typeHandler 会有一个优先级的匹配,如果实现类只有一个,可以省略不写
但是我在测试的时候会提示找不到jdbctype,报错信息,java方向实属太弱,没时间调试,留个尾巴,哪位大佬如果清楚可以沟通沟通:

1
2
Analyzing error : org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='ComponentAnalysisCache.result', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #3 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #3 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: java.sql.SQLException: Invalid argument value: java.io.NotSerializableException

运行结果:
20211123-01.png

参考


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!