题记: 本文对 Mybatis 框架相关内容进行整理,从最开始使用JDBC 操作数据库,理解 DAO 层底层需要执行的步骤,到仿照 MyBatis 自定义框架,对 MyBatis 框架结构进行梳理。之后再介绍 MyBatis 框架的基本使用以及常用特性,了解 MyBatis 的日常应用,最后深入框架源码去感受 MyBatis 框架的精妙设计。
- 注:文章内容输出来源:拉勾教育Java高薪训练营;
 
复习JDBC操作流程
- 加载数据库连接驱动
 - 通过驱动管理类获取数据库连接
 - 获取预编译语句
 - 设置预编译语句参数
 - 执行SQL, 处理结果集
 - 释放资源
 
1  | 复制代码public static void main(String[] args) {  | 
直接使用JDBC存在的问题:
- 数据库连接的创建、销毁频繁造成系统资源浪费。
 - SQL 语句在代码中硬编码,SQL 语句的变化需要改变Java代码
 - 在向 preparedStatement 占位符传参存在硬编码
 - 对结果集解析存在硬编码(查询列名),系统不易维护
 
问题解决思路
- 数据库频繁创建连接、释放资源 ⇒ 数据库连接池
 - SQL语句及参数硬编码 ⇒ 配置文件
 - 手动解析封装返回结果集 ⇒ 反射、内省
 
自定义框架设计
使用端:
提供核心配置文件
1  | 复制代码/**  | 
框架端:
- 读取配置文件
 
1  | 复制代码/**  | 
- 解析配置文件
 
创建SqlSessionFactoryBuilder类
使用 dom4j 解析配置文件,将解析出来的内容封装到 Configuration 和 MappedStatement 中
3. 创建SqlSessionFactory
创建SqlSessionFactory的实现类DefaultSqlSession,并实现openSession() 方法,获取sqlSession接口的实现类实例对象 ( 传递Configuration 对象)
4. 创建SqlSession接口及实现类 主要封装CRUD方法
方法:selectList(String StatementId,Object param)查询所有
selectOne(String StatementId,Object param)查询单个
close() 释放资源
具体实现:封装JDBC完成对数据库表的查询操作
Executor 类,从Configuration类中获取,DataSource、SQL、paramterType、resultType,通过反射设置预编译语句占位符的值,执行SQL,通过内省,通过列名与对象属性的对应关系解析结果为对象
自定义框架优化
上述自定义框架存在的问题
- dao 的实现类中存在重复的代码,整个操作的过程重复(创建SqlSession, 调用SqlSession方法、关闭SqlSession)
 - dao 的实现类中存在硬编码,调用SqlSession 的方法时,参数 Statement 的 Id硬编码
 
解决方法
使用代理模式来创建接口的代理对象
Mybatis快速使用
1. 依赖
1  | 复制代码<!--mybatis-->  | 
2. 分别创建数据表及实体类
3. 编写 UserMapper 映射文件
1  | 复制代码<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper  | 
4. 编写 MyBatis 核心文件
1  | 复制代码<?xml version="1.0" encoding="UTF-8" ?>  | 
5. CRUD
注:增删改需要提交事务或设置自动提交
示例:
1  | 复制代码InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");  | 
MyBatis 核心配置文件层级关系
- configuration 配置
- properties 属性
 - settings 设置
 - typeAliases 类型别名
 - typeHandlers 类型处理器
 - objectFactory 对象工厂
 - plugins 插件
 - environments 环境
- environment 环境变量
- transactionManager 事务管理器type: [JDBC, MANAGED]
 - dataSource 数据源type: [UNPOOLED, POOLED, JNDI]
 
 
 - environment 环境变量
 - databaseIdProvider 数据库厂商标识
 - mappers 映射器
 
 
mapper.xml
动态 SQL 语句
SQL语句的主体结构,在编译时尚无法确定,只有等到程序运行起来,在执行的过程中才能确定,这种SQL叫做动态SQL。
常用标签:
1  | 复制代码<select id="findByCondition" parameterType="user" resultType="user">  | 
属性:
+ collection: 代表要遍历的集合元素
    1. 集合 ⇒  list
    2. 数组 ⇒ array
    3. Map ⇒ 集合对应的key
+ open: 代表语句的开始部分
+ close: 语句结束部分
+ item: 代表遍历集合的每个元素,生成的变量名
+ separator: 分隔符1  | 复制代码<select id="findByIds" parameterType="list" resultType="user">  | 
Mybatis 注解开发
常用注解
- @Insert:新增
 - @Update:更新
 - @Delete:删除
 - @Select:查询
 - @Result:实现结果集封装 [
, , , ]  - @Results:可以与@Result 一起使用,封装多个结果集
 - @One:实现一对一结果封装
 - @Many:实现一对多结果集封装
 
1  | 复制代码public interface UserMapper {  | 
Mybatis 缓存
- 一级缓存是 SqlSession 级别的缓存,由sqlSession 对象中的 HashMap 数据结构来储存, 不同sqlSession 之间的缓存互不影响,一级缓存默认开启
 - 二级缓存是 mapper 级别的缓存,二级缓存需手动开启
 
一级缓存
+ 执行commit() 操作, 会清空对应 sqlSession 下的一级缓存Map 中 CacheKey
1  | 复制代码CacheKey cacheKey = new CacheKey();  | 
二级缓存
- 二级缓存与一级缓存流程类似,但二级缓存基于 mapper 文件的 namespace
 - 二级缓存底层还是 HashMap 结构
 - 执行 commit() 操作会清空二级缓存数据
 
1. 开启二级缓存
- 在全局配置文件sqlMapConfig.xml 中加入
 
1  | 复制代码<!-- 开启二级缓存 -->  | 
- 在 Mapper.xml 中开启缓存
 
1  | 复制代码<!-- 开启二级缓存 -->  | 
- pojo类实现序列化接口
 
因为二级缓存存储介质并非内存一种,可能会序列化到硬盘中
userCache 与 flushCache 配置项
- userCache: 设置是否禁用二级缓存,控制粒度为 SQL,在statement中默认为true
 - flushCache: 刷新缓存,防止出现脏读,默认为true
 - 使用缓存是如果手动修改数据库表中的查询数据会出现脏读
 
二级缓存整合Redis
- 目的:实现分布式缓存
 
1. 依赖
1  | 复制代码<dependency>  | 
2. 配置文件
Mapper.xml
1  | 复制代码<cache type="org.mybatis.caches.redis.RedisCache" />  | 
3. Redis连接配置文件
1  | 复制代码redis.host=localhost  | 
注:
mybatis-redis 在存储数据的时候,使用的 hash 结构
key: namespace
field: Cachekey
value: result
因为需要序列化与反序列化,所以第二次从缓存中获取的对象和之前的对象并不是同一个
Mybatis 插件介绍
Mybatis作为一个应用广泛的优秀的ORM框架,这个框架具有强大的灵活性,在四大组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易用的插件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象。
MyBatis 所允许拦截的方法如下:
- 执行器Executor [update、query、commit、rollback]
 - SQL语法构建器StatementHandler[prepare、parameterize、batch、update、query]
 - 参数处理器ParameterHandler[gerParameterObject、setParameters方法]
 - 结果集处理器ResultSetHandler[handleResultSets、handleOutputParameters等方法]
 
自定义插件
- 创建自定义插件类实现Interceptor接口
 - 重写Intercept()方法
 - 然后给插件编写注解,指定需要拦截 的接口和方法
 - 在mybatis配置文件中配置所写的插件类
 
MyBatis 执行流程
设计模式
MyBatis 用到的设计模式
| 设计模式 | MyBatis 体现 | 
|---|---|
| Builder模式 | SqlSessionFactoryBuilder、Environment | 
| 工厂方法 | SqlSessionFactory、TransactionFactory、LogFactory | 
| 单例模式 | ErrorContext、LogFactory | 
| 代理模式 | Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk动态代理,还有executor.loader包使用了cglib或者javassist达到延迟加载的效果 | 
| 组合模式 | SqlNode 和各个子类ChooseSqlNode等 | 
| 模板方法模式 | BaseExecutor和SimpleExecutor,还有BaseTypeHandler和他的子类例如IntegerTypeHandler; | 
| 适配器模式 | 例如Log的Mybatis接口和它对jdbc、log4j等日志框架的适配实现 | 
| 装饰者模式 | 例如Cache包中的cache.decorators子包中等各个装饰者的实现 | 
| 迭代器模式 | 例如迭代器模式PropertyTokenizer | 
本文转载自: 掘金