目录
开撕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;@Datapublic 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中
//1public void addMappers(String packageName) { addMappers(packageName, Object.class);}//2public 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); } }//3public <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、构建MappedStatementprivate void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } // 构建MappedStatement buildStatementFromContext(list, null); }//2、专门用来解析MappedStatementprivate 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(#{}/${})
@Overridepublic 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); }//2public <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
// CachingExecutorpublic <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
@Overridepublic 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()@Overridepublic 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 实现