MyBatis理论与实践
2022-03-05 01:18:40 5 举报
AI智能生成
MyBatis理论与实践
作者其他创作
大纲/内容
第一部分:mybatis模块回顾
二:基础回顾及高级应用
三:源码分析及设计模式
一:自定义持久层框架
JDBC问题
1. 频繁创建释放数据库连接、数据库配置信息硬编码
2. sql语句、设置参数、获取返回结果集参数存在硬编码
3. 手动封装返回结果集
解决思路
连接池:解决频繁创建释放数据库连接
配置文件:解决硬编码
反射、内省:解决手动封装返回结果集
持久层框架设计思路
使用端
(1)引入依赖
(2)编写配置文件
sqlMapConfig.xml:存放数据库配置信息
mapper.xml : 存放sql配置信息
自定义持久层框架本身
(1)Resources类 :
方法:InputSteam getResource(String path)
(2)编写pojo类:
Configuration:核心配置类:对应sqlMapConfig.xml
MappedStatement:映射配置类:对应mapper.xml
(3)SqlSessionFactoryBuilder类:
方法:build(InputSteam in) :
使用dom4j解析配置文件,封装Configuration
创建SqlSession实现类DefaultSqlSession实例对象:new DefaultSqlSessionFactory(Confguration);
(4)SqlSessionFactory接口及实现类:
方法:openSession()
(5)SqlSession接口及实现类
方法:crud方法
(6)编写执行器 Executor接口及实现类simpleExecutor
方法:query(Configuration configuration,MappedStatement ms,Object[] ...):执行底层的JDBC
方法:InputSteam getResource(String path)
(2)编写pojo类:
Configuration:核心配置类:对应sqlMapConfig.xml
MappedStatement:映射配置类:对应mapper.xml
(3)SqlSessionFactoryBuilder类:
方法:build(InputSteam in) :
使用dom4j解析配置文件,封装Configuration
创建SqlSession实现类DefaultSqlSession实例对象:new DefaultSqlSessionFactory(Confguration);
(4)SqlSessionFactory接口及实现类:
方法:openSession()
(5)SqlSession接口及实现类
方法:crud方法
(6)编写执行器 Executor接口及实现类simpleExecutor
方法:query(Configuration configuration,MappedStatement ms,Object[] ...):执行底层的JDBC
优化(mapper代理)
修改:sqlSession接口及实现类DefaultSqlSession
方法:getMapper(Class<?> inferfaceClass)
总结:底层是JDK动态代理,返回接口的代理对象
代理对象调用接口中任意方法,都会执行invocationHandler接口实现类的invoke方法
invoke方法中:调用的还是sqlSession的crud方法
方法:getMapper(Class<?> inferfaceClass)
总结:底层是JDK动态代理,返回接口的代理对象
代理对象调用接口中任意方法,都会执行invocationHandler接口实现类的invoke方法
invoke方法中:调用的还是sqlSession的crud方法
二:基础回顾及高级应用
基本概念
ORM:Object/Relation Mapping:对象关系映射:
让实体类和表产生映射关系:把对持久化对象的保存、修改、删除等操作,转换为对数据库的操作
MyBatis 是一款优秀的基于ORM的半自动轻量级持久层框架
配置文件
environments:数据源环境配置标签
properties: 属性配置
resource属性:用于指定 properties 配置文件的位置,要求配置文件必须在类路径下
resource="jdbcConfig.properties"
resource="jdbcConfig.properties"
typeAliases:自定义别名
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="com.lagou.domain"/>;
<!-- 单个别名定义 -->;
<typeAlias alias="user"; type="com.lagou.pojo.User">;
<typeAlias alias="user"; type="com.lagou.pojo.User">;
<!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
<package name="com.lagou.domain"/>;
mappers:映射器
方式一:
<mapper resource=" " />
使用相对于类路径的资源
如:<mapper resource="com/lagou/dao/IUserDao.xml" />
方式一:
<mapper resource=" " />
使用相对于类路径的资源
如:<mapper resource="com/lagou/dao/IUserDao.xml" />
方式二:
<mapper class=" " />
使用 mapper 接口类路径
如:<mapper class="com.lagou.UserMapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中
<mapper class=" " />
使用 mapper 接口类路径
如:<mapper class="com.lagou.UserMapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中
方式三:
<package name=""/>
注册指定包下的所有 mapper 接口
如:<package name="com.lagou.mapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中
<package name=""/>
注册指定包下的所有 mapper 接口
如:<package name="com.lagou.mapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中
动态sql
<if test=条件判断></if>
注意问题:1.条件判断中不能使用&&,要使用and
2.<if>标签的 test 属性中写的是对象的属性
2.<if>标签的 test 属性中写的是对象的属性
<where></where>
简化 where 1=1 的条件拼装,我们可以采用<where>标签来简化开发,会自动删去
第一个and
第一个and
<foreach> </foreach>
<foreach>标签用于遍历集合
collection: 代表要遍历的集合元素,注意编写时不要写#{}
open: 代表语句的开始部分
close: 代表结束部分
item: 代表遍历集合的每个元素,生成的变量名
separator: 代表分隔符
<select>
抽取重复的语句代码片段
<sql id="defaultSql">
select * from user
</sql>
select * from user
</sql>
使用
include refid="defaultSql"></include>
复杂映射
一对一
实体中表示一方关系:实体
使用 resultMap,定义专门的 resultMap 用于映射一对一查询结果。
<association property="user" javaType="com.lagou.pojo.User">
<association property="user" javaType="com.lagou.pojo.User">
一对多
实体中表示多方关系:集合
使用 resultMap,定义专门的 resultMap 用于映射一对多查询结果。
<collection property="orderList" ofType="com.lagou.pojo.Order">
<collection property="orderList" ofType="com.lagou.pojo.Order">
多对多
双方实体中表示对方关系:集合
使用 resultMap,定义专门的 resultMap 用于映射一对多查询结果。
<collection property="orderList" ofType="com.lagou.pojo.Order">
<collection property="orderList" ofType="com.lagou.pojo.Order">
注解开发
常用注解
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@CacheNamespace:实现注解二级缓存的使用
复杂关系映射的注解说明
@Results 注解
代替的是标签<resultMap>
@Result 注解
代替了 <id> 标签和<result> 标签
id 是否是主键字段
column 数据库的列名
property 需要装配的属性名
one 需要使用的@One 注解(@Result(one=@One)()))
many 需要使用的@Many 注解(@Result(many=@many)())
@One 注解(一对一)
代替了<assocation> 标签,是多表查询的关键,在注解中用来指定子查询返回单一对象
select 指定用的 来多表查询的 sql mapper
fetchType 会覆盖全局的配置参数 lazyLoadingEnabled
取值: eager
lazy
default
@Many 注解(多对一)
代替了<assocation> 标签,是多表查询的关键,在注解中用来指定子查询返回单一对象
缓存原理
缓存概念
缓存就是内存中的数据,常常来自对数据库查询结果的保存,使用缓存, 我们可以避免频繁的与数据库进行交互, 进而提高响应速度
一级缓存
存储作用域
sqlSession级别
底层数据结构
基于 PerpetualCache 的 HashMap 本地缓存
失效情况
二级缓存
存储作用域
namespace(跨sqlSession)
底层数据结果
基于 PerpetualCache 的 HashMap 本地缓存
生效:当sqlSession执行commit或者close会情况一级缓存,将缓存内容存到二级缓存
对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存
Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将
被 clear。
问题:关于mybatis二级缓存的一些考虑,例如集成redis实现分布式环境下的缓存,情况一:如果当前应用如果是更新操作多过查询操作,就会出现频繁删除缓存操作;情况二:连表查询会有问题,该如何考虑二级缓存?
插件机制
理解:类似拦截器,对四大对象进行拦截,用来增强核心对象的功能,本质上是借助底层的动态代理实现的
拦截方法:
执行器Executor
SQL语法构建器StatementHandler
参数处理器ParameterHandler
结果集处理器ResultSetHandler
自定义插件
@Intercepts({//注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这个拦截器
@Signature(type = StatementHandler.class,//这是指拦截哪个接口
method = "prepare",//这个接口内的哪个方法名,不要拼错了
args = { Connection.class, Integer.class}),///这是拦截的方法的入参
})
public class MyPlugin implements Interceptor {
@Signature(type = StatementHandler.class,//这是指拦截哪个接口
method = "prepare",//这个接口内的哪个方法名,不要拼错了
args = { Connection.class, Integer.class}),///这是拦截的方法的入参
})
public class MyPlugin implements Interceptor {
<plugins>
<plugin interceptor="com.lagou.plugin.MySqlPagingPlugin">
<!--配置参数-->
<property name="name" value="Bob"/>
</plugin>
</plugins>
<plugin interceptor="com.lagou.plugin.MySqlPagingPlugin">
<!--配置参数-->
<property name="name" value="Bob"/>
</plugin>
</plugins>
常用插件
pageHelper
①导入通用PageHelper的坐标
②在mybatis核心配置文件中配置PageHelper插件
③测试分页数据获取
通用mapper
① 引入mapper的依赖
②Mybatis配置文件中完成配置
③实体类设置主键
③定义通用mapper
三:源码分析及设计模式
架构原理
功能架构分层
API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理
数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作
基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑
总体流程
(1)加载配置并初始化
触发条件:加载配置文件
配置来源于两个地方,一个是配置文件(主配置文件conf.xml,mapper文件*.xml),一个是java代码中的注解,将主配置文件内容解析封装到Configuration,将sql的配置信息加载成为一个mappedstatement对象,存储在内存之中
(2)接收调用请求
触发条件:调用Mybatis提供的API
传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。
(3)处理操作请求
触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象
处理过程:
(A)根据SQL的ID查找对应的MappedStatement对象。
(B)根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
(C)获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
(D)根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果。
(E)释放连接资源
源码分析
第二部分:Mybatis思路分析
问题:
请完善自定义持久层框架,在现有代码基础上添加修改及删除功能
思路:
(1) 修改XMLMapperBuilder的parse方法:解析<update><delete>标签
(2) 在SqlSession及DefalutSqlSession添加update及delete方法
(3) 在getMapper方法中对当前执行的sql语句进行判断,决定调用增删改查的那个方法
(4) 在Executor及simpleExecutor中添加update及delete方法
请完善自定义持久层框架,在现有代码基础上添加修改及删除功能
思路:
(1) 修改XMLMapperBuilder的parse方法:解析<update><delete>标签
(2) 在SqlSession及DefalutSqlSession添加update及delete方法
(3) 在getMapper方法中对当前执行的sql语句进行判断,决定调用增删改查的那个方法
(4) 在Executor及simpleExecutor中添加update及delete方法
第三部分:mybatis若干问题
mybatis collection 如何解决n+1问题(批量查询sql,many 一方会产生 n次io查询请求)
对于动态代理的使用场景有点困惑,Mybatis因为使用了代理模式,所以只创建mapper接口,然后定义操作数据库的方法,无需创建mapper实例对象,这都理解,但是在动态代理的demo中,Person本身已经定义了子类Bob,然后再执行 Person proxyPerson = new JDKDynamicProxy(new Bob()).getTarget(); 把Bob的实例对象作为参数传给了动态代理对象,为什么这里动态代理的使用又和子类实例对象联系在了一起?不是说不需要接口的实现类吗?这里很困惑,不知道什么场景应该使用动态代理,如何使用才是合理的?
在一对多返回结果集映射中,为什么collection的映射主键属性使用<id>就只会返回一条记录,而使用<result>就可以返回多条记录。
#和$的区别和联系
为什么mapper中paramterType不能设置为对象属性的类型,如User中int型属性id,paramterType设成Integer,执行时报反射异常。
既然有一级缓存、二级缓存,为什么还要用redis做缓存呢?
CachingExecutor对二级缓存的实现原理和存储过程?以及一二级缓存的协调互斥性?
当库表外键字段与对应表主键字段一样时,resultMap里的column该怎么设置
介绍下mybatis的日志模块,工作原理,如何利用日志进行排障
xml与注解方式的开发各有利弊。注解方式每次改了sql都要重新编译,这种模式方便了开发,真正开发中是否可取?
jdbc批量插入10000条数据跟在一个session中执行10000次插入,有区别吗?
sqlsession线程安全与不安全?并发情况如何保证线程安全?
通过注解的方式完成crud,传递多个参数,能否做一下演示?
sql注入是怎么一回事?使用mybatis哪些地方要防止sql注入?
pageHelper对于一对多分页嵌套结果集的方式会分页不准确,不使用嵌套子查询(效率太低),有什么好的解决方案吗,如自定义一对多分页的思路等
Mybatis如何执行批处理?
mybatis插件在实际项目中具体有哪些应用,多举些例子吧,像记录操作日志或数据埋点适合使用吗?
用PerpetualCache作为二级缓存应该有线程安全问题吧
分布式的情况下,一级缓存也会导致脏数据的读取,这样有解决方案吗?
sqlsessionFactoryBuilder采用的是建造者模式还是策略模式?
自定义的插件不需要一个执行顺序吗?
动态sql中是如何解析每个节点,在mappedstatement中是以何种形式存储的?
Mybatis的执行器BaseExecutor, CachingExecutor, ClosedExecutor 有啥用?
mybatis在插入过程中主键的生成原理,以及过程。
使用注解开发的时候如果牵扯的表多的话,或者是SQL复杂的话,感觉不如直接使用SQL简单明了;
二级缓存开启后,一级缓存还存在吗?源码中从一级缓存取数据时好像并没有判断二级缓存是否开启
一级缓存,某张表执行更新操作后,是所有表的缓存失效,还是只有相关表的缓存失效?
一级缓存,某张表执行更新操作后,是所有表的缓存失效,还是只有相关表的缓存失效?
mybatis中配置事务管理类型为“JDBC”,底层具体事务是怎么控制实现的?
Mybatis是怎么实现延迟加载的,它背后的原理是什么?
模板方法模式和策略模式区分不清。
讲一下SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession的应用范围和生命周期吗?
MyBatis 的 mapper 接口调用时有哪些要求?
在部分公司的实战环境中不推荐甚至禁止$出现在映射xml中,核心原因是什么?仅仅是因为SQL注入导致的安全问题?
mybatis-plus在项目中的选型是否可以考虑,和mybatis相比有什么好的地方
如何对mybatis进行性能调优?从哪几个方面入手?
收藏
收藏
0 条评论
下一页