第一阶段-框架源码分析
2020-06-27 00:17:39 0 举报仅支持查看
AI智能生成
Mybatis相关知识汇总
Mybatis
模板推荐
作者其他创作
大纲/内容
Mybatis
JDBC
JDBC的步骤
加载数据库驱动<br>Class.forName("com.mysql.jdbc.Driver");
通过驱动管理类获取数据库连接<br>Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8",root,root);
定义sql语句,使用?表示占位符<br>String sql = "select * from user where id = ?";
获取预处理statement<br>PreparedStatement preparedStatement = connection.prepareStatement(sql);
设置参数,从1开始,第一个参数为sql语句中参数的序号,第二个参数为设置的参数值。<br>preparedStatement.setInt(1,26);
向数据库发出查询请求,查询出结果集<br>ResultSet resultSet = prepareStatedment.executeQuery();
遍历查询结果<br>while(resultSet.next()){<br> int id = resultSet.getInt("id");<br> String username = resultSet.getString("username");<br>}
封装到实体类中<br>User user =new User();<br>user.setId(id);<br>user.setUserName(username);
JDBC问题回顾
硬编码问题
数据库配置信息存在硬编码问题
sql语句、设置参数、获取结果集参数均存在硬编码问题
此配置文件不建议与数据库配置信息使用同一个配置文件,因为sql语句需要经常去修改
频繁创建释放数据库连接
使用连接池解决
手动封装返回结果集,繁琐
使用反射、内省让它进行自动封装
自定义持久层框架
设计步骤
读取配置文件
Configuration:存放数据库基本信息、Map<唯一标识,MapperStatement>,唯一标识:namespace+"."+id
MapperStatement:sql语句、statement类型、输入参数java类型、返回结果集Java类型
解析配置文件
创建SqlSessionBuilder类
使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
创建SqlSessionFactory的实现类DefaultSqlSessionFactory<font color="#c41230">(工厂模式)</font>
创建SqlSessionFactory
通过openSession()方法获取SqlSession对象
创建SqlSession接口和实现类:主要封装CRUD方法
创建Excutor和对应实现类SimpleExcutor
query方法执行具体的JDBC操作
使用反射获取输入参数对象的值
使用反射和内省机制封装resultSet对象
自定义框架出现的问题
Dao层使用自定义持久层框架,存在代码重复,整个操作过程模板重复(加载配置文件、创建sqlSessionFactory、生产SqlSession)
statementid存在硬编码问题
这里的statementId是手动写入的,硬编码
Mybatis的应用
基础配置文件回顾
sqlMapConfig.xml
properties标签
作用:引入外部配置文件
typeAliases标签
作用:给实体类的全限定类名起别名
方式有两种
给单独的实体起别名
<typeAlias type="xxx" alias"xxx" />
扫描整个包,给包下所有的实体起别名
<package name="xxx.xxx.xxx.xxx" />
environments标签 <environments default="xxx">,default指定默认的环境名称
environment
id="",指定当前环境名称
transactionManager标签
类型type
JDBC
这个配置是使用了JDBC的提交和回滚设置,依赖于从数据源得到的连接来管理事务作用域
MANAGED
(不常用)这个配置几乎不做什么,从来不提交或者回滚一个连接,而是让容器来管理事务的整个生命周期(比如JEE应用服务器的上下文)。默认情况下会关闭连接,然而一些容器并不希望这样,因此需要将closeCOnnection属性设置为false来阻止它默认的关闭行为
dataSource标签
类型type
UNPOOLED
不使用连接池,每次被请求就打开一个新的连接,使用完就关闭
POOLED
使用“池”的概念将JDBC连接对象组织起来
JNDI
实现是为了能在EJB或者应用服务器这类容器中使用,容器可以集中或外部配置数据源,然后放置一个JNDI上下文的引用
mapper标签
作用:加载映射配置文件
引用方式有四种
resource
url
class
package
mapper.xml
mapper标签
namespace
相当于找到某个方法的入口,填入Dao接口全限定类名,为了让Mybatis动态代理调用时知道去调用哪个方法
parameterType
入参的类型
单个参数,可以是基本类型,也可以是对象类型
多个参数,比较关键,可以是多个基本类型,也可以是Map类型
多个参数的时候,可以在方法的参数签名中使用@Param()注解进行标注,在mapper.xml文件中获取的时候就可以用#{标注的名称}来进行调用
如果不使用@Param进行标注的话,调用的时候则根据参数的顺序,使用#{arg0} #{arg1} 来进行调用
resultType
封装结果集类型
动态Sql
if标签
foreach标签
connection
传过来的parameterType是什么值类型,就写什么值类型
open
语句开始(拼接)
close
语句结束(拼接)
item
调用的别名
separator
分隔符
例子
where标签
作用:当做where语句拼接,并且会去掉动态的第一个and
Sql语句抽取
定义:<sql id=""><br> xxxxxxxx抽取出来的语句<br></sql>
调用:使用<include refid="xxx">
复杂映射
一对一
映射标签<resultMap>
<result>
property
代表实体类中的属性
column
代表查询结果中的字段名字
<association>
属性
property
代表实体类中引用的对象的变量名
javaType
引用的对象的全限定类名
子标签
<result>
property
代表实体类中的属性
column
代表查询结果中的字段名字
一对多
映射标签<resultMap>
<result>
property
代表实体类中的属性
column
代表查询结果中的字段名字
<collection>
属性
property
代表实体类中引用对象的变量名
ofType
引用的对象的全限定类名
<font color="#c41230">注意:</font>由于一对多或者多对多要查询的不一定是后面的多有才能查询,而是查询前面的一顺带查询多,所以要用左外或者右外连接查询
例如:用户可以有多个订单,我查询所有用户并且顺便查询所有与其对应的订单,但是并不是这个用户没有订单我就不要这个数据了
多对多
注解开发
基本CRUD
@Insert
@Update
@Delete
@Select
复杂映射
主要通过@Results注解封装结果集
子属性@Result
封装变量与xml格式相同 property 和 value
封装引用的对象类型
属性
property
引用变量的名字,与xml一样
column
传过去参与查询的参数字段,因为是要与另一个查询方法做连接的,所以需要传递连接参数
javaType
根据你实体类里面引用的对象类型,来确定。
例如引用的是User user,那么类型就是User,class<br>引用的是List<User>,类型就是List.class
one
如果对应数据是对一,使用one
many
如果对应数据是对多,使用many
注解做复杂映射,不像xml可以一个语句到位,需要和另一个方法配合才能做复杂映射
一对一
@Result( one = @One(select = "namespace + . + statementId") )
一对多
@Result(many = @Many(select = "namespace + . + statementId") )
多对多
@Result(many = @Many(select = "namespace + . + statementId"))
缓存
概念:缓存就是内存中的数据,常常来自对数据库查询结果的保存,使用缓存,我们可以避免频繁的与数据库进行交互,进而提高响应速度
缓存级别
一级缓存
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存区域(HashMap)是互相不影响的
关于缓存操作和刷新
查询操作时,会先去一级缓存中去拿,如果缓存中没有,会去数据库中查询。但是在经历过增删改操作,并且事务提交之后,会清空缓存,目的是为了让缓存中的数据保持最新,防止脏读
缓存操作流程
第二个刷新缓存的方法就是调用 sqlSession.clearCache()
一级缓存原理分析
源码调用链路
cache其实是一个HashMap,那么它是何时被创建?何时被调用?何时被存放数据的?
结构如图
所以从缓存中查找数据的步骤
首先BaseExecutor通过createCacheKey()创建一个cacheKey,因为有了key才能得到value
在调用query方法的时候将CacheKey传入
先通过localCache.getObject(key)获取,如果获取不到调用queryFromDatabase方法
通过doQuery方法获取value,也就是去数据库查到数据,然后通过localCache.putObject(key,list)方法将数据存入缓存
二级缓存
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的
二级缓存执行流程
如图
注意:只有在一级缓存sqlSession关闭的时候,数据才会刷新到二级缓存
开启二级缓存
xml配置,需要两步
在全局配置文件中开启
<settings><br> <setting name="cacheEnable" value="true" /><br></settings>
在Mapper文件中开启
<cache></cache>
注解配置
在对应的接口类上加注解开启
@CacheNamespace
禁用二级缓存
xml方式
在mapper.xml中某个想禁用的方法的标签里加上
userCache = false
注解方式
在Dao中的方法上加上
@Options(userCache = false)
注意:二级缓存中存放的不是对象的地址,只是一个数据,然后通过对象返回回来,所以连续从二级缓存中拿到数据并将对象的地址作比较,地址是不同的,因为两个对象不是一个
使用redis实现分布式缓存
导入mybatis-redis的依赖,mybatis有集成好redis的包
<dependency><br><br> <groupId>org.mybatis.caches</groupId><br><br> <artifactId>mybatis-redis</artifactId><br><br> <version>1.0.0-beta2</version><br><br> </dependency>
配置redis.properties
host=localhost<br><br>port=6379<br><br>connectionTimeout=5000<br><br>password=<br><br>database=0
如果不配置,源码中会默认取本地无密码配置
给接口上加上@CacheNameSpace(implementation = RedisCache.class)
源码分析
最终存储的结构是jedis.hset
插件机制
介绍
一般情况下,开源框架都会提供插件或者其他形式的拓展点,供开发者自行拓展
Mybatis作为一个应用广泛的ORM开源框架,这个框架具有强大的灵活性,有四大组件提供了简单易用的插件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象
执行器Executor
允许拦截的方法,下面展示也是
update
query
commit
rollback
等
Sql语法构建器StatementHandler
parameterize
prepare
batch
update
query
等
参数处理器ParameterHandler
getParameterObject
setParameters
结果集处理器ResultSetHandler
handleResultSets
handleOutputParameters
自定义插件
sqlMapConfig.xml配置
<plugins><br><br>配置自定义好的插件的全限定类名<br><plugin interceptor="com.lvqz.plugins.MyPlugin"><br><br>参数配置<br> <property name="name" value="jerry"/><br><br> </plugin><br><br> </plugins><br>
自定义插件类
实现Interceptor方法<br>
添加注解
@Intercepts({<br><br><br><br> @Signature(type = StatementHandler.class,<br><br> method = "prepare",<br><br> args = {Connection.class, Integer.class})<br><br><br><br>})<br>
type
要拦截的类的.class
method
要拦截的方法的名字
args
要拦截的方法的参数的类型
原理
每个创建出来的对象不是直接返回的,而是intercepetorChain.pluginAll(parameterHandle);
获取到所有的interceptor(拦截器)(插件需要实现的接口),调用interceptor.plugin(target),返回target包装后的对象
插件机制,我们可以使用插件为目标对象创建一个代理对象,我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行
源码追踪流程(用拦截ParameterHandler举例)
Mybatis初始化的时候会调用newParameterHandler方法,创建ParameterHandler的代理对象
并调用pluginAll方法执行拦截器链<br>
进入拦截器链之后,调用interceptor.plugin方法,也就是自己自定义插件中需要实现的plugin方法,这个方法目的是为了返回一个通过自定义插件类封装代理后的ParamaterHandler的代理对象(此时相当于再进行第二层代理)
调用interceptor接口实现类(也就是自定义实现的插件类的方法)<br>
然后会去调用Plugin.wrap方法返回代理对象
那么Plugin.wrap方法是如何返回的代理对象呢?
所以其实一开始newParameterHandler返回的ParameterHandler对象,就是经历了重重磨难,最后被plugh插件对象代理出来的一个ParameterHandler对象,也是一个Plugin对象
调用方法的时候
调用方法,其实就是调用的Plugin对象中的invoke
Plugin是一个动态代理类,他的invoke方法
最终调用到自己的自定义插件中来
pageHelper插件
准备步骤
导入依赖
<!-- 导入分页依赖--><br><dependency><br><br> <groupId>com.github.pagehelper</groupId><br><br> <artifactId>pagehelper</artifactId><br><br> <version>3.7.5</version><br><br></dependency> <dependency><br><br> <groupId>com.github.jsqlparser</groupId><br><br> <artifactId>jsqlparser</artifactId><br><br> <version>0.9.1</version><br><br></dependency>
配置plugin
<!-- 分页助手的插件,配置在通用mapper之前 --><br><br><plugin interceptor="com.github.pagehelper.PageHelper"><br><br> <!-- 执行方言--><br><br> <property name="helperDialect" value="mysql"/><br><br></plugin>
通用mapper插件
概念和作用
通用mapper是为了解决<font color="#31a8e0">单表</font>增删改查,基于Mybatis的插件机制。开发人员不需要编写SQL,不需要在DAO中增加方法,只要写好实体类,就能支持相应的增删改查方法
使用方法
导入依赖
<dependency><br><br> <groupId>tk.mybatis</groupId><br><br> <artifactId>mapper</artifactId><br><br> <version>3.1.2</version><br><br></dependency>
配置文件中完成配置
<plugin<br><br>interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor"><br><br> <!--指定当前通用mapper接口使用的是哪一个--><br><br> <property name="mappers"<br><br>value="tk.mybatis.mapper.common.Mapper"/>
实体类设置主键
@Table指定对应的表
@Id //指定主键<br>@GeneratedValue() //指定主键的类型
编写Dao
public interface UserMapper extends Mapper<User>
源码剖析
架构原理-架构设计
架构分层及其层次作用
接口层
基于Statement ID
数据增加接口
数据删除接口
数据查询接口
数据修改接口
配置信息维护接口
基于Mapper接口
数据处理层
ParameterHandler:参数映射
参数映射配置
参数映射解析
参数类型解析
SqlSource:SQL解析
SQL语句配置
SQL语句解析
SQL语句动态生成
Executor:SQL执行
SimpleExecutor
BatchExecutor
ReuseExecutor
ResultSetHandler:结果处理和映射
结果映射配置
结果类型转换
框架支撑层
SQL语句配置方式
基于XML配置
基于注解配置
事务管理
连接池管理
缓存机制
主要构件和其相互关系
SqlSession
作为MyBatis工作的主要顶层API,表示和数据库交互的回话,完成必要数据库增删改查功能
Executor
MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler
封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换成List集合
ParameterHandler
负责对用户传递的参数转换成JDBC statement所需要的参数
ResultSetHandler
负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler
负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement
MappedStatement维护了一条<select|update|delete|insert>节点的封装
SqlSource
负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql
标识动态生成的SQL语句以及相应的参数信息
总体流程
加载配置并初始化
触发条件
加载配置文件
接受调用请求
触发条件
调用Mybatis提供的API
传入参数
为SQL的ID和传入参数对象
处理过程
将请求传递给下层的请求处理层进行处理
处理操作请求
触发条件
API接口层传递请求过来
传入参数
为SQL的ID和传入参数独享
处理过程
根据SQL的ID查找对应的MappedStatement对象
根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数
获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果
根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果
释放连接资源
返回处理结果
初始化过程
Resources.getResourcesAsStream
SqlSessionFactory.build()
执行sql流程
executor源码解析
StatementHandler源码剖析
mapper代理方式getMapper方法剖析
invoke方法源码解析
收藏
立即使用
收藏
立即使用
收藏
立即使用
收藏
立即使用
Collect
Get Started
Collect
Get Started
Collect
Get Started
Collect
Get Started
评论
0 条评论
下一页