public final class TypeHandlerRegistry { private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class); private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>(); private final TypeHandler<Object> unknownTypeHandler; private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>(); private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap(); private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class; /** * 堆代码 duidaima.com * The default constructor. */ public TypeHandlerRegistry() { this(new Configuration()); } /** * The constructor that pass the MyBatis configuration. * * @param configuration a MyBatis configuration * @since 3.5.4 */ public TypeHandlerRegistry(Configuration configuration) { this.unknownTypeHandler = new UnknownTypeHandler(configuration); register(Boolean.class, new BooleanTypeHandler()); register(boolean.class, new BooleanTypeHandler()); register(JdbcType.BOOLEAN, new BooleanTypeHandler()); register(JdbcType.BIT, new BooleanTypeHandler()); register(Byte.class, new ByteTypeHandler()); register(byte.class, new ByteTypeHandler()); register(JdbcType.TINYINT, new ByteTypeHandler()); //.........省略.......... }以 StringTypeHandler 为例,了解 typeHandler 的实现逻辑。
public class StringTypeHandler extends BaseTypeHandler<String> { @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); } }StringTypeHandler 是一个最常用的 typeHandler,处理 String 类型。StringTypeHandler 继承了 BaseTypeHandler,而 BaseTypeHandler 实现了 TypeHandler 接口,TypeHandler 接口定义了4个抽象方法,所以实现类需要实现这四个方法。
public interface TypeHandler<T> { void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; }BaseTypeHandler 实现了 setParameter 方法。
getResult 是对 ResultSet 结果集的转换处理,分为用列名(columnName),或者使用列下标(columnIndex)来获取结果数据。还包括使用 CallableStatement(存储过程)获取结果及数据的方法。
<?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="com.gxitsky.mapper.ActorMapper"> <resultMap id="actorMap" type="actor"> <id column="actor_id" property="actorId" javaType="long" jdbcType="BIGINT"/> <result column="first_name" property="firstName" javaType="string" jdbcType="VARCHAR"/> <result column="last_name" property="last_name" typeHandler="com.gxitsky.config.MyStringTypeHandler"/> </resultMap> <select id="queryById" parameterType="long" resultType="actor"> SELECT * FROM actor WHERE actor_id = #{actor_id} </select> <select id="queryById" resultMap="actorMap"> SELECT * FROM actor </select> <select id="findActor" resultMap="actorMap"> SELECT * FROM actor WHERE first_name LIKE concat('%', #{firstName javaType=string jdbcType=VARCHAR typeHadler=com.gxitsky.config.MyStringTypeHandler} , '%'); </select> </mapper>在配置文件里面配置,结果集中字段指定的 JdbcType 和 JavaType 与定义的 typeHandler 一致,MyBatis 才能知道使用自定义的类型转换器进行转换。在配置 typeHandler 时也可以进行包配置,MyBatis 就会扫描包中的 typeHander,就不用一个一个配置,减少配置工作量。
<typeHandlers> <typeHandler handler="com.gxitsky.config.mybatis.typehandler.MyStringTypeHandler" javaType="string" jdbcType="VARCHAR"/> <package name="com.gxitsky.config.mybatis.typehandler"/> </typeHandlers>映射集中的字段直接指定 typeHandler 属性,就不需要在配置文件中定义了。在参数中指定 typeHandler ,MyBatis 就会用对应的 typeHandler 进行转换,这样也不需要在配置里面定义。
package org.apache.ibatis.type; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @堆代码 duidaima.com */ public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> { private final Class<E> type; public EnumTypeHandler(Class<E> type) { if (type == null) { throw new IllegalArgumentException("Type argument cannot be null"); } this.type = type; } @Override public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException { if (jdbcType == null) { // 取的是字典的 name 属性 ps.setString(i, parameter.name()); } else { ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE); // see r3589 } } @Override public E getNullableResult(ResultSet rs, String columnName) throws SQLException { String s = rs.getString(columnName); // 返回枚举值 return s == null ? null : Enum.valueOf(type, s); } @Override public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String s = rs.getString(columnIndex); // 返回枚举值 return s == null ? null : Enum.valueOf(type, s); } @Override public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { // 返回枚举值 String s = cs.getString(columnIndex); return s == null ? null : Enum.valueOf(type, s); } }验证枚举的 ``Enum.name()方法和Enum.valueOf()` 方法:
public enum SexEnum { MALE(1, "男"), FEMAIL(2, "女"), ; private int code; private String name; SexEnum(int code, String name) { this.code = code; this.name = name; } public static void main(String[] args) { String name = SexEnum.MALE.name(); System.out.println(name);// name = MALE 字符串 SexEnum male = Enum.valueOf(SexEnum.class, "MALE"); System.out.println(male); // male = MALE 枚举值 } }EnumOrdinalTypeHandler
package org.apache.ibatis.type; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * @堆代码 duidaima.com */ public class EnumOrdinalTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> { private final Class<E> type; private final E[] enums; public EnumOrdinalTypeHandler(Class<E> type) { if (type == null) { throw new IllegalArgumentException("Type argument cannot be null"); } // 枚举class this.type = type; // 拿到所有元素 this.enums = type.getEnumConstants(); if (this.enums == null) { throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type."); } } @Override public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException { // 入参取枚举的下标,存储的是枚举的下标 ps.setInt(i, parameter.ordinal()); } @Override public E getNullableResult(ResultSet rs, String columnName) throws SQLException { int ordinal = rs.getInt(columnName); if (ordinal == 0 && rs.wasNull()) { return null; } // 取出枚举的下标, 返回枚举 return toOrdinalEnum(ordinal); } @Override public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException { int ordinal = rs.getInt(columnIndex); if (ordinal == 0 && rs.wasNull()) { return null; } return toOrdinalEnum(ordinal); } @Override public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { int ordinal = cs.getInt(columnIndex); if (ordinal == 0 && cs.wasNull()) { return null; } return toOrdinalEnum(ordinal); } private E toOrdinalEnum(int ordinal) { try { return enums[ordinal]; } catch (Exception ex) { throw new IllegalArgumentException("Cannot convert " + ordinal + " to " + type.getSimpleName() + " by ordinal value.", ex); } } }验证枚举元素的下标:
public enum SexEnum { FEMALE(2, "女"), MALE(1, "男"), ; private int code; private String name; SexEnum(int code, String name) { this.code = code; this.name = name; } public static void main(String[] args) { int index = FEMALE.ordinal(); Class<SexEnum> sexEnumClass = SexEnum.class; SexEnum[] enumConstants = sexEnumClass.getEnumConstants(); SexEnum enumConstant = enumConstants[index]; System.out.println(enumConstant); } }输出结果:
0 FEMALE
package com.gxitsky.config; import com.gxitsky.enums.SexEnum; 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; /** * @堆代码 duidaima.com * @desc 自定义性别枚举处理器 * @date 2023/7/30 */ @MappedTypes(value = {Integer.class}) @MappedJdbcTypes(value = JdbcType.INTEGER) public class SexEnumTypeHandler implements TypeHandler<SexEnum> { @Override public void setParameter(PreparedStatement ps, int i, SexEnum parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter.getCode()); } @Override public SexEnum getResult(ResultSet rs, String columnName) throws SQLException { int code = rs.getInt(columnName); return SexEnum.getByCode(code); } @Override public SexEnum getResult(ResultSet rs, int columnIndex) throws SQLException { int code = rs.getInt(columnIndex); return SexEnum.getByCode(code); } @Override public SexEnum getResult(CallableStatement cs, int columnIndex) throws SQLException { int code = cs.getInt(columnIndex); return SexEnum.getByCode(code); } }把映射结果集中的 sex 字段 typeHandler 改为 SexEnumTypeHandler。