计算机系统应用教程网站

网站首页 > 技术文章 正文

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

btikc 2024-09-18 08:37:49 技术文章 20 ℃ 0 评论

这个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,具体是什么样呢?请看下图:

从上图可以看出,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.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="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.2.1.写明property与column映射到typeHandler的绑定

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


2.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"/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呢?看到<id>标签的父标签<resultMap>的type="calendaritem"了吗?没错这里就是**根据CalendarItem.class的java字段的id属性来推断出**<id>标签对应的javaType为Integer.class(具体怎么推断的这里不展开讲,后面会在ResultMap解析的时候说明).那么mybatis又能通过<Intgeger.class,null>找到对应的IntegerTypeHandler.>
    <result property="contentType" javaType="int" column="content_type"></result>
    <id property="id" column="id"></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呢?看到<id>标签的父标签<resultMap>的type="calendaritem"了吗?没错这里就是根据CalendarItem.class的java字段的id属性来推断出<id>标签对应的javaType为Integer.class(具体怎么推断的这里不展开讲,后面会在ResultMap解析的时候说明).那么mybatis又能通过<Intgeger.class,null>找到对应的IntegerTypeHandler.

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表