简述
注册了的类型处理器会用于处理下面两种情形:
- 为PreparedStatement设置一个参数,将参数从Java类型转为JDBC类型。
- 从ResultSet中取出一个值,将结果从JDBC类型转为Java类型。
类型处理器可分为以下两类:
- MyBatis系统定义的类型处理器
- 用户自定义的类型处理器
认识系统定义的类型处理器
在org.apache.ibatis.type.TypeHandlerRegistry
类中的构造器可以看到系统注册的大量的类型处理器,如其中一段:
register(Reader.class, new ClobReaderTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler());
点开其中的org.apache.ibatis.type.StringTypeHandler
类查看一下具体实现:
//继承BaseTypeHandler抽象接口,间接实现了必要的TypeHandler接口
public class StringTypeHandler extends BaseTypeHandler<String> {
//设置非空参数(要设置参数的预处理语句,设置参数的下标,使用的Java类型字符串,对应数据库的JDBC类型)
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);
}
//从结果集中按列名获取(结果集,列名)
@Override
public String getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getString(columnName);
}
//从结果集中按列下标获取(结果集,列下标)
@Override
public String getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getString(columnIndex);
}
//从存储过程中按列下标获取(存储过程,列下标)
@Override
public String getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getString(columnIndex);
}
}
自定义的类型处理器
按照书上的例子,先尝试自定义覆盖掉一个系统定义的类型处理器,实现String
到VARCHAR
的转换。
配置文件中
<!--注册自定义的类型处理器-->
<typeHandlers>
<typeHandler jdbcType="VARCHAR" javaType="string" handler="test.MyStringTypeHandler"/>
</typeHandlers>
该类型处理器类
一定要实现TypeHandler
接口,可以像前面系统实现的那样通过继承BaseTypeHandler
类来实现,也可以直接实现。
对于处理器类,使用@MappedTypes
注解Java类型;使用@MappedJdbcTypes
注解JDBC类型,即org.apache.ibatis.type.JdbcType
枚举类所列的枚举类型。
package test;
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;
//Java类型为String
@MappedTypes({String.class})
//JDBC类型为VARCHAR
@MappedJdbcTypes(JdbcType.VARCHAR)
//直接实现TypeHandler接口,泛型<T>影响了后三个方法的返回值<T>
public class MyStringTypeHandler implements TypeHandler<String> {
//为PreparedStatement设置参数
@Override
public void setParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
System.out.println("调用了自定的setParameter");
preparedStatement.setString(i, s);
}
//从结果集中按列名获取
@Override
public String getResult(ResultSet resultSet, String s) throws SQLException {
System.out.println("调用了自定的getResult(1)");
return resultSet.getString(s);
}
//从结果集中按列号获取
@Override
public String getResult(ResultSet resultSet, int i) throws SQLException {
System.out.println("调用了自定的getResult(2)");
return resultSet.getString(i);
}
//从存储过程中按列号获取
@Override
public String getResult(CallableStatement callableStatement, int i) throws SQLException {
System.out.println("调用了自定的getResult(3)");
return callableStatement.getString(i);
}
}
不改变其行为,只是在调用其内方法时多输出一句话,以验证是否配置上了这个自定义的类型处理器。
映射文件中
- 为映射文件添加
<resultMap>
标签(构建ResultSet类型处理器) - 为查找的标签添加
resultMap
属性指向这个刚构建的类型处理器(增删改不会返回ResultSet所以不用) - 为SQL语句中使用到参数的位置添加上
javaType
、jdbcType
、typeHandler
属性于#{}
内(参数使用类型处理器)。
前两件事共同配置了ResultSet使用类型处理器,后面一件事是在配置PreparedStatement的参数传入时使用类型处理器。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.StudentMapper">
<!--设置关于ResultSet使用类型处理器-->
<!--id用来给后面使用,type就使用PO类或其别名表示结果集对应的是这个类的对象-->
<resultMap id="stuMap" type="stu">
<!--id子元素用来标识主键,column标识在数据库中的列或属性,property标识PO类的属性,后两个属性不用说了-->
<id column="id" property="id" javaType="int" jdbcType="INTEGER"/>
<!--result子元素标识非主属性,元素内的四个属性都和前面id的一样-->
<!--可以直接使用typeHandler属性,就不必指出javaType和jdbcType了,或只给出javaType和jdbcType也够用-->
<result column="name" property="stuName" typeHandler="test.MyStringTypeHandler"/>
</resultMap>
<!--查询语句需要给出resultMap属性,指向前面配置的resultMap的id,表示对该语句的结果集的处理使用前面resultMap的配置-->
<select id="getStudent" parameterType="int" resultType="stu" resultMap="stuMap">
SELECT
id,
name
FROM student
WHERE id = #{id}
</select>
<insert id="addStudent" parameterType="stu">
INSERT INTO student (name) VALUES (#{stuName})
</insert>
<delete id="deleteStudent" parameterType="int">
DELETE FROM student
WHERE id = #{id}
</delete>
<!--这里面的name设置了参数使用这个自定的类型处理器-->
<update id="updateStudent" parameterType="stu">
UPDATE student
SET name = #{stuName,javaType=string,jdbcType=VARCHAR,typeHandler=test.MyStringTypeHandler}
WHERE id = #{id}
</update>
</mapper>
运行结果
可以看到输出了自定义的类型处理器中的这两句话,说明成功配置了自定义的类型处理器。
未解决的疑问
在前面映射文件中,可以看到除了新的和类型处理器相关的配置之外,还有一个和之前不一样的地方:
SELECT
id,
name
FROM student
WHERE id = #{id}
这里之前是:
SELECT
id,
name as stuName
FROM student
WHERE id = #{id}
如果采用这种带有AS
的写法,读出来的name始终是null,而且不会输出第一句调用(即没有调用自定义的类型处理器中的getResult()
方法)。
我猜测是因为添加ResultMap已经给出了这种映射关系,即从
column
到property
的映射关系。