本文最后更新于:2022-11-04T23:28:32+08:00
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的步骤如下:
实现自定义TypeHandler,重写其中的抽象方法
注册自定义TypeHandler
在Mapper配置文件中使用自定义TypeHandler
下面通过一个案例来介绍自定义TypeHandler的使用过程。考虑下面的场景,我们有一个实体类Unit
,其中有两个属性:
1 2 3 4 public class Unit { private Integer id; private String orderStr; }
同时我们在数据库中有表格test_unit
,内容如下:
我们希望完成上面表格和实体类之间的映射,其中id属性相互映射,orderStr
和order_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 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" ; } } @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; public JsonTypeHandler (Class<T> type) { if (type == null ) { throw new IllegalArgumentException ("Type argument cannot be null" ); } this .type = type; } private String parseJsonString (T parameter) { if (parameter == null ) { return null ; } return JSON.toJSONString(parameter); } @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" />
参考文章
基于fastjson的mybatis自定义类型处理器JsonTypeHanlder代码示例_ty41232X32的博客-CSDN博客_fastjson
mybatis