文章目录
上篇文章Mybatis系列(五)typeAliases源码解析,主要介绍了mybatis中别名的使用,这一节我们来看TypeHandler节点,翻译成中文就是类型处理器 。
在JDBC中,需要在PrepareStatement对象中设置那些已经预编译过的SQL语句的参数。执行SQL后,会通过ResultSet对象获取得到数据库的数据,而这些mybatis是数据的类型是通过typeHandler来实现的。在typeHandler中,分为jdbcType和JavaType,其中jdbcType用于定义数据库类型,而JavaType用于定义java类型,那么typeHandler的作用就是承担jdbcType和javaType之间的相互转换
一、typeHandler配置
<configuration>
<typeHandlers>
<!--
当配置package的时候,mybatis会去配置的package扫描TypeHandler
<package name="com.sean.entity"/>
-->
<!-- handler属性直接配置我们要指定的TypeHandler -->
<typeHandler handler=""/>
<!-- javaType 配置java类型,例如String, 如果配上javaType, 那么指定的typeHandler就只作用于指定的类型 -->
<typeHandler javaType="" handler=""/>
<!-- jdbcType 配置数据库基本数据类型,例如varchar, 如果配上jdbcType, 那么指定的typeHandler就只作用于指定的类型 -->
<typeHandler jdbcType="" handler=""/>
<!-- 也可两者都配置 -->
<typeHandler javaType="" jdbcType="" handler=""/>
</typeHandlers>
......
</configuration>
二、typeHandlerElement源码
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
Iterator i$ = parent.getChildren().iterator();
while(i$.hasNext()) {
XNode child = (XNode)i$.next();
String typeHandlerPackage;
//子节点为package时,获取name属性的值,然后自动扫描package下的自定义的typeHandler
if ("package".equals(child.getName())) {
typeHandlerPackage = child.getStringAttribute("name");
this.typeHandlerRegistry.register(typeHandlerPackage);
} else {
//子节点为typeHandler时,可以指定JavaType,jdbcType属性
typeHandlerPackage = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
//handler就是我们配置的typeHandler
String handlerTypeName = child.getStringAttribute("handler");
//resolveClass方法就是我们上篇文章所讲的TypeAliasRegistry里面处理别名的方法
Class<?> javaTypeClass = this.resolveClass(typeHandlerPackage);
//JdbcType是一个枚举类型,resolveJdbcType方法是在获取枚举类型的值
JdbcType jdbcType = this.resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = this.resolveClass(handlerTypeName);
if (javaTypeClass != null) {
注册typeHandler, typeHandler通过TypeHandlerRegistry这个类管理
if (jdbcType == null) {
this.typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
this.typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
this.typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
根据配置属性的不同,TypeHandlerRegistry一共提供了有三种注册方式。
- 只配置了typeHandler, 没有配置jdbcType 或者javaType
register(Class<?> typeHandlerClass)
- 配置了javaType和typeHandler
register(Class<?> javaTypeClass, Class<?> typeHandlerClass)
- typeHandlerhe、javaType、jdbcType都配置了
register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass)
最终都是调用下面的方法
//注册typeHandler的核心方法
//就是想Map中新增数据
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = (Map)this.TYPE_HANDLER_MAP.get(javaType);
if (map == null) {
map = new HashMap();
this.TYPE_HANDLER_MAP.put(javaType, map);
}
((Map)map).put(jdbcType, handler);
if (reversePrimitiveMap.containsKey(javaType)) {
this.register((Type)reversePrimitiveMap.get(javaType), jdbcType, handler);
}
}
this.ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
仔细观察源码,可以看到typeHandler
和typeAliases
类似,mybatis内部都提供了系统的默认配置,初始化的时候放入到一个Map中,当然用户也可以自定义配置,最终新增到Map。
三、系统定义的typeHandler
mybatis系统已经创建好了typeHandler
,在大部分情况下无须显式地声明jdbcType
和javaType
,或者用typeHandler
去指定对应的typeHandler
来实现数据类型的转换,因为mybatis系统会自动检测。
public TypeHandlerRegistry() {
this.register((Class)Boolean.class, (TypeHandler)(new BooleanTypeHandler()));
this.register((Class)Boolean.TYPE, (TypeHandler)(new BooleanTypeHandler()));
this.register((JdbcType)JdbcType.BOOLEAN, (TypeHandler)(new BooleanTypeHandler()));
this.register((JdbcType)JdbcType.BIT, (TypeHandler)(new BooleanTypeHandler()));
this.register((Class)Byte.class, (TypeHandler)(new ByteTypeHandler()));
this.register((Class)Byte.TYPE, (TypeHandler)(new ByteTypeHandler()));
this.register((JdbcType)JdbcType.TINYINT, (TypeHandler)(new ByteTypeHandler()));
this.register((Class)Short.class, (TypeHandler)(new ShortTypeHandler()));
this.register((Class)Short.TYPE, (TypeHandler)(new ShortTypeHandler()));
this.register((JdbcType)JdbcType.SMALLINT, (TypeHandler)(new ShortTypeHandler()));
this.register((Class)Integer.class, (TypeHandler)(new IntegerTypeHandler()));
this.register((Class)Integer.TYPE, (TypeHandler)(new IntegerTypeHandler()));
this.register((JdbcType)JdbcType.INTEGER, (TypeHandler)(new IntegerTypeHandler()));
this.register((Class)Long.class, (TypeHandler)(new LongTypeHandler()));
this.register((Class)Long.TYPE, (TypeHandler)(new LongTypeHandler()));
this.register((Class)Float.class, (TypeHandler)(new FloatTypeHandler()));
this.register((Class)Float.TYPE, (TypeHandler)(new FloatTypeHandler()));
this.register((JdbcType)JdbcType.FLOAT, (TypeHandler)(new FloatTypeHandler()));
this.register((Class)Double.class, (TypeHandler)(new DoubleTypeHandler()));
this.register((Class)Double.TYPE, (TypeHandler)(new DoubleTypeHandler()));
this.register((JdbcType)JdbcType.DOUBLE, (TypeHandler)(new DoubleTypeHandler()));
this.register((Class)String.class, (TypeHandler)(new StringTypeHandler()));
this.register((Class)String.class, JdbcType.CHAR, (TypeHandler)(new StringTypeHandler()));
this.register((Class)String.class, JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler()));
this.register((Class)String.class, JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler()));
this.register((Class)String.class, JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler()));
this.register((Class)String.class, JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler()));
this.register((Class)String.class, JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler()));
this.register((Class)String.class, JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler()));
this.register((JdbcType)JdbcType.CHAR, (TypeHandler)(new StringTypeHandler()));
this.register((JdbcType)JdbcType.VARCHAR, (TypeHandler)(new StringTypeHandler()));
this.register((JdbcType)JdbcType.CLOB, (TypeHandler)(new ClobTypeHandler()));
this.register((JdbcType)JdbcType.LONGVARCHAR, (TypeHandler)(new ClobTypeHandler()));
this.register((JdbcType)JdbcType.NVARCHAR, (TypeHandler)(new NStringTypeHandler()));
this.register((JdbcType)JdbcType.NCHAR, (TypeHandler)(new NStringTypeHandler()));
this.register((JdbcType)JdbcType.NCLOB, (TypeHandler)(new NClobTypeHandler()));
this.register((Class)Object.class, JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler()));
this.register((JdbcType)JdbcType.ARRAY, (TypeHandler)(new ArrayTypeHandler()));
this.register((Class)BigInteger.class, (TypeHandler)(new BigIntegerTypeHandler()));
this.register((JdbcType)JdbcType.BIGINT, (TypeHandler)(new LongTypeHandler()));
this.register((Class)BigDecimal.class, (TypeHandler)(new BigDecimalTypeHandler()));
this.register((JdbcType)JdbcType.REAL, (TypeHandler)(new BigDecimalTypeHandler()));
this.register((JdbcType)JdbcType.DECIMAL, (TypeHandler)(new BigDecimalTypeHandler()));
this.register((JdbcType)JdbcType.NUMERIC, (TypeHandler)(new BigDecimalTypeHandler()));
this.register((Class)Byte[].class, (TypeHandler)(new ByteObjectArrayTypeHandler()));
this.register((Class)Byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobByteObjectArrayTypeHandler()));
this.register((Class)Byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobByteObjectArrayTypeHandler()));
this.register((Class)byte[].class, (TypeHandler)(new ByteArrayTypeHandler()));
this.register((Class)byte[].class, JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler()));
this.register((Class)byte[].class, JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler()));
this.register((JdbcType)JdbcType.LONGVARBINARY, (TypeHandler)(new BlobTypeHandler()));
this.register((JdbcType)JdbcType.BLOB, (TypeHandler)(new BlobTypeHandler()));
this.register(Object.class, this.UNKNOWN_TYPE_HANDLER);
this.register(Object.class, JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER);
this.register(JdbcType.OTHER, this.UNKNOWN_TYPE_HANDLER);
this.register((Class)Date.class, (TypeHandler)(new DateTypeHandler()));
this.register((Class)Date.class, JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler()));
this.register((Class)Date.class, JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler()));
this.register((JdbcType)JdbcType.TIMESTAMP, (TypeHandler)(new DateTypeHandler()));
this.register((JdbcType)JdbcType.DATE, (TypeHandler)(new DateOnlyTypeHandler()));
this.register((JdbcType)JdbcType.TIME, (TypeHandler)(new TimeOnlyTypeHandler()));
this.register((Class)java.sql.Date.class, (TypeHandler)(new SqlDateTypeHandler()));
this.register((Class)Time.class, (TypeHandler)(new SqlTimeTypeHandler()));
this.register((Class)Timestamp.class, (TypeHandler)(new SqlTimestampTypeHandler()));
this.register((Class)Character.class, (TypeHandler)(new CharacterTypeHandler()));
this.register((Class)Character.TYPE, (TypeHandler)(new CharacterTypeHandler()));
}
在mybatis中typeHandler都要实现接口org.apache.ibatis.type.typeHandler
,我们来看下这个接口
public interface TypeHandler<T> {
void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;
T getResult(ResultSet var1, String var2) throws SQLException;
T getResult(ResultSet var1, int var2) throws SQLException;
T getResult(CallableStatement var1, int var2) throws SQLException;
}
- 其中T是泛型,专之javaType,比如我们需要String的时候,那么实现类就可以写为implements TypeHandler < String>
setParameter
方法,是使用typeHandler通过PreparedStatement对象进行设置SQL参数的时候使用的具体方法,其中i是参数在SQL的下标,parameter是参数,jdbcType是数据库类型- 其中3个
getResult
的方法,它的作用是从JDBC结果集中获取数据进行转换,要么使用列名(columnName)要么使用下标(columnIndex)获取数据库的数据,其中最后一个getResult方法是存储过程专用的。
四、自定义typeHandler
从系统定义的typeHandler可以知道,要实现typeHandler就需要实现接口typeHandler,或者继承BaseTypeHandler
(实际上上,BaseTypeHandler实现了typeHandler接口),这里我们自定义一个typeHandler——MyTypeHander
。
/**
* @Author: sean.xu
* @Date: 2020/4/17
*/
public class MyTypeHandler implements TypeHandler<String> {
Logger logger = Logger.getLogger(MyTypeHandler .class);
public void setParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
logger.info("设置String参数:-----"+s);
preparedStatement.setString(i,s);
}
public String getResult(ResultSet resultSet, String s) throws SQLException {
String result = resultSet.getString(s);
logger.info("读取String参数1:-----"+result);
return result;
}
public String getResult(ResultSet resultSet, int i) throws SQLException {
String result = resultSet.getString(i);
logger.info("读取String参数2:-----"+result);
return result;
}
public String getResult(CallableStatement callableStatement, int i) throws SQLException {
String result = callableStatement.getString(i);
logger.info("读取String参数3:-----"+result);
return result;
}
}
配置typeHandler
<typeHandlers>
<typeHandler jdbcType="VARCHAR" javaType="String" handler="com.sean.handler.MyTypeHandler "/>
</typeHandlers>
配置完成后系统才会读取它,这样注册后,当jdbcType和javaType能与MytypeHandler对应的时候,就会启动MytypeHandler。一般而言启动这个typeHandler有两种方式。
<resultMap id="userMapper" type="user">
<result property="id" column="id"/>
<result property="name" column="name" typeHandler="com.sean.handler.MyTypeHander"/>
<result property="mobile" column="mobile" jdbcType="VARCHAR" javaType="String"/>
</resultMap>
<select id="findUserByName" parameterType="String" resultMap="userMapper">
SELECT id,name FROM user WHERE name = #{name,typeHandler=com.sean.handler.MyTypeHander}
</select>
<select id="findUserByMobile" parameterType="String" resultMap="userMapper">
SELECT id,name,mobile FROM user WHERE mobile = #{mobile,jdbcType=VARCHAR,javaType=string}
</select>
一种是指定了自定义typeHandler一致的jdbcType和javaType,另一种是直接使用typeHandler指定具体的实现类。
运行代码查看日志:
显然我们配置的MytypeHandler已经启用了。当我们自定义的typeHandler太多时,也可以像别名一样,直接指定对应的package。
五、枚举typeHandler
在大多数情况下,typeHandler因为枚举而使用,mybatis已经定义了两个类作为枚举类型的支持:
- EnumOrdinalTypeHandler
- EnumTypeHandler
我们来测试下,先创建枚举类
public enum SexEnum {
MALE(0,"男"),
FEMALE(1,"女");
private int id;
private String name;
SexEnum(int id,String name){
this.id = id;
this.name = name;
}
public int getId(){
return id;
}
public String getName(){
return name;
}
public static SexEnum getSex(int id){
for (SexEnum sex : SexEnum.values()){
if (sex.getId() == id){
return sex;
}
}
return null;
}
}
EnumOrdinalTypeHandler是根据枚举数组下标索引的方式进行匹配的,它要求数据返回一个整数作为其下标,它会根据下标找到对应的枚举类型。
我们配置下userMapper.xml
<mapper namespace="com.sean.mapper.UserMapper">
<resultMap id="userMapper" type="user">
<result property="id" column="id"/>
<result property="name" column="name" typeHandler="com.sean.handler.MyTypeHander"/>
<result property="mobile" column="mobile" jdbcType="VARCHAR" javaType="String"/>
<result property="gender" column="gender" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
</resultMap>
<select id="findUserByName" parameterType="String" resultMap="userMapper">
SELECT * FROM user WHERE name = #{name}
</select>
</mapper>
我们数据库的数据,0代表男,1代表女
执行Test代码,查询名字为"ss"
User user = userMapper.findUserByName("ss");
logger.debug(user.getGender().getName());
打印结果
EnumTypeHandler会把使用的名称转换为对应的枚举,比如它会根据数据库返回的字符串“FEMALE”进行转换。为了测试我们把数据库名字为“ss”的性别修改为“FEMALE”。
userMapper.xml改为
<result property="gender" column="gender" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
执行结果:
和上边一样的结果。有同学可能会问了,如果我数据库gender不改为"FEMALE",还是"1"呢。
这里我贴出执行结果,大家可以思考一下
mybatis内部提供两种转换的typeHandler,但是有很大的局限性,就比如我们上边的报错,必须要指定数据库类型,那怎么行,所以更多时候需要我们自定义枚举的typeHandler。
六、自定义枚举typeHandler——SexEnumHandler
@MappedTypes(value = {SexEnum.class})
@MappedJdbcTypes(JdbcType.INTEGER)
public class SexEnumHandler extends BaseTypeHandler<SexEnum> {
public void setNonNullParameter(PreparedStatement preparedStatement, int i, SexEnum e, JdbcType jdbcType) throws SQLException {
preparedStatement.setInt(i,e.getId());
}
public SexEnum getNullableResult(ResultSet resultSet, String s) throws SQLException {
int id = resultSet.getInt(s);
return SexEnum.getSex(id);
}
public SexEnum getNullableResult(ResultSet resultSet, int i) throws SQLException {
int id = resultSet.getInt(i);
return SexEnum.getSex(id);
}
public SexEnum getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
int id = callableStatement.getInt(i);
return SexEnum.getSex(id);
}
}
typeHandler换成自定义的SexEnumHandler,xml文件就可以改成这样
<result property="gender" column="gender" typeHandler="com.sean.handler.SexEnumHandler"/>
运行程序就可以得到我们想要的结果。
好了本章的typeHandler讲解就到此结束了