一文帮你搞定MyBatis的类型转换模块,深度好文,欢迎一键三连!!!

本文详细介绍了MyBatis的类型转换机制,包括BaseTypeHandler抽象类的实现,如何处理参数和结果,以及TypeHandlerRegistry在注册和获取TypeHandler中的作用。通过解析SqlSessionFactory的创建过程,揭示了SQL解析中占位符转换的过程,解析了如何将#{id}转换为?。此外,文章还提到了TypeAliasRegistry在管理类型别名中的角色。

T getResult(ResultSet rs, int columnIndex) throws SQLException;

T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

我们可以看到这个接口中定义的两类方法分别是

  • setParameter 对占位符赋值

  • getResult 根据字段获取值

2.BaseTypeHandler

为了方便用户自定义TypeHandler的实现,在MyBatis中提供了BaseTypeHandler这个抽象类,它实现了TypeHandler接口,并继承了TypeReference类,

在这里插入图片描述

在BaseTypeHandler中的实现方法中实现了对null的处理,非空的处理是交给各个子类去实现的。这个在代码中很清楚的体现了出来. — 代码有简化,方便查看

@Override

public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {

if (parameter == null) {

// 简化了代码 如果参数为空就设置 null

ps.setNull(i, jdbcType.TYPE_CODE);

} else {

// 省略 try 语句 参数不为空就调用子类的实现

setNonNullParameter(ps, i, parameter, jdbcType);

}

}

@Override

public T getResult(ResultSet rs, String columnName) throws SQLException {

return getNullableResult(rs, columnName);

}

@Override

public T getResult(ResultSet rs, int columnIndex) throws SQLException {

return getNullableResult(rs, columnIndex);

}

@Override

public T getResult(CallableStatement cs, int columnIndex) throws SQLException {

return getNullableResult(cs, columnIndex);

}

public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

/**

  • 子类重写这些抽象方法!!!

*/

public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

3.TypeHandler实现类

TypeHandler的实现类比较多,而且实现也都比较简单。

在这里插入图片描述

以Integer为例

/**

  • @author Clinton Begin

*/

public class IntegerTypeHandler extends BaseTypeHandler {

@Override

public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)

throws SQLException {

ps.setInt(i, parameter); // 实现参数的绑定

}

@Override

public Integer getNullableResult(ResultSet rs, String columnName)

throws SQLException {

int result = rs.getInt(columnName); // 获取指定列的值

return result == 0 && rs.wasNull() ? null : result;

}

@Override

public Integer getNullableResult(ResultSet rs, int columnIndex)

throws SQLException {

int result = rs.getInt(columnIndex); // 获取指定列的值

return result == 0 && rs.wasNull() ? null : result;

}

@Override

public Integer getNullableResult(CallableStatement cs, int columnIndex)

throws SQLException {

int result = cs.getInt(columnIndex); // 获取指定列的值

return result == 0 && cs.wasNull() ? null : result;

}

}

4.TypeHandlerRegistry

通过前面的介绍我们发现在MyBatis中给我们提供的具体的类型转换器实在是太多了,那么在实际的使用时我们是如何知道使用哪个转换器类处理的呢?实际上再MyBatis中是将所有的TypeHandler都保存注册在了TypeHandlerRegistry中的。首先注意声明的相关属性

// 记录JdbcType和TypeHandle的对应关系

private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);

// 记录Java类型向指定的JdbcType转换时需要使用到的TypeHandle

private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();

private final TypeHandler unknownTypeHandler;

// 记录全部的TypeHandle类型及对应的TypeHandle对象

private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();

// 空TypeHandle的标识

private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();

然后在器构造方法中完成了系统提供的TypeHandler的注册

在这里插入图片描述

代码太长,请自行查阅。注意的是register()方法, 关键几个实现如下

private void register(Type javaType, TypeHandler<? extends T> typeHandler) {

// 获取@MappedJdbcTypes注解

MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);

if (mappedJdbcTypes != null) {

// 遍历获取注解中指定的 JdbcType 类型

for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {

// 调用下一个重载的方法

register(javaType, handledJdbcType, typeHandler);

}

if (mappedJdbcTypes.includeNullJdbcType()) {

// JdbcType类型为空的情况

register(javaType, null, typeHandler);

}

} else {

register(javaType, null, typeHandler);

}

}

private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {

if (javaType != null) {// 如果不为空

// 从 TypeHandle集合中根据Java类型来获取对应的集合

Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);

if (map == null || map == NULL_TYPE_HANDLER_MAP) {

// 如果没有就创建一个新的

map = new HashMap<>();

}

// 把对应的jdbc类型和处理器添加到map集合中

map.put(jdbcType, handler);

// 然后将 java类型和上面的map集合保存到TypeHandle的容器中

typeHandlerMap.put(javaType, map);

}

// 同时也把这个处理器添加到了 保存有所有处理器的容器中

allTypeHandlersMap.put(handler.getClass(), handler);

}

有注册的方法,当然也有从注册器中获取TypeHandler的方法,getTypeHandler方法,这个方法也有多个重载的方法,这里重载的方法最终都会执行的方法是

/**

  • 根据对应的Java类型和Jdbc类型来查找对应的TypeHandle

*/

private TypeHandler getTypeHandler(Type type, JdbcType jdbcType) {

if (ParamMap.class.equals(type)) {

return null;

}

// 根据Java类型获取对应的 Jdbc类型和TypeHandle的集合容器

Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);

TypeHandler<?> handler = null;

if (jdbcHandlerMap != null) {

// 根据Jdbc类型获取对应的 处理器

handler = jdbcHandlerMap.get(jdbcType);

if (handler == null) {

// 获取null对应的处理器

handler = jdbcHandlerMap.get(null);

}

if (handler == null) {

// #591

handler = pickSoleHandler(jdbcHandlerMap);

}

}

// type drives generics here

return (TypeHandler) handler;

}

当然除了使用系统提供的TypeHandler以外,我们还可以创建我们自己的TypeHandler了,之前讲解案例的时候已经带大家写过了,如果忘记可以复习下。

5 TypeAliasRegistry

我们在MyBatis的应用的时候会经常用到别名,这能大大简化我们的代码,其实在MyBatis中是通过TypeAliasRegistry类管理的。首先在构造方法中会注入系统常见类型的别名

在这里插入图片描述

注册的方法逻辑也比较简单

public void registerAlias(String alias, Class<?> value) {

if (alias == null) {

throw new TypeException(“The parameter alias cannot be null”);

}

// issue #748 别名统一转换为小写

String key = alias.toLowerCase(Locale.ENGLISH);

// 检测别名是否存在

if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {

throw new TypeException(“The alias '” + alias + “’ is already mapped to the value '” + typeAliases.get(key).getName() + “'.”);

}

// 将 别名 和 类型 添加到 Map 集合中

typeAliases.put(key, value);

}

那么我们在实际使用时通过package指定别名路径和通过@Alisa注解来指定别名的操作是如何实现的呢?也在TypeAliasRegistry中有实现

/**

  • 根据 packagename 来指定

  • @param packageName

  • @param superType

*/

public void registerAliases(String packageName, Class<?> superType) {

ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();

resolverUtil.find(new ResolverUtil.IsA(superType), packageName);

Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();

for (Class<?> type : typeSet) {

// Ignore inner classes and interfaces (including package-info.java)

// Skip also inner classes. See issue #6

if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {

registerAlias(type);

}

}

}

/**

  • 扫描 @Alias注解

  • @param type

*/

public void registerAlias(Class<?> type) {

String alias = type.getSimpleName();

// 扫描 @Alias注解

Alias aliasAnnotation = type.getAnnotation(Alias.class);

if (aliasAnnotation != null) {

// 获取注解中定义的别名名称

alias = aliasAnnotation.value();

}

registerAlias(alias, type);

}

二.核心流程应用


1.SqlSessionFactory

在构建SqlSessionFactory时,在Configuration对象实例化的时候在成员变量中完成了TypeHandlerRegistry和TypeAliasRegistry的实例化,在全局配置文件解析的时候完成了自定义的TypeAlias和TypeHandler的注册,在配置文件的加载解析中完成了SQL语句占位符的处理。

1.1 TypeHandlerRegistry和TypeAliasRegistry初始化

在这里插入图片描述进入1中查看

在这里插入图片描述

成员变量中完成了TypeHandlerRegistry和TypeAliasRegistry的初始化操作

在这里插入图片描述

同时在Configuration的构造方法中完成了系统提供的类型别名的注册工作。

在这里插入图片描述

以上步骤完成了TypeHandlerRegistry和TypeAliasRegistry的初始化操作

然后在解析全局配置文件时会通过解析标签和标签,可以注册我们添加的别名和TypeHandler。

在这里插入图片描述

到这儿我们搞清楚了系统初始化时的TypeHandlerRegistry和TypeAliasRegistry的操作,但是我们在 标签中写的SQL语句是什么时候处理的呢?

1.2 SQL解析

映射文件解析中怎么实现的占位符的处理的呢?

select

id,

user_name ,

real_name ,

password,

age,

d_id

from t_user where id = #{id}

怎么转换为

select

id,

user_name ,

real_name ,

password,

age,

d_id

from t_user where id = ?

从开始解析映射文件的位置进入。

在这里插入图片描述

继续进入

在这里插入图片描述

继续进入

public void parse() {

// 总体上做了两件事情,对于语句的注册和接口的注册

// 判断是否已经加载过了 映射文件

if (!configuration.isResourceLoaded(resource)) {

// 1、具体增删改查标签的解析。

// 一个标签一个MappedStatement。 >>

configurationElement(parser.evalNode(“/mapper”));

configuration.addLoadedResource(resource);

// 2、把namespace(接口类型)和工厂类绑定起来,放到一个map。

// 一个namespace 一个 MapperProxyFactory >>

bindMapperForNamespace(); // 注册 Mapper 接口

}

// 处理 configurationElement 方法中解析失败的 节点

parsePendingResultMaps();

// 处理 configurationElement 方法中解析失败的 节点

parsePendingCacheRefs();

// 处理 configurationElement 方法中解析失败的 SQL 语句节点

parsePendingStatements();

}

进入configurationElement方法中。

在这里插入图片描述

继续

在这里插入图片描述

继续进入

在这里插入图片描述

开始解析具体的 select 标签

在这里插入图片描述

继续往下,看到关键方法调用

在这里插入图片描述

进入createSQLSource 方法中

在这里插入图片描述

进入构造器

public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {

// 获取解析器

SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);

// 得到参数类型

Class<?> clazz = parameterType == null ? Object.class : parameterType;

// 解析器解析sql

sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());

}

进入parse方法

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {

// 为参数构建 ParameterMapping

ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);

// 把 #参数 解析成 ? 占位符 select * from t_user where id = #{id}

GenericTokenParser parser = new GenericTokenParser(“#{”, “}”, handler);

String sql = parser.parse(originalSql); // select * from t_user where id = ?

return new StaticSqlSource(configuration, sql, handler.getParameterMappings());

}

Debug可以看到的效果

在这里插入图片描述

在这里插入图片描述

到这儿我们可以总结出在构建SqlSessionFactory的过程中完成了和类型转换模块有关的操作为:

在这里插入图片描述

2.SQL执行

执行SQL语句的时候和类型转换模块有关系的应该是两块

  • 占位符的赋值

  • 结果集的映射

2.1 占位符的赋值

要掌握这块的内容首先要搞清楚在我们获取了PreparedStatement对象后及在执行SQL之前我们需要完成占位符的赋值操作。

String sql = “SELECT * from t_user where id = ? and user_name = ?”;

ps = conn.prepareStatement(sql);

ps.setInt(1,2);

ps.setString(2,“张三”);

ps.executeQuery();

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

本人也收藏了一份Java面试核心知识点来应付面试,借着这次机会可以送给我的读者朋友们

目录:

全靠这套面试题,才让我有惊无险美团二面拿offer  (面经解析)

Java面试核心知识点

一共有30个专题,足够读者朋友们应付面试啦,也节省朋友们去到处搜刮资料自己整理的时间!

全靠这套面试题,才让我有惊无险美团二面拿offer  (面经解析)

Java面试核心知识点

已经有读者朋友靠着这一份Java面试知识点指导拿到不错的offer了

全靠这套面试题,才让我有惊无险美团二面拿offer  (面经解析)

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
片转存中…(img-uXbY8g98-1712265641961)]

[外链图片转存中…(img-TgrecOEX-1712265641962)]

[外链图片转存中…(img-D1E8wRpr-1712265641962)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

本人也收藏了一份Java面试核心知识点来应付面试,借着这次机会可以送给我的读者朋友们

目录:

[外链图片转存中…(img-69sBSZ9n-1712265641962)]

Java面试核心知识点

一共有30个专题,足够读者朋友们应付面试啦,也节省朋友们去到处搜刮资料自己整理的时间!

[外链图片转存中…(img-ZmthnSau-1712265641962)]

Java面试核心知识点

已经有读者朋友靠着这一份Java面试知识点指导拿到不错的offer了

[外链图片转存中…(img-Na62QmOC-1712265641963)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值