MyBatis笔记(6)-TypeHandler的使用

TypeHandler

简介

MyBaits是一个工作在Java程序与数据库之间的框架,因此会涉及到双方的数据结构,那么一定会处理的一个问题就是Java类型与JDBC类型之间的相互转换。在MyBatis中使用类型处理器TypeHandler来完成这一工作。在设置预处理语句(PreparedStatement)中的参数的时候,使用类型处理器来完成Java类型到JDBC类型的转换,为后续存入数据库做准备;在从结果集取出一个值的时候,也会使用类型处理器完成从JDBC类型到Java类型的转换,为形成Java中的实体类做准备。

自定义TypeHandler

MyBatis中提供了一些默认的类型处理器,可以完成一些基本类型的转换。不过对于一些比较复杂的转换逻辑,默认提供的类型处理器无法完成功能,则需要我们自定义实现TypeHandler。

自定义实现TypeHandler有两种方式,一种是实现 org.apache.ibatis.type.TypeHandler 接口,另一种是继承一个基本的类org.apache.ibatis.type.BaseTypeHandler。两种方式都需要重写其中固定的抽象方法。

使用自定义TypeHandler的步骤如下:

  1. 实现自定义TypeHandler,重写其中的抽象方法
  2. 注册自定义TypeHandler
  3. 在Mapper配置文件中使用自定义TypeHandler

下面通过一个案例来介绍自定义TypeHandler的使用过程。考虑下面的场景,我们有一个实体类Unit,其中有两个属性:

1
2
3
4
public class Unit {
private Integer id;
private String orderStr;
}

同时我们在数据库中有表格test_unit,内容如下:

id order_num
1 1
2 2
3 3
4 4

我们希望完成上面表格和实体类之间的映射,其中id属性相互映射,orderStrorder_num也相互映射。并且映射规则如下,在数据库的order_num中存放int类型数字,在实体类中orderStr存放String类型字符串,并且1对应A,2对应B,3对应C。

如果不做任何配置的话直接利用MyBatis完成映射,是无法完成需求的。我们直接查询数据库中的所有数据为实体,得到如下的结果:

1
[{"id":1,"orderStr":null},{"id":2,"orderStr":null},{"id":3,"orderStr":null},{"id":4,"orderStr":null}]

id属性能够完成相互映射因此能够查询得到,但是另外一个属性无法完成映射则查询为空。

接下来我们需要实现自定义TypeHandler,完整代码如下:

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
@MappedJdbcTypes(JdbcType.INTEGER)
public class MyTypeHandler implements TypeHandler<String> {
@Override
// Java->DataBase
public void setParameter(PreparedStatement preparedStatement, int index, String parameter, JdbcType jdbcType) throws SQLException {
if ("A".equals(parameter)) {
preparedStatement.setInt(index, 1);
} else if ("B".equals(parameter)) {
preparedStatement.setInt(index, 2);
} else if ("C".equals(parameter)) {
preparedStatement.setInt(index, 3);
} else {
preparedStatement.setInt(index, 4);
}
}

private String numToString(int num) {
if (num == 1) {
return "A";
} else if (num == 2) {
return "B";
} else if (num == 3) {
return "C";
} else {
return "D";
}
}

// DataBase->Java
@Override
public String getResult(ResultSet resultSet, String name) throws SQLException {
return numToString(resultSet.getInt(name));
}

@Override
public String getResult(ResultSet resultSet, int index) throws SQLException {
return numToString(resultSet.getInt(index));
}

@Override
public String getResult(CallableStatement callableStatement, int index) throws SQLException {
return numToString(callableStatement.getInt(index));
}
}

首先我们需要实现TypeHandler接口,指定泛型为String。这里的泛型对应需要转换的Java实体类类型。之后要实现接口中的抽象方法,抽象方法一共有四个,分别为setParameter方法和getResult的三个重载方法。前者完成的是Java类型到JDBC类型的转换,后者完成的是JDBC类型到Java类型的转换。

setParameter方法中,我们需要完成的是将Java类型的参数转化成JDBC类型,之后设置在PreparedStatement中。这里我们完成了对应逻辑,首先获取Java类型的参数,对应形参中的parameter,之后利用setInt方法设置了JDBC类型的参数,完成类型转换。

getResult方法中,我们需要完成的是从结果集中取得JDBC类型的参数,三种重载方式对应获取方式的不同,获取到参数后进行处理,返回值就是最终转化为的Java类型。这里我们通过getInt方法获得了JDBC类型的参数,之后根据我们的自定义逻辑将其转化为Java类型并返回。

TypeHandler对应处理的两种类型可以通过以下的方法指定:

Java类型:

  • 在类型处理器的配置元素(typeHandler 元素)上增加一个 javaType 属性(比如:javaType="String");
  • 在类型处理器的类上增加一个 @MappedTypes 注解指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解上的配置将被忽略。

JDBC类型:

  • 在类型处理器的配置元素上增加一个 jdbcType 属性(比如:jdbcType="VARCHAR");
  • 在类型处理器的类上增加一个 @MappedJdbcTypes 注解指定与其关联的 JDBC 类型列表。 如果在 jdbcType 属性中也同时指定,则注解上的配置将被忽略。

之后我们就可以在Mapper配置文件中使用对应的TypeHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<mapper namespace="com.syh.springboot.mapper.UnitMapper">
<resultMap id="unitMap" type="com.syh.springboot.bean.Unit">
<result property="id" column="id"/>
<result property="orderStr" column="order_num" typeHandler="com.syh.springboot.handler.MyTypeHandler"/>
</resultMap>

<select id="getAllUnit" resultMap="unitMap">
select *
from test_unit
</select>

<insert id="insert">
insert into test_unit
values (#{id}, #{orderStr,typeHandler=com.syh.springboot.handler.MyTypeHandler})
</insert>
</mapper>

这里我们在resultMap中设置映射方式,其中指定使用我们自定义的TypeHandler,之后重新执行select操作,可以得到转化后的结果,发现符合我们的逻辑:

1
[{"id":1,"orderStr":"A"},{"id":2,"orderStr":"B"},{"id":3,"orderStr":"C"},{"id":4,"orderStr":"D"}]

同时我们也可以在执行insert的时候完成转换,只需要在#{}中指定使用对应的TypeHandler即可。这样我们直接执行insert,也能够完成符合我们自定义映射逻辑的转换。

JsonTypeHandler

在实际场景中,一种常见的映射方式是,在数据库中存放json字符串,而在Java实体类中准备对应的类型,如列表等。此时我们需要自定义TypeHandler来实现相关功能:

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
public class JsonTypeHandler<T> implements TypeHandler<T> {
private Class<T> type;

// 获取指定的Type
public JsonTypeHandler(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
}

// 将Java实体类参数转换为Json字符串,为存入数据库做准备
private String parseJsonString(T parameter) {
if (parameter == null) {
return null;
}
return JSON.toJSONString(parameter);
}

// 将Json字符串转化为Java实体类
@SuppressWarnings("unchecked")
private T parseJavaObject(String parameter) {
if (parameter == null) {
return null;
}
if (parameter.startsWith("[")) {
return (T) JSON.parseArray(parameter, this.type);
}
return JSON.parseObject(parameter, this.type);
}

@Override
public void setParameter(PreparedStatement preparedStatement, int i, T t, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, parseJsonString(t));
}

@Override
public T getResult(ResultSet resultSet, String s) throws SQLException {
return parseJavaObject(resultSet.getString(s));
}

@Override
public T getResult(ResultSet resultSet, int i) throws SQLException {
return parseJavaObject(resultSet.getString(i));
}

@Override
public T getResult(CallableStatement callableStatement, int i) throws SQLException {
return parseJavaObject(callableStatement.getString(i));
}
}

这里我们没有指定实际的泛型,在使用的时候,可以通过标签中javaType来指定。

1
2
<result property="images" column="images" javaType="java.lang.String"
typeHandler="com.course.java.handler.JsonTypeHandler"/>

参考文章

  1. 基于fastjson的mybatis自定义类型处理器JsonTypeHanlder代码示例_ty41232X32的博客-CSDN博客_fastjson mybatis

MyBatis笔记(6)-TypeHandler的使用
http://example.com/2022/11/04/MyBatis笔记-6-TypeHandler的使用/
作者
EverNorif
发布于
2022年11月4日
许可协议