从源码深入理解Mybatis 一、前言 1.1 mybatis简述 myBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射,几乎免除了所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO为数据库中的记录。
官网地址:https://mybatis.org/mybatis-3/zh_CN/index.html
Github仓库:
1.2 核心功能与优势 myBatis 的核心在于其灵活性 和简化性
灵活的自定义SQL :允许开发者编写精确的SQL,优化数据库操作性能。
简化JDBC操作 :封装了JDBC的繁琐流程(如连接管理、语句准备、结果集处理),开发者只需关注SQL本身和对象映射
强大的映射能力 :提供多种结果映射方式,轻松处理数据库字段与Java属性名不一致的情况
动态SQL支持 :通过XML标签构建灵活查询,避免拼接SQL字符串的繁琐与错误
二、使用JDBC操作数据库 JDBC (Java Database Connectivity) 是 Java 提供的一套用于执行 SQL 语句的 API,它允许 Java 程序与各种关系型数据库进行连接和交互。JDBC 的工作原理基于”驱动程序管理器”模式,主要包括以下几个核心组件:
JDBC API :提供应用程序到 JDBC 管理器之间的连接
JDBC Driver Manager :管理不同数据库的驱动程序
JDBC Driver :由数据库厂商提供,实现与特定数据库的通信
数据库 :实际存储数据的地方
2.1 代码示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class JDBCDemo { private static final String JDBC_URL = "jdbc:mysql://localhost:3306/user" ; private static final String USERNAME = "root" ; private static final String PASSWORD = "" ; public static void main (String[] args) throws Exception { List<User> userList = new ArrayList <>(); Class.forName("com.mysql.jdbc.Driver" ); try (Connection conn = DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD)) { try (Statement stmt = conn.createStatement()) { try (ResultSet rs = stmt.executeQuery("SELECT id,name,age FROM user WHERE id=1;" )) { while (rs.next()) { Integer id = rs.getInt(1 ); String name = rs.getString(2 ); Integer age = rs.getInt(3 ); User user = new User (id,name,age); userList.add(user); } } } } System.out.println("查询出用户数量:" + userList.size()); } }
2.2 为什么需要加载驱动程序 1 Class.forName("com.mysql.jdbc.Driver" );
在使用数据库时,需要加载数据库驱动是因为数据库驱动是连接数据库和应用程序之间的桥梁 。数据库驱动提供了与特定数据库通信所需的功能和方法,使得应用程序能够与数据库进行交互。
加载数据库驱动的过程实际上是将数据库驱动程序的类加载到JVM中 ,使得应用程序可以使用驱动程序中的方法和功能 。通过加载数据库驱动,应用程序可以建立与数据库的连接,执行SQL语句,读取和写入数据库中的数据等操作。
三、mybatis入门使用 我们先简单了解下mybatis的使用,看看相比原生的JDBC到底好在哪里。
3.1 mybatis使用 pom文件引入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <dependencies > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.7</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.16</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.2.8</version > </dependency > </dependencies >
mybatis配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/user?serverTimezone=UTC" /> <property name ="username" value ="root" /> <property name ="password" value ="" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="com/jiang/UserMapper.xml" /> </mappers > </configuration >
Mapper接口以及xml文件:
1 2 3 public interface UserMapper { UserPo getUserById (int id) ; }
1 2 3 4 5 6 7 8 9 <?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.jiang.dao.mapper.UserMapper" > <select id ="getUserById" resultType ="com.jiang.dao.po.UserPo" > SELECT * FROM user WHERE id = #{id} </select > </mapper >
使用 MyBatis 进行数据库操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class UserTest { public static void main (String[] args) throws IOException { String resource = "mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); UserMapper mapper = session.getMapper(UserMapper.class); UserPo userById = mapper.getUserById(1 ); System.out.println("id:" + userById.getId()); System.out.println("姓名:" + userById.getName()); System.out.println("年龄:" + userById.getAge()); } }
3.2 mybatis与JDBC对比 简单来说:
JDBC (Java Database Connectivity) 是 Java 提供的、用于连接和操作所有关系型数据库的标准 API 和基础 。它是一个底层规范。
MyBatis 是一个构建在 JDBC 之上的持久层框架 。它封装了 JDBC 的复杂和繁琐操作,让你用更简单、更高效的方式操作数据库。
我们用一个表格来详细对比下mybatis和JDBC的特性:
维度
JDBC
MyBatis
本质
标准接口和规范
基于 JDBC 的持久层框架
SQL 管理
SQL 硬编码在 Java 代码中,难以维护
SQL 与 Java 代码解耦 ,写在 XML 或注解中
结果映射
手动编写代码从 ResultSet 中获取数据并塞入对象,非常繁琐
自动映射 :将查询结果集自动转换为 Java 对象(POJO)
开发效率
低 ,需要编写大量样板代码 (创建连接、处理异常、关闭资源等)
高 ,框架处理了大部分重复性工作,开发者专注业务 SQL
性能
手动控制,理论上可以做到最优,但需要开发者有很高水平
内置连接池、缓存等优化机制,开箱即用 的性能较好
数据库兼容性
最好,因为是标准接口,所有数据库都提供 JDBC 驱动
很好,MyBatis 底层仍是 JDBC,但某些数据库特定语法可能需要调整 SQL
学习成本
较低,核心是几个固定接口(Connection, Statement, ResultSet)
中等,需要学习框架的配置、Mapper 接口、XML 标签等
灵活性
极致灵活 ,可以完全控制每一个细节和 SQL 执行过程
灵活但受约束 ,对复杂和高度优化的 SQL 支持极好,但框架本身有一定约定
社区和生态
是 Java 标准的一部分,所有数据库操作都基于它
拥有强大的社区和丰富的第三方插件(如分页插件、代码生成器)
3.3 mybatis全局配置文件 我们在mybatis代码中,我们会配置一个全局配置文件mybatis-config.xml,它包含了影响 MyBatis 行为的所有核心设置和属性。我们先简单了解下这个全局配置文件有哪些配置项,以及这些配置项的作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <properties resource ="db.properties" /> <settings > <setting name ="mapUnderscoreToCamelCase" value ="true" /> </settings > <typeAliases > <package name ="com.example.model" /> </typeAliases > <typeHandlers > </typeHandlers > <objectFactory type ="" > </objectFactory > <plugins > </plugins > <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="${db.driver}" /> <property name ="url" value ="${db.url}" /> <property name ="username" value ="${db.username}" /> <property name ="password" value ="${db.password}" /> </dataSource > </environment > </environments > <databaseIdProvider type ="DB_VENDOR" > </databaseIdProvider > <mappers > <mapper resource ="mapper/UserMapper.xml" /> </mappers > </configuration >
3.3.1 properties属性 用于引入外部属性文件(如数据库连接信息),以便在配置中使用 ${} 占位符来动态替换值。这实现了配置与代码的分离。
**resource (常用)**:从类路径(classpath)下加载属性文件。
**url**:使用完整的 URL 路径加载属性文件(如 file:///var/config/db.properties)。
可以直接内嵌 <property> 子标签 ,但不如外部文件灵活。
xml
1 <properties resource ="jdbc.properties" />
在 jdbc.properties 文件中:
properties
1 2 3 4 jdbc.driver =com.mysql.cj.jdbc.Driverjdbc.url =jdbc:mysql://localhost:3306 /mybatis_dbjdbc.username =rootjdbc.password =secret
3.3.2 settings设置 这是 MyBatis 中最为重要和复杂 的配置部分,它调整 MyBatis 的运行时行为。每个设置都有默认值。
常用设置名
描述
有效值
默认值
cacheEnabled
全局开启或关闭缓存
true/false
true
lazyLoadingEnabled
开启延迟加载(按需加载关联对象)
true/false
false
mapUnderscoreToCamelCase
重要 :自动将下划线命名映射为驼峰命名(user_name -> userName)
true/false
false
logImpl
指定 MyBatis 使用的日志框架
SLF4J/LOG4J2/STDOUT_LOGGING 等
未设置
defaultExecutorType
配置默认的执行器
SIMPLE/REUSE/BATCH
SIMPLE
xml
1 2 3 4 5 <settings > <setting name ="mapUnderscoreToCamelCase" value ="true" /> <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="logImpl" value ="SLF4J" /> </settings >
3.3.3 typeAliases 类型别名 为 Java 类型设置一个简短的名称,避免在 Mapper XML 中反复输入冗长的完全限定类名。
**<typeAlias>**:为单个类设置别名。
<package> (常用):扫描整个包,默认别名为该包下类的 类名首字母小写 (如 User 的别名是 user)。使用 @Alias("myUser") 注解可以自定义别名。
xml
1 2 3 4 5 6 <typeAliases > <typeAlias alias ="User" type ="com.example.model.User" /> <package name ="com.example.model" /> </typeAliases >
之后在 Mapper XML 中,resultType="user" 即可,而不用写 resultType="com.example.model.User"。
3.3.4 typeHandlers 类型处理器 用于在 Java 类型和 JDBC 类型之间进行转换。MyBatis 内置了大部分常用类型的处理器(如 String、Integer 等)。这个配置通常只在需要处理自定义类型 (如枚举、JSON 对象)时才使用。
3.3.5 environments 环境配置 核心中的核心 ,用于配置数据库连接。default 属性指定默认使用的环境 ID。
**<environment>**:定义一个环境,id 是该环境的唯一标识。
**<transactionManager>**:事务管理器。
type="JDBC":使用 JDBC 的事务管理(默认,推荐)。
type="MANAGED":几乎什么都不做,让容器(如 Spring)来管理事务。
**<dataSource>**:数据源,负责连接数据库。
type="UNPOOLED":每次请求都打开和关闭连接,速度慢。
type="POOLED" (最常用 ):使用连接池,优化性能。
type="JNDI":从容器(如 Tomcat)中获取数据源。
重要提示 :在 Spring 等框架集成 MyBatis 后,事务管理和数据源通常会交由 Spring 来管理 ,此部分的配置会被忽略。
3.3.6 mappers 映射器 告诉 MyBatis 去哪里寻找定义 SQL 的 Mapper XML 文件或接口。这是必须配置 的。
有四种指定方式:
**<mapper resource=""> (最常用)**:从类路径查找 XML 文件。
**<mapper url="">**:通过绝对文件路径查找。
**<mapper class="">**:使用注解的 Mapper 接口。要求 XML 和接口同名同路径。
**<package name=""> (推荐)**:扫描包下的所有 Mapper 接口。
xml
1 2 3 4 5 6 7 8 <mappers > <mapper resource ="org/mybatis/mapper/UserMapper.xml" /> <mapper class ="org.mybatis.mapper.UserMapper" /> <package name ="org.mybatis.mapper" /> </mappers >
四、mybatis架构&组件 在分析mybatis源码之前
4.1 mybatis架构 MyBatis 的架构是典型的分层架构,职责清晰,模块化程度高。其核心架构可以划分为三大层:
接口层 、核心处理层 和基础支撑层 。
架构层次
核心组件
主要职责
接口层
SqlSession MapperProxy
应用程序与 MyBatis 框架交互的窗口和桥梁 。目标是简化API ,让开发者能够以最自然的方式(面向接口编程)进行数据库操作,而无需关心底层JDBC的复杂细节。
核心处理层
Executor MappedStatement StatementHandler
执行SQL语句的核心流程,包括参数处理、SQL执行、结果映射。
基础支撑层
Transaction DataSource Cache TypeHandler
为上层核心处理提供通用的、可复用的技术服务。
4.2 mybatis核心组件 我们根据上述mybatis架构的层级来简单介绍下mybatis组件。
4.2.1 接口层 - SqlSession SqlSession是MyBatis工作的主要顶层接口,代表了一次数据库会话。你可以将它视为一个 数据库命令执行器 。
SqlSession是非线程安全,每次请求都应创建新的实例,使用完毕后必须关闭。它的生命周期通常对应一次数据库事务或一次请求。
SqlSession会提供的方法:
selectOne, selectList, insert, update, delete:直接执行SQL映射语句。
commit, rollback:手动提交或回滚事务。
getMapper(Class<T> type): 这是最核心、最常用的方法 ,用于获取Mapper接口的动态代理对象。
4.2.2 接口层 - MapperProxy MapperProxy是是MyBatis实现“接口式编程”的魔法核心。它通过 JDK动态代理 技术,为开发者定义的Mapper接口生成一个代理实例。
SqlSession的工作流程 :
当应用程序调用 userMapper.getUserById(1) 时,实际上调用的是 MapperProxy 的 invoke 方法。
MapperProxy 会拦截这次方法调用,并解析出方法名、参数等信息。
然后,它将这些信息转换为对 SqlSession 底层方法(如 selectOne)的调用,并将具体的SQL执行任务委托 给下一层(核心处理层)。
4.2.2 核心处理层 - Configuration Configuration是MyBatis的“大脑”。在应用启动时,它会解析所有的 mybatis-config.xml 和 Mapper.xml 文件,将所有配置信息(数据源、事务、Mapper语句、类型处理器等)存储在一个全局唯一的 Configuration 对象中。它是整个框架运行时的配置信息仓库 。
4.2.3 核心处理层 - Executor Executor是SQL执行的调度器 。SqlSession 只是门面,真正执行数据库操作的是 Executor。
Executor类型与策略 :
SimpleExecutor:默认,每次执行都创建一个新的 PreparedStatement。
ReuseExecutor:复用 PreparedStatement 对象。
BatchExecutor:用于批处理操作。
CachingExecutor:装饰器模式,为其他执行器增加二级缓存功能。
Executor工作流程 :
Executor 会按顺序调用 StatementHandler、ParameterHandler 和 ResultSetHandler 来完成SQL执行。
4.2.4 核心处理层 - MappedStatement 封装了一条SQL语句的所有信息 。每一个 <select|insert|update|delete> 标签都会被解析成一个 MappedStatement 对象,并以其唯一ID(如 com.jiang.UserMapper.selectById)为Key,存储在 Configuration 中。它是SQL在MyBatis内部的运行时表示 。
4.2.5 核心处理层 - ResultSetHandler 负责将JDBC返回的 ResultSet 结果集,映射成我们定义的Java对象。这是ORM(对象关系映射)的核心实现 。它会根据 <resultMap> 配置,使用 TypeHandler 将列值填充到对象的属性中。
4.2.6 基础支撑层 - Transaction Transaction是管理数据库事务的生命周期,包括创建、提交、回滚和关闭连接。
Transaction实现:
JdbcTransaction:使用JDBC原生事务管理。
ManagedTransaction:将事务管理权交给容器(如Spring),自己不做任何处理。
4.2.7 基础支撑层 - DataSource DataSource是管理数据库连接,是连接池技术的抽象。它负责高效地创建、管理和回收数据库连接,避免了频繁创建和销毁连接的开销。
DataSource实现了MyBatis内置了简单实现,但生产环境通常集成Druid、HikariCP等高性能连接池。
4.3 总结 通过这三层架构,MyBatis实现了清晰的职责分离 :
接口层 关注做什么 (What),为开发者提供友好的API。
核心处理层 关注怎么做 (How),负责SQL执行的核心逻辑。
基础支撑层 关注用什么做 (With What),提供底层的技术能力和扩展点。
这种分层设计使得MyBatis在保持了强大功能和灵活性的同时,也具备了良好的可维护性和可扩展性。
五、源码解析 - Mybatis初始化源码解析 5.1 初始化入口:SqlSessionFactoryBuilder Mybatis的初始化通常从SqlSessionFactoryBuilder.build()方法开始。无论是使用XML配置文件还是Java配置,最终都会进入这个方法。
1 2 3 4 String resource = "mybatis-config.xml" ;InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream);
SqlSessionFactoryBuilder.build()方法的核心逻辑是创建XMLConfigBuilder解析器,然后调用parse()方法得到Configuration对象,最后创建DefaultSqlSessionFactory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public SqlSessionFactory build (InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder (inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession." , e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { } } }
5.2 XMLConfigBuilder解析全局配置文件 XMLConfigBuilder是专门用于解析mybatis-config.xml配置文件的构建器。它继承自BaseBuilder,使用XPathParser解析XML文档。
parse()方法调用parseConfiguration()方法,从根节点开始逐个解析配置文件的不同部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public Configuration parse () { if (parsed) { throw new BuilderException ("Each XMLConfigBuilder can only be used once." ); } parsed = true ; parseConfiguration(parser.evalNode("/configuration" )); return configuration; }private void parseConfiguration (XNode root) { try { propertiesElement(root.evalNode("properties" )); typeAliasesElement(root.evalNode("typeAliases" )); pluginElement(root.evalNode("plugins" )); objectFactoryElement(root.evalNode("objectFactory" )); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory" )); reflectorFactoryElement(root.evalNode("reflectorFactory" )); settingsElement(root.evalNode("settings" )); environmentsElement(root.evalNode("environments" )); databaseIdProviderElement(root.evalNode("databaseIdProvider" )); typeHandlerElement(root.evalNode("typeHandlers" )); mapperElement(root.evalNode("mappers" )); } catch (Exception e) { throw new BuilderException ("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
5.3 解析Mapper映射文件 在parseConfiguration()方法中,mapperElement(root.evalNode(“mappers”))负责解析mapper映射文件。mappers标签可以配置四种方式引入Mapper:resource、url、class和package。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private void mapperElement (XNode parent) throws Exception { if (parent != null ) { for (XNode child : parent.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 ) { InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder (inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null ) { 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); } } } } }
5.4 XMLMapperBuilder解析Mapper文件 XMLMapperBuilder负责解析具体的Mapper XML文件。它的parse()方法会检查文件是否已经加载过,然后解析namespace和具体的SQL语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public void parse () { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper" )); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }private void configurationElement (XNode context) { try { String namespace = context.getStringAttribute("namespace" ); if (namespace == null || namespace.equals("" )) { throw new BuilderException ("Mapper's namespace cannot be empty" ); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref" )); cacheElement(context.evalNode("cache" )); parameterMapElement(context.evalNodes("/mapper/parameterMap" )); resultMapElements(context.evalNodes("/mapper/resultMap" )); sqlElement(context.evalNodes("/mapper/sql" )); buildStatementFromContext(context.evalNodes("select|insert|update|delete" )); } catch (Exception e) { throw new BuilderException ("Error parsing Mapper XML. Cause: " + e, e); } }
5.5 XMLStatementBuilder解析SQL语句 buildStatementFromContext()方法会创建XMLStatementBuilder来解析每个SQL标签,最终创建MappedStatement对象。
1 2 3 4 5 6 7 8 9 10 11 12 private void buildStatementFromContext (List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder (configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
parseStatementNode()方法会解析SQL标签的各种属性,如id、parameterType、resultType等,然后使用LanguageDriver解析SQL内容,最后通过MapperBuilderAssistant创建MappedStatement并注册到Configuration中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public void parseStatementNode () { String id = context.getStringAttribute("id" ); String databaseId = context.getStringAttribute("databaseId" ); String parameterType = context.getStringAttribute("parameterType" ); String resultType = context.getStringAttribute("resultType" ); String resultMap = context.getStringAttribute("resultMap" ); LanguageDriver langDriver = getLanguageDriver(context.getStringAttribute("language" )); XmlScriptBuilder builder = new XmlScriptBuilder (configuration, context, parameterTypeClass); SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
5.6 MappedStatement的创建 MapperBuilderAssistant.addMappedStatement()方法负责创建MappedStatement对象并将其添加到Configuration中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public MappedStatement addMappedStatement ( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, // ... 其他参数 ) { id = applyCurrentNamespace(id, false ); MappedStatement.Builder statementBuilder = new MappedStatement .Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) if (resultMap != null ) { String[] resultMapIds = resultMap.split("," ); Set<String> resultMapSet = new HashSet <String>(); for (String resultMapId : resultMapIds) { resultMapId = applyCurrentNamespace(resultMapId, true ); resultMapSet.add(resultMapId); } statementBuilder.resultMaps(resultMapSet); } MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }
至此,Mybatis的初始化阶段完成,Configuration对象中包含了所有解析后的配置信息、Mapper映射信息,为后续的SQL执行做好了准备。
六、Mapper代理实现源码解析 6.1 Mapper接口注册过程 在初始化阶段,无论是通过XML的<mapper class="..."/>配置,还是通过@MapperScan注解,最终都会调用Configuration.addMapper()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public <T> void addMapper (Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new RuntimeException ("Type " + type + " is already known to the MapperRegistry." ); } boolean loadCompleted = false ; try { knownMappers.put(type, new MapperProxyFactory <T>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder (config, type); parser.parse(); loadCompleted = true ; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
6.2 MapperAnnotationBuilder解析注解 MapperAnnotationBuilder负责解析Mapper接口方法上的注解(如@Select、@Insert等),将其转换为MappedStatement。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public void parse () { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { assistant.setCurrentNamespace(type.getName()); Method[] methods = type.getMethods(); for (Method method : methods) { if (!method.isBridge()) { parseStatement(method); } } } }private void parseStatement (Method method) { Class<?> parameterTypeClass = getParameterType(method); LanguageDriver languageDriver = getLanguageDriver(method); SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver); if (sqlSource != null ) { final String mappedStatementId = type.getName() + "." + method.getName(); SqlCommandType sqlCommandType = getSqlCommandType(method); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; String resultMapId = null ; if (isSelect) { resultMapId = parseResultMap(method); } assistant.addMappedStatement( mappedStatementId, sqlSource, ); } }
3.3 代理对象的创建:MapperProxyFactory 当调用SqlSession.getMapper(Class type)方法时,Mybatis会从MapperRegistry中获取对应的MapperProxyFactory,然后创建代理对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 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); } }
MapperProxyFactory使用JDK动态代理创建代理对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class MapperProxyFactory <T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap <Method, MapperMethod>(); public MapperProxyFactory (Class<T> mapperInterface) { this .mapperInterface = mapperInterface; } public T newInstance (SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy <T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance (MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class [] { mapperInterface }, mapperProxy); } }
3.4 代理逻辑:MapperProxy MapperProxy实现了InvocationHandler接口,当调用Mapper接口的方法时,会进入invoke方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class MapperProxy <T> implements InvocationHandler , Serializable { private static final long serialVersionUID = -6424540398559729838L ; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> 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); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod (Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null ) { mapperMethod = new MapperMethod (mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } }
3.5 MapperMethod封装 MapperMethod封装了方法对应的SQL命令类型和接口方法信息,它的execute方法会根据SQL命令类型调用SqlSession的相应方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public Object execute (SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break ; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break ; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break ; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null ; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break ; case FLUSH: result = sqlSession.flushStatements(); break ; default : throw new BindingException ("Unknown execution method for: " + command.getName()); } return result; } }
七、SQL执行流程源码深度解析 7.1 SqlSession的实现类DefaultSqlSession DefaultSqlSession是SqlSession接口的默认实现,它持有Configuration和Executor引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final Executor executor; private final boolean autoCommit; private boolean dirty; private List<Cursor<?>> cursorList; @Override public <E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } }
7.2 Executor体系结构 Executor是Mybatis的执行器接口,定义了数据库操作的基本方法。它有多个实现类,形成了模板方法模式和装饰器模式的典型应用。
BaseExecutor :抽象类,实现了Executor接口的大部分方法,主要提供缓存管理和事务管理的能力。
SimpleExecutor :默认执行器,每次执行都会创建一个Statement,用完后关闭。
ReuseExecutor :可重用执行器,将Statement存入Map中,操作Map中的Statement而不会重复创建。
BatchExecutor :批量执行所有更新语句,基于JDBC的batch操作实现批处理。
CachingExecutor :使用装饰器模式,对真正提供数据库查询的Executor增强了二级缓存的能力。
7.3 查询入口:CachingExecutor.query() 当开启了二级缓存时,首先会进入CachingExecutor的query方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public <E> List<E> query (MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }public <E> List<E> query (MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null ) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null ) { ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> cachedList = (List<E>) cache.getObject(key); if (cachedList == null ) { cachedList = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); cache.putObject(key, cachedList); } return cachedList; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
7.4 BaseExecutor的模板方法 BaseExecutor是一个抽象类,实现了缓存的共用逻辑,将实际的doQuery方法留给子类实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public abstract class BaseExecutor implements Executor { protected Transaction transaction; protected Executor wrapper; protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads; protected PerpetualCache localCache; protected PerpetualCache localOutputParameterCache; protected Configuration configuration; @Override public <E> List<E> query (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query" ).object(ms.getId()); if (closed) { throw new ExecutorException ("Executor was closed." ); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null ; if (list != null ) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0 ) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { clearLocalCache(); } } return list; } private <E> List<E> queryFromDatabase (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); return list; } protected abstract <E> List<E> doQuery (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException; }
7.5 SimpleExecutor.doQuery SimpleExecutor是Executor的默认实现,它的doQuery方法创建StatementHandler并执行查询。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class SimpleExecutor extends BaseExecutor { @Override public <E> List<E> doQuery (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null ; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement (StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; } }
7.6 StatementHandler体系 StatementHandler负责创建Statement、设置参数、执行SQL和处理结果集。它有多个实现类:
RoutingStatementHandler :路由StatementHandler,根据MappedStatement的StatementType创建对应的StatementHandler。
SimpleStatementHandler :处理JDBC Statement。
PreparedStatementHandler :处理PreparedStatement(最常用)。
CallableStatementHandler :处理存储过程。
Configuration.newStatementHandler()方法创建的是RoutingStatementHandler,它会根据配置路由到具体的实现:
1 2 3 4 5 6 7 public StatementHandler newStatementHandler (Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler (executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
RoutingStatementHandler的构造函数会根据StatementType创建对应的StatementHandler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class RoutingStatementHandler implements StatementHandler { private final StatementHandler delegate; public RoutingStatementHandler (Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; case PREPARED: delegate = new PreparedStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; case CALLABLE: delegate = new CallableStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; default : throw new ExecutorException ("Unknown statement type: " + ms.getStatementType()); } } }
7.7 PreparedStatementHandler执行流程 PreparedStatementHandler是最常用的StatementHandler,它处理PreparedStatement。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class PreparedStatementHandler extends BaseStatementHandler { @Override public void parameterize (Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); } @Override public <E> List<E> query (Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); } }
7.8 ParameterHandler参数设置 ParameterHandler负责将Java参数设置到PreparedStatement中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class DefaultParameterHandler implements ParameterHandler { private final TypeHandlerRegistry typeHandlerRegistry; private final MappedStatement mappedStatement; private final Object parameterObject; private final BoundSql boundSql; private final Configuration configuration; @Override public void setParameters (PreparedStatement ps) { ErrorContext.instance().activity("setting parameters" ).object(mappedStatement.getParameterMap().getId()); 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) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null ) { value = null ; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null ) { jdbcType = configuration.getJdbcTypeForNull(); } typeHandler.setParameter(ps, i + 1 , value, jdbcType); } } } } }
4.9 ResultSetHandler结果集处理 ResultSetHandler负责将ResultSet映射为Java对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class DefaultResultSetHandler implements ResultSetHandler { @Override public List<Object> handleResultSets (Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results" ).object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList <Object>(); int resultSetCount = 0 ; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); if (rsw != null && resultMapCount > 0 ) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null ); } while (rsw != null && rsw.hasMoreResults()) { } return collapseSingleResultList(multipleResults); } private void handleResultSet (ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { if (resultHandler == null ) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler (objectFactory); handleRowValuesForSimpleResultMap(rsw, resultMap, defaultResultHandler, rowBounds, null ); multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, null ); } } private void handleRowValuesForSimpleResultMap (ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext <Object>(); while (resultContext.getResultCount() < rowBounds.getLimit() && rsw.getResultSet().next()) { Object rowValue = getRowValue(rsw, resultMap); resultHandler.handleResult(resultContext.createResultObject(rowValue)); } } private Object getRowValue (ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { Object resultObject = createResultObject(rsw, resultMap, null ); if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(resultObject); boolean foundValues = resultMap.getConstructorResultMappings().size() > 0 ; if (shouldApplyAutomaticMappings(resultMap, true )) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null ) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, null ) || foundValues; } return resultObject; } }
八、SQL解析与动态SQL源码解析 8.1 SqlSource与BoundSql SqlSource是SQL语句的来源接口,负责根据参数对象生成BoundSql。BoundSql则封装了最终的SQL语句、参数映射和参数值
1 2 3 4 5 6 7 8 9 10 public interface SqlSource { BoundSql getBoundSql (Object parameterObject) ; }public class BoundSql { private final String sql; private final List<ParameterMapping> parameterMappings; private final Object parameterObject; private final Map<String, Object> additionalParameters; }
5.2 SqlSource的两种类型 根据SQL语句是否包含动态内容(如、标签或${}),Mybatis会创建不同类型的SqlSource:
DynamicSqlSource :包含动态内容的SQL,每次执行都需要重新解析。
RawSqlSource :不包含动态内容的SQL(只包含#{}),可以在初始化时一次性解析。
8.3 XMLScriptBuilder解析动态SQL XMLScriptBuilder负责解析动态SQL标签,创建对应的SqlNode对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class XMLScriptBuilder extends BaseBuilder { public SqlSource parseScriptNode () { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource = null ; if (isDynamic) { sqlSource = new DynamicSqlSource (configuration, rootSqlNode); } else { sqlSource = new RawSqlSource (configuration, rootSqlNode, parameterType); } return sqlSource; } protected MixedSqlNode parseDynamicTags (XNode node) { List<SqlNode> contents = new ArrayList <SqlNode>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0 ; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody("" ); TextSqlNode textSqlNode = new TextSqlNode (data); if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true ; } else { contents.add(new StaticTextSqlNode (data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlers(nodeName); handler.handleNode(child, contents); isDynamic = true ; } } return new MixedSqlNode (contents); } }
8.4 SqlNode组合模式 Mybatis使用组合模式来管理动态SQL标签。每个标签都对应一个实现了SqlNode接口的类:
IfSqlNode :对应标签
WhereSqlNode :对应标签
SetSqlNode :对应标签
ForEachSqlNode :对应标签
TrimSqlNode :对应标签
ChooseSqlNode :对应标签
这些SqlNode形成树形结构,通过apply()方法递归生成SQL片段。
8.5 DynamicSqlSource获取BoundSql 当执行动态SQL时,DynamicSqlSource会重新解析SQL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class DynamicSqlSource implements SqlSource { private final Configuration configuration; private final SqlNode rootSqlNode; @Override public BoundSql getBoundSql (Object parameterObject) { DynamicContext context = new DynamicContext (configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder (configuration); Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; } }
8.6 SqlSourceBuilder解析#{}占位符 SqlSourceBuilder负责解析SQL中的#{}占位符,将其替换为?,并创建ParameterMapping。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class SqlSourceBuilder extends BaseBuilder { public SqlSource parse (String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler (configuration, parameterType, additionalParameters); GenericTokenParser parser = new GenericTokenParser ("#{" , "}" , handler); String sql = parser.parse(originalSql); return new StaticSqlSource (configuration, sql, handler.getParameterMappings()); } private static class ParameterMappingTokenHandler implements TokenHandler { private List<ParameterMapping> parameterMappings = new ArrayList <ParameterMapping>(); private Class<?> parameterType; private Map<String, Object> additionalParameters; @Override public String handleToken (String content) { parameterMappings.add(buildParameterMapping(content)); return "?" ; } private ParameterMapping buildParameterMapping (String content) { Map<String, String> propertiesMap = parseParameterMapping(content); String property = propertiesMap.get("property" ); Class<?> propertyType = parameterType; ParameterMapping.Builder builder = new ParameterMapping .Builder(configuration, property, propertyType); return builder.build(); } } }
九、插件实现原理源码解析 9.1 插件体系结构 Mybatis的插件机制允许拦截四大核心对象的方法调用:
Executor :拦截update、query、flushStatements、commit、rollback等方法
StatementHandler :拦截prepare、parameterize、batch、update、query等方法
ParameterHandler :拦截getParameterObject、setParameters等方法
ResultSetHandler :拦截handleResultSets、handleOutputParameters等方法
9.2 Interceptor接口 自定义插件需要实现Interceptor接口:
1 2 3 4 5 6 7 8 9 10 public interface Interceptor { Object intercept (Invocation invocation) throws Throwable; Object plugin (Object target) ; void setProperties (Properties properties) ; }
9.3 插件加载过程 在初始化阶段,XMLConfigBuilder解析plugins标签,创建Interceptor并添加到Configuration的interceptorChain中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void pluginElement (XNode parent) throws Exception { if (parent != null ) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor" ); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } }
9.4 代理链的生成 当创建Executor、StatementHandler、ParameterHandler、ResultSetHandler时,会调用InterceptorChain.pluginAll()方法,为这些核心对象生成代理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList <Interceptor>(); public Object pluginAll (Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } public void addInterceptor (Interceptor interceptor) { interceptors.add(interceptor); } }
Interceptor接口的plugin()方法通常使用Plugin.wrap()方法来生成代理对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static Object wrap (Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0 ) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin (target, interceptor, signatureMap)); } return target; }
9.5 Plugin的invoke方法 Plugin实现了InvocationHandler接口,在invoke方法中判断是否拦截该方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Plugin implements InvocationHandler { private final Object target; private final Interceptor interceptor; private final Map<Class<?>, Set<Method>> signatureMap; private Plugin (Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this .target = target; this .interceptor = interceptor; this .signatureMap = signatureMap; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation (target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } }
十、缓存实现原理源码解析 10.1 一级缓存 一级缓存是SqlSession级别的缓存,默认开启。它存储在BaseExecutor的localCache属性中,是一个PerpetualCache对象。
1 2 3 4 5 6 7 8 9 public abstract class BaseExecutor implements Executor { protected PerpetualCache localCache; public class PerpetualCache implements Cache { private final String id; private Map<Object, Object> cache = new HashMap <Object, Object>(); } }
一级缓存的生命周期与SqlSession相同。当执行commit、rollback、close或update操作时,会清空一级缓存。
10.2 二级缓存 二级缓存是Mapper级别的缓存,多个SqlSession共享。它使用装饰器模式对Cache接口进行功能增强。
1 2 3 4 5 6 7 8 9 public interface Cache { String getId () ; void putObject (Object key, Object value) ; Object getObject (Object key) ; Object removeObject (Object key) ; void clear () ; int getSize () ; ReadWriteLock getReadWriteLock () ; }
PerpetualCache是Cache的基本实现,其他缓存装饰器在其基础上添加功能:
LruCache :最近最少使用算法的缓存装饰器
FifoCache :先进先出算法的缓存装饰器
SoftCache :软引用缓存装饰器
WeakCache :弱引用缓存装饰器
ScheduledCache :定时清空缓存装饰器
BlockingCache :阻塞缓存装饰器,保证只有一个线程查询数据库
10.3 二级缓存的初始化 在解析Mapper文件时,cacheElement()方法会创建缓存对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private void cacheElement (XNode context) throws Exception { if (context != null ) { String type = context.getStringAttribute("type" , "PERPETUAL" ); Class<? extends Cache > typeClass = typeAliasRegistry.resolveAlias(type); String eviction = context.getStringAttribute("eviction" , "LRU" ); Class<? extends Cache > evictionClass = typeAliasRegistry.resolveAlias(eviction); Long flushInterval = context.getLongAttribute("flushInterval" ); Integer size = context.getIntAttribute("size" ); boolean readWrite = !context.getBooleanAttribute("readOnly" , false ); boolean blocking = context.getBooleanAttribute("blocking" , false ); Cache cache = new CacheBuilder (currentNamespace) .implementation(typeClass) .addDecorator(evictionClass) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) .build(); configuration.addCache(cache); currentCache = cache; } }
CacheBuilder的build()方法会按照顺序包装缓存装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public Cache build () { setDefaultImplementations(); Cache cache = newBaseCacheInstance(implementation, id); setCacheProperties(cache); if (PerpetualCache.class.equals(cache.getClass())) { for (Class<? extends Cache > decorator : decorators) { cache = newCacheDecoratorInstance(decorator, cache); setCacheProperties(cache); } cache = setStandardDecorators(cache); } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) { cache = new LoggingCache (cache); cache = new SynchronizedCache (cache); } return cache; }
10.4 二级缓存的使用 CachingExecutor在执行查询时,会先从二级缓存中获取数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public <E> List<E> query (MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null ) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null ) { List<E> cachedList = (List<E>) cache.getObject(key); if (cachedList == null ) { cachedList = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); cache.putObject(key, cachedList); } return cachedList; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
10.5 缓存Key的生成 CacheKey用于唯一标识一次查询。它由多个影响查询结果的因素组成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public CacheKey createCacheKey (MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException ("Executor was closed." ); } CacheKey cacheKey = new CacheKey (); cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null ) { value = null ; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } cacheKey.update(value); } } if (configuration.getEnvironment() != null ) { cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; }
CacheKey的update()方法会计算哈希值,并将对象加入对象列表:
1 2 3 4 5 6 7 8 9 public void update (Object object) { int baseHashCode = object == null ? 1 : object.hashCode(); count++; checksum += baseHashCode; baseHashCode *= count; hashcode = multiplier * hashcode + baseHashCode; updateList.add(object); }
当比较两个CacheKey是否相等时,会比较哈希值、校验和、计数以及每个对象的值。
十一、Mybatis事务处理机制源码解析 事务管理是数据库操作的核心特性之一,Mybatis提供了灵活的事务管理机制,既支持直接使用JDBC的事务,也支持托管给外部容器(如Spring)管理。
11.1 事务顶层接口:Transaction Mybatis定义了org.apache.ibatis.transaction.Transaction接口,作为所有事务操作的抽象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface Transaction { Connection getConnection () throws SQLException; void commit () throws SQLException; void rollback () throws SQLException; void close () throws SQLException; Integer getTimeout () throws SQLException; }
该接口定义了事务的基本操作,具体的实现类负责与底层JDBC或容器交互。
11.2 事务的两种默认实现 Mybatis内置了两个Transaction实现类:JdbcTransaction和ManagedTransaction。
11.2.1 JdbcTransaction JdbcTransaction直接使用JDBC Connection的事务管理功能,通过Connection的commit()、rollback()和close()方法实现事务控制。它是Mybatis默认使用的事务实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public class JdbcTransaction implements Transaction { protected Connection connection; protected DataSource dataSource; protected TransactionIsolationLevel level; protected boolean autoCommit; public JdbcTransaction (DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) { dataSource = ds; level = desiredLevel; autoCommit = desiredAutoCommit; } @Override public Connection getConnection () throws SQLException { if (connection == null ) { openConnection(); } return connection; } protected void openConnection () throws SQLException { connection = dataSource.getConnection(); if (level != null ) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommit); } protected void setDesiredAutoCommit (boolean desiredAutoCommit) { try { if (connection.getAutoCommit() != desiredAutoCommit) { connection.setAutoCommit(desiredAutoCommit); } } catch (SQLException e) { throw new TransactionException ("Error configuring AutoCommit. Cause: " + e, e); } } @Override public void commit () throws SQLException { if (connection != null && !connection.getAutoCommit()) { connection.commit(); } } @Override public void rollback () throws SQLException { if (connection != null && !connection.getAutoCommit()) { connection.rollback(); } } @Override public void close () throws SQLException { if (connection != null ) { resetAutoCommit(); connection.close(); } } protected void resetAutoCommit () { try { if (!connection.getAutoCommit()) { connection.setAutoCommit(true ); } } catch (SQLException e) { } } }
关键点:
JdbcTransaction维护一个数据库连接,该连接从数据源获取。
它允许设置事务隔离级别和自动提交模式。
commit()和rollback()只有在连接非自动提交状态下才真正执行。
close()方法会重置自动提交并关闭连接。
11.2.2 ManagedTransaction ManagedTransaction将事务管理委托给外部容器(如Spring、Java EE容器)。它本身不执行任何事务操作,commit()和rollback()均为空实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class ManagedTransaction implements Transaction { private DataSource dataSource; private TransactionIsolationLevel level; private Connection connection; private final boolean closeConnection; public ManagedTransaction (Connection connection, boolean closeConnection) { this .connection = connection; this .closeConnection = closeConnection; } public ManagedTransaction (DataSource ds, TransactionIsolationLevel level, boolean closeConnection) { this .dataSource = ds; this .level = level; this .closeConnection = closeConnection; } @Override public Connection getConnection () throws SQLException { if (this .connection == null ) { openConnection(); } return this .connection; } private void openConnection () throws SQLException { this .connection = this .dataSource.getConnection(); if (this .level != null ) { this .connection.setTransactionIsolation(this .level.getLevel()); } } @Override public void commit () throws SQLException { } @Override public void rollback () throws SQLException { } @Override public void close () throws SQLException { if (this .closeConnection && this .connection != null ) { this .connection.close(); } } }
在ManagedTransaction中,事务的提交和回滚由容器负责,Mybatis不干预。通常与Spring集成时会使用SpringManagedTransaction(稍后介绍)。
11.3 事务工厂 TransactionFactory 为了创建Transaction实例,Mybatis提供了TransactionFactory接口。
1 2 3 4 5 6 7 8 9 10 public interface TransactionFactory { default void setProperties (Properties props) {} Transaction newTransaction (Connection conn) ; Transaction newTransaction (DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) ; }
两个内置实现类:
JdbcTransactionFactory :创建JdbcTransaction实例。
ManagedTransactionFactory :创建ManagedTransaction实例。
在Configuration初始化时,会根据<environment>配置中的<transactionManager>标签决定使用哪个事务工厂。
1 2 3 4 5 6 <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > ...</dataSource > </environment > </environments >
XML解析时,type属性值会被映射到对应的事务工厂别名(JDBC→JdbcTransactionFactory,MANAGED→ManagedTransactionFactory)。
11.4 Environment 与事务的创建 Environment是Mybatis环境配置类,它聚合了DataSource和TransactionFactory。
1 2 3 4 5 6 public final class Environment { private final String id; private final TransactionFactory transactionFactory; private final DataSource dataSource; }
当我们通过SqlSessionFactoryBuilder构建SqlSessionFactory时,Configuration对象中持有一个Environment实例。
1 2 3 4 public class Configuration { protected Environment environment; }
8.4.1 创建事务的时机 当调用SqlSessionFactory.openSession()时,会根据当前环境创建事务和Executor。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; @Override public SqlSession openSession () { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null , false ); } private SqlSession openSessionFromDataSource (ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null ; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession (configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } private TransactionFactory getTransactionFactoryFromEnvironment (Environment environment) { if (environment == null || environment.getTransactionFactory() == null ) { return new ManagedTransactionFactory (); } return environment.getTransactionFactory(); } }
关键点:
从事务工厂创建事务时,传入DataSource、隔离级别和自动提交标志。
JdbcTransaction会根据autoCommit参数设置连接的初始自动提交状态。
Executor的构造器会接收事务对象,后续所有数据库操作都通过该事务获取连接和管理事务。
11.5 Executor 与事务的协作 Executor是Mybatis的执行器,它持有事务对象,并将事务操作委托给事务。
11.5.1 BaseExecutor 中的事务引用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public abstract class BaseExecutor implements Executor { protected Transaction transaction; protected Executor wrapper; protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads; protected PerpetualCache localCache; protected BaseExecutor (Configuration configuration, Transaction transaction) { this .transaction = transaction; this .configuration = configuration; this .wrapper = this ; } @Override public void commit (boolean required) throws SQLException { if (closed) { throw new ExecutorException ("Cannot commit, transaction is already closed" ); } clearLocalCache(); flushStatements(); if (required) { transaction.commit(); } } @Override public void rollback (boolean required) throws SQLException { if (!closed) { try { clearLocalCache(); flushStatements(true ); } finally { if (required) { transaction.rollback(); } } } } @Override public void close (boolean forceRollback) { try { try { rollback(forceRollback); } finally { if (transaction != null ) { transaction.close(); } } } catch (SQLException e) { } finally { transaction = null ; deferredLoads = null ; localCache = null ; closed = true ; } } protected Connection getConnection (Log statementLog) throws SQLException { Connection connection = transaction.getConnection(); if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } } }
BaseExecutor中所有涉及事务的方法(commit、rollback、close、getConnection)都直接调用transaction对象的对应方法。因此,事务的具体行为完全由Transaction的实现类决定。
11.5.2 事务提交与回滚的传递 当我们调用SqlSession.commit()时,最终会调用到Executor.commit(true),然后触发transaction.commit()。对于JdbcTransaction,就是调用connection.commit();对于ManagedTransaction,则什么都不做。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public void commit () { commit(false ); }@Override public void commit (boolean force) { try { executor.commit(isCommitOrRollbackRequired(force)); dirty = false ; } catch (Exception e) { throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
其中isCommitOrRollbackRequired(force)判断是否需要提交事务,如果force为true或者dirty标志为true(表示有更新操作),则提交。
11.6 事务与一级缓存的关系 一级缓存(localCache)的生命周期与SqlSession绑定,而事务的提交和回滚会影响缓存的状态。
提交时 :BaseExecutor.commit()会调用clearLocalCache()清空一级缓存。这是因为事务提交后,数据可能发生变化,之前的缓存可能失效。
回滚时 :BaseExecutor.rollback()同样会清空一级缓存。
关闭时 :BaseExecutor.close()也会清空缓存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public abstract class BaseExecutor implements Executor { @Override public void commit (boolean required) throws SQLException { if (closed) { throw new ExecutorException ("Cannot commit, transaction is already closed" ); } clearLocalCache(); flushStatements(); if (required) { transaction.commit(); } } @Override public void rollback (boolean required) throws SQLException { if (!closed) { try { clearLocalCache(); flushStatements(true ); } finally { if (required) { transaction.rollback(); } } } } }
这种设计保证了事务结束后,下一次查询不会读到过期的缓存数据。
十二、Spring集成源码解析 12.1 SqlSessionFactoryBean 在Spring环境中,我们通常使用SqlSessionFactoryBean来创建SqlSessionFactory。它实现了FactoryBean和InitializingBean接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class SqlSessionFactoryBean implements FactoryBean <SqlSessionFactory>, InitializingBean { @Override public void afterPropertiesSet () throws Exception { notNull(dataSource, "Property 'dataSource' is required" ); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required" ); this .sqlSessionFactory = buildSqlSessionFactory(); } @Override public SqlSessionFactory getObject () throws Exception { if (this .sqlSessionFactory == null ) { afterPropertiesSet(); } return this .sqlSessionFactory; } protected SqlSessionFactory buildSqlSessionFactory () throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null ; if (this .configLocation != null ) { xmlConfigBuilder = new XMLConfigBuilder (this .configLocation.getInputStream(), null , this .configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { configuration = new Configuration (); } if (this .mapperLocations != null ) { for (Resource mapperLocation : this .mapperLocations) { if (mapperLocation == null ) { continue ; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder (mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } finally { ErrorContext.instance().reset(); } } } return this .sqlSessionFactoryBuilder.build(configuration); } }
12.2 MapperFactoryBean与@MapperScan 当我们使用@MapperScan注解时,它会导入MapperScannerRegistrar,注册ClassPathMapperScanner,将Mapper接口的BeanDefinition替换为MapperFactoryBean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class MapperFactoryBean <T> extends SqlSessionDaoSupport implements FactoryBean <T> { private Class<T> mapperInterface; private boolean addToConfig = true ; @Override protected void checkDaoConfig () { Configuration configuration = getSqlSession().getConfiguration(); if (this .addToConfig && !configuration.hasMapper(this .mapperInterface)) { try { configuration.addMapper(this .mapperInterface); } catch (Exception e) { throw new IllegalArgumentException (e); } } } @Override public T getObject () throws Exception { return getSqlSession().getMapper(this .mapperInterface); } }
在XML配置方式中,可以使用MapperScannerConfigurer来扫描Mapper接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor , InitializingBean { @Override public void postProcessBeanDefinitionRegistry (BeanDefinitionRegistry registry) { ClassPathMapperScanner scanner = new ClassPathMapperScanner (registry); scanner.setResourceLoader(this .applicationContext); scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this .basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); } }
ClassPathMapperScanner扫描后会将BeanDefinition的beanClass改为MapperFactoryBean:
1 2 3 4 5 6 7 8 9 10 11 12 13 private void processBeanDefinitions (Set<BeanDefinitionHolder> beanDefinitions) { for (BeanDefinitionHolder holder : beanDefinitions) { GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); definition.setBeanClass(MapperFactoryBean.class); definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); } }
12.4 SqlSessionTemplate与SqlSessionInterceptor 在Spring中,我们使用SqlSessionTemplate作为SqlSession的实现。它内部使用SqlSessionInterceptor来动态代理SqlSession方法,确保每个方法都在Spring事务中正确执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class SqlSessionTemplate implements SqlSession , DisposableBean { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; public SqlSessionTemplate (SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { this .sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class [] { SqlSession.class }, new SqlSessionInterceptor ()); } private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this .sqlSessionFactory, SqlSessionTemplate.this .executorType, SqlSessionTemplate.this .exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this .sqlSessionFactory)) { sqlSession.commit(true ); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this .exceptionTranslator != null && unwrapped instanceof PersistenceException) { Throwable translated = SqlSessionTemplate.this .exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null ) { unwrapped = translated; } } throw unwrapped; } finally { closeSqlSession(sqlSession, SqlSessionTemplate.this .sqlSessionFactory); } } } }
12.5 Spring集成中的事务处理:SpringManagedTransaction 在Spring与Mybatis集成时,为了利用Spring的事务管理,Mybatis提供了SpringManagedTransaction类(位于mybatis-spring模块中)。它实现了Transaction接口,但与Spring的事务同步机制紧密结合。
12.5.1 SpringManagedTransaction 源码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class SpringManagedTransaction implements Transaction { private final DataSource dataSource; private Connection connection; private boolean isConnectionTransactional; private boolean autoCommit; public SpringManagedTransaction (DataSource dataSource) { this .dataSource = dataSource; } @Override public Connection getConnection () throws SQLException { if (this .connection == null ) { openConnection(); } return this .connection; } private void openConnection () throws SQLException { this .connection = DataSourceUtils.getConnection(this .dataSource); this .isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this .connection, this .dataSource); this .autoCommit = this .connection.getAutoCommit(); } @Override public void commit () throws SQLException { if (this .connection != null && !this .isConnectionTransactional && !this .autoCommit) { this .connection.commit(); } } @Override public void rollback () throws SQLException { if (this .connection != null && !this .isConnectionTransactional && !this .autoCommit) { this .connection.rollback(); } } @Override public void close () throws SQLException { if (this .connection != null ) { if (this .isConnectionTransactional) { DataSourceUtils.doReleaseConnection(this .connection, this .dataSource); } else { this .connection.close(); } } } }
关键点:
getConnection()通过DataSourceUtils.getConnection(dataSource)获取连接,该方法会从当前Spring事务上下文中获取绑定的连接(如果有),否则从数据源获取新连接,并注册事务同步。
isConnectionTransactional标识当前连接是否处于Spring事务管理下。
commit()和rollback()仅在连接非事务性且非自动提交时才执行真正的提交/回滚;否则说明事务由Spring管理,Mybatis不操作。
close()如果是事务性连接,则通过DataSourceUtils.doReleaseConnection()释放(而非关闭),这会让Spring事务同步管理器在事务结束时统一处理连接的关闭。
12.5.2 在SqlSessionTemplate中的应用 SqlSessionTemplate是Mybatis-Spring提供的线程安全的SqlSession实现,它内部使用动态代理,在每次方法调用时从Spring事务中获取合适的SqlSession。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this .sqlSessionFactory, SqlSessionTemplate.this .executorType, SqlSessionTemplate.this .exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this .sqlSessionFactory)) { sqlSession.commit(true ); } return result; } catch (Throwable t) { throw unwrapped; } finally { closeSqlSession(sqlSession, SqlSessionTemplate.this .sqlSessionFactory); } } }
十三、设计模式 在上述中,我们对myBatis进行详细的解析, 我们可以看到源码中广泛应用了多种设计模式:
建造者模式 :SqlSessionFactoryBuilder
工厂模式 :SqlSessionFactory
单例模式 :Configuration、ErrorContext
代理模式 :Mapper 接口代理、Plugin
装饰器模式 :CachingExecutor 装饰其他 Executor
模板方法模式 :BaseExecutor
责任链模式 :插件拦截器
我们可以简单分析下这些设计模式,以及这些设计模式在mybatis源码中的使用。
13.1 建造者模式 建造者模式是一种创建型设计模式,它的主要目的是将一个复杂对象的构建与其表示分离 ,使得同样的构建过程可以创建不同的表示。
13.1.1 SqlSessionFactoryBuilder SqlSessionFactoryBuilder是建造者模式的典型应用,它负责读取配置信息并构建SqlSessionFactory。虽然它没有显式的Builder接口,但其职责符合建造者模式的定义——将复杂对象的构建过程封装起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class SqlSessionFactoryBuilder { public SqlSessionFactory build (InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder (inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { } } public SqlSessionFactory build (Configuration config) { return new DefaultSqlSessionFactory (config); } }
13.1.2 XMLConfigBuilder XMLConfigBuilder是专门用于解析mybatis-config.xml配置文件的建造者,它逐步解析各个节点,最终构建出Configuration对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class XMLConfigBuilder extends BaseBuilder { private boolean parsed; private XPathParser parser; private String environment; public Configuration parse () { if (parsed) { throw new BuilderException ("Each XMLConfigBuilder can only be used once." ); } parsed = true ; parseConfiguration(parser.evalNode("/configuration" )); return configuration; } private void parseConfiguration (XNode root) { try { propertiesElement(root.evalNode("properties" )); typeAliasesElement(root.evalNode("typeAliases" )); pluginElement(root.evalNode("plugins" )); } catch (Exception e) { throw new BuilderException ("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } }
13.1.3 XMLMapperBuilder 类似地,XMLMapperBuilder负责解析Mapper映射文件,构建MappedStatement、ResultMap等对象。
13.1.4 CacheBuilder CacheBuilder用于构建二级缓存对象,支持链式调用设置缓存属性,最后通过build()方法创建出经过多层装饰的缓存实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 public Cache build () { setDefaultImplementations(); Cache cache = newBaseCacheInstance(implementation, id); setCacheProperties(cache); if (PerpetualCache.class.equals(cache.getClass())) { for (Class<? extends Cache > decorator : decorators) { cache = newCacheDecoratorInstance(decorator, cache); setCacheProperties(cache); } cache = setStandardDecorators(cache); } return cache; }
13.2 工厂模式 (Factory Pattern) 工厂模式用于创建对象,隐藏对象创建的复杂性。Mybatis中多处使用工厂模式,尤其是SqlSessionFactory和ObjectFactory。
13.2.1 SqlSessionFactory SqlSessionFactory是典型的工厂接口,它的openSession()方法创建SqlSession对象。DefaultSqlSessionFactory是具体工厂实现。
1 2 3 4 5 6 public interface SqlSessionFactory { SqlSession openSession () ; SqlSession openSession (boolean autoCommit) ; SqlSession openSession (Connection connection) ; }
13.2.2 ObjectFactory Mybatis提供了ObjectFactory接口,用于创建结果对象。默认实现是DefaultObjectFactory,通过反射调用构造方法创建实例。
1 2 3 4 5 public interface ObjectFactory { <T> T create (Class<T> type) ; <T> T create (Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) ; }
当需要自定义对象创建逻辑(如使用特定构造器或工厂方法)时,可以实现该接口。
13.2.3 MapperProxyFactory MapperProxyFactory用于为每个Mapper接口创建代理对象。它维护了方法缓存,每次调用newInstance(sqlSession)都会创建一个新的MapperProxy实例。
1 2 3 4 5 6 7 8 9 public class MapperProxyFactory <T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap <>(); public T newInstance (SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy <>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
13.2.4 TransactionFactory TransactionFactory是事务工厂,根据配置创建不同类型的Transaction(如JdbcTransaction、ManagedTransaction)。
1 2 3 4 public interface TransactionFactory { Transaction newTransaction (Connection conn) ; Transaction newTransaction (DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) ; }
13.3 单例模式 (Singleton Pattern) 单例模式确保一个类只有一个实例,并提供全局访问点。Mybatis中多个核心类采用单例模式。
13.3.1 Configuration Configuration对象在整个Mybatis应用生命周期内通常只有一个实例(除非创建多个SqlSessionFactory)。它存储了所有配置信息、映射语句等,是全局唯一的配置容器。
1 2 3 public class Configuration { }
13.3.2 ErrorContext ErrorContext是线程级别的单例(实际是每个线程一个实例),用于记录错误上下文信息。它通过ThreadLocal保证每个线程拥有自己的实例。
1 2 3 4 5 6 7 8 9 10 public class ErrorContext { private static final ThreadLocal<ErrorContext> LOCAL = ThreadLocal.withInitial(ErrorContext::new ); private ErrorContext () { } public static ErrorContext instance () { return LOCAL.get(); } }
13.3.3 LogFactory LogFactory负责选择合适的日志实现,也是单例模式(通过静态方法返回适配器)。
13.4 代理模式 (Proxy Pattern) 代理模式为其他对象提供一种代理以控制对这个对象的访问。Mybatis在Mapper接口和插件机制中大量使用动态代理。
13.4.1 MapperProxy MapperProxy实现了InvocationHandler,是Mapper接口的代理实现。当调用Mapper接口的方法时,会被MapperProxy拦截,转发给MapperMethod执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class MapperProxy <T> implements InvocationHandler , Serializable { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } }
13.4.2 Plugin Mybatis的插件机制使用JDK动态代理为目标对象(Executor、StatementHandler等)创建代理对象,在方法调用前后执行拦截逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Plugin implements InvocationHandler { private final Object target; private final Interceptor interceptor; private final Map<Class<?>, Set<Method>> signatureMap; public static Object wrap (Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0 ) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin (target, interceptor, signatureMap)); } return target; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation (target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } }
13.5 责任链模式 (Chain of Responsibility Pattern) 责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。Mybatis的插件机制本质上是责任链模式的实现。
13.5.1 InterceptorChain InterceptorChain维护了一个拦截器列表,在创建核心对象时,通过pluginAll()方法将目标对象依次传递给每个拦截器的plugin()方法,形成代理链。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList <>(); public Object pluginAll (Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } public void addInterceptor (Interceptor interceptor) { interceptors.add(interceptor); } }
每个拦截器可以决定是否对目标对象进行代理,以及是否将调用传递给下一个拦截器(通过Invocation.proceed())。这形成了一个责任链:请求(方法调用)沿着拦截器链传递,每个拦截器都有机会处理它。
1 2 3 4 5 6 7 8 9 public class Invocation { private final Object target; private final Method method; private final Object[] args; public Object proceed () throws InvocationTargetException, IllegalAccessException { return method.invoke(target, args); } }
13.6 策略模式 (Strategy Pattern) 策略模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。Mybatis在Executor、StatementHandler、TypeHandler等多个地方应用策略模式。
13.6.1 Executor类型 Mybatis提供了三种Executor类型:SIMPLE、REUSE、BATCH。它们实现了不同的Statement处理策略,但都继承自BaseExecutor。
1 2 3 public enum ExecutorType { SIMPLE, REUSE, BATCH }
用户可以通过配置选择使用哪种Executor,这就是策略模式的典型应用——算法(执行策略)可以互相替换。
13.6.2 StatementHandler StatementHandler也有多种实现,对应不同的JDBC Statement类型:
SimpleStatementHandler:使用Statement
PreparedStatementHandler:使用PreparedStatement
CallableStatementHandler:使用CallableStatement
在RoutingStatementHandler中,根据MappedStatement的statementType属性动态选择具体的策略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class RoutingStatementHandler implements StatementHandler { private final StatementHandler delegate; public RoutingStatementHandler (Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; case PREPARED: delegate = new PreparedStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; case CALLABLE: delegate = new CallableStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; default : throw new ExecutorException ("Unknown statement type: " + ms.getStatementType()); } } }
13.6.3 TypeHandler TypeHandler是类型处理器策略,负责Java类型与JDBC类型之间的相互转换。每种Java类型或JDBC类型都可以有对应的TypeHandler实现。
1 2 3 4 5 6 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; }
Mybatis内置了大量TypeHandler,如StringTypeHandler、IntegerTypeHandler等。用户也可以自定义TypeHandler,实现特定的转换逻辑。
13.7 装饰器模式 (Decorator Pattern) 装饰器模式动态地给一个对象添加一些额外的职责。Mybatis的缓存模块大量使用装饰器模式,为基本缓存添加各种功能。
13.7.1 Cache接口 Cache是缓存组件的顶层接口,定义了基本的缓存操作。
1 2 3 4 5 6 7 8 9 10 11 public interface Cache { String getId () ; void putObject (Object key, Object value) ; Object getObject (Object key) ; Object removeObject (Object key) ; void clear () ; int getSize () ; default ReadWriteLock getReadWriteLock () { return null ; } }
13.7.2 PerpetualCache PerpetualCache是最基础的缓存实现,内部使用HashMap存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class PerpetualCache implements Cache { private final String id; private Map<Object, Object> cache = new HashMap <>(); @Override public void putObject (Object key, Object value) { cache.put(key, value); } @Override public Object getObject (Object key) { return cache.get(key); } }
13.7.3 缓存装饰器 Mybatis提供了多个缓存装饰器,它们都实现Cache接口,并持有一个被装饰的Cache对象:
LruCache :最近最少使用算法的缓存,淘汰最久未使用的元素。
FifoCache :先进先出算法的缓存。
SoftCache :基于软引用的缓存,内存不足时会被GC回收。
WeakCache :基于弱引用的缓存。
ScheduledCache :定时清空缓存。
BlockingCache :阻塞缓存,保证只有一个线程查询数据库。
以LruCache为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class LruCache implements Cache { private final Cache delegate; private Map<Object, Object> keyMap; private Object eldestKey; public LruCache (Cache delegate) { this .delegate = delegate; setSize(1024 ); } @Override public void putObject (Object key, Object value) { delegate.putObject(key, value); cycleKeyList(key); } @Override public Object getObject (Object key) { keyMap.get(key); return delegate.getObject(key); } private void cycleKeyList (Object key) { keyMap.put(key, key); if (eldestKey != null ) { delegate.removeObject(eldestKey); eldestKey = null ; } } }
在创建二级缓存时,CacheBuilder会按照配置依次包装装饰器,形成功能增强的缓存链。
十四、总结 通过以上分析,我们可以看到Mybatis是一个设计精巧、层次清晰的持久层框架。它的核心流程可以概括为三个阶段:
初始化阶段 :读取XML配置文件和注解中的配置信息,创建Configuration对象,并完成各个模块的初始化工作。这一阶段的核心是XMLConfigBuilder、XMLMapperBuilder等解析器,它们将配置信息解析为MappedStatement、ResultMap等对象存储在Configuration中。
代理封装阶段 :为Mapper接口创建代理对象,封装了iBatis的编程模型。这一阶段的核心是MapperProxyFactory、MapperProxy和MapperMethod,它们通过JDK动态代理将接口方法调用转换为对SqlSession方法的调用。
数据访问阶段 :通过SqlSession完成SQL的解析、参数的映射、SQL的执行、结果的解析过程。这一阶段的核心是Executor、StatementHandler、ParameterHandler和ResultSetHandler四大组件,它们协同完成从SQL到结果对象的完整映射。
Mybatis还通过插件机制提供了良好的扩展性,通过缓存机制提升了性能,通过与Spring的无缝集成使其成为Java应用中最流行的持久层框架之一。
理解Mybatis的源码实现,不仅可以帮助我们更好地使用这个框架,还可以学习到许多优秀的设计思想和编程技巧,如Builder模式、工厂模式、代理模式、装饰器模式、组合模式、模板方法模式等设计模式的灵活运用。