mybatis-configuration容器(2)--类型处理注册表--TypeHandlerRegistry

        这个handler的主要功能就是完成mysql的列值与java字段相互转换的处理器。那么这里就会出现两个种不同的的类型。

  • mysql的列类型叫做jdbcType(由jdbc提供);
  • java的字段类型叫做javaType(由jdk提供);

首先我们先看一段熟悉的代码

public static void main(String[] args) {
    Connection conn = DriverManager.getConnection(url,user, password)
    String sql = "select name from student where id = ?";
	PreparedStatement pst = conn.prepareStatement(sql);
    // 1.java转jdbc
    pst.setInt(1,10);
    ResultSet rs = pst.executeQuery();
    while(rs.next()) {
        // 2.jdbc转java
        String name = rs.getString("name");
        // 3.想对出参附加"小一班"前缀
        String schoolName = "小1班--" + name;
    }
}

看到了吧,其实jdbc和java之间的互转,说的就是:

  1. 当我java转jdbc时,我是调用jdbc的setInt()呢还是setString()?
  2. 当我jdbc转java时,我是调用jdbc的getInt()呢还是getString()?

        那么如何确定是调用哪个方法呢?那么就有了typeHandler.这里要说明一点:mybatis查出的每个mysql的列字都需要一个typeHandler才能知道调用jdbc的哪个方法.那么获取typeHandler理论上需要2个条件,javaType、jdbcType.

首先我们来看一下TypeHandlerRegistry的结构

public final class TypeHandlerRegistry {
    
    // java类型 -> mysql类型 -> TypeHandler.很常用.
    // 根据javaType和jdbcType获取对应的TypeHandler时就是使用的该map
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap 
    										= new ConcurrentHashMap<>();
    
    // mysql类型 -> TypeHanlder.   暂时未找到使用的地方
    private final Map<JdbcType, TypeHandler<?>>  jdbcTypeHandlerMap 
    									= new EnumMap<>(JdbcType.class);
    
    // 当无法获取typeHandler时,暂时绑定的就是该typeHandler
    private final TypeHandler<Object> unknownTypeHandler;
    
    // 快速根据我们写的TypeHandler获取到该typeHandler对象,
    // 不用创建新的typeHandler对象
    private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap 
    												= new HashMap<>();
}

        当TypeHandlerRegistry初始化完毕,typeHandlerMap、jdbcTypeHandlerMap、allTypeHandlersMap,具体是什么样呢?请看下图:
图1

        从上图可以看出,typeHandlerMap是最重要的typeHandler推断的map.只要给出javaType和jdbcType就能推断出typeHandler是什么,有时就算没有给出jdbcType也能根据jdbcType=null进行推断.那么TypeHandlerRegistry是如何完成那么多typeHandler的注册与推断的呢?请看下文

1.typeHandler的注册

1.1.mybatis默认typeHandler加载

        默认typeHandler加载的地方只有一处,就是TypeHandlerRegistry对象初始化的时候.

public final class TypeHandlerRegistry {
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap 
    									= new ConcurrentHashMap<>();
    private final Map<JdbcType, TypeHandler<?>>  jdbcTypeHandlerMap 
    									= new EnumMap<>(JdbcType.class);
    private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap 
    									= new HashMap<>();
    private final TypeHandler<Object> unknownTypeHandler;
    
    // 构造器
    public TypeHandlerRegistry(Configuration configuration) {
		
        this.unknownTypeHandler = new UnknownTypeHandler(configuration);
        
        register(JdbcType.INTEGER, new IntegerTypeHandler());
        // 这里是注册到typeHandlerMap与allTypeHandlersMap,形成的结构是
        // String.class --> null --> StringTypeHandler
        register(String.class, new StringTypeHandler());
        // 这里也是注册到typeHandlerMap与allTypeHandlersMap,形成的结构是
        // String.class --> JdbcType.VARCHAR --> StringTypeHandler
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
        // 这里是注册到jdbcTypeHandlerMap中,形成的结构是
        // JdbcType.VARCHAR --> StringTypeHandler
        register(JdbcType.VARCHAR, new StringTypeHandler());
        // ... 以下省略
    } 
}

        以上代码执行会形成下面的结构(简单举例并非全部默认数据):

1.2.自定义typeHandler集中注册

        自定义typeHandler举例

@MappedTypes(value = {String.class})
@MappedJdbcTypes(value = {JdbcType.VARCHAR},includeNullJdbcType = true)
public class SexTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, 
  		int i, String parameter, JdbcType jdbcType) throws SQLException {
        // 性别:1、男,2、女
        int sex = parameter.equals("1") ? 1 : 2;
        ps.setInt(i,sex);
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName)
    		 throws SQLException {
        int sex = rs.getInt(columnName);
        return sex == 1 ? "男" : "女";
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) 
   			  throws SQLException {
        return null;
    }

    @Override
    public String getNullableResult(CallableStatement cs, 
    			int columnIndex) throws SQLException {
        return null;
    }
}

        此类typeHandler的注册都是在mybatis的核心配置文件mybatis-config.xml中配置。自定义typeHandler的注册规则如下:

  • 规则一:标签中明确定义了javaType、jdbcType、typeHandler属性,mybatis会根据明确定义的javaType、jdbcType、typeHandler加载到TypeHandlerRegistry
// TypeHandlerRegistry
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
        Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
        if (map == null || map == NULL_TYPE_HANDLER_MAP) {
            map = new HashMap<>();
        }
        map.put(jdbcType, handler);
        // 注册到typeHandlerMap
        typeHandlerMap.put(javaType, map);
    }
    // 注册到allTypeHandlers
    allTypeHandlersMap.put(handler.getClass(), handler);
}
  • 规则二:标签中只定义了javaType、typeHandler没有定义jdbcType,mybatis会根据明确定义的javaType、@MappedJdbcTypes注解中标明的jdbcType(包含includeNullJdbcType属性)取笛卡尔积,分别注册到TypeHandlerRegistry
// TypeHandlerRegistry
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    // 获取@MappedJdbcTypes(value = {JdbcType.VARCHAR},includeNullJdbcType = true)
    // 标签里标明的jdbcType属性
    MappedJdbcTypes mappedJdbcTypes = 
    				typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
        // javaType 与 jdbcType取笛卡尔积进行注册.
        for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
            // 调用【规则一】中代码
            register(javaType, handledJdbcType, typeHandler);
        }
        // 注册javaType --> jdbcType = null --> typeHandler的数据进来
        // @MappedJdbcTypes中includeNullJdbcType属性
        if (mappedJdbcTypes.includeNullJdbcType()) {
            // 调用【规则一】中代码
            register(javaType, null, typeHandler);
        }
    } else {
        // 调用【规则一】中代码
        register(javaType, null, typeHandler);
    }
}
  • 规则三:标签中只定义了typeHandler,没有明确定义javaType、jdbcType.mybatis会根据typeHanlder上的@MappedTypes注解(如果没有该注解mybatis会根据该类中的泛型来确定javaType)中标明的javaType与@MappedJdbcTypes注解中标明的jdbcType(包含includeNullJdbcType属性)取笛卡尔积,分别注册到TypeHandlerRegistry
// TypeHandlerRegistry
public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    // 获取@MappedTypes(value = {String.class})标签里标明的javaType属性.
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
        // 遍历@MappedTypes中标明的所有javaType属性,看到了吧,
        // 这里与【规则二】中的代码构成了javaType与jdbcType
        // 确定typeHandler的笛卡尔积注册规则
        for (Class<?> javaTypeClass : mappedTypes.value()) {
            // 调用【规则二】中代码
            register(javaTypeClass, typeHandlerClass);
            mappedTypeFound = true;
        }
    }
    // 如果没有标明@MappedTypes注解,根据typeHandler中定义的泛型去推断javaType
    if (!mappedTypeFound) {
        register(getInstance(null, typeHandlerClass));
    }
}

public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    // 1.又找了一遍@MappedTypes注解
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
        for (Class<?> handledType : mappedTypes.value()) {
            register(handledType, typeHandler);
            mappedTypeFound = true;
        }
    }

    // 2.根据typeHandler的泛型作为javaType去注册
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
        try {
            TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
            // 这里就是typeHandler的泛型
            register(typeReference.getRawType(), typeHandler);
            mappedTypeFound = true;
        } catch (Throwable t) {
            // maybe users define the TypeReference with a different 
            // type and are not assignable, so just ignore it
        }
    }

    if (!mappedTypeFound) {
        register((Class<T>) null, typeHandler);
    }
}

1.2.1.包扫描

<configuration> 
    <typeHandlers>
    	<!--这里是扫描test.typeHandler包下的所有typeHandler-->
        <package name="test.typeHandler"/>  
    </typeHandlers>
</configuration>

        扫描注册规则为:是TypeHandler的子类(实现类),而且不是内部类、接口、抽象类。因为package中只有包名.没有办法像单个类注册typeHandler那样指明javaType、jdbcType.所以只能走规则三.

        由上图的继承关系看,我们实现TypeHandler接口或者继承BaseTypeHandler都可以通过这种方式注册到TypeHandlerRegistry中.看TypeHandlerRegistry源码.

// TypeHandlerRegistry
public void register(String packageName) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // 1.TypeHandler的子类(实现类)
    resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
    Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
    for (Class<?> type : handlerSet) {
        //Ignore inner classes and interfaces (including package-info.java) 
        // and abstract classes
       	// 2.不是内部类、接口、抽象类
        if (!type.isAnonymousClass() && !type.isInterface() 
        			&& !Modifier.isAbstract(type.getModifiers())) {
            register(type);
        }
    }
}

1.2.2.单个注册

<configuration> 
    <typeHandlers>
        <!--如果定义了别名扫描,这里就不用写类的全限定名,只需要类名全小写即可-->
    	<typeHandler handler="sextypehandler" javaType="string" jdbcType="VARCHAR"/>
    </typeHandlers>
</configuration> 

        单个类注册的规则:因可以明确指定javaType、jdbcType,所以参考规则一、规则二、规则三。

// XMLConfigBuilder
private void typeHandlerElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String typeHandlerPackage = child.getStringAttribute("name");
                // 规则三
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
                String javaTypeName = child.getStringAttribute("javaType");
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                String handlerTypeName = child.getStringAttribute("handler");
                Class<?> javaTypeClass = resolveClass(javaTypeName);
                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                Class<?> typeHandlerClass = resolveClass(handlerTypeName);
                if (javaTypeClass != null) {
                    if (jdbcType == null) {
                        // 规则二
                        typeHandlerRegistry.register(javaTypeClass, 
                        						typeHandlerClass);
                    } else {
                        // 规则一
                        typeHandlerRegistry.register(javaTypeClass, 
                        						jdbcType, typeHandlerClass);
                    }
                } else {
                    // 规则三
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }
}

1.3.ResultMap中typeHandler注册

<resultMap id="calendarItemMap" type="mapper.discriminator.domain.CalendarItem">
    <result property="sex" column="sex" typeHandler="sextypehandler"></result>
</resultMap>

        这里只会把SexTypeHandler对象存放到ResultMapping结构中,不会注册到TypeHandlerRegistry中.具体看typeHandler与字段的绑定.

2.typeHandler与字段的绑定

2.1.javaType转jdbcType

        该类typeHandler就是当我们的sql语句中有参数,需要用PreparedStatement去set参数的时候需要确定的typeHandler.该类typeHandler全是TypeHandlerRegistry中的unknownTypeHandler,因为我们set参数的时候是无法知道对应的jdbcType是什么的,而且typeHandler与MappedStatement属于不同的结构体,而且unknownTypeHandler结构中有个再次确认typeHandler的机制,当执行set的时候,根据我们的javaType --> null --> typeHandler的方式寻找set时对应的typeHandler.

// DefaultParameterHandler
@Override
public void setParameters(PreparedStatement ps) {
	// 省略..
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                // 省略..
                // 这里是获取该参数的typeHandler,这里的handler为unknownTypeHandler
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                // 获取jdbcType = null
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) {
                    jdbcType = configuration.getJdbcTypeForNull();
                }
                try {
                    // 这里调用unknownTypeHandler的方法
                    // 因为PrepareStatement的set***和get***方法都是下标从1开始的,
                    // 这里循环是从0开始的,所以需要i+1
                    typeHandler.setParameter(ps, i + 1, value, jdbcType);
                } catch (TypeException | SQLException e) {
                    throw new TypeException("Could not set parameters for mapping: "
                    			 + parameterMapping + ". Cause: " + e, e);
                }
            }
        }
    }
}

// UnknownTypeHandler
@Override
public void setNonNullParameter(PreparedStatement ps, int i, 
					Object parameter, JdbcType jdbcType) throws SQLException {
    // 二次推断typeHandler
    TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
    handler.setParameter(ps, i, parameter, jdbcType);
}

2.2.jdbcType转javaType

2.1.写明property与column映射到typeHandler的绑定

        该类不需要任何的typeHandler推断,只需要在找到该字段对应的ResultMapping时,调用相应typeHandler的get或set方法即可.

2.2.未写明property与column映射到typeHandler的绑定

        该类需要typeHandler推断,还记得开篇地方说的mybatis查询的列转换成java字段,都需要一个typeHandler吗,如果我们ResultMap的property中未标明typeHandler程序怎么处理的呢?当mybatis生成ResultMap结构后还有还有一步二次确认typeHandler的过程.

// ResultMapping
public ResultMapping build() {
    // lock down collections
    resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
    resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
    // 二次确认typeHandler
    resolveTypeHandler();
    // 校验
    validate();
    return resultMapping;
}

private void resolveTypeHandler() {
    // 	如果resultMap中指明了typeHandler,typeHandler那么对应字段的解析就不会走这里,
    // 大部分情况是需要走这里的
    // 	从而可以看出,这里就是对未指明typeHandler的字段进行typeHandler推断
    if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
        Configuration configuration = resultMapping.configuration;
        TypeHandlerRegistry typeHandlerRegistry = 
        							configuration.getTypeHandlerRegistry();
        // 根据javaType,jdbcType推断.结构见图1
        resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(
            resultMapping.javaType, resultMapping.jdbcType);
    }
}

推断举例:

// java类
public class CalendarItem {
    private String contentId;
    private Integer contentType;
    private Integer id;
    // getter、setter省略
}
<resultMap id="calendarItemMap" type="calendaritem"> <!--别名不在赘述-->
    <result property="contentId" javaType="string" 
    						column="content_id" jdbcType="JdbcType.VARCHAR"/>
    <result property="contentType" javaType="int" column="content_type"/>
    <id property="id" column="id"/>
</resultMap>
  1. property="contentId"的result,这里因为写了javaType=“string”,jdbcType=“JdbcType.VARCHAR”.看图一,mybatis能很轻松的通过key1=String.class,key2=JdbcType.VARCHAR很快能定义出StringTypeHandler
  2. property="contentType"的result,因为这里顶一个javaType=“int”.看图一,虽然没有写jdbcType,mybatis也能很轻松的通过key1=Integer.class,javaType=null,很快能定义出IntegerTypeHandler
  3. property=“id”,这里javaType和jdbcType都没有写,那么mybatis是如何对id字段绑定typeHandler呢?看到标签的父标签的type="calendaritem"了吗?没错这里就是根据CalendarItem.class的java字段的id属性来推断出标签对应的javaType为Integer.class(具体怎么推断的这里不展开讲,后面会在ResultMap解析的时候说明).那么mybatis又能通过<Intgeger.class,null>找到对应的IntegerTypeHandler.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值