文章内容输出来源:拉勾教育Java高薪训练营。P7课程
本篇文章是学习课程中的一部分课后笔记。
项目代码:
链接:https://pan.baidu.com/s/1pdtE7NkQb1SHQo9RnfSOTw
提取码:sc4m
码云地址:https://gitee.com/nie_jian_ming/njm_all_homework/tree/master/%E7%AC%AC%E4%B8%80%E9%98%B6%E6%AE%B5.%E6%A8%A1%E5%9D%97%E4%B8%80.mybatis%E4%BD%9C%E4%B8%9A
问题:
1.频繁创建释放数据库连接,造成系统资源浪费,从⽽影响系统性能。
2.sql语句,传参数,解析结果集均存在硬编码,sql变动需要改变java代码,造成代码不易维护。
解决方案:
1.使用数据库连接池初始化连接资源,解决频繁创建释放数据库连接问题。
2.将sql语句抽取到xml配置⽂件中,使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射,反射进行参数的设置,内省进行返回结果集的封装。
涉及到的设计模式:
Builder构建者设计模式、⼯⼚模式(会话工厂)、代理模式(JDK动态代理来为Dao接口生成代理对象)
实行步骤:
创建SqlSessionFactoryBuilder类,再创建一个方法:build(InputSteam in) 构建会话工厂。
build方法里实现两件事:
1.使用dom4j解析配置文件,将解析出来的内容封装到容器对象中。
2.创建SqlSessionFactory(工厂模式)对象;生产sqlSession会话对象
然后基于开闭原则去创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory。写个方法用于生产sqlSession。
⽅法:openSession() //获取sqlSession接⼝的实现类实例对象
记得创建好sqlSession接口及实现类DefaultSqlSession,封装JDBC的CRUD方法。
方法:query(Configuration,MappedStatement,Object… params);
Configuration:使用dom4j对sqlMapConfig.xml解析时会将解析出来的内容以不同形式封装到Configuration对象中,里面存的是数据库配置中信息。
MappedStatement:使用dom4j对mapper.xml解析时,每一个标签的内容均对应一个mappedStatement对象,里面存的是sql信息
Object… params:这个就是使用端传入的参数,因为不确定到底会穿几个,所以是Object…可变参
Mybatis支持延迟加载
仅支持 association 关联对象和 collection 关联集合对象的延迟加载;
association 指的是一对一,collection 指的是一对多查询。
配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
实现原理
使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦
截器方法,当数据需要的时候在调用sql查询DB。
例如 : 调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是
null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。
默认是SimplExcutor
SimpleExecutor: 每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
ReuseExecutor: 执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。
简言之,就是重复使用Statement对象。
BatchExecutor: 执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中addBatch(),等待统一执行executeBatch(),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。
与JDBC批处理相同。
作用范围: Executor的这些特点,都严格限制在SqlSession生命周期范围内。
在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。
1.存储结构: 一级缓存、二级缓存都是缓存到HashMap结构中。
2.范围:
一级缓存 是SqlSession级别的,作用域是SqlSession,Mybatis默认开启一级缓存,在同一个SqlSession中,相同的Sql查询的时候,第一次查询的时候,就会从缓存中取,如果发现没有数据,那么就从DB查询出来,并且缓存到HashMap中,第二次查询直接在缓存中取,有数据就直接返回,不查DB。
二级缓存 是mapper级别的,多个SqlSession去操作同一个mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession。
第一次调用mapper下的sql 的时候去查询信息,查询到的信息会存放到该mapper对应的二级缓存区域,第二次调用namespace下的mapper映射文件中,相同的SQL去查询,直接在二级缓存内取结果。
3.失效场景:
一级缓存当进行增删改的操作的时候,缓存将会失效。
在spring容器管理中每次查询都是创建一个新的sqlSession,所以在分布式环境中不会出现数据不一致的问题。
二级缓存 使用时需要开启cache标签,在select上添加useCache属性为true,在更新和删除时候需要手动开启flushCache刷新缓存。如果设置useCache=false则是关闭二级缓存。
MyBatis所允许拦截的⽅法如下:
执⾏器Executor :(update、query、commit、rollback等⽅法);
SQL语法构建器StatementHandler :(prepare、parameterize、batch、updates query等⽅ 法);
参数处理器ParameterHandler :(getParameterObject、setParameters⽅法);
结果集处理器ResultSetHandler :(handleResultSets、handleOutputParameters等⽅法);
1.运行原理:
mybatis可以编写针对Executor、StatementHandler、ParameterHandler、ResultSetHandler四个接口的插件,mybatis使用JDK的动态代理为需要拦截的接口生成代理对象,然后实现接口的拦截方法,所以当执行需要拦截的接口方法时,会进入拦截方法(AOP思想)。
2.如何编写:
1.编写Intercepror接口的实现类
2.设置插件的签名,告诉mybatis拦截哪个对象的哪个方法
3.最后将插件注册到全局配置文件中
示例:
package com.njm.plugin;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Connection;
import java.util.Properties;
//插件签名,告诉mybatis当前插件拦截哪个对象的哪个方法
@Intercepts({ //这是个⼤花括号,也就这说这⾥可以定义多个@Signature,对多个地⽅拦截,都⽤这个拦截器
/*
type:表示要拦截的核心(目标)对象,拦截哪个接⼝,StatementHandler是一个sql语句构建器,用来完成sql语句预处理
method:表示要要拦截的方法,prepare是StatementHandler里的sql预编译方法
args:表示要拦截方法的参数,按方法里的参数顺序写,可能方法有重载,所以要通过⽅法名和⼊参来确定唯⼀。
*/
@Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class,Integer.class})
})
public class MyPlugin implements Interceptor {
//截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法
@Override
public Object intercept(Invocation invocation) throws Throwable {
/*
* 插件的主要功能:在执行目标方法之前,可以对sql进行修改已完成特定的功能
* 例如增加分页功能,实际就是给sql语句添加limit;还有其他等等操作都可以
* */
System.out.println("对方法进行了增强。。。。。。。");
return invocation.proceed(); //invocation.proceed():原方法执行并返回值
}
//主要为了把当前的拦截器生成代理存到拦截器链中,包装目标对象,为目标对象创建代理对象
@Override
public Object plugin(Object target) {
//target:被拦截的目标对象,this:表示当前自定义的插件实现类,当前拦截器,也就是现在这个类,
//wrap方法利用mybatis封装的方法为目标对象创建代理对象(没有拦截的对象会直接返回,不会创建代理对象)
Object wrap = Plugin.wrap(target, this);
return wrap;
}
//获取配置文件的参数,就是获取插件在配置文件中配置的参数值
//插件初始化的时候调⽤,也只调⽤⼀次,插件配置的属性从这⾥设置进来
@Override
public void setProperties(Properties properties) {
System.out.println("获取到的配置文件的参数是:"+properties);
}
}
插件注册到全局配置文件中