1.TypeHandler概念
TypeHandler,类型转换器,在mybatis中用于实现java类型和JDBC类型的相互转换.mybatis使用prepareStatement来进行参数设置的时候,需要通过typeHandler将传入的java参数设置成合适的jdbc类型参数,这个过程实际上是通过调用PrepareStatement不同的set方法实现的;在获取结果返回之后,也需要将返回的结果转换成我们需要的java类型,这时候是通过调用ResultSet对象不同类型的get方法时间的;所以不同类型的typeHandler其实就是调用PrepareStatement和ResultSet的不同方法来进行类型的转换,有些时候会在调用PrepareStatement和ResultSet的相关方法之前,可以对传入的参数进行一定的处理.
当我们没有指定typeHandler的时候mybatis会根据传入参数的类型和返回值的类型调用默认的typeHandler进行处理.对于一个typeHandler需要配置java类型(javaType)和JDBC类型(jdbcType),typeHandler的作用就是实现这两种类型的转换,在传入的参数为指定的Java类型时,将其转换为指定的JDBC类型,当返回值为指定JDBC类型时将其转换为配置的Java类型.
2.mybatis默认定义的TypeHandler
mybatis默认定义了一批TypeHandler,正常情况下这些TypeHandler就可以满足我们的使用了.mybatis通过TypeHandlerRegister来管理TypeHandler
所以在这个类里面可以看到所有定义好的typeHandler,下面是源码.
private final Map> JDBC_TYPE_HANDLER_MAP = new EnumMap>(JdbcType.class);
private final Map>> TYPE_HANDLER_MAP = new ConcurrentHashMap>>();
private final TypeHandler
可以看到对于一个Java类型是可以有多个JDBC类型相对应的,所以会存在多个TypeHandler,在这种情况下需要根据传入参数的javaType以及其在数据库中对应JdbcType一起来选定一个TypeHandler进行处理(mybatis如何知道每个字段对应的数据库字段类型的?).下面来看一个具体的TypeHandler的实现:实现Java中Date类型和jdbc中JdbcType.Time类型转换的TimeOnlyTypeHandler.JDBC中的Time类型只记录时分秒,所以如果我们传入一个代表2017-11-21 11:55:59的Date对象,那么数据库中存储的时间是11:55:59.下面看下具体的源码实现:
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.util.Date;
/**
* @author Clinton Begin
*/
public class TimeOnlyTypeHandler extends BaseTypeHandler<Date> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType)
throws SQLException {
ps.setTime(i, new Time(parameter.getTime()));
}
@Override
public Date getNullableResult(ResultSet rs, String columnName)
throws SQLException {
java.sql.Time sqlTime = rs.getTime(columnName);
if (sqlTime != null) {
return new Date(sqlTime.getTime());
}
return null;
}
@Override
public Date getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
java.sql.Time sqlTime = rs.getTime(columnIndex);
if (sqlTime != null) {
return new Date(sqlTime.getTime());
}
return null;
}
@Override
public Date getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
java.sql.Time sqlTime = cs.getTime(columnIndex);
if (sqlTime != null) {
return new Date(sqlTime.getTime());
}
return null;
}
}
3.使用自定义TypeHandler
虽然大部分时候mybatis提供的typeHandler已经够用了,但总有些情况下需要我们自己定义TypeHandler.下面通过一个实例来研究如何自己定义和使用TypeHandler.
现在的场景是:首先是建立了一个person表存储个人信息,这个表里有一栏hobbys是记录个人爱好的,爱好包括足球,排球,游泳之类的,在Java的Person对象中对应的是一个String类型的list,而在数据库中是以逗号分隔的字符串表示的,心事如”足球,排球,游泳”,所以现在需要定义一个TypeHandler实现在插入数据和查询数据时string和list类型的相互转换.自定义一个TypeHandler需要继承TypeHandler T接口,T是传入的java类型,并需要使用@MappedTypes定义需要被拦截的java类型@MappedJdbcTypes配置jdbc类型,这里的JdbcType必须org.apache.ibatis.type.JdbcType中的枚举类型,然后还需要实现TypeHandler接口中一系列的get set方法,具体实现的代码如下:
package com.sankuai.lkl.typeHandler;
import com.sun.deploy.util.StringUtils;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes({List.class})
public class ListTypeHandler implements TypeHandler> {
@Override
public void setParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType) throws SQLException {
String hobbys = StringUtils.join(parameter, ",");
try {
ps.setString(i, hobbys);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public List getResult(CallableStatement cs, int columnIndex) throws SQLException {
String hobbys = cs.getString(columnIndex);
return Arrays.asList(hobbys.split(","));
}
@Override
public List getResult(ResultSet rs, int columnIndex) throws SQLException {
return Arrays.asList(rs.getString(columnIndex).split(","));
}
@Override
public List getResult(ResultSet rs, String columnName) throws SQLException {
return Arrays.asList(rs.getString(columnName).split(","));
}
}
自定义完成之后就需要在mybatis-config.xml文件中配置以注册到mybatis中
<typeHandlers>
<typeHandler jdbcType="VARCHAR" javaType="list"
handler="com.sankuai.lkl.typeHandler.ListTypeHandler"/>
typeHandlers>
但注册完成之后也仍然不能起作用,因为还需要标识那些参数和返回的结果是需要使用这个TypeHandler进行处理的;具体来说,在插入数据和对返回结果进行处理的时候,可以对参数配置javaType和jdbcType或直接配置typeHandler属性来进行标识
下面是插入数据时标识用指定TypeHandler进行处理
INSERT INTO person (id,name,sex,hobbys,data_time) values(#{id},#{name},#{sex},#{hobbys,typeHandler=com.sankuai.lkl.typeHandler.ListTypeHandler},#{date})
下面是标识对返回的结果用指定TypeHandler进行处理
id="personMap" type="person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="hobbys" column="hobbys" typeHandler="com.sankuai.lkl.typeHandler.ListTypeHandler"/>
<result property="date" column="data_time"/>
这样进行配置之后就可以使用自定义的TypeHandler处理javaType和JdbcType的转换了.