共计 124484 个字符,预计需要花费 312 分钟才能阅读完成。
目录
-
开撕 MyBatis 源码
-
1. 手写长久层框架 -ipersistent
- 1.1 JDBC 操作数据库 \_问题剖析
- 1.2 JDBC 问题剖析 & 解决思路
-
1.3 自定义长久层框架 \_思路剖析
- 应用 JDBC 和应用长久层框架区别:
- 框架,除了思考自身的工程设计,还须要思考到理论我的项目端的应用场景,干系方波及两端:
- 外围接口 / 类重点阐明:
- 我的项目应用端:
- 自定义框架自身:
- 最终手写的长久层框架结构参考:
- 1.4 自定义长久层框架 \_编码
- 1.5 自定义长久层框架 \_优化
-
2. MyBatis 架构原理 & 次要组件
- 2.1 MyBatis 的架构设计
- 2.2 MyBatis 次要组件及其互相关系
-
3. 源码分析 - 源码环境搭建
- 3.1 源码环境搭建
- 3.2 源码导入 & 编译
-
3.3 编写测试代码
- 3.3.1 配置 sqlMapConfig.xml
- 3.3.2 配置 UserMapper.xml
- 3.3.3 编写 User 类
- 3.3.5 编写测试类
-
4. 源码分析 - 初始化 \_如何解析的全局配置文件?
- 前言
- 解析配置文件源码流程:
- 入口:SqlSessionFactoryBuilder#build
-
XMLConfigBuilder# 结构参数
- 1. XpathParser# 构造函数
- 1.1 XPathParser#createDocument
- 2. XMLConfigBuilder# 构造函数
- 2.1 Configuration# 构造函数
-
XMLConfigBuilder#parse
- 1. XPathParser#evalNode(xpath 语法)
- 2. XMLConfigBuilder#parseConfiguration(XNode)
-
5. 源码分析 - 初始化 \_如何解析的映射配置文件?
- 前言
- 解析映射配置文件源码流程:
- 入口:XMLConfigBuilder#mapperElement
-
\<package> 子标签
- 1. Configuration#addMappers
- 1.1 MapperRegistry#addMappers
- 1.1.1 MapperAnnotationBuilder#parse
- 1.1.1.1 MapperAnnotationBuilder#parseStatement
- 1.1.1.1.2 MapperBuilderAssistant#addMappedStatement
-
\<mapper> 子标签
- 1.XMLMapperBuilder# 构造函数
- 1.1 XPathParser# 构造函数
- 1.1.1 XPathParser#createDocument
- 1.2 XMLMapperBuilder# 构造函数
- 1.2.1MapperBuilderAssistant# 构造函数
- 2. XMLMapperBuilder#parse
- 2.1 XMLMapperBuilder#configurationElement
- 2.1.1 XMLMapperBuilder#buildStatementFromContext
- 2.1.1.1 XMLStatementBuilder# 构造函数
- 2.1.1.2 XMLStatementBuilder#parseStatementNode
- 2.1.1.2.1 MapperBuilderAssistant#addMappedStatement
- 2.1.1.2.1.1 MappedStatement.Builder# 构造函数
- 2.1.1.2.1.2 MappedStatement#build
-
6. 源码分析 -SqlSource 创立流程
- 相干类及对象
-
SqlSource 创立流程
- 入口:XMLLanguageDriver#createSqlSource
- XMLScriptBuilder# 构造函数
- 1.XMLScriptBuilder#initNodeHandlerMap
- XMLScriptBuilder#parseScriptNode
- 1 XMLScriptBuilder#parseDynamicTags
- 2. DynamicSqlSource# 构造函数
- 3. RawSqlSource# 构造函数
- 3.1 SqlSourceBuilder#parse
- 3.1.1 ParameterMappingTokenHandler# 构造函数
- 3.1.2 GenericTokenParser# 构造函数
- 3.1.3 GenericTokenParser#parse
- 3.1.4 StaticSqlSource# 构造函数
-
7. 源码分析 - 揭秘 SqlSession 执行主流程
- 7.1 相干类与接口
- 7.2 流程剖析
-
入口:DefaultSqlSession#selectList
- 1. CachingExecutor#query
- 2. BaseExecutor#query
- 3. BaseExecutor#queryFromDatabase
- 4. SimpleExecutor#doQuery
- 4.1 Configuration#newStatementHandler
- 4.1.1 RoutingStatementHandler# 构造函数
- 4.2 SimpleExecutor#prepareStatement
- 4.2.1 BaseExecutor#getConnection
- 4.2.2 BaseStatementHandler#prepare
- 4.2.2.1 PreparedStatementHandler#instantiateStatement
- 4.2.3 PreparedStatementHandler#parameterize
- 4.3 PreparedStatementHandler#query
- 4.3.1 PreparedStatement#execute
- 4.3.2 DefaultResultSetHandler#handleResultSets
- 执行 sqlsession:参数有两个(statementId 和参数对象)
-
8. 源码分析 - 揭秘如何设置的参数?
-
入口:PreparedStatementHandler#parameterize 办法
- DefaultParameterHandler#setParameters
- BaseTypeHandler#setParameter
- xxxTypeHandler#setNonNullParameter
-
-
9. 源码分析 - 后果集映射流程
-
入口:DefaultResultSetHandler#handleResultSets
- DefaultResultSetHandler#handleRowValues
- DefaultResultSetHandler#handleRowValuesForSimpleResultMap
- 1. DefaultResultSetHandler#getRowValue
- 1.1 DefaultResultSetHandler#createResultObject
- 1.2 DefaultResultSetHandler#applyAutomaticMappings
- 1.3 DefaultResultSetHandler#applyPropertyMappings
-
-
10. 源码分析 - 获取 Mapper 代理对象流程
-
入口:DefaultSqlSession#getMapper
- Configuration#getMapper
- 1. MapperRegistry#getMapper
- 1.1 MapperProxyFactory#newInstance
-
-
11. 源码分析 -invoke 办法
-
入口:MapperProxy#invoke
- MapperMethod
-
-
12. 源码分析 - 插件机制
- 12.1 插件概述
-
12.2 Mybatis 插件介绍
- 能干什么?
- 如何自定义插件?
-
12.3 自定义插件
- 核心思想:
-
12.4 源码剖析 - 插件
- 插件配置信息的加载
- 代理对象的生成
- 拦挡逻辑的执行
-
13. 源码分析 - 缓存策略
-
一级缓存
- 一级缓存原理探索与源码剖析
- 1. 一级缓存 底层数据结构到底是什么?
- 2. 一级缓存的执行流程
- 一级缓存源码剖析论断:
-
二级缓存
- 启用二级缓存
- 二级缓存源码剖析
- 标签 < cache/> 的解析
- buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)); 将 Cache 包装到 MappedStatement
- 查问源码剖析
- CachingExecutor
- TransactionalCacheManager
- TransactionalCache
- 为何只有 SqlSession 提交或敞开之后?
- 二级缓存的刷新
- 总结:
-
-
开撕 MyBatis 源码
* 手写长久层框架 - 仿写 mybatis
* Mybatis 架构设计 & 次要组件
* Mybatis 如何实现的初始化?
* Mybatis 如何实现的 sql 解析及执行?
* Mybatis 如何设置的参数?
* Mybatis 如何进行的类型转换?
* Mybatis 如何封装的返回后果集?
* Mybatis 插件原理是什?
* Mybatis 缓存底层数据结构是什么?
1. 手写长久层框架 -ipersistent
1.1 JDBC 操作数据库 \_问题剖析
JDBC API 容许应用程序拜访任何模式的表格数据,特地是存储在关系数据库中的数据
代码示例:
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 通过驱动治理类获取数据库链接
connection =
DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding=utf-8", "root", "root");
// 定义 sql 语句?示意占位符
String sql = "select * from user where username = ?";
// 获取预处理 statement
preparedStatement = connection.prepareStatement(sql);
// 设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的参数值 preparedStatement.setString(1, "tom");
// 向数据库收回 sql 执行查问,查问出后果集
resultSet = preparedStatement.executeQuery();
// 遍历查问后果集
while (resultSet.next()) {int id = resultSet.getInt("id");
String username = resultSet.getString("username");
// 封装 User
user.setId(id);
user.setUsername(username);
}
System.out.println(user);
}
} catch (Exception e) {e.printStackTrace();
} finally {
// 开释资源
if (resultSet != null) {
try {resultSet.close();
} catch (SQLException e) {e.printStackTrace();
}
}
if (preparedStatement != null) {
try {preparedStatement.close();
} catch (SQLException e) {e.printStackTrace();
}
}
if (connection != null) {
try {connection.close();
} catch (SQLException e) {e.printStackTrace();
}
}
}
}
1.2 JDBC 问题剖析 & 解决思路
剖开代码,一一剖析:
(1)加载驱动,获取链接:
-
存在问题 1:数据库配置信息存在 硬编码 问题。
优化思路:应用配置文件!
-
存在问题 2:频繁创立、开释 数据库连贯 问题。
优化思路:应用数据连接池!
(2)定义 sql、设置参数、执行查问:
-
存在问题 3:SQL 语句、设置参数、获取后果集参数均存在 硬编码 问题。
优化思路:应用配置文件!
(2)遍历查问后果集:
-
存在问题 4:手动封装 返回后果集,较为繁琐
优化思路:应用 Java 反射、内省!
针对 JDBC 各个环节中存在的有余,当初,咱们整顿出对应的优化思路,对立汇总:
存在问题 | 优化思路 |
---|---|
数据库配置信息存在硬编码问题 | 应用配置文件 |
频繁创立、开释数据库连贯问题 | 应用数据连接池 |
SQL 语句、设置参数、获取后果集参数均存在硬编码问题 | 应用配置文件 |
手动封装返回后果集,较为繁琐 | 应用 Java 反射、内省 |
1.3 自定义长久层框架 \_思路剖析
JDBC 是集体作战,凡事亲力亲为,低效而高险,本人加载驱动,本人建连贯,本人 …
而长久层框架好比是多工种合作,分工明确,执行高效,有专门负责解析注册驱动建设连贯的,有专门治理数据连接池的,有专门执行 sql 语句的,有专门做预处理参数的,有专门拆卸后果集的 …
优化思路:框架的作用,就是为了帮忙咱们减去沉重开发细节与冗余代码,使咱们能更加专一于业务利用开发。
应用 JDBC 和应用长久层框架区别:
是不是发现,领有这么一套长久层框架是如此舒服,咱们仅仅须要干两件事:
- 配置数据源(地址 / 数据名 / 用户名 / 明码)
- 编写 SQL 与参数筹备(SQL 语句 / 参数类型 / 返回值类型)
框架,除了思考自身的工程设计,还须要思考到理论我的项目端的应用场景,干系方波及两端:
- 应用端(理论我的项目)
- 长久层框架自身
以上两步,咱们通过一张架构图《手写长久层框架基本思路》来梳理分明:
外围接口 / 类重点阐明:
分工协作 | 角色定位 | 类名定义 |
---|---|---|
负责读取配置文件 | 资源辅助类 | Resources |
负责存储数据库连贯信息 | 数据库资源类 | Configuration |
负责存储 SQL 映射定义、存储后果集映射定义 | SQL 与后果集资源类 | MappedStatement |
负责解析配置文件,创立会话工厂 SqlSessionFactory | 会话工厂构建者 | SqlSessionFactoryBuilder |
负责创立会话 SqlSession | 会话工厂 | SqlSessionFactory |
指派执行器 Executor | 会话 | SqlSession |
负责执行 SQL(配合指定资源 Mapped Statement) | 执行器 | Executor |
失常来说我的项目只对应一套数据库环境,个别对应一个 SqlSessionFactory 实例对象,咱们应用单例模式只创立一个 SqlSessionFactory 实例。
如果须要配置多套数据库环境,那须要做一些拓展,例如 Mybatis 中通过 environments 等配置就能够反对多套测试 / 生产数据库环境进行切换。
我的项目应用端:
(1)调用框架 API,除了引入自定义长久层框架的 jar 包
(2)提供两局部配置信息:1.sqlMapConfig.xml : 数据库配置信息(地址 / 数据名 / 用户名 / 明码),以及 mapper.xml 的全门路
2.mapper.xml : SQL 配置信息,寄存 SQL 语句、参数类型、返回值类型相干信息
自定义框架自身:
1、加载配置文件:依据配置文件的门路,加载配置文件成字节输出流,存储在内存中。
2、创立两个 javaBean(容器对象):寄存配置文件解析进去的内容
3、解析配置文件(应用 dom4j),并创立 SqlSession 会话对象
4、创立 SqlSessionFactory 接口以及实现类 DefaultSqlSessionFactory
5、创立 SqlSession 接口以及实现类 DefaultSqlSession
6、创立 Executor 接口以及实现类 SimpleExecutor
根本过程咱们曾经清晰,咱们再细化一下类图,更好的助于咱们理论编码:
最终手写的长久层框架结构参考:
1.4 自定义长久层框架 \_编码
<properties>
<!-- Encoding -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<!-- 引入 ipersistent 的依赖 >
在应用端我的项目中创立配置配置文件
创立 sqlMapConfig.xml
<configuration>
<!--1. 配置数据库配置信息 -->
<dataSource>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///zdy_mybatis?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</dataSource>
<!--2. 引入映射配置文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
mapper.xml
<mapper namespace="User">
<!-- 依据条件查问单个 -->
<select id="selectOne" resultType="com.itheima.pojo.User" parameterType="com.itheima.pojo.User">
select * from user where id = #{id} and username = #{username}
</select>
<!-- 查问所有 -->
<select id="selectList" resultType="com.itheima.pojo.User">
select * from user
</select>
</mapper>
User 实体
public class User {
// 主键标识
private Integer id;
// 用户名
private String username;
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getUsername() {return username;}
public void setUsername(String username) {this.username = username;}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' + '}';
}
}
再创立一个 Maven 子工程并且导入须要用到的依赖坐标
<properties>
<!-- Encoding -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!-- mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--junit 依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!-- 作用域测试范畴 -->
<scope>test</scope>
</dependency>
<!--dom4j 依赖 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--xpath 依赖 -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<!--druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- log 日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
Resources
public class Resources {
/**
* 依据配置文件的门路,加载成字节输出流,存到内存中
* @param path
* @return
*/
public static InputStream getResourceAsSteam(String path){InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
Configuration
/**
* 寄存外围配置文件解析的内容
*/
public class Configuration {
// 数据源对象
private DataSource dataSource;
// map : key :statementId value : 封装好的 MappedStatement
private Map<String,MappedStatement> mappedStatementMap = new HashMap<>();
public DataSource getDataSource() {return dataSource;}
public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}
public Map<String, MappedStatement> getMappedStatementMap() {return mappedStatementMap;}
public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {this.mappedStatementMap = mappedStatementMap;}
}
MappedStatement
/**
* 寄存解析映射配置文件的内容
* <select id="selectOne" resultType="com.itheima.pojo.User" parameterType="com.itheima.pojo.User">
* select * from user where id = #{id} and username = #{username}
* </select>
*/
public class MappedStatement {
// 1. 惟一标识(statementId namespace.id)private String statementId;
// 2. 返回后果类型
private String resultType;
// 3. 参数类型
private String parameterType;
// 4. 要执行的 sql 语句
private String sql;
// 5.mapper 代理:sqlcommandType
private String sqlcommandType;
public String getSqlcommandType() {return sqlcommandType;}
public void setSqlcommandType(String sqlcommandType) {this.sqlcommandType = sqlcommandType;}
public String getStatementId() {return statementId;}
public void setStatementId(String statementId) {this.statementId = statementId;}
public String getResultType() {return resultType;}
public void setResultType(String resultType) {this.resultType = resultType;}
public String getParameterType() {return parameterType;}
public void setParameterType(String parameterType) {this.parameterType = parameterType;}
public String getSql() {return sql;}
public void setSql(String sql) {this.sql = sql;}
}
SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder {
/**
* 1. 解析配置文件,封装 Configuration 2. 创立 SqlSessionFactory 工厂对象
* @return
*/
public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
// 1. 解析配置文件,封装 Configuration
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parse(inputStream);
SqlSessionFactory defatultSqlSessionFactory = new DefatultSqlSessionFactory(configuration);
return defatultSqlSessionFactory;
}
}
XMLConfigerBuilder
public class XMLConfigBuilder {
private Configuration configuration;
public XMLConfigBuilder() {configuration = new Configuration();
}
/**
* 应用 dom4j 解析 xml 文件,封装 configuration 对象
* @param inputStream
* @return
*/
public Configuration parse(InputStream inputStream) throws DocumentException {Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
// 解析外围配置文件中数据源局部
List<Element> list = rootElement.selectNodes("//property");
// <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
Properties properties = new Properties();
for (Element element : list) {String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name,value);
}
// 创立数据源对象(连接池)DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(properties.getProperty("driverClassName"));
druidDataSource.setUrl(properties.getProperty("url"));
druidDataSource.setUsername(properties.getProperty("username"));
druidDataSource.setPassword(properties.getProperty("password"));
// 创立好的数据源对象封装进 configuration 中、configuration.setDataSource(druidDataSource);
// 解析映射配置文件
// 1. 获取映射配置文件的门路 2. 解析 3. 封装好 mappedStatement
List<Element> mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {String mapperPath = element.attributeValue("resource");
InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(resourceAsSteam);
}
return configuration;
}
}
XMLMapperBuilder
public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration) {this.configuration = configuration;}
public void parse(InputStream inputStream) throws DocumentException, ClassNotFoundException {Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
String namespace = rootElement.attributeValue("namespace");
List<Element> select = rootElement.selectNodes("select");
for (Element element : select) { //id 的值
String id = element.attributeValue("id");
String paramterType = element.attributeValue("paramterType");
String resultType = element.attributeValue("resultType"); // 输出参数 class
Class<?> paramterTypeClass = getClassType(paramterType);
// 返回后果 class
Class<?> resultTypeClass = getClassType(resultType);
//statementId
String key = namespace + "." + id;
//sql 语句
String textTrim = element.getTextTrim();
// 封装 mappedStatement
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setParamterType(paramterTypeClass);
mappedStatement.setResultType(resultTypeClass);
mappedStatement.setSql(textTrim);
// 填充 configuration
configuration.getMappedStatementMap().put(key, mappedStatement);
private Class<?> getClassType (String paramterType) throws ClassNotFoundException {Class<?> aClass = Class.forName(paramterType);
return aClass;
}
}
sqlSessionFactory 接口及 D efaultSqlSessionFactory 实现类
public interface SqlSessionFactory {
/**
* 生产 sqlSession:封装着与数据库交互的办法
* @return
*/
public SqlSession openSession();}
public class DefatultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefatultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}
@Override
public SqlSession openSession() {
// 执行器创立进去
Executor executor = new SimpleExecutor();
DefaultSqlSession defaultSqlSession = new DefaultSqlSession(configuration,executor);
return defaultSqlSession;
}
}
sqlSession 接口及 DefaultSqlSession 实现类
public interface SqlSession {
/**
* 查问所有的办法 select * from user where username like '%aaa%' and sex = ''
* 参数 1:惟一标识
* 参数 2:入参
*/
public <E> List<E> selectList(String statementId,Object parameter) throws Exception;
/**
* 查问单个的办法
*/
public <T> T selectOne(String statementId,Object parameter) throws Exception;
}
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
public DefaultSqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
@Override // user.selectList 1 tom user
public <E> List<E> selectList(String statementId, Object params) throws Exception {MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
// 将查问操作委派给底层的执行器
List<E> list = executor.query(configuration,mappedStatement,params);
return list;
}
@Override
public <T> T selectOne(String statementId, Object params) throws Exception {List<Object> list = this.selectList(statementId, params);
if(list.size() == 1){return (T) list.get(0);
}else if(list.size() > 1){throw new RuntimeException("返回后果过多");
}else {return null;}
}
}
Executor
public interface Executor {<E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object params) throws Exception;
}
SimpleExecutor
public class SimpleExecutor implements Executor {
/**
* 执行 JDBC 操作
* @param configuration
* @param mappedStatement
* @param params
* @param <E>
* @return
*/
@Override // user product
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object params) throws Exception {
// 1. 加载驱动,获取连贯
Connection connection = configuration.getDataSource().getConnection();
// 2. 获取 prepareStatement 预编译对象
/*
select * from user where id = #{id} and username = #{username}
select * from user where id = ? and username = ?
占位符替换:#{}替换成?留神:#{id}外面的 id 名称要保留
*/
String sql = mappedStatement.getSql();
BoundSql boundSql = getBoundSQL(sql);
String finaLSql = boundSql.getFinaLSql();
PreparedStatement preparedStatement = connection.prepareStatement(finaLSql);
// 3. 设置参数
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if(parameterMappings.size() > 0){
// com.itheima.pojo.User
String parameterType = mappedStatement.getParameterType();
Class<?> parameterTypeClass = Class.forName(parameterType);
for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);
// id
String content = parameterMapping.getContent();
// 反射
Field declaredField = parameterTypeClass.getDeclaredField(content);
// 暴力拜访
declaredField.setAccessible(true);
Object value = declaredField.get(params);
preparedStatement.setObject(i+1 ,value);
}
}
// 4. 执行 sql, 发动查问
ResultSet resultSet = preparedStatement.executeQuery();
String resultType = mappedStatement.getResultType();
Class<?> resultTypeClass = Class.forName(resultType);
ArrayList<E> list = new ArrayList<>();
// 5. 遍历封装
while (resultSet.next()){
// 元数据信息中蕴含了字段名 字段的值
ResultSetMetaData metaData = resultSet.getMetaData();
Object obj = resultTypeClass.newInstance();
for (int i = 1; i <= metaData.getColumnCount() ; i++) {
// id username
String columnName = metaData.getColumnName(i);
Object value = resultSet.getObject(columnName);
// 属性形容器
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(obj,value);
}
list.add((E) obj);
}
return list;
}
/**
* 1. 将 sql 中 #{}替换成?2. 将 #{}外面的值保留
* @param sql
* @return
*/
private BoundSql getBoundSQL(String sql) {
// 标记处理器:配合标记解析器实现标记的解析工作
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
// 标记解析器
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
String finalSql = genericTokenParser.parse(sql);
// #{}外面的值的汇合
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BoundSql boundSql = new BoundSql(finalSql, parameterMappings);
return boundSql;
}
}
BoundSql
public class BoundSql {
// 解析过后的 sql 语句
private String sqlText;
// 解析进去的参数
private List<ParameterMapping> parameterMappingList = new ArrayList<ParameterMapping>();
public BoundSql(String sqlText, List<ParameterMapping>
parameterMappingList) {
this.sqlText = sqlText;
this.parameterMappingList = parameterMappingList;
}
public String getSqlText() {return sqlText;}
public void setSqlText(String sqlText) {this.sqlText = sqlText;}
public List<ParameterMapping> getParameterMappingList() {return parameterMappingList;}
public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {this.parameterMappingList = parameterMappingList;}
}
1.5 自定义长久层框架 \_优化
通过上述咱们的自定义框架,咱们解决了 JDBC 操作数据库带来的一些问题:例如频繁创立开释数据库连 接,硬编码,手动封装返回后果集等问题,然而当初咱们持续来剖析刚刚实现的自定义框架代码,有没 有什么问题?
问题如下:
- dao 的实现类中存在反复的代码,整个操作的过程模板反复(创立 sqlsession, 调用 sqlsession 方 法,敞开 sqlsession)
- dao 的实现类中存在硬编码,调用 sqlsession 的办法时,参数 statement 的 id 硬编码
解决:应用代理模式来创立接口的代理对象
@Test
public void test2() throws Exception {InputStream resourceAsSteam = Resources.getResourceAsSteam(path:"sqlMapConfig.xml")
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);
SqlSession sqlSession = build.openSession();
User user = new User();
user.setld(l);
user.setUsername("tom");
// 代理对象
UserMapper userMapper = sqlSession.getMappper(UserMapper.class);
User userl = userMapper.selectOne(user);
System ・ out.println(userl);
}
在 sqlSession 中增加办法
public interface SqlSession {public <T> T getMappper(Class<?> mapperClass);
实现类
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
public DefaultSqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
@Override
public <T> T getMapper(Class<?> c) {
// 基于 JDK 动静代理产生接口的代理对象
Object proxy = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{c}, new InvocationHandler() {
/*
o : 代理对象:很少用到
method:正在执行的办法
objects:办法的参数
*/
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
// findByCondition
String methodName = method.getName();
// com.itheima.dao.IUserDao
String className = method.getDeclaringClass().getName();
// 惟一标识:namespace.id com.itheima.dao.IUserDao.findByCondition
String statementId = className + "." +methodName;
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
String sql = mappedStatement.getSql();
// sqlcommandType select insert update delete
String sqlcommandType = mappedStatement.getSqlcommandType();
switch (sqlcommandType){
case "select":
// 查问操作 问题来了:selectList 还是 selectOne?
Type genericReturnType = method.getGenericReturnType();
// 判断是否实现泛型类型参数化
if(genericReturnType instanceof ParameterizedType){return selectList(statementId,objects);
}
return selectOne(statementId,objects);
case "update":
break;
// 更新操作
case "delete":
break;
// 删除操作
case "insert":
break;
// 增加操作
}
return null;
}
});
return (T) proxy;
}
2. MyBatis 架构原理 & 次要组件
2.1 MyBatis 的架构设计
mybatis 架构四层作用是什么呢?
- Api 接口层:提供 API 减少、删除、批改、查问等接口,通过 API 接口对数据库进行操作。
- 数据处理层:次要负责 SQL 的 查问、解析、执行以及后果映射的解决,次要作用解析 sql 依据调用申请实现一次数据库操作.
- 框架撑持层:负责通用根底服务撑持, 蕴含事务管理、连接池治理、缓存治理等共用组件的封装,为下层提供根底服务撑持.
- 疏导层:疏导层是配置和启动 MyBatis 配置信息的形式
2.2 MyBatis 次要组件及其互相关系
组件关系如下图所示:
组件介绍:
- SqlSession:是 Mybatis 对外裸露的外围 API,提供了对数据库的 DRUD 操作接口。
- Executor:执行器,由 SqlSession 调用,负责数据库操作以及 Mybatis 两级缓存的保护
- StatementHandler:封装了 JDBC Statement 操作,负责对 Statement 的操作,例如 PrepareStatement 参数的设置以及后果集的解决。
- ParameterHandler:是 StatementHandler 外部一个组件,次要负责对 ParameterStatement 参数的设置
- ResultSetHandler:是 StatementHandler 外部一个组件,次要负责对 ResultSet 后果集的解决,封装成指标对象返回
- TypeHandler:用于 Java 类型与 JDBC 类型之间的数据转换,ParameterHandler 和 ResultSetHandler 会别离应用到它的类型转换性能
- MappedStatement:是对 Mapper 配置文件或 Mapper 接口办法上通过注解申明 SQL 的封装
- Configuration:Mybatis 所有配置都对立由 Configuration 进行治理,外部由具体对象别离治理各自的小功能模块
3. 源码分析 - 源码环境搭建
3.1 源码环境搭建
- mybatis 源码地址:https://github.com/mybatis/mybatis-3
3.2 源码导入 & 编译
3.3 编写测试代码
3.3.1 配置 sqlMapConfig.xml
<?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">
<!-- 应用 jdbc 事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url"
value="jdbc:mysql:///zdy_mybatis?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 第二局部:引入映射配置文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
3.3.2 配置 UserMapper.xml
<?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="user">
<select id="findUserById" parameterType="int" resultType="com.itheima.pojo.User">
SELECT id,username FROM user WHERE id = #{id}
</select>
</mapper>
3.3.3 编写 User 类
package com.itheima.pojo;
import lombok.Data;
@Data
public class User {
// ID 标识
private Integer id;
// 用户名
private String username;
}
3.3.5 编写测试类
public class MybatisTest {
@Test
public void test1() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("user.findUserById", user1);
System.out.println(user);
System.out.println("MyBatis 源码环境搭建胜利...");
sqlSession.close();}
}
输入:
4. 源码分析 - 初始化 \_如何解析的全局配置文件?
前言
全局配置文件可配置参数:https://mybatis.org/mybatis-3/zh/configuration.html
- Configuration 对象
public class Configuration {
protected Environment environment;
// 容许在嵌套语句中应用分页(RowBounds)。如果容许应用则设置为 false。默认为 false
protected boolean safeRowBoundsEnabled;
// 容许在嵌套语句中应用分页(ResultHandler)。如果容许应用则设置为 false
protected boolean safeResultHandlerEnabled = true;
// 是否开启主动驼峰命名规定(camel case)映射,即从经典数据库列名 A_COLUMN
// 到经典 Java 属性名 aColumn 的相似映射。默认 false
protected boolean mapUnderscoreToCamelCase;
// 当开启时,任何办法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。默认值 false (true in ≤3.4.1)
protected boolean aggressiveLazyLoading;
// 是否容许繁多语句返回多后果集(须要兼容驱动)。protected boolean multipleResultSetsEnabled = true;
// 容许 JDBC 反对主动生成主键,须要驱动兼容。这就是 insert 时获取 mysql 自增主键 /oracle sequence 的开关。// 注:一般来说, 这是心愿的后果, 应该默认值为 true 比拟适合。protected boolean useGeneratedKeys;
// 应用列标签代替列名, 一般来说, 这是心愿的后果
protected boolean useColumnLabel = true;
// 是否启用缓存
protected boolean cacheEnabled = true;
// 指定当后果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)办法,// 这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。protected boolean callSettersOnNulls;
// 容许应用办法签名中的名称作为语句参数名称。为了应用该个性,你的工程必须采纳 Java 8 编译,// 并且加上 -parameters 选项。(从 3.4.1 开始)protected boolean useActualParamName = true;
// 当返回行的所有列都是空时,MyBatis 默认返回 null。当开启这个设置时,MyBatis 会返回一个空实例。// 请留神,它也实用于嵌套的后果集 (i.e. collectioin and association)。(从 3.4.2 开始)// 注:这里应该拆分为两个参数比拟适合, 一个用于后果集,一个用于单记录。// 通常来说,咱们会心愿后果集不是 null,单记录依然是 null
protected boolean returnInstanceForEmptyRow;
protected boolean shrinkWhitespacesInSql;
// 指定 MyBatis 减少到日志名称的前缀。protected String logPrefix;
// 指定 MyBatis 所用日志的具体实现,未指定时将主动查找。个别倡议指定为 slf4j 或 log4j
protected Class<? extends Log> logImpl;
// 指定 VFS 的实现, VFS 是 mybatis 提供的用于拜访 AS 内资源的一个简便接口
protected Class<? extends VFS> vfsImpl;
protected Class<?> defaultSqlProviderType;
// MyBatis 利用本地缓存机制(Local Cache)避免循环援用(circular references)和减速反复嵌套查问。// 默认值为 SESSION,这种状况下会缓存一个会话中执行的所有查问。// 若设置值为 STATEMENT,本地会话仅用在语句执行上,对雷同 SqlSession 的不同调用将不会共享数据。protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
// 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。某些驱动须要指定列的 JDBC 类型,// 少数状况间接用个别类型即可,比方 NULL、VARCHAR 或 OTHER。protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
// 指定对象的哪个办法触发一次提早加载。protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
// 设置超时工夫,它决定驱动期待数据库响应的秒数。默认不超时
protected Integer defaultStatementTimeout;
// 为驱动的后果集设置默认获取数量。protected Integer defaultFetchSize;
// SIMPLE 就是一般的执行器;REUSE 执行器会重用预处理语句(prepared statements);// BATCH 执行器将重用语句并执行批量更新。protected ResultSetType defaultResultSetType;
// 默认执行器类型
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// 指定 MyBatis 应如何主动映射列到字段或属性。// NONE 示意勾销主动映射;// PARTIAL 只会主动映射没有定义嵌套后果集映射的后果集。// FULL 会主动映射任意简单的后果集(无论是否嵌套)。protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
// 指定发现主动映射指标未知列(或者未知属性类型)的行为。这个值应该设置为 WARNING 比拟适合
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
// settings 下的 properties 属性
protected Properties variables = new Properties();
// 默认的反射器工厂, 用于操作属性、结构器不便
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
// 对象工厂, 所有的类 resultMap 类都须要依赖于对象工厂来实例化
protected ObjectFactory objectFactory = new DefaultObjectFactory();
// 对象包装器工厂, 次要用来在创立非原生对象, 比方减少了某些监控或者非凡属性的代理类
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
// 提早加载的全局开关。当开启时,所有关联对象都会提早加载。特定关联关系中可通过设置 fetchType 属性来笼罩该项的开关状态
protected boolean lazyLoadingEnabled = false;
// 指定 Mybatis 创立具备提早加载能力的对象所用到的代理工具。MyBatis 3.3+ 应用 JAVASSIST
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
// MyBatis 能够依据不同的数据库厂商执行不同的语句,这种多厂商的反对是基于映射语句中的 databaseId 属性。protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// mybatis 插件列表
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
// 类型注册器, 用于在执行 sql 语句的出入参映射以及 mybatis-config 文件里的各种配置
// 比方 <transactionManager type="JDBC"/><dataSource type="POOLED"> 时应用简写
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check" + savedValue.getResource() + "and" + targetValue.getResource());
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<>();
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<>();
public Configuration(Environment environment) {this();
this.environment = environment;
}
问题:外围配置文件 & 映射配置文件如何被解析的?
解析配置文件源码流程:
入口:SqlSessionFactoryBuilder#build
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// XMLConfigBuilder: 用来解析 XML 配置文件
// 应用构建者模式
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// parser.parse():应用 XPATH 解析 XML 配置文件,将配置文件封装为 Configuration 对象
// 返回 DefaultSqlSessionFactory 对象,该对象领有 Configuration 对象(封装配置文件信息)return build(parser.parse());
} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {ErrorContext.instance().reset();
try {inputStream.close();
} catch (IOException e) {// Intentionally ignore. Prefer previous error.}
}
}
创立 XMLConfigBuilder 对象,这个类是 BaseBuilder 的子类,BaseBuilder 类图:
看到这些子类基本上都是以 Builder 结尾,这里应用的是Builder 建造者设计模式。
XMLConfigBuilder# 结构参数
XMLConfigBuilder: 用来解析 XML 配置文件(应用构建者模式)
// XMLConfigBuilder: 用来解析 XML 配置文件
// 应用构建者模式
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
Mybatis 对应解析包 org.apache.ibatis.parsing:
XPathParser 基于 Java XPath 解析器,用于解析 MyBatis 中
- SqlMapConfig.xml
- mapper.xml
XPathParser 次要内容:
1. XpathParser# 构造函数
用来应用 XPath 语法解析 XML 的解析器
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {commonConstructor(validation, variables, entityResolver);
// 解析 XML 文档为 Document 对象
this.document = createDocument(new InputSource(inputStream));
}
1.1 XPathParser#createDocument
解析全局配置文件,封装为 Document 对象(封装一些子节点,应用 XPath 语法解析获取)
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 进行 dtd 或者 Schema 校验
factory.setValidating(validation);
factory.setNamespaceAware(false);
// 设置疏忽正文为 true
factory.setIgnoringComments(true);
// 设置是否疏忽元素内容中的空白
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {throw exception;}
@Override
public void fatalError(SAXParseException exception) throws SAXException {throw exception;}
@Override
public void warning(SAXParseException exception) throws SAXException {}});
// 通过 dom 解析,获取 Document 对象
return builder.parse(inputSource);
} catch (Exception e) {throw new BuilderException("Error creating document instance. Cause:" + e, e);
}
}
2. XMLConfigBuilder# 构造函数
创立 Configuration 对象,同时初始化内置类的别名
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 创立 Configuration 对象,并通过 TypeAliasRegistry 注册一些 Mybatis 外部相干类的别名
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
2.1 Configuration# 构造函数
创立 Configuration 对象,同时初始化内置类的别名
public Configuration() {
//TypeAliasRegistry(类型别名注册器)typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
XMLConfigBuilder#parse
// 应用 XPATH 解析 XML 配置文件,将配置文件封装为 Configuration 对象
parser.parse();
XMLConfigBuilder#parse
解析 XML 配置文件
/**
* 解析 XML 配置文件
* @return
*/
public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// parser.evalNode("/configuration"):通过 XPATH 解析器,解析 configuration 根节点
// 从 configuration 根节点开始解析,最终将解析出的内容封装到 Configuration 对象中
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
1. XPathParser#evalNode(xpath 语法)
XPath 解析器,专门用来通过 Xpath 语法解析 XML 返回 XNode 节点
public XNode evalNode(String expression) {
// 依据 XPATH 语法,获取指定节点
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) {Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {return null;}
return new XNode(this, node, variables);
}
2. XMLConfigBuilder#parseConfiguration(XNode)
从 configuration 根节点开始解析,最终将解析出的内容封装到 Configuration 对象中
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析 </properties> 标签
propertiesElement(root.evalNode("properties"));
// 解析 </settings> 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
// 解析 </typeAliases> 标签
typeAliasesElement(root.evalNode("typeAliases"));
// 解析 </plugins> 标签
pluginElement(root.evalNode("plugins"));
// 解析 </objectFactory> 标签
objectFactoryElement(root.evalNode("objectFactory"));
// 解析 </objectWrapperFactory> 标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析 </reflectorFactory> 标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析 </environments> 标签
environmentsElement(root.evalNode("environments"));
// 解析 </databaseIdProvider> 标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析 </typeHandlers> 标签
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析 </mappers> 标签 加载映射文件流程主入口
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:" + e, e);
}
}
### SqlSessionFactoryBuilder#build
返回 DefaultSqlSessionFactory 对象,该对象领有 Configuration 对象(封装配置文件信息)
// 返回 DefaultSqlSessionFactory 对象,该对象领有 Configuration 对象(封装配置文件信息)return build(parser.parse());
public SqlSessionFactory build(Configuration config) {
// 创立 SqlSessionFactory 接口的默认实现类
return new DefaultSqlSessionFactory(config);
}
总结
5. 源码分析 - 初始化 \_如何解析的映射配置文件?
前言
### select
select 元素容许你配置很多属性来配置每条语句的行为细节
<select
id="select"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">
### insert, update 和 delete
数据变更语句 insert,update 和 delete 的实现十分靠近
<insert
id="insert"
parameterType="com.itheima.pojo.User"
flushCache="true"
statementType="PREPARED"
keyProperty=""keyColumn=""
useGeneratedKeys=""timeout="20">
<update
id="update"
parameterType="com.itheima.pojo.User"
flushCache="true"
statementType="PREPARED"
timeout="20">
<delete
id="delete"
parameterType="com.itheima.pojo.User"
flushCache="true"
statementType="PREPARED"
timeout="20">
### 动静 sql
借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素品种
- if
- choose (when, otherwise)
MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state =‘ACTIVE’<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
问题:映射配置文件中标签和属性如何被解析封装的?
问题:sql 占位符如何进行的替换?动静 sql 如何进行的解析?
解析映射配置文件源码流程:
入口:XMLConfigBuilder#mapperElement
解析全局配置文件中的
标签
/**
* 解析 <mappers> 标签
* @param parent mappers 标签对应的 XNode 对象
* @throws Exception
*/
private void mapperElement(XNode parent) throws Exception {if (parent != null) {
// 获取 <mappers> 标签的子标签
for (XNode child : parent.getChildren()) {
// <package> 子标签
if ("package".equals(child.getName())) {
// 获取 mapper 接口和 mapper 映射文件对应的 package 包名
String mapperPackage = child.getStringAttribute("name");
// 将包下所有的 mapper 接口以及它的代理对象存储到一个 Map 汇合中,key 为 mapper 接口类型,value 为代理对象工厂
configuration.addMappers(mapperPackage);
} else {// <mapper> 子标签
// 获取 <mapper> 子标签的 resource 属性
String resource = child.getStringAttribute("resource");
// 获取 <mapper> 子标签的 url 属性
String url = child.getStringAttribute("url");
// 获取 <mapper> 子标签的 class 属性
String mapperClass = child.getStringAttribute("class");
// 它们是互斥的
if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 专门用来解析 mapper 映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 通过 XMLMapperBuilder 解析 mapper 映射文件
mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
// 通过 XMLMapperBuilder 解析 mapper 映射文件
mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);
// 将指定 mapper 接口以及它的代理对象存储到一个 Map 汇合中,key 为 mapper 接口类型,value 为代理对象工厂
configuration.addMapper(mapperInterface);
} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
\<package> 子标签
1. Configuration#addMappers
将包下所有的 mapper 接口以及它的代理对象存储到一个 Map 汇合中,key 为 mapper 接口类型,value 为代理对象工厂
public void addMappers(String packageName) {mapperRegistry.addMappers(packageName);
}
1.1 MapperRegistry#addMappers
将 Mapper 接口增加到 MapperRegistry 中
//1
public void addMappers(String packageName) {addMappers(packageName, Object.class);
}
//2
public void addMappers(String packageName, Class<?> superType) {ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 依据 package 名称,加载该包下 Mapper 接口文件(不是映射文件)resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 获取加载的 Mapper 接口
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
// 将 Mapper 接口增加到 MapperRegistry 中
addMapper(mapperClass);
}
}
//3
public <T> void addMapper(Class<T> type) {if (type.isInterface()) {
// 如果 Map 汇合中曾经有该 mapper 接口的映射,就不须要再存储了
if (hasMapper(type)) {throw new BindingException("Type" + type + "is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 将 mapper 接口以及它的代理对象存储到一个 Map 汇合中,key 为 mapper 接口类型,value 为代理对象工厂
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 用来解析注解形式的 mapper 接口
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析注解形式的 mapper 接口
parser.parse();
loadCompleted = true;
} finally {if (!loadCompleted) {knownMappers.remove(type);
}
}
}
}
1.1.1 MapperAnnotationBuilder#parse
解析注解形式的 mapper 接口
public void parse() {
// 获取 mapper 接口的全门路
String resource = type.toString();
// 是否解析过该 mapper 接口
if (!configuration.isResourceLoaded(resource)) {
// 先解析 mapper 映射文件
loadXmlResource();
// 设置解析标识
configuration.addLoadedResource(resource);
// Mapper 构建者助手
assistant.setCurrentNamespace(type.getName());
// 解析 CacheNamespace 注解
parseCache();
// 解析 CacheNamespaceRef 注解
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
// 每个 mapper 接口中的办法,都解析成 MappedStatement 对象
parseStatement(method);
}
} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 去查看所有的 incompleteMethods, 如果能够解析了. 那就移除
parsePendingMethods();}
1.1.1.1 MapperAnnotationBuilder#parseStatement
每个 mapper 接口中的办法,都解析成 MappedStatement 对象
void parseStatement(Method method) {
// 获取 Mapper 接口的形参类型
Class<?> parameterTypeClass = getParameterType(method);
// 解析 Lang 注解
LanguageDriver languageDriver = getLanguageDriver(method);
//
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {Options options = method.getAnnotation(Options.class);
// 组装 mappedStatementId
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = null;
// 获取该 mapper 接口中的办法是 CRUD 操作的哪一种
SqlCommandType sqlCommandType = getSqlCommandType(method);
// 是否是 SELECT 操作
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
// 主键生成器,用于主键返回
KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();} else if (options == null) {keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();}
} else {keyGenerator = NoKeyGenerator.INSTANCE;}
if (options != null) {if (FlushCachePolicy.TRUE.equals(options.flushCache())) {flushCache = true;} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {flushCache = false;}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();}
// 解决 ResultMap 注解
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {if (sb.length() > 0) {sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();} else if (isSelect) {resultMapId = parseResultMap(method);
}
// 通过 Mapper 构建助手,创立一个 MappedStatement 对象,封装信息
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
1.1.1.1.2 MapperBuilderAssistant#addMappedStatement
通过 Mapper 构建助手,创立一个 MappedStatement 对象,封装信息
public 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) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 利用构建者模式,去创立 MappedStatement.Builder, 用于创立 MappedStatement 对象
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(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);
}
// 通过 MappedStatement.Builder,构建一个 MappedStatement
MappedStatement statement = statementBuilder.build();
// 将 MappedStatement 对象存储到 Configuration 中的 Map 汇合中,key 为 statement 的 id,value 为 MappedStatement 对象
configuration.addMappedStatement(statement);
return statement;
}
\<mapper> 子标签
1.XMLMapperBuilder# 构造函数
专门用来解析 mapper 映射文件
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
1.1 XPathParser# 构造函数
用来应用 XPath 语法解析 XML 的解析器
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {commonConstructor(validation, variables, entityResolver);
// 解析 XML 文档为 Document 对象
this.document = createDocument(new InputSource(inputStream));
}
1.1.1 XPathParser#createDocument
创立 Mapper 映射文件对应的 Document 对象
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 进行 dtd 或者 Schema 校验
factory.setValidating(validation);
factory.setNamespaceAware(false);
// 设置疏忽正文为 true
factory.setIgnoringComments(true);
// 设置是否疏忽元素内容中的空白
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {throw exception;}
@Override
public void fatalError(SAXParseException exception) throws SAXException {throw exception;}
@Override
public void warning(SAXParseException exception) throws SAXException {}});
// 通过 dom 解析,获取 Document 对象
return builder.parse(inputSource);
} catch (Exception e) {throw new BuilderException("Error creating document instance. Cause:" + e, e);
}
}
1.2 XMLMapperBuilder# 构造函数
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
1.2.1MapperBuilderAssistant# 构造函数
用于构建 MappedStatement 对象的
public MapperBuilderAssistant(Configuration configuration, String resource) {super(configuration);
ErrorContext.instance().resource(resource);
this.resource = resource;
}
2. XMLMapperBuilder#parse
通过 XMLMapperBuilder 解析 mapper 映射文件
public void parse() {
// mapper 映射文件是否曾经加载过
if (!configuration.isResourceLoaded(resource)) {
// 从映射文件中的 <mapper> 根标签开始解析,直到残缺的解析结束
configurationElement(parser.evalNode("/mapper"));
// 标记曾经解析
configuration.addLoadedResource(resource);
bindMapperForNamespace();}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();}
2.1 XMLMapperBuilder#configurationElement
从映射文件中的
根标签开始解析,直到残缺的解析结束
/**
* 解析映射文件
* @param context 映射文件根节点 <mapper> 对应的 XNode
*/
private void configurationElement(XNode context) {
try {
// 获取 <mapper> 标签的 namespace 值,也就是命名空间
String namespace = context.getStringAttribute("namespace");
// 命名空间不能为空
if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置以后的命名空间为 namespace 的值
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> 子标签,也就是 SQL 片段
sqlElement(context.evalNodes("/mapper/sql"));
// 解析 <select>\<insert>\<update>\<delete> 子标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is'" + resource + "'. Cause:" + e, e);
}
}
2.1.1 XMLMapperBuilder#buildStatementFromContext
用来创立 MappedStatement 对象的
//1、构建 MappedStatement
private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());
}
// 构建 MappedStatement
buildStatementFromContext(list, null);
}
//2、专门用来解析 MappedStatement
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {
// MappedStatement 解析器
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析 select 等 4 个标签,创立 MappedStatement 对象
statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);
}
}
}
2.1.1.1 XMLStatementBuilder# 构造函数
专门用来解析 MappedStatement
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {super(configuration);
this.builderAssistant = builderAssistant;
this.context = context;
this.requiredDatabaseId = databaseId;
}
2.1.1.2 XMLStatementBuilder#parseStatementNode
解析
子标签
/**
* 解析 <select>\<insert>\<update>\<delete> 子标签
*/
public void parseStatementNode() {
// 获取 statement 的 id 属性(特地要害的值)String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
// 获取入参类型
String parameterType = context.getStringAttribute("parameterType");
// 别名解决,获取入参对应的 Java 类型
Class<?> parameterTypeClass = resolveClass(parameterType);
// 获取 ResultMap
String resultMap = context.getStringAttribute("resultMap");
// 获取后果映射类型
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 别名解决,获取返回值对应的 Java 类型
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
// 设置默认 StatementType 为 Prepared,该参数指定了前面的 JDBC 解决时,采纳哪种 Statement
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
// 解析 SQL 命令类型是什么?确定操作是 CRUD 中的哪一种
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 是否查问语句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
// <include> 标签解析
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
// 解析 <selectKey> 标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// 创立 SqlSource,解析 SQL,封装 SQL 语句(未参数绑定)和入参信息
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
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;
}
// 通过构建者助手,创立 MappedStatement 对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
2.1.1.2.1 MapperBuilderAssistant#addMappedStatement
通过构建者助手,创立 MappedStatement 对象
public 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) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 利用构建者模式,去创立 MappedStatement.Builder, 用于创立 MappedStatement 对象
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(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);
}
// 通过 MappedStatement.Builder,构建一个 MappedStatement
MappedStatement statement = statementBuilder.build();
// 将 MappedStatement 对象存储到 Configuration 中的 Map 汇合中,key 为 statement 的 id,value 为 MappedStatement 对象
configuration.addMappedStatement(statement);
return statement;
}
2.1.1.2.1.1 MappedStatement.Builder# 构造函数
利用构建者模式,去创立 MappedStatement.Builder, 用于创立 MappedStatement 对象
public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
mappedStatement.configuration = configuration;
mappedStatement.id = id;
mappedStatement.sqlSource = sqlSource;
mappedStatement.statementType = StatementType.PREPARED;
mappedStatement.resultSetType = ResultSetType.DEFAULT;
mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build();
mappedStatement.resultMaps = new ArrayList<>();
mappedStatement.sqlCommandType = sqlCommandType;
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
String logId = id;
if (configuration.getLogPrefix() != null) {logId = configuration.getLogPrefix() + id;
}
mappedStatement.statementLog = LogFactory.getLog(logId);
mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();}
2.1.1.2.1.2 MappedStatement#build
通过 MappedStatement.Builder,构建一个 MappedStatement
public MappedStatement build() {
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
assert mappedStatement.sqlSource != null;
assert mappedStatement.lang != null;
mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
return mappedStatement;
}
6. 源码分析 -SqlSource 创立流程
问题:sql 占位符如何进行的替换?动静 sql 如何进行的解析?
相干类及对象
- XMLLanguageDriver
- XMLScriptBuilder
- SqlSource 接口
- SqlSourceBuilder
- DynamicSqlSource:次要是封装动静 SQL 标签解析之后的 SQL 语句和带有 ${}的 SQL 语句
- RawSqlSource:次要封装带有 #{}的 SQL 语句
- StaticSqlSource:是 BoundSql 中要存储 SQL 语句的一个载体,下面两个 SqlSource 的 SQL 语句,最终都会存储到该 SqlSource 实现类
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = #{ACTIVE}
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
SqlSource 创立流程
入口:XMLLanguageDriver#createSqlSource
创立 SqlSource,解析 SQL,封装 SQL 语句(未参数绑定)和入参信息
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
// 初始化了动静 SQL 标签处理器
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
// 解析动静 SQL
return builder.parseScriptNode();}
XMLScriptBuilder# 构造函数
初始化了动静 SQL 标签处理器
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {super(configuration);
this.context = context;
this.parameterType = parameterType;
// 初始化动静 SQL 中的节点处理器汇合
initNodeHandlerMap();}
1.XMLScriptBuilder#initNodeHandlerMap
初始化动静 SQL 中的节点处理器汇合
private void initNodeHandlerMap() {nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
XMLScriptBuilder#parseScriptNode
解析动静 SQL
public SqlSource parseScriptNode() {
// 解析 select\insert\ update\delete 标签中的 SQL 语句,最终将解析到的 SqlNode 封装到 MixedSqlNode 中的 List 汇合中
// **** 将带有 ${}号的 SQL 信息封装到 TextSqlNode
// **** 将带有 #{}号的 SQL 信息封装到 StaticTextSqlNode
// **** 将动静 SQL 标签中的 SQL 信息别离封装到不同的 SqlNode 中
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
// 如果 SQL 中蕴含 ${}和动静 SQL 语句,则将 SqlNode 封装到 DynamicSqlSource
if (isDynamic) {sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {// 如果 SQL 中蕴含 #{},则将 SqlNode 封装到 RawSqlSource 中,并指定 parameterType
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
1 XMLScriptBuilder#parseDynamicTags
解析 select\insert\ update\delete 标签中的 SQL 语句,最终将解析到的 SqlNode 封装到 MixedSqlNode 中的 List 汇合中。
- 将带有 ${}号的 SQL 信息封装到 TextSqlNode;
- 将带有 #{}号的 SQL 信息封装到 StaticTextSqlNode
- 将动静 SQL 标签中的 SQL 信息别离封装到不同的 SqlNode 中
protected MixedSqlNode parseDynamicTags(XNode node) {List<SqlNode> contents = new ArrayList<>();
// 获取 <select>\<insert> 等 4 个标签的子节点,子节点包含元素节点和文本节点
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("");
// 将文本内容封装到 SqlNode 中
TextSqlNode textSqlNode = new TextSqlNode(data);
// SQL 语句中带有 ${}的话,就示意是 dynamic 的
if (textSqlNode.isDynamic()) {contents.add(textSqlNode);
isDynamic = true;
} else {// SQL 语句中(除了 ${}和上面的动静 SQL 标签),就示意是 static 的
// StaticTextSqlNode 的 apply 只是进行字符串的追加操作
contents.add(new StaticTextSqlNode(data));
}
// 解决元素节点
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
// 动静 SQL 标签处理器
// 思考,此处应用了哪种设计模式?--- 策略模式
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
// 动静 SQL 标签是 dynamic 的
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
2. DynamicSqlSource# 构造函数
如果 SQL 中蕴含 ${}和动静 SQL 语句,则将 SqlNode 封装到 DynamicSqlSource
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
3. RawSqlSource# 构造函数
如果 SQL 中蕴含 #{},则将 SqlNode 封装到 RawSqlSource 中,并指定 parameterType
private final SqlSource sqlSource;
// 先调用 getSql(configuration, rootSqlNode)获取 sql, 再走上面的构造函数
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
// 解析 SQL 语句
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
// 获取入参类型
Class<?> clazz = parameterType == null ? Object.class : parameterType;
// 开始解析
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();}
3.1 SqlSourceBuilder#parse
解析 SQL 语句
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);
// 将解析之后的 SQL 信息,封装到 StaticSqlSource 对象中
// SQL 字符串是带有? 号的字符串,? 相干的参数信息,封装到 ParameterMapping 汇合中
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
3.1.1 ParameterMappingTokenHandler# 构造函数
public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType,Map<String, Object> additionalParameters) {super(configuration);
this.parameterType = parameterType;
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
3.1.2 GenericTokenParser# 构造函数
创立分词解析器,指定待剖析的 openToken 和 closeToken,并指定处理器
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
3.1.3 GenericTokenParser#parse
解析 SQL 语句,解决 openToken 和 closeToken 中的内容
/**
* 解析 ${}和 #{}
* @param text
* @return
*/
public String parse(String text) {if (text == null || text.isEmpty()) {return "";}
// search open token
int start = text.indexOf(openToken, 0);
if (start == -1) {return text;}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();} else {
// found open token. let's search close token.
if (expression == null) {expression = new StringBuilder();
} else {expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {builder.append(src, offset, src.length - offset);
}
return builder.toString();}
#### 3.1.3.1 ParameterMappingTokenHandler#handleToken
解决 token(#{}/${})
@Override
public String handleToken(String content) {parameterMappings.add(buildParameterMapping(content));
return "?";
}
#### 3.1.3.1.1 ParameterMappingTokenHandler#buildParameterMapping
创立 ParameterMapping 对象
private ParameterMapping buildParameterMapping(String content) {Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
Class<?> propertyType;
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {propertyType = parameterType;} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {propertyType = java.sql.ResultSet.class;} else if (property == null || Map.class.isAssignableFrom(parameterType)) {propertyType = Object.class;} else {MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {propertyType = metaClass.getGetterType(property);
} else {propertyType = Object.class;}
}
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {builder.mode(resolveParameterMode(value));
} else if ("numericScale".equals(name)) {builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {typeHandlerAlias = value;} else if ("jdbcTypeName".equals(name)) {builder.jdbcTypeName(value);
} else if ("property".equals(name)) {// Do Nothing} else if ("expression".equals(name)) {throw new BuilderException("Expression based parameters are not supported yet");
} else {
throw new BuilderException("An invalid property'" + name + "'was found in mapping #{" + content
+ "}. Valid properties are" + parameterProperties);
}
}
if (typeHandlerAlias != null) {builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();}
3.1.4 StaticSqlSource# 构造函数
将解析之后的 SQL 信息,封装到 StaticSqlSource
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Configuration configuration;
public StaticSqlSource(Configuration configuration, String sql) {this(configuration, sql, null);
}
public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.configuration = configuration;
}
7. 源码分析 - 揭秘 SqlSession 执行主流程
7.1 相干类与接口
- DefaultSqlSession:SqlSession 接口的默认实现类
- Executor 接口
- BaseExecutor:根底执行器,封装了子类的公共办法,包含一级缓存、提早加载、回滚、敞开等性能;
- SimpleExecutor:简略执行器,每执行一条 sql,都会关上一个 Statement,执行实现后敞开;
- ReuseExecutor:重用执行器,相较于 SimpleExecutor 多了 Statement 的缓存性能,其外部保护一个
Map<String, Statement>
,每次编译实现的 Statement 都会进行缓存,不会敞开; - BatchExecutor:批量执行器,基于 JDBC 的
addBatch、executeBatch
性能,并且在以后 sql 和上一条 sql 齐全一样的时候,重用 Statement,在调用doFlushStatements
的时候,将数据刷新到数据库; - CachingExecutor:缓存执行器,装璜器模式,在开启缓存的时候。会在下面三种执行器的外面包上 CachingExecutor;
- StatementHandler 接口:
- RoutingStatementHandler:路由。Mybatis 理论应用的类,拦挡的 StatementHandler 理论就是它。它会依据 Exector 类型创立对应的 StatementHandler,保留到属性 delegate 中
- PreparedStatementHandler:预编译 Statement
- ResultSetHandler 接口:解决 Statement 执行后产生的后果集,生成后果列表;解决存储过程执行后的输入参数
- DefaultResultSetHandler:ResultSetHandler 的 默认实现类
7.2 流程剖析
入口:DefaultSqlSession#selectList
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 依据传入的 statementId,获取 MappedStatement 对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 调用执行器的查询方法
// RowBounds 是用来逻辑分页(依照条件将数据从数据库查问到内存中,在内存中进行分页)// wrapCollection(parameter)是用来装璜汇合或者数组参数
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();}
}
1. CachingExecutor#query
Configuration 中 cacheEnabled 属性值默认为 true
// 第一步
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取绑定的 SQL 语句,比方“SELECT * FROM user WHERE id = ?”BoundSql boundSql = ms.getBoundSql(parameterObject);
// 生成缓存 Key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// 第二步
@Override
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) {
// 当为 select 语句时,flushCache 默认为 false,示意任何时候语句被调用,都不会去清空本地缓存和二级缓存
// 当为 insert、update、delete 语句时,useCache 默认为 true,示意会将本条语句的后果进行二级缓存
// 刷新二级缓存(存在缓存且 flushCache 为 true 时)flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);
// 从二级缓存中查问数据
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
// 如果二级缓存中没有查问到数据,则查询数据库
if (list == null) {
// 委托给 BaseExecutor 执行
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 委托给 BaseExecutor 执行
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
2. BaseExecutor#query
二级缓存设置开启且缓存中没有或者未开启二级缓存,则从一级缓存中查找后果集
@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();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();}
}
return list;
}
3. BaseExecutor#queryFromDatabase
如果一级缓存没有数据,则从数据库查问数据
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);
if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);
}
return list;
}
4. SimpleExecutor#doQuery
- BaseStatementHandler:根底语句处理器(抽象类),它根本把语句处理器接口的外围局部都实现了,包含配置绑定、执行器绑定、映射器绑定、参数处理器构建、后果集处理器构建、语句超时设置、语句敞开等,并另外定义了新的办法 instantiateStatement 供不同子类实现以便获取不同类型的语句连贯,子类能够一般执行 SQL 语句,也能够做预编译执行,还能够执行存储过程等。
- SimpleStatementHandler:一般语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.Statement 对象的解决,解决一般的不带动静参数运行的 SQL,即执行简略拼接的字符串语句,同时因为 Statement 的个性,SimpleStatementHandler 每次执行都须要编译 SQL(留神:咱们晓得 SQL 的执行是须要编译和解析的)。
- PreparedStatementHandler:预编译语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.PrepareStatement 对象的解决,相比下面的一般语句处理器,它反对可变参数 SQL 执行,因为 PrepareStatement 的个性,它会进行预编译,在缓存中一旦发现有预编译的命令,会间接解析执行,所以缩小了再次编译环节,可能无效进步零碎性能,并预防 SQL 注入攻打(所以是零碎默认也是咱们举荐的语句处理器)。
- CallableStatementHandler:存储过程处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.CallableStatement 对象的解决,很明了,它是用来调用存储过程的,减少了存储过程的函数调用以及输入 / 输出参数的解决反对。
- RoutingStatementHandler:路由语句处理器,间接实现了 StatementHandler 接口,作用如其名称,确确实实只是起到了路由性能,并把下面介绍到的三个语句处理器实例作为本身的委托对象而已,所以执行器在构建语句处理器时,都是间接 new 了 RoutingStatementHandler 实例。
执行查问
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 获取 Configuration 对象
Configuration configuration = ms.getConfiguration();
// 创立 RoutingStatementHandler,用来解决 Statement
// RoutingStatementHandler 类中初始化 delegate 类(SimpleStatementHandler、PreparedStatementHandler)StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds,
resultHandler, boundSql);
// 子流程 1:设置参数
stmt = prepareStatement(handler, ms.getStatementLog());
// 子流程 2:执行 SQL 语句(曾经设置过参数),并且映射后果集
return handler.query(stmt, resultHandler);
} finally {closeStatement(stmt);
}
}
4.1 Configuration#newStatementHandler
创立 StatementHandler,用来执行 MappedStatement 对象
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创立路由性能的 StatementHandler,依据 MappedStatement 中的 StatementType
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
4.1.1 RoutingStatementHandler# 构造函数
创立路由性能的 StatementHandler,依据 MappedStatement 中的 StatementType
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());
}
}
4.2 SimpleExecutor#prepareStatement
设置参数
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取连贯
Connection connection = getConnection(statementLog);
// 创立 Statement(PreparedStatement、Statement、CallableStatement)stmt = handler.prepare(connection, transaction.getTimeout());
// SQL 参数设置
handler.parameterize(stmt);
return stmt;
}
4.2.1 BaseExecutor#getConnection
获取数据库连贯
protected Connection getConnection(Log statementLog) throws SQLException {Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {return connection;}
}
4.2.2 BaseStatementHandler#prepare
创立 Statement(PreparedStatement、Statement、CallableStatement)
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 实例化 Statement,比方 PreparedStatement
statement = instantiateStatement(connection);
// 设置查问超时工夫
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {closeStatement(statement);
throw e;
} catch (Exception e) {closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause:" + e, e);
}
}
4.2.2.1 PreparedStatementHandler#instantiateStatement
实例化 PreparedStatement
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 获取带有占位符的 SQL 语句
String sql = boundSql.getSql();
// 解决带有主键返回的 SQL
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {return connection.prepareStatement(sql);
} else {return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
4.2.3 PreparedStatementHandler#parameterize
SQL 参数设置, 参数映射流程会具体合成
@Override
public void parameterize(Statement statement) throws SQLException {
// 通过 ParameterHandler 解决参数
parameterHandler.setParameters((PreparedStatement) statement);
}
4.3 PreparedStatementHandler#query
执行 SQL 语句(曾经设置过参数),并且映射后果集
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;
// 执行 PreparedStatement,也就是执行 SQL 语句
ps.execute();
// 处理结果集
return resultSetHandler.handleResultSets(ps);
}
4.3.1 PreparedStatement#execute
调用 JDBC 的 api 执行 Statement
4.3.2 DefaultResultSetHandler#handleResultSets
处理结果集 , 后果映射流程会具体合成
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// <select> 标签的 resultMap 属性,能够指定多个值,多个值之间用逗号(,)宰割
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 这里是获取第一个后果集,将传统 JDBC 的 ResultSet 包装成一个蕴含后果列元信息的 ResultSetWrapper 对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 这里是获取所有要映射的 ResultMap(依照逗号宰割进去的)List<ResultMap> resultMaps = mappedStatement.getResultMaps();
// 要映射的 ResultMap 的数量
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
// 循环解决每个 ResultMap,从第一个开始解决
while (rsw != null && resultMapCount > resultSetCount) {
// 失去后果映射信息
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理结果集
// 从 rsw 后果集参数中获取查问后果,再依据 resultMap 映射信息,将查问后果映射到 multipleResults 中
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 对应 <select> 标签的 resultSets 属性,个别不应用该属性
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {while (rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
// 如果只有一个后果汇合,则间接从多后果集中取出第一个
return collapseSingleResultList(multipleResults);
}
四、总结
执行 sqlsession:参数有两个(statementId 和参数对象)
- 依据 statementId,去 Configuration 中的 MappedStatement 汇合中查找
对应的 MappedStatement 对象; - 取出 MappedStatement 中的 SQL 信息;
-
取出 MappedStatement 中的 statementType,用来创立 Statement 对象;
- 取出 MappedStatement 中的 Configuration 对象,通过 Configuration 对象,获取 DataSource 对象,通过 DataSource 对象,创立 Connection,通过 Connection 创立 Statement 对象。
- 设置参数
- 执行 Statement
- 处理结果集
8. 源码分析 - 揭秘如何设置的参数?
入口:PreparedStatementHandler#parameterize 办法
设置 PreparedStatement 的参数
@Override
public void parameterize(Statement statement) throws SQLException {
// 通过 ParameterHandler 解决参数
parameterHandler.setParameters((PreparedStatement) statement);
}
DefaultParameterHandler#setParameters
设置参数
@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)) { // issue #448 ask first for additional params
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 jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 给 PreparedStatement 设置参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping:" + parameterMapping + ". Cause:" + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping:" + parameterMapping + ". Cause:" + e, e);
}
}
}
}
}
BaseTypeHandler#setParameter
给 PreparedStatement 设置参数
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {if (parameter == null) {if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + "with JdbcType" + jdbcType + "."
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property."
+ "Cause:" + e, e);
}
} else {
try {
// 通过 PreparedStatement 的 API 去设置非空参数
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + "with JdbcType" + jdbcType
+ "."
+ "Try setting a different JdbcType for this parameter or a different configuration property."
+ "Cause:" + e, e);
}
}
}
xxxTypeHandler#setNonNullParameter
通过 PreparedStatement 的 API 去设置非空参数
例如:ArrayTypeHandler
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {ps.setArray(i, (Array) parameter);
}
9. 源码分析 - 后果集映射流程
入口:DefaultResultSetHandler#handleResultSets
从 rsw 后果集参数中获取查问后果,再依据 resultMap 映射信息,将查问后果映射到 multipleResults 中
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// <select> 标签的 resultMap 属性,能够指定多个值,多个值之间用逗号(,)宰割
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 这里是获取第一个后果集,将传统 JDBC 的 ResultSet 包装成一个蕴含后果列元信息的 ResultSetWrapper 对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 这里是获取所有要映射的 ResultMap(依照逗号宰割进去的)List<ResultMap> resultMaps = mappedStatement.getResultMaps();
// 要映射的 ResultMap 的数量
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
// 循环解决每个 ResultMap,从第一个开始解决
while (rsw != null && resultMapCount > resultSetCount) {
// 失去后果映射信息
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理结果集
// 从 rsw 后果集参数中获取查问后果,再依据 resultMap 映射信息,将查问后果映射到 multipleResults 中
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 对应 <select> 标签的 resultSets 属性,个别不应用该属性
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {while (rsw != null && resultSetCount < resultSets.length) {ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
// 如果只有一个后果汇合,则间接从多后果集中取出第一个
return collapseSingleResultList(multipleResults);
}
DefaultResultSetHandler#handleRowValues
解决行数据,其实就是实现后果映射
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 是否有内置嵌套的后果映射
if (resultMap.hasNestedResultMaps()) {ensureNoRowBounds();
checkResultHandler();
// 嵌套后果映射
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 简略后果映射
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
DefaultResultSetHandler#handleRowValuesForSimpleResultMap
简略后果映射
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
// 获取后果集信息
ResultSet resultSet = rsw.getResultSet();
// 应用 rowBounds 的分页信息,进行逻辑分页(也就是在内存中分页)skipRows(resultSet, rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 通过 <resultMap> 标签的子标签 <discriminator> 对后果映射进行甄别
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 将查问后果封装到 POJO 中
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 解决对象嵌套的映射关系
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
1. DefaultResultSetHandler#getRowValue
将查问后果封装到 POJO 中
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
// 提早加载的映射信息
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 创立要映射的 PO 类对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 是否利用主动映射,也就是通过 resultType 进行映射
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 依据 columnName 和 type 属性名映射赋值
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 依据咱们配置 ResultMap 的 column 和 property 映射赋值
// 如果映射存在 nestedQueryId,会调用 getNestedQueryMappingValue 办法获取返回值
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}
return rowValue;
}
1.1 DefaultResultSetHandler#createResultObject
创立映射后果对象
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
final List<Class<?>> constructorArgTypes = new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
// 创立后果映射的 PO 类对象
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
// 获取要映射的 PO 类的属性信息
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
// 提早加载解决
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
// 通过动静代理工厂,创立提早加载的代理对象
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping
// result
return resultObject;
}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
List<Object> constructorArgs, String columnPrefix) throws SQLException {final Class<?> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
if (hasTypeHandlerForResultObject(rsw, resultType)) {return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
} else if (!constructorMappings.isEmpty()) {
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes,
constructorArgs, columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
// 对象工厂创建对象
return objectFactory.create(resultType);
} else if (shouldApplyAutomaticMappings(resultMap, false)) {return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
}
throw new ExecutorException("Do not know how to create an instance of" + resultType);
}
1.2 DefaultResultSetHandler#applyAutomaticMappings
依据 columnName 和 type 属性名映射赋值
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
String columnPrefix) throws SQLException {List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {for (UnMappedColumnAutoMapping mapping : autoMapping) {final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {foundValues = true;}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
1.3 DefaultResultSetHandler#applyPropertyMappings
依据咱们配置 ResultMap 的 column 和 property 映射赋值, 如果映射存在 nestedQueryId,会调用 getNestedQueryMappingValue 办法获取返回值
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader,
columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {continue;} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {foundValues = true;}
if (value != null || (configuration.isCallSettersOnNulls()
&& !metaObject.getSetterType(property).isPrimitive())) {// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
10. 源码分析 - 获取 Mapper 代理对象流程
入口:DefaultSqlSession#getMapper
从 Configuration 对象中,依据 Mapper 接口,获取 Mapper 代理对象
@Override
public <T> T getMapper(Class<T> type) {
// 从 Configuration 对象中,依据 Mapper 接口,获取 Mapper 代理对象
return configuration.<T>getMapper(type, this);
}
Configuration#getMapper
获取 Mapper 代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);
}
1. MapperRegistry#getMapper
通过代理对象工厂,获取代理对象:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 依据 Mapper 接口的类型,从 Map 汇合中获取 Mapper 代理对象工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {throw new BindingException("Type" + type + "is not known to the MapperRegistry.");
}
try {
// 通过 MapperProxyFactory 生产 MapperProxy,通过 MapperProxy 产生 Mapper 代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause:" + e, e);
}
}
1.1 MapperProxyFactory#newInstance
调用 JDK 的动静代理形式,创立 Mapper 代理
//1
protected T newInstance(MapperProxy<T> mapperProxy) {
// 应用 JDK 动静代理形式,生成代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface}, mapperProxy);
}
//2
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 依据 Mapper 接口的类型,从 Map 汇合中获取 Mapper 代理对象工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {throw new BindingException("Type" + type + "is not known to the MapperRegistry.");
}
try {
// 通过 MapperProxyFactory 生产 MapperProxy,通过 MapperProxy 产生 Mapper 代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause:" + e, e);
}
}
11. 源码分析 -invoke 办法
// 通过 JDK 动静代理生成并获取代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 代理对象对象调用办法,底层执行 invoke 办法
List<User> allUser = userMapper.findAllUser();
在动静代理返回了示例后,咱们就能够间接调用 mapper 类中的办法了,但 代理对象调用办法,执行是在 MapperProxy 中的 invoke 办法 ,该类实现InvocationHandler 接口,并重写 invoke()办法。
问题:invoke 办法执行逻辑是什么?
入口:MapperProxy#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是 Object 定义的办法,间接调用
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);
}
// 取得 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 重点在这:MapperMethod 最终调用了执行的办法
return mapperMethod.execute(sqlSession, args);
}
MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 判断 mapper 中的办法类型,最终调用的还是 SqlSession 中的办法
switch (command.getType()) {
case INSERT: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行 INSERT 操作
// 转换 rowCount
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 转换 rowCount
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 转换 rowCount
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// 无返回,并且有 ResultHandler 办法参数,则将查问的后果,提交给 ResultHandler 进行解决
if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);
result = null;
// 执行查问,返回列表
} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);
// 执行查问,返回 Map
} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);
// 执行查问,返回 Cursor
} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);
// 执行查问,返回单个对象
} else {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 查问单条
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for:" + command.getName());
}
// 返回后果为 null,并且返回类型为根本类型,则抛出 BindingException 异样
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method'" + command.getName()
+ "attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
// 返回后果
return result;
}
12. 源码分析 - 插件机制
12.1 插件概述
- 问题:什么是 Mybatis 插件?有什么作用?
个别开源框架都会提供扩大点,让开发者自行扩大,从而实现逻辑的加强。
基于插件机制能够实现了很多有用的性能,比如说分页,字段加密,监控等性能,这种通用的性能,就如同 AOP 一样,横切在数据操作上
而通过 Mybatis 插件能够实现对框架的扩大,来实现自定义性能,并且对于用户是无感知的。
12.2 Mybatis 插件介绍
Mybatis 插件实质上来说就是一个拦截器,它体现了JDK 动静代理和责任链设计模式的综合使用
Mybatis 中针对四大组件提供了扩大机制,这四个组件别离是:
Mybatis 中所容许拦挡的办法如下:
- Executor【SQL 执行器】【update,query,commit,rollback】
- StatementHandler【Sql 语法构建器对象】【prepare,parameterize,batch,update,query 等】
- ParameterHandler【参数处理器】【getParameterObject,setParameters 等】
- ResultSetHandler【后果集处理器】【handleResultSets,handleOuputParameters 等】
能干什么?
- 分页性能:mybatis 的分页默认是基于内存分页的(查出所有,再截取),数据量大的状况下效率较低,不过应用 mybatis 插件能够扭转该行为,只须要拦挡 StatementHandler 类的 prepare 办法,扭转要执行的 SQL 语句为分页语句即可
- 性能监控:对于 SQL 语句执行的性能监控,能够通过拦挡 Executor 类的 update, query 等办法,用日志记录每个办法执行的工夫
如何自定义插件?
在应用之前,咱们先来看看 Mybatis 提供的插件相干的类,过一遍它们别离提供了哪些性能,最初咱们本人定义一个插件
用于定义插件的类
后面曾经晓得 Mybatis 插件是能够对 Mybatis 中四大组件对象的办法进行拦挡,那拦截器拦挡哪个类的哪个办法如何晓得,就由上面这个注解提供拦挡信息
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {Signature[] value();}
因为一个拦截器能够同时拦挡多个对象的多个办法,所以就应用了 Signture
数组,该注解定义了拦挡的残缺信息
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
// 拦挡的类
Class<?> type();
// 拦挡的办法
String method();
// 拦挡办法的参数
Class<?>[] args();
}
曾经晓得了该拦挡哪些对象的哪些办法,拦挡后要干什么就须要实现 Intercetor#intercept
办法,在这个办法外面实现拦挡后的解决逻辑
public interface Interceptor {
/**
* 真正办法被拦挡执行的逻辑
*
* @param invocation 次要目标是将多个参数进行封装
*/
Object intercept(Invocation invocation) throws Throwable;
// 生成指标对象的代理对象
default Object plugin(Object target) {return Plugin.wrap(target, this);
}
// 能够拦截器设置一些属性
default void setProperties(Properties properties) {// NOP}
}
12.3 自定义插件
需要:把 Mybatis 所有执行的 sql 都记录下来
步骤:① 创立 Interceptor 的实现类,重写办法
② 应用 @Intercepts 注解实现插件签名 阐明插件的拦挡四大对象之一的哪一个对象的哪一个办法
③ 将写好的插件注册到全局配置文件中
①. 创立 Interceptor 的实现类
public class MyPlugin implements Interceptor {private final Logger logger = LoggerFactory.getLogger(this.getClass());
// // 这里是每次执行操作的时候,都会进行这个拦截器的办法内
Override
public Object intercept(Invocation invocation) throws Throwable {
// 加强逻辑
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
logger.info("mybatis intercept sql:{}", sql);
return invocation.proceed(); // 执行原办法}
/**
*
* ^Description 包装指标对象 为指标对象创立代理对象
* @Param target 为要拦挡的对象
* @Return 代理对象
*/
Override
public Object plugin(Object target) {System.out.println("将要包装的指标对象:"+target);
return Plugin.wrap(target,this);
}
/** 获取配置文件的属性 **/
// 插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来
Override
public void setProperties(Properties properties) {System.out.println("插件配置的初始化参数:"+properties);
}
}
② 应用 @Intercepts 注解实现插件签名 阐明插件的拦挡四大对象之一的哪一个对象的哪一个办法
@Intercepts({ @Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}) })
public class SQLStatsInterceptor implements Interceptor {
③ 将写好的插件注册到全局配置文件中
<?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>
<plugins>
<plugin interceptor="com.itheima.interceptor.MyPlugin">
<property name="dialect" value="mysql" />
</plugin>
</plugins>
</configuration>
核心思想:
就是应用 JDK 动静代理的形式,对这四个对象进行包装加强。具体的做法是,创立一个类实现 Mybatis 的拦截器接口,并且退出到拦截器链中,在创立外围对象的时候,不间接返回,而是遍历拦截器链,把每一个拦截器都作用于外围对象中。这么一来,Mybatis 创立的外围对象其实都是代理对象,都是被包装过的。
12.4 源码剖析 - 插件
- 插件的初始化:插件对象是如何实例化的?插件的实例对象如何增加到拦截器链中的?组件对象的代理对象是如何产生的?
- 拦挡逻辑的执行
插件配置信息的加载
咱们定义好了一个拦截器,那咱们怎么通知 Mybatis 呢?Mybatis 所有的配置都定义在 XXx.xml 配置文件中
<?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>
<plugins>
<plugin interceptor="com.itheima.interceptor.MyPlugin">
<property name="dialect" value="mysql" />
</plugin>
</plugins>
</configuration>
对应的解析代码如下(XMLConfigBuilder#pluginElement):
private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {
// 获取拦截器
String interceptor = child.getStringAttribute("interceptor");
// 获取配置的 Properties 属性
Properties properties = child.getChildrenAsProperties();
// 依据配置文件中配置的插件类的全限定名 进行反射初始化
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
// 将属性增加到 Intercepetor 对象
interceptorInstance.setProperties(properties);
// 增加到配置类的 InterceptorChain 属性,InterceptorChain 类保护了一个 List<Interceptor>
configuration.addInterceptor(interceptorInstance);
}
}
}
次要做了以下工作:
- 遍历解析 plugins 标签下每个 plugin 标签
- 依据解析的类信息创立 Interceptor 对象
- 调用 setProperties 办法设置属性
- 将拦截器增加到 Configuration 类的 IntercrptorChain 拦截器链中
对应时序图如下:
代理对象的生成
Executor 代理对象(Configuration#newExecutor)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);
} else {executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {executor = new CachingExecutor(executor);
}
// 生成 Executor 代理对象逻辑
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
ParameterHandler 代理对象(Configuration#newParameterHandler)
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 生成 ParameterHandler 代理对象逻辑
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
ResultSetHandler 代理对象(Configuration#newResultSetHandler)
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 生成 ResultSetHandler 代理对象逻辑
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
StatementHandler 代理对象(Configuration#newStatementHandler)
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 = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
通过查看源码会发现,所有代理对象的生成都是通过 InterceptorChain#pluginAll
办法来创立的,进一步查看 pluginAll 办法
public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);
}
return target;
}
InterceptorChain#pluginAll 外部通过遍历 Interceptor#plugin 办法来创立代理对象,并将生成的代理对象又赋值给 target, 如果存在多个拦截器的话,生成的代理对象会被另一个代理对象所代理,从而造成一个代理链,执行的时候,顺次执行所有拦截器的拦挡逻辑代码,咱们再跟进去
default Object plugin(Object target) {return Plugin.wrap(target, this);
}
Interceptor#plugin
办法最终将指标对象和以后的拦截器交给 Plugin.wrap
办法来创立代理对象。该办法是默认办法,是 Mybatis 框架提供的一个典型 plugin 办法的实现。让咱们看看在 Plugin#wrap
办法中是如何实现代理对象的
public static Object wrap(Object target, Interceptor interceptor) {
// 1. 解析该拦截器所拦挡的所有接口及对应拦挡接口的办法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 2. 获取指标对象实现的所有被拦挡的接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 3. 指标对象有实现被拦挡的接口,生成代理对象并返回
if (interfaces.length > 0) {
// 通过 JDK 动静代理的形式实现
return Proxy.newProxyInstance(type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
// 指标对象没有实现被拦挡的接口,间接返回原对象
return target;
}
最终咱们看到其实是通过 JDK 提供的 Proxy.newProxyInstance 办法来生成代理对象
以上代理对象生成过程的时序图如下:
拦挡逻辑的执行
通过下面的剖析,咱们晓得 Mybatis 框架中执行 Executor、ParameterHandler、ResultSetHandler 和 StatementHandler 中的办法时真正执行的是代理对象对应的办法。而且该代理对象是通过 JDK 动静代理生成的,所以执行办法时实际上是调用 InvocationHandler#invoke 办法(Plugin 类实现 InvocationHandler 接口), 上面是 Plugin#invoke 办法
@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);
}
}
注:一个对象被代理很屡次
问题:同一个组件对象的同一个办法是否能够被多个拦截器进行拦挡?
答案是必定的,所以咱们 配置在最后面的拦截器最先被代理,然而执行的时候却是最外层的先执行。
具体点:
如果顺次定义了三个插件:插件 1
,插件 2 和 插件 3
。
那么 List 中就会按顺序存储:插件 1
,插件 2
和 插件 3
。
而解析的时候是遍历 list,所以解析的时候也是依照:插件 1
,插件 2
,插件 3
的程序。
然而执行的时候就要反过来了,执行的时候是依照:插件 3
,插件 2
和 插件 1
的程序进行执行。
当 Executor 的某个办法被调用的时候,插件逻辑会后行执行。执行程序由外而内,比方上图的执行程序为 plugin3 → plugin2 → Plugin1 → Executor
。
13. 源码分析 - 缓存策略
缓存就是内存中的数据,经常来自对数据库查问后果的保留。应用缓存,咱们能够防止频繁的与数据库进行交互,进而进步响应速度 MyBatis 也提供了对缓存的反对,分为一级缓存和二级缓存,能够通过下图来了解:
①、一级缓存是 SqlSession 级别的缓存。在操作数据库时须要结构 sqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的 sqlSession 之间的缓存数据区域(HashMap)是相互不影响的。
②、二级缓存是 mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 能够共用二级缓存,二级缓存是跨 SqlSession 的
一级缓存
默认是开启的
①、咱们应用同一个 sqlSession,对 User 表依据雷同 id 进行两次查问,查看他们收回 sql 语句的状况
@Test
public void firstLevelCacheTest() throws IOException {
// 1. 通过类加载器对配置文件进行加载,加载成了字节输出流,存到内存中 留神:配置文件并没有被解析
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2. (1)解析了配置文件,封装 configuration 对象(2)创立了 DefaultSqlSessionFactory 工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 问题:openSession()执行逻辑是什么?// 3. (1)创立事务对象(2)创立了执行器对象 cachingExecutor (3)创立了 DefaultSqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 委派给 Executor 来执行,Executor 执行时又会调用很多其余组件(参数设置、解析 sql 的获取,sql 的执行、后果集的封装)User user = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);
User user2 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);
System.out.println(user == user2);
sqlSession.close();}
查看控制台打印状况:
② 同样是对 user 表进行两次查问,只不过两次查问之间进行了一次 update 操作。
@Test
public void test3() throws IOException {
// 1. 通过类加载器对配置文件进行加载,加载成了字节输出流,存到内存中 留神:配置文件并没有被解析
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2. (1)解析了配置文件,封装 configuration 对象(2)创立了 DefaultSqlSessionFactory 工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 问题:openSession()执行逻辑是什么?// 3. (1)创立事务对象(2)创立了执行器对象 cachingExecutor (3)创立了 DefaultSqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 委派给 Executor 来执行,Executor 执行时又会调用很多其余组件(参数设置、解析 sql 的获取,sql 的执行、后果集的封装)User user = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);
User user1 = new User();
user1.setId(1);
user1.setUsername("zimu");
sqlSession.update("com.itheima.mapper.UserMapper.updateUser",user1);
sqlSession.commit();
User user2 = sqlSession.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);
System.out.println(user == user2);
System.out.println(user);
System.out.println(user2);
System.out.println("MyBatis 源码环境搭建胜利....");
sqlSession.close();}
查看控制台打印状况:
③、总结
1、第一次发动查问用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从 数据库查问用户信息。失去用户信息,将用户信息存储到一级缓存中。
2、如果两头 sqlSession 去执行 commit 操作(执行插入、更新、删除),则会清空 SqlSession 中的 一级缓存,这样做的目标为了让缓存中存储的是最新的信息,防止脏读。
3、第二次发动查问用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直 接从缓存中获取用户信息
一级缓存原理探索与源码剖析
问题 1:一级缓存 底层数据结构到底是什么?
问题 2:一级缓存的工作流程是怎么的?
1. 一级缓存 底层数据结构到底是什么?
之前说 不同 SqlSession 的一级缓存互不影响
, 所以我从 SqlSession 这个类动手
能够看到,org.apache.ibatis.session.SqlSession
中有一个和缓存无关的办法——clearCache()
刷新缓存的办法,点进去,找到它的实现类DefaultSqlSession
@Override
public void clearCache() {executor.clearLocalCache();
}
再次点进去executor.clearLocalCache()
, 再次点进去并找到其实现类BaseExecutor
,
@Override
public void clearLocalCache() {if (!closed) {localCache.clear();
localOutputParameterCache.clear();}
进入 localCache.clear()
办法。进入到了 org.apache.ibatis.cache.impl.PerpetualCache
类中
package org.apache.ibatis.cache.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
/**
* @author Clinton Begin
*/
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {this.id = id;}
// 省略局部...
@Override
public void clear() {cache.clear();
}
// 省略局部...
}
咱们看到了 PerpetualCache
类中有一个属性 private Map<Object, Object> cache = new HashMap<Object, Object>()
,很显著它是一个 HashMap,咱们所调用的.clear()
办法,实际上就是调用的 Map 的 clear 办法
得出结论:
一级缓存的数据结构的确是 HashMap
2. 一级缓存的执行流程
咱们进入到 org.apache.ibatis.executor.Executor
中
看到一个办法 CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql)
,见名思意是一个创立 CacheKey 的办法
找到它的实现类和办法org.apache.ibatis.executor.BaseExecuto.createCacheKey
咱们剖析一下创立 CacheKey 的这块代码:
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {if (closed) {throw new ExecutorException("Executor was closed.");
}
// 初始化 CacheKey
CacheKey cacheKey = new CacheKey();
// 存入 statementId
cacheKey.update(ms.getId());
// 别离存入分页须要的 Offset 和 Limit
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
// 把从 BoundSql 中封装的 sql 取出并存入到 cacheKey 对象中
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);
}
}
// 从 configuration 对象中(也就是载入配置文件后寄存的对象)把 EnvironmentId 存入
/**
* <environments default="development">
* <environment id="development"> // 就是这个 id
* <!-- 以后事务交由 JDBC 进行治理 -->
* <transactionManager type="JDBC"></transactionManager>
* <!-- 以后应用 mybatis 提供的连接池 -->
* <dataSource type="POOLED">
* <property name="driver" value="${jdbc.driver}"/>
* <property name="url" value="${jdbc.url}"/>
* <property name="username" value="${jdbc.username}"/>
* <property name="password" value="${jdbc.password}"/>
* </dataSource>
* </environment>
* </environments>
*/
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
// 返回
return cacheKey;
}
咱们再点进去 cacheKey.update()
办法看一看
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;
private final int multiplier;
private int hashcode;
private long checksum;
private int count;
// 值存入的中央
private transient List<Object> updateList;
// 省略局部办法......
// 省略局部办法......
public void update(Object object) {int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
// 看到把值传入到了一个 list 中
updateList.add(object);
}
// 省略局部办法......
}
咱们晓得了那些数据是在 CacheKey 对象中如何存储的了。上面咱们返回 createCacheKey()
办法。
咱们进入 BaseExecutor
,能够看到一个query()
办法:
这里咱们很分明的看到,在执行 query()
办法前,CacheKey
办法被创立了
咱们能够看到,创立 CacheKey 后调用了 query()办法,咱们再次点进去:
在执行 SQL 前如何在一级缓存中找不到 Key,那么将会执行 sql,咱们来看一下执行 sql 前后会做些什么,进入list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
剖析一下:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//1. 把 key 存入缓存,value 放一个占位符
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//2. 与数据库交互
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//3. 如果第 2 步出了什么异样,把第 1 步存入的 key 删除
localCache.removeObject(key);
}
//4. 把后果存入缓存
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);
}
return list;
}
一级缓存源码剖析论断:
- 一级缓存的数据结构是一个
HashMap<Object,Object>
,它的 value 就是查问后果,它的 key 是CacheKey
,CacheKey
中有一个 list 属性,statementId,params,rowbounds,sql
等参数都存入到了这个 list 中 - 先创立
CacheKey
,会首先依据CacheKey
查问缓存中有没有,如果有,就解决缓存中的参数,如果没有,就执行 sql,执行 sql 后执行 sql 后把后果存入缓存
二级缓存
留神:Mybatis 的二级缓存不是默认开启的,是须要通过配置能力应用的
启用二级缓存
分为三步走:
1)开启映射器配置文件中的缓存配置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 在须要应用二级缓存的 Mapper 配置文件中配置标签
<!--type:cache 应用的类型,默认是 PerpetualCache,这在一级缓存中提到过。eviction:定义回收的策略,常见的有 FIFO,LRU。flushInterval:配置肯定工夫主动刷新缓存,单位是毫秒。size:最多缓存对象的个数。readOnly:是否只读,若配置可读写,则须要对应的实体类可能序列化。blocking:若缓存中找不到对应的 key,是否会始终 blocking,直到有对应的数据进入缓存。-->
<cache></cache>
3)在具体 CURD 标签上配置 useCache=true
<select id="findById" resultType="com.itheima.pojo.User" useCache="true">
select * from user where id = #{id}
</select>
** 留神:实体类要实现 Serializable 接口,因为二级缓存会将对象写进硬盘,就必须序列化,以及兼容对象在网络中的传输
具体实现
/**
* 测试一级缓存
*/
@Test
public void secondLevelCacheTest() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2. (1)解析了配置文件,封装 configuration 对象(2)创立了 DefaultSqlSessionFactory 工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 问题:openSession()执行逻辑是什么?// 3. (1)创立事务对象(2)创立了执行器对象 cachingExecutor (3)创立了 DefaultSqlSession 对象
SqlSession sqlSession1 = sqlSessionFactory.openSession();
// 发动第一次查问,查问 ID 为 1 的用户
User user1 = sqlSession1.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);
// *** 必须调用 sqlSession1.commit()或者 close(), 一级缓存中的内容才会刷新到二级缓存中
sqlSession1.commit();// close();
// 发动第二次查问,查问 ID 为 1 的用户
SqlSession sqlSession2 = sqlSessionFactory.openSession();
User user2 = sqlSession2.selectOne("com.itheima.mapper.UserMapper.findByCondition", 1);
System.out.println(user1 == user2);
System.out.println(user1);
System.out.println(user2);
sqlSession1.close();
sqlSession2.close();}
二级缓存源码剖析
问题:
① cache 标签如何被解析的(二级缓存的底层数据结构是什么?)?
② 同时开启一级缓存二级缓存,优先级?
③ 为什么只有执行 sqlSession.commit 或者 sqlSession.close 二级缓存才会失效
④ 更新办法为什么不会清空二级缓存?
标签 < cache/> 的解析
二级缓存和具体的命名空间绑定,一个 Mapper 中有一个 Cache, 雷同 Mapper 中的 MappedStatement 共用同一个 Cache
依据之前的 mybatis 源码分析,xml 的解析工作次要交给 XMLConfigBuilder.parse()办法来实现
// XMLConfigBuilder.parse()
public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));// 在这里
return configuration;
}
// parseConfiguration()
// 既然是在 xml 中增加的,那么咱们就间接看对于 mappers 标签的解析
private void parseConfiguration(XNode root) {
try {Properties settings = settingsAsPropertiess(root.evalNode("settings"));
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
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);
}
}
// mapperElement()
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 判断
if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 生成 XMLMapperBuilder,并执行其 parse 办法
mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);
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 new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
咱们来看看解析 Mapper.xml
// XMLMapperBuilder.parse()
public void parse() {if (!configuration.isResourceLoaded(resource)) {
// 解析 mapper 属性
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();}
// configurationElement()
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"));
// 最终在这里看到了对于 cache 属性的解决
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// 这里会将生成的 Cache 包装到对应的 MappedStatement
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. Cause:" + e, e);
}
}
// cacheElement()
private void cacheElement(XNode context) throws Exception {if (context != null) {
// 解析 <cache/> 标签的 type 属性,这里咱们能够自定义 cache 的实现类,比方 redisCache,如果没有自定义,这里应用和一级缓存雷同的 PERPETUAL
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);
Properties props = context.getChildrenAsProperties();
// 构建 Cache 对象
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
先来看看是如何构建 Cache 对象的
MapperBuilderAssistant.useNewCache()
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
// 1. 生成 Cache 对象
Cache cache = new CacheBuilder(currentNamespace)
// 这里如果咱们定义了 <cache/> 中的 type,就应用自定义的 Cache, 否则应用和一级缓存雷同的 PerpetualCache
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
// 2. 增加到 Configuration 中
configuration.addCache(cache);
// 3. 并将 cache 赋值给 MapperBuilderAssistant.currentCache
currentCache = cache;
return cache;
}
咱们看到一个 Mapper.xml 只会解析一次
标签,也就是只创立一次 Cache 对象,放进 configuration 中,并将 cache 赋值给 MapperBuilderAssistant.currentCache
buildStatementFromContext(context.evalNodes(“select|insert|update|delete”)); 将 Cache 包装到 MappedStatement
// buildStatementFromContext()
private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//buildStatementFromContext()
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 每一条执行语句转换成一个 MappedStatement
statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);
}
}
}
// XMLStatementBuilder.parseStatementNode();
public void parseStatementNode() {String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
...
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
...
// 创立 MappedStatement 对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
// builderAssistant.addMappedStatement()
public MappedStatement addMappedStatement(
String id,
...) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 创立 MappedStatement 对象
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
...
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);// 在这里将之前生成的 Cache 封装到 MappedStatement
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
咱们看到将 Mapper 中创立的 Cache 对象,退出到了每个 MappedStatement 对象中,也就是同一个 Mapper 中所有的 MappedStatement 中的 cache 属性援用的是同一个
有对于
标签的解析就到这了。
查问源码剖析
CachingExecutor
// CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创立 CacheKey
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 {
// 从 MappedStatement 中获取 Cache,留神这里的 Cache 是从 MappedStatement 中获取的
// 也就是咱们下面解析 Mapper 中 <cache/> 标签中创立的,它保留在 Configration 中
// 咱们在下面解析 blog.xml 时剖析过每一个 MappedStatement 都有一个 Cache 对象,就是这里
Cache cache = ms.getCache();
// 如果配置文件中没有配置 <cache>,则 cache 为空
if (cache != null) {
// 如果须要刷新缓存的话就刷新:flushCache="true"
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);
// 拜访二级缓存
List<E> list = (List<E>) tcm.getObject(cache, key);
// 缓存未命中
if (list == null) {
// 如果没有值,则执行查问,这个查问理论也是先走一级缓存查问,一级缓存也没有的话,则进行 DB 查问
list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 缓存查问后果
tcm.putObject(cache, key, list);
}
return list;
}
}
return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
如果设置了flushCache=”true”,则每次查问都会刷新缓存
<!-- 执行此语句清空缓存 -->
<select id="findbyId" resultType="com.itheima.pojo.user" useCache="true" flushCache="true" >
select * from t_demo
</select>
如上,留神二级缓存是从 MappedStatement 中获取的。因为 MappedStatement 存在于全局配置中,能够多个 CachingExecutor 获取到,这样就会呈现线程平安问题。除此之外,若不加以控制,多个事务共用一个缓存实例,会导致脏读问题。至于脏读问题,须要借助其余类来解决,也就是下面代码中 tcm 变量对应的类型。上面剖析一下。
TransactionalCacheManager
/** 事务缓存管理器 */
public class TransactionalCacheManager {
// Cache 与 TransactionalCache 的映射关系表
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
public void clear(Cache cache) {
// 获取 TransactionalCache 对象,并调用该对象的 clear 办法,下同
getTransactionalCache(cache).clear();}
public Object getObject(Cache cache, CacheKey key) {
// 间接从 TransactionalCache 中获取缓存
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
// 间接存入 TransactionalCache 的缓存中
getTransactionalCache(cache).putObject(key, value);
}
public void commit() {for (TransactionalCache txCache : transactionalCaches.values()) {txCache.commit();
}
}
public void rollback() {for (TransactionalCache txCache : transactionalCaches.values()) {txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
// 从映射表中获取 TransactionalCache
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
// TransactionalCache 也是一种装璜类,为 Cache 减少事务性能
// 创立一个新的 TransactionalCache,并将真正的 Cache 对象存进去
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
TransactionalCacheManager 外部保护了 Cache 实例与 TransactionalCache 实例间的映射关系,该类也仅负责保护两者的映射关系,真正做事的还是 TransactionalCache。TransactionalCache 是一种缓存装璜器,能够为 Cache 实例减少事务性能。上面剖析一下该类的逻辑。
TransactionalCache
public class TransactionalCache implements Cache {
// 真正的缓存对象,和下面的 Map<Cache, TransactionalCache> 中的 Cache 是同一个
private final Cache delegate;
private boolean clearOnCommit;
// 在事务被提交前,所有从数据库中查问的后果将缓存在此汇合中
private final Map<Object, Object> entriesToAddOnCommit;
// 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此汇合中
private final Set<Object> entriesMissedInCache;
@Override
public Object getObject(Object key) {
// 查问的时候是间接从 delegate 中去查问的,也就是从真正的缓存对象中查问
Object object = delegate.getObject(key);
if (object == null) {
// 缓存未命中,则将 key 存入到 entriesMissedInCache 中
entriesMissedInCache.add(key);
}
if (clearOnCommit) {return null;} else {return object;}
}
@Override
public void putObject(Object key, Object object) {
// 将键值对存入到 entriesToAddOnCommit 这个 Map 中中,而非实在的缓存对象 delegate 中
entriesToAddOnCommit.put(key, object);
}
@Override
public Object removeObject(Object key) {return null;}
@Override
public void clear() {
clearOnCommit = true;
// 清空 entriesToAddOnCommit,但不清空 delegate 缓存
entriesToAddOnCommit.clear();}
public void commit() {
// 依据 clearOnCommit 的值决定是否清空 delegate
if (clearOnCommit) {delegate.clear();
}
// 刷新未缓存的后果到 delegate 缓存中
flushPendingEntries();
// 重置 entriesToAddOnCommit 和 entriesMissedInCache
reset();}
public void rollback() {unlockMissedEntries();
reset();}
private void reset() {
clearOnCommit = false;
// 清空集合
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();}
private void flushPendingEntries() {for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
// 将 entriesToAddOnCommit 中的内容转存到 delegate 中
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {if (!entriesToAddOnCommit.containsKey(entry)) {
// 存入空值
delegate.putObject(entry, null);
}
}
}
private void unlockMissedEntries() {for (Object entry : entriesMissedInCache) {
try {
// 调用 removeObject 进行解锁
delegate.removeObject(entry);
} catch (Exception e) {log.warn("...");
}
}
}
}
存储二级缓存对象的时候是放到了 TransactionalCache.entriesToAddOnCommit 这个 map 中,然而每次查问的时候是间接从 TransactionalCache.delegate 中去查问的,所以这个二级缓存查询数据库后,设置缓存值是没有立即失效的,次要是因为间接存到 delegate 会导致脏数据问题
为何只有 SqlSession 提交或敞开之后?
那咱们来看下 SqlSession.commit()办法做了什么
SqlSession
@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();}
}
// CachingExecutor.commit()
@Override
public void commit(boolean required) throws SQLException {delegate.commit(required);
tcm.commit();// 在这里}
// TransactionalCacheManager.commit()
public void commit() {for (TransactionalCache txCache : transactionalCaches.values()) {txCache.commit();// 在这里
}
}
// TransactionalCache.commit()
public void commit() {if (clearOnCommit) {delegate.clear();
}
flushPendingEntries();// 这一句
reset();}
// TransactionalCache.flushPendingEntries()
private void flushPendingEntries() {for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
// 在这里真正的将 entriesToAddOnCommit 的对象一一增加到 delegate 中,只有这时,二级缓存才真正的失效
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {if (!entriesToAddOnCommit.containsKey(entry)) {delegate.putObject(entry, null);
}
}
}
二级缓存的刷新
咱们来看看 SqlSession 的更新操作
public int update(String statement, Object parameter) {
int var4;
try {
this.dirty = true;
MappedStatement ms = this.configuration.getMappedStatement(statement);
var4 = this.executor.update(ms, this.wrapCollection(parameter));
} catch (Exception var8) {throw ExceptionFactory.wrapException("Error updating database. Cause:" + var8, var8);
} finally {ErrorContext.instance().reset();}
return var4;
}
public int update(MappedStatement ms, Object parameterObject) throws SQLException {this.flushCacheIfRequired(ms);
return this.delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
// 获取 MappedStatement 对应的 Cache,进行清空
Cache cache = ms.getCache();
//SQL 需设置 flushCache="true" 才会执行清空
if (cache != null && ms.isFlushCacheRequired()) {this.tcm.clear(cache);
}
}
MyBatis 二级缓存只实用于不常进行增、删、改的数据,比方国家行政区省市区街道数据。一但数据变更,MyBatis 会清空缓存。因而二级缓存不适用于常常进行更新的数据。
总结:
在二级缓存的设计上,MyBatis 大量地使用了装璜者模式,如 CachingExecutor, 以及各种 Cache 接口的装璜器。
- 二级缓存实现了 Sqlsession 之间的缓存数据共享,属于 namespace 级别
- 二级缓存具备丰盛的缓存策略。
- 二级缓存可由多个装璜器,与根底缓存组合而成
- 二级缓存工作由 一个缓存装璜执行器 CachingExecutor 和 一个事务型预缓存 TransactionalCache 实现