v20190523
http://www.mybatis.org/mybatis-3/zh/index.html
架构图
5.1 parameterType 输入映射
输入参数类型 简单类型POJO类型 #{} 通过反射获取数据 StaticSqlSource (RawSqlSource)${} 通过OGNL表达式获取数据 DynamicSqlSourceMap类型List类型 动态SQL会讲解5.2 resultType/resultMap 结果映射
resultType 查询结果列名和属性名一致不一致的会得不到结果resultMap 关联查询 一对多查询可以进行对象嵌套延时加载(懒加载)Java 常见三类23种设计模式
创建型 5 工厂模式 同属性的同一对象构建者模式 不同属性的同一对象结构型 7行为性 112.1 核心流程
全局 XML 配置文件 -> Configuration 配置对象 -> SqlSessionFactory对象
// 1. 顶层 创建SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 1.1 new SqlSessionFactoryBuilder().build()底层 // 1.1.1 创建XMLConfigBuilder XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 1.1.2 XMLConfigBuilder进行解析,返回Configuration Configuration config = parser.parse(); // 1.1.3 根据配置构造,返回DefaultSqlSessionFactory build(config); public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }2.2 XMLxxxBuilder XML文件解析构造器
父类 BaseBuilder(Configuration、TypeAliasRegistry、TypeHandlerRegistry)
XMLConfigBuilder 用来解析 MyBatis 的全局配置文件 mybatis-config.xmlXMLMapperBuilder 用来解析 MyBatis 中的映射文件 xxxMapper.xmlXMLStatementBuilder 用来解析映射文件中的 statement 语句 <select>xxx</select>MapperBuilderAssistant 用来辅助解析映射文件并生成 MappedStatement 对象XML 解析使用了 XPath(javax.xml.xpath.XPath),将 XML 解析为七种类型节点(XNode)
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { // 1 初始化Configuration super(new Configuration()); ... this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; } // 1.1 new Configuration() // 1.1.1 注册常用别名 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); ...2.3 parser.parse() 解析成 Configuration 流程
// 1 只解析一次,从configuration根节点开始 if (parsed) { ... } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; // 1.1 parseConfiguration() 流程 XML -> Configuration // 按顺序解析XML标签 // 解析properties propertiesElement(root.evalNode("properties")); ... // 解析Mappers mapperElement(root.evalNode("mappers"));2.4 mapperElement() Mapper 解析流程
XMLMapperBuilder
// 1 全局配置文件获取到resource资源路径,加载xxxMapper.xml映射文件 String resource = child.getStringAttribute("resource"); ... InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(...); // 2 执行Mapper解析 mapperParser.parse(); // 2.1 执行Mapper解析流程 ... configurationElement(parser.evalNode("/mapper")); ... parsePendingResultMaps(); ... parsePendingStatements(); // 2.1.1 configurationElement() ... parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); // 2.1.1.1 buildStatementFromContext() // 使用 XMLStatementBuilder 解析 select|insert|update|delete 标签为 statement statementParser.parseStatementNode(); // 2.1.1.1.1 statementParser.parseStatementNode() 解析核心部分 ... // 2.1.1.1.2 转换为真正的SQL语言 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); ... // 2.1.1.1.3 使用 MapperBuilderAssistant 转换为 statement builderAssistant.addMappedStatement(...);3.1 核心流程
openSession ->
SqlSession sqlSession = sqlSessionFactory.openSession(); User user = sqlSession.selectOne("findUserById", 1);3.2 openSessionFromDataSource() 从数据源打开session
执行器类型 ExecutorType SIMPLEREUSEBATCH设置事务级别 TransactionIsolationLevel NONEREAD_COMMITTEDREAD_UNCOMMITTEDREPEATABLE_READSERIALIZABLE是否自动提交 autoCommit // 创建 DefaultSqlSession 流程 ... final Environment environment = configuration.getEnvironment(); ... tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); ...3.3 查询执行流程解析 sqlSession.selectList()
参数1 statement "com.aizain.jhome.user.findUserById"参数2 唯一入参 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { // 根据 statement(id)获取 MappedStatement MappedStatement ms = configuration.getMappedStatement(statement); // 委托者模式,委托给 executor 去执行 // 默认使用 CachingExecutor // CachingExecutor 主要用于处理二级缓存 // 如果无二级缓存 CachingExecutor 默认委托给 SimpleExecutor/BaseExecutor 真正执行 return executor.query( ms, // 特殊处理入参为集合类型的 涉及到动态SQL wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER ); ... }3.4 executor.query() 执行细节
CachingExecutor sqlSource绑定parameter -> CachingExecutor 查二级缓存 -> BaseExecutor 查一级缓存 -> BaseExecutor 查数据库 -> SimpleExecutor 真正执行查询
// 1 executor.query() // 绑定入参 通过sqlSource,之前初始化流程时,已装载过sqlSource BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); // query 会先走二级缓存,之后走一级缓存,最后走数据库查询 BaseExecutor.queryFromDatabase() query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 1.1 BaseExecutor.queryFromDatabase() 部分 // queryFromDatabase会调用SimpleExecutor.doQuery()真正执行查询,然后保存一级缓存 ... // 为了防止缓存穿透 后续讲解 localCache.putObject(key, EXECUTION_PLACEHOLDER); ... // SimpleExecutor.doQuery() list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); ... localCache.removeObject(key); ... localCache.putObject(key, list);3.5 SimpleExecutor.doQuery() 真正执行查询
真正开始与 jdbc 打交道根据 Statement 类型路由到不同 statement 处理 本质上是 jdbc statement 类型STATEMENT 用于对数据库进行通用访问在运行时使用静态SQL语句时很有用Statement 接口不能接受参数PREPARED 当计划要多次使用SQL语句时使用PreparedStatement 接口在运行时接受输入参数CALLABLE 当想要访问数据库存储过程时使用CallableStatement 接口也可以接受运行时输入参数 // 1 SimpleExecutor.doQuery() Configuration configuration = ms.getConfiguration(); // 获取不同类型的Statement处理器 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); handler.query(stmt, resultHandler); ... closeStatement(stmt); // 1.1 prepareStatement() Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); // 内部执行了 DefaultParameterHandler.setParameters() // 底层根据参数类型,调用不同的 prepareStatement.setXXX() handler.parameterize(stmt); // 1.1.1 handler.prepare() ... // 内部执行了 connection.prepareStatement() 生成 prepareStatement statement = instantiateStatement(connection); setStatementTimeout(statement, transactionTimeout); setFetchSize(statement); ...1.1 对象嵌套前
列列2a1a2a3b1b21.2 对象嵌套后
列列2a[ 1 2 3 ]b[ 1 2 ]2.1 延迟加载 映射文件配置
select 指定延迟执行的语句column 以哪一列为基准进行延迟查询property 放入当前java对象的那个属性javaType 延迟查询结果的java类型 <association column="user_id" property="user" javaType="User" select="xxx.findUserById"/>2.2 延迟加载 全局文件配置
lazyLoadingEnabled 是否启用延迟加载默认 falsefalse 所有延迟加载配置都不会生效aggressiveLazyLoading 任何方法的调用都会触发延迟加载true 侵入式延迟模式 false 深度延迟默认 truelazyLoadTriggerMethods 指定哪些方法触发延迟加载默认 equals,clone,hashCode,toString <settings> <setting name="lazyLoadingEnabled" value="false"/> <setting name="aggressiveLazyLoading" value="true"/> </settings> for (OrderWithUser orderWithUser : orderWithUsers) { // debug可能看不出来区别,需要日志观察 // 开启侵入式延迟加载时 aggressiveLazyLoading true, 这步之前就会查询sql log.debug("Order get id {}", orderWithUser.getId()); // 开启深度延迟加载时 aggressiveLazyLoading false, 这步之前才会查询sql log.debug("Order get user {}", orderWithUser.getUser()); }2.3 延迟加载 N+1问题
主表查询一次,关联表查询 N 次1.1 全局配置
<setting name="cacheEnabled" value="true"/>1.2 Mapper配置
指定 mapper 文件命名空间下的二级缓存 <cache/> <select useCache="true" >xxx</select>1.3.1 简介
开源的 Java 分布式缓存主要面向通用缓存具有内存和磁盘存储、缓存加载器、缓存扩展、缓存异常处理程序支持 REST、SOAP 等分布式缓存常用 RedisMybatis 的定位是做持久层框架,缓存数据管理不是 Mybatis 特长1.3.2 特点
快速、简单多种缓存策略支持磁盘和内存存储缓存数据会在虚拟机重启的过程中写入磁盘可通过 RMI 可插入 API 等方式进行分布式缓存具有缓存和缓存管理器的侦听接口支持多缓存管理实例,以及一个实例的多个缓存区域提供 Hibernate 的缓存实现1.3.3 分布式缓存
Mybatis 自身无法支持分布式缓存,需要整合其他框架分布式缓存场景:集群部署方式可以解决缓存不一致问题1.3.4 整合思路
Mybatis Cache 接口 默认实现 PerpetualCache实现该接口,就可以实现二级缓存引入 jar 包 ehcachemybatis-ehcache指定 type 类型 <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>