MyBatis3源码深度解析(十五)SqlSession的创建与执行(二)Mapper接口和XML配置文件的注册与获取

本文主要是介绍MyBatis3源码深度解析(十五)SqlSession的创建与执行(二)Mapper接口和XML配置文件的注册与获取,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 前言
    • 5.5 Mapper接口与XML配置文件的注册过程
      • 5.5.1 Mapper接口的注册过程
      • 5.5.2 XML配置文件的注册过程
    • 5.6 MappedStatement对象的注册过程
    • 5.7 Mapper接口的动态代理对象的获取

前言

SqlSession对象创建后,接下来是执行Mapper。执行Mapper的过程可以拆解为三步:注册Mapper接口与XML配置文件;注册MappedStatement对象;调用Mapper方法。

5.5 Mapper接口与XML配置文件的注册过程

Mapper接口用于定义执行SQL语句相关的方法,方法名一般和Mapper XML配置文件中的<select|update|insert|delete>标签的id属性相同,Mapper接口的完全限定名一般和Mapper XML配置文件的命名空间相同。例如:

public interface UserMapper {List<User> selectAll();@Select("select * from user where id = #{id, jdbcType=INTEGER}")User selectById(@Param("id") Integer id);}
<!--UserMapper.xml-->
<mapper namespace="com.star.mybatis.mapper.UserMapper"><select id="selectAll" resultType="User">select * from user</select>
</mapper>

如上面的代码所示,UserMapper接口的selectAll()方法名与UserMapper.xml中的<select>标签的id属性相同;UserMapper接口的完全限定名与<mapper>标签的namespace属性相同。

如果要将Mapper接口或Mapper XML配置文件注册到Configuration组件中,需要在主配置文件mybatis-config.xml中配置<mappers>标签。

<!--mybatis-config.xml-->
<mappers><!--方式一:通过指定XML文件的类路径来注册--><mapper resource="mapper/UserMapper.xml"/><!--方式二:通过指定XML文件的完全限定资源定位符来注册--><mapper url="file:///C:\workspace\mybatis_demo2\src\main\resources\mapper\UserMapper.xml"/><!--方式三:通过Mapper接口的类路径来注册--><mapper class="com.star.mybatis.mapper.UserMapper"/><!--方式四:通过Mapper接口所在包路径类注册--><package name="com.star.mybatis.mapper"/>
</mappers>

如上面的代码所示,<mappers>标签支持四种配置方式,其中方式一和方式二指定XML配置文件的相对和绝对路径,方式三和方式四指定Mapper接口的类路径和所在包路径。实际开发中,选择其一即可。

在【MyBatis3源码深度解析(十四)Configuration与SqlSession的创建过程 5.2 Configuration实例创建过程】中已经研究过,在mappersElement()方法中,会对<mappers>标签的四种配置方式分别进行解析。

源码1org.apache.ibatis.builder.xml.XMLConfigBuilderprivate void mappersElement(XNode context) throws Exception {if (context == null) {return;}for (XNode child : context.getChildren()) {if ("package".equals(child.getName())) {// 方式四的处理String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) {// 方式一的处理 ...ErrorContext.instance().resource(resource);try (InputStream inputStream = Resources.getResourceAsStream(resource)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,configuration.getSqlFragments());mapperParser.parse();}} else if (resource == null && url != null && mapperClass == null) {// 方式二的处理 ...ErrorContext.instance().resource(url);try (InputStream inputStream = Resources.getUrlAsStream(url)) {XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,configuration.getSqlFragments());mapperParser.parse();}} else if (resource == null && url == null && mapperClass != null) {// 方式三的处理 ...Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {// throw ...}}
}

5.5.1 Mapper接口的注册过程

由 源码1 可知,如果是方式三或方式四这两种配置Mapper接口的方式,则会调用Configuration对象的addMappers()方法。

源码2org.apache.ibatis.session.Configurationprotected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// 用于处理指定包路径下的所有Mapper接口的注册
public void addMappers(String packageName) {mapperRegistry.addMappers(packageName);
}
// 用于处理指定的Mapper接口的注册
public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);
}

由 源码2 可知,Configuration组件中组合了一个MapperRegistry对象,其addMappers(String)方法用于处理指定包路径下的所有Mapper接口的注册,其addMapper(Class<T>)方法用于处理指定的Mapper接口的注册。

先来看addMapper(Class<T>)方法:

源码3org.apache.ibatis.binding.MapperRegistryprivate final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new ConcurrentHashMap<>();
public <T> void addMapper(Class<T> type) {// 判断是否是Mapper接口if (type.isInterface()) {// 判断是否已经注册过if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {// 注册Mapper接口到knownMappers属性中knownMappers.put(type, new MapperProxyFactory<>(type));MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}
}

由 源码3 可知,MapperRegistry类中有一个knownMappers属性,用于注册Mapper接口对应的Class对象和MapperProxyFactory对象之间的关系。addMapper(Class<T>)方法经过type.isInterface()hasMapper(type)两个前置检查之后,然后为Mapper接口对应的Class对象创建一个MapperProxyFactory对象,并添加到knownMappers属性中。

再来看addMappers(String)方法:

源码4org.apache.ibatis.binding.MapperRegistrypublic void addMappers(String packageName, Class<?> superType) {// 借助ResolverUtil工具类获取指定包路径下的全部Mapper接口对应的Class对象ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();resolverUtil.find(new ResolverUtil.IsA(superType), packageName);Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();// 遍历Class对象集合,分别调用addMapper方法注册到MapperRegistry类的knownMappers属性中for (Class<?> mapperClass : mapperSet) {addMapper(mapperClass);}
}

由 源码4 可知,addMappers(String)方法借助ResolverUtil工具类获取指定包路径下的全部Mapper接口对应的Class对象,然后遍历Class对象集合,分别调用addMapper(Class<T>)方法将Class对象注册到MapperRegistry类的knownMappers属性中。

再深入resolverUtil.find()方法的源码可以发现,ResolverUtil是使用VFS工具扫描指定包路径下所有Class文件,然后再交给addMapper(Class<T>)方法进行筛选。

至此,Mapper接口的注册完毕,MapperRegistry类的knownMappers属性中保存了Mapper接口对应的Class对象与MapperProxyFactory对象之间的映射关系。

5.5.2 XML配置文件的注册过程

回到 源码1 可知,如果是方式一或方式二这两种配置Mapper XML配置文件的方式,则会先获取Mapper XML配置文件的输入流,再调用XMLMapperBuilder类的parse()方法来完成注册。

源码5org.apache.ibatis.builder.xml.XMLMapperBuilderprivate final XPathParser parser;
public void parse() {if (!configuration.isResourceLoaded(resource)) {// 调用XPathParser的evalNode方法获取mapper根节点对应的XNode对象// 调用configurationElement方法进一步解析configurationElement(parser.evalNode("/mapper"));// 将资源路径添加到Configuration对象中configuration.addLoadedResource(resource);bindMapperForNamespace();}// ......
}

由 源码5 可知,parse()方法首先调用XPathParser的evalNode()方法获取<mapper>根节点对应的XNode对象,然后调用configurationElement()方法进一步解析XNode对象。

源码6org.apache.ibatis.builder.xml.XMLMapperBuilderprivate void configurationElement(XNode context) {try {// 获取并设置命名空间String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.isEmpty()) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);// 解析<cache-ref>标签cacheRefElement(context.evalNode("cache-ref"));// 解析<cache>标签cacheElement(context.evalNode("cache"));// 解析<parameterMap>标签parameterMapElement(context.evalNodes("/mapper/parameterMap"));// 解析<resultMap>标签resultMapElements(context.evalNodes("/mapper/resultMap"));// 解析<sql>标签sqlElement(context.evalNodes("/mapper/sql"));// 解析<select|insert|update|delete>标签buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} // catch ......
}

由 源码6 可知,configurationElement()方法会对Mapper XML配置文件的所有标签进行解析,并封装成一个MappedStatement对象。

5.6 MappedStatement对象的注册过程

重点关注<select|insert|update|delete>标签的解析。由 源码6 可知,获取<select|insert|update|delete>标签节点对应的XNode对象后,调用buildStatementFromContext()方法做进一步的解析。

源码7org.apache.ibatis.builder.xml.XMLMapperBuilderprivate void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {// 创建XMLStatementBuilder对象final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context,requiredDatabaseId);try {// 真正解析标签statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}
}

由 源码7 可知,buildStatementFromContext()方法会对所有的XNode对象进行遍历,为每一个XNode对象创建一个XMLStatementBuilder对象。然后调用该对象的parseStatementNode()方法真正进行解析标签。

源码8org.apache.ibatis.builder.xml.XMLStatementBuilderpublic void parseStatementNode() {// 获取id、databaseId属性并进行前置判断String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}// 根据标签名字得到SQL语句的类型String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));// 获取flushCache属性,如果是SELECT语句,则默认为falseboolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);// 获取useCache属性,如果是SELECT语句,则默认为trueboolean useCache = context.getBooleanAttribute("useCache", isSelect);// 获取resultOrdered属性,默认为falseboolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// 将<include>标签内容替换为<sql>标签定义的SQL片段XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// 获取parameterType属性,并转换成对应的Class对象String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);// 获取lang属性,并转换成对应的LanguageDriver对象String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);// 解析<selectKey>标签processSelectKeyNodes(id, parameterTypeClass, langDriver);// 获取主键生成策略KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}// 通过LanguageDriver解析SQL内容,生成SqlSource对象SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// 获取statementType属性,默认为PREPARED类型StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));// 获取fetchSize、timeout、parameterMap属性Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");// 获取resultType属性,并转换为对应的Class对象String resultType = context.getStringAttribute("resultType");Class<?> resultTypeClass = resolveClass(resultType);String resultMap = context.getStringAttribute("resultMap");if (resultTypeClass == null && resultMap == null) {resultTypeClass = MapperAnnotationBuilder.getMethodReturnType(builderAssistant.getCurrentNamespace(), id);}// 获取resultSetType属性,并转换为对应的ResultSetType对象String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType();}// 获取keyProperty、keyColumn、resultSets、affectData属性String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");String resultSets = context.getStringAttribute("resultSets");boolean dirtySelect = context.getBooleanAttribute("affectData", Boolean.FALSE);// 使用上面获取的所有属性构造一个MappedStatement对象builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
}

如 源码8 所示,parseStatementNode()方法篇幅很长,但逻辑很清晰,主要做了以下几件事情:

(1)获取<select|insert|update|delete>标签的所有属性信息。
(2)将<include>标签引用的SQL片段替换为对应的<sql>标签中定义的内容。
(3)获取lang属性指定的LanguageDriver对象,通过该对象创建代表SQL资源的SqlSource对象。
(4)获取KeyGenerator对象。KeyGenerator的不同实例代表不同的主键生成策略。
(5)所有解析工作完成后,使用MapperBuilderAssistant对象的addMappedStatement()方法创建一个MappedStatement对象。

源码9org.apache.ibatis.builder.MapperBuilderAssistantpublic MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,LanguageDriver lang, String resultSets, boolean dirtySelect) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");}id = applyCurrentNamespace(id, false);// 创建MappedStatement.Builder对象MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect);ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);}// 创建MappedStatement对象MappedStatement statement = statementBuilder.build();// 注册到Configuration组件中configuration.addMappedStatement(statement);return statement;
}

由 源码9 可知,addMappedStatement()方法会通过MappedStatement.Builder对象的build()方法创建一个MappedStatement对象,最后注册到Configuration组件中。

至此,Mapper接口和Mapper XML配置文件的解析全部完成,保存这些信息的位置是Configuration组件中的mappedStatements属性和mapperRegistry属性。

借助Debug,可以查看到这两个属性保存的信息:

5.7 Mapper接口的动态代理对象的获取

Mapper接口和Mapper XML配置文件解析注册完成后,接下来是执行Mapper接口中定义的方法。

为了执行Mapper接口中定义的方法,首先需要调用SqlSession对象的getMapper()方法获取一个Mapper接口的动态代理对象,然后通过代理对象调用Mapper方法。

代码如下:

SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.selectAll();

SqlSession对象的getMapper()方法的源码如下:

源码10org.apache.ibatis.session.defaults.DefaultSqlSession@Override
public <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);
}
源码11org.apache.ibatis.session.Configurationpublic <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);
}
源码12org.apache.ibatis.binding.MapperRegistryprivate final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new ConcurrentHashMap<>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}
}

由 源码10-12 可知,SqlSession对象的getMapper()方法最终会从MapperRegistry对象的knownMappers属性中,根据传入的Class对象取出一个MapperProxyFactory对象,并调用其newInstance()方法创建一个实例并返回。

由类名可知,MapperProxyFactory是一个代理工厂类,其newInstance()方法会创建一个代理对象实例。

源码13org.apache.ibatis.binding.MapperProxyFactorypublic T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);
}protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

由 源码13 可知,MapperProxyFactory对象的newInstance()方法会创建一个MapperProxy对象,并调用Proxy类的newProxyInstance()方法创建一个代理对象实例。

源码14org.apache.ibatis.binding.MapperProxypublic class MapperProxy<T> implements InvocationHandler, Serializable {public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {this.sqlSession = sqlSession;this.mapperInterface = mapperInterface;this.methodCache = methodCache;}
}@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);}return cachedInvoker(method).invoke(proxy, method, args, sqlSession);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}
}

由 源码14 可知,MapperProxy类实现了InvocationHandler接口,使用的是JDK内置的动态代理

通过以上分析可以知道,SqlSession对象的getMapper()方法返回的是一个MapperProxy动态代理对象。


本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析

这篇关于MyBatis3源码深度解析(十五)SqlSession的创建与执行(二)Mapper接口和XML配置文件的注册与获取的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/825319

相关文章

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

MyBatis常用XML语法详解

《MyBatis常用XML语法详解》文章介绍了MyBatis常用XML语法,包括结果映射、查询语句、插入语句、更新语句、删除语句、动态SQL标签以及ehcache.xml文件的使用,感兴趣的朋友跟随小... 目录1、定义结果映射2、查询语句3、插入语句4、更新语句5、删除语句6、动态 SQL 标签7、ehc

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

一文解析C#中的StringSplitOptions枚举

《一文解析C#中的StringSplitOptions枚举》StringSplitOptions是C#中的一个枚举类型,用于控制string.Split()方法分割字符串时的行为,核心作用是处理分割后... 目录C#的StringSplitOptions枚举1.StringSplitOptions枚举的常用

Python函数作用域与闭包举例深度解析

《Python函数作用域与闭包举例深度解析》Python函数的作用域规则和闭包是编程中的关键概念,它们决定了变量的访问和生命周期,:本文主要介绍Python函数作用域与闭包的相关资料,文中通过代码... 目录1. 基础作用域访问示例1:访问全局变量示例2:访问外层函数变量2. 闭包基础示例3:简单闭包示例4

MyBatis延迟加载与多级缓存全解析

《MyBatis延迟加载与多级缓存全解析》文章介绍MyBatis的延迟加载与多级缓存机制,延迟加载按需加载关联数据提升性能,一级缓存会话级默认开启,二级缓存工厂级支持跨会话共享,增删改操作会清空对应缓... 目录MyBATis延迟加载策略一对多示例一对多示例MyBatis框架的缓存一级缓存二级缓存MyBat