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