MyBatis(下)
缓存
缓存介绍
一级缓存体验
* 一级缓存:(本地缓存):sqlSession级别的缓存。一级缓存是一直开启的;SqlSession级别的一个Map
* 与数据库同一次会话期间查询到的数据会放在本地缓存中。
* 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
Employee emp02 = mapper.getEmpById(1);
System.out.println(emp02);
System.out.println(emp01==emp02);
控制台打印
发现只发起了一次sql查询,且相同查询返回的是同一个对象
一级缓存失效的四种情况
* 一级缓存失效情况(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询)
* 1、sqlSession不同。
* 2、sqlSession相同,查询条件不同.(当前一级缓存中还没有这个数据)
* 3、sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
* 4、sqlSession相同,手动清除了一级缓存(缓存清空)
//1、sqlSession不同。
SqlSession openSession2 = sqlSessionFactory.openSession();
EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
//2、sqlSession相同,查询条件不同
//3、sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
mapper.addEmp(new Employee(null, "testCache", "cache", "1"));
System.out.println("数据添加成功");
//4、sqlSession相同,手动清除了一级缓存(缓存清空)
openSession.clearCache();
二级缓存介绍
二级缓存:(全局缓存):基于namespace级别的缓存:一个namespace对应一个二级缓存:
工作机制:
1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
2、如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
3、sqlSession===EmployeeMapper==>Employee
DepartmentMapper===>Department
不同namespace查出的数据会放在自己对应的缓存中(map)
效果:数据会从二级缓存中获取
查出的数据都会被默认先放在一级缓存中。
只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
使用:
1)、开启全局二级缓存配置:<setting name="cacheEnabled" value="true"/>
2)、去mapper.xml中配置使用二级缓存:
<cache></cache>
3)、我们的POJO需要实现序列化接口
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
<!--
eviction:缓存的回收策略:
• LRU – 最近最少使用的:移除最长时间不被使用的对象。
• FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
• SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
• WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
• 默认的是 LRU。
flushInterval:缓存刷新间隔
缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读:
true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
false:非只读:mybatis觉得获取的数据可能会被修改。
mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:缓存存放多少元素;
type="":指定自定义缓存的全类名;
实现Cache接口即可;
-->
效果:数据会从二级缓存中获取
查出的数据都会被默认先放在一级缓存中。
只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
//1、
DepartmentMapper mapper = openSession.getMapper(DepartmentMapper.class);
DepartmentMapper mapper2 = openSession2.getMapper(DepartmentMapper.class);
Department deptById = mapper.getDeptById(1);
System.out.println(deptById);
openSession.close();
Department deptById2 = mapper2.getDeptById(1);
System.out.println(deptById2);
//openSession.close(); 若会话在这儿关闭,则第二次查询不会在二级缓存中查
openSession2.close();
//第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
缓存有关的设置以及属性
和缓存有关的设置/属性:
1)、cacheEnabled=true:false:关闭缓存(二级缓存关闭)(一级缓存一直可用的)
2)、每个select标签都有useCache="true":
false:不使用缓存(只对二级缓存生效)
3)、【每个增删改标签的:flushCache="true":(一级二级都会清除)】
增删改执行完成后就会清楚缓存;
测试:flushCache="true":一级缓存就清空了;二级也会被清除;
查询标签:flushCache="false":
如果flushCache=true;每次查询之后都会清空缓存;缓存是没有被使用的;
4)、sqlSession.clearCache();只是清楚当前session的一级缓存;
5)、localCacheScope:本地缓存作用域:(一级缓存SESSION);当前会话的所有数据保存在会话缓存中;
STATEMENT:可以禁用一级缓存;
缓存原理图示
第三方缓存整合原理&ehcache适配包下载
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
MyBatis整合ehcache&总结
mapper.xml 配置文件
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
第三方缓存整合:
1)、导入第三方缓存包即可;
2)、导入与第三方缓存整合的适配包;官方有;
3)、mapper.xml中使用自定义缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
整合Spring
逆向工程
mbg简介
运行原理
框架分层架构
1、获取sqlSessionFactory对象:
解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession;
注意:【MappedStatement】:代表一个增删改查的详细信息
2、获取sqlSession对象
返回一个DefaultSQlSession对象,包含Executor和Configuration;
这一步会创建Executor对象;
3、获取接口的代理对象(MapperProxy)
getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象
代理对象里面包含了,DefaultSqlSession(Executor)
4、执行增删改查方法
SQLSessionFactory的初始化
-
创建一个SqlSessionFactoryBuilder调用他的build方法传入文件流
-
SqlSessionFactoryBuilder调用build方法,方法中创建一个解析xml文件的解析器XMLConfigBuilder 调用他的parse方法
- parser是基于dom4j的解析器
- 挨个解析全局配置文件中configuration节点中的所有节点
- settingsElement方法中 解析每一个标签,把详细信息保存在configuration中
- mapperElement方法中 三种设置mapper的方法,resource、url、class,看resource方法,还是xml解析解析mapper
- 解析方法中 获取了mapper标签节点再configurationElement方法
- 拿到mapper中能设置的每一个标签
- 看
buildStatementFromContext
方法如何操作"select|insert|update|delete"
,configuration已经保存了所有配置信息,包括全局配置中的信息,以及之前解析了的mapper中的信息,由于没有设置DatabaseId 所以进入厦门那个buildStatementFromContext方法
- 循环所有list中所有查询,创建了一个能接增删查改sql语句的 解析器statementParser ,调用这个解析的parseStatementNode方法
- parseStatementNode方法,拿到增删查改所有的标签值,并在这个方法里调用了addMappedStatement方法,传入所有标签值
这个方法将增删查改标签每一个标签每一个属性都解析出来封装成一个MappedStatement
所以一个MappedStatement就代表一个增删改查标签的详细信息
- 最后,configuration保存了所有配置文件的详细信息
其中mapperRegister中 每一个MapperProxyFactory接口对应里一个mapper文件
mappedStatements保存了每一个增删查改的id和详细信息
- 最后回到 parse方法,此时已经返回了configuration,再调用单参数的build方法,返回了DefaultSqlSessionFactory
所以得到的是SqlSessionFactory接口的实现类DefaultSqlSessionFactory 其中包含configuration
openSession获取SqlSession对象
- 我们知道是调用了DefaultSqlSessionFactory的openSession方法
- 其中调用了openSessionFromDataSource,传入configuration中默认的执行器,默认为SIMPLE
- openSessionFromDataSource方法中调用configuration返回了四大对象之一的Executor对象
- newExecutor方法内,根据一开始的getDefaultExecutorType传入的 类型,返回不同的 Executor,且如果有二级缓存cacheEnabled配置开启,就会创建CachingExecutor,
最后调用拦截器链interceptorChain
的pluginAll
方法
- pluginAll方法,使用每一个连接器重新包装Executor并返回
- 回到第一步,Executor创建好后,传入DefaultSqlSession ,所以最后返回了SqlSession接口的实现类DefaultSqlSession,其中也包含了configuration
getMapper获取到接口的代理对象
- getMapper方法中,调用的是configuration的getMapper方法
- configuration的getMapper方法中又是调用的mapperRegistry的getMapper方法
- 方法内调用的也就是当前表(Employee)的代理对象
- newInstance方法内,先创建MapperProxy,他是实现了jdk 的 InvocationHandler的能做动态代理的类
- 创建MapperProxy的代理对象,最后返回他
查询实现
- 由于先前获取到的mapper是MapperProxy代理对象,所以调用getEmpById查询方法会进入到MapperProxy的invoke方法,根据动态代理,执行目标方法之前会执行InvocationHandler的invoke。(MapperProxy实现了InvocationHandler),首先判断本次调用的方法是不是Object中的方法(如toString、hashCode、getClass),如果不是接口的方法二是Object的方法,则直接执行。所以invoke调用mapperMethod的execute,传入sqlSession和查询需要的参数
- execute方法中判断增删查改,和查询要封装成什么类型,这边是查询方法进入SELECT,判断方法的返回值执行不同操作,本次查询返回单个Employee对象进入到else中,首先用method的参数解析器,判断参数的个数,如果是多个则把参数封装成map返回,如果是单个则直接返回。最后调用sqlSession的selectOne
- selectOne中还是调用自己的selectList,只不过会根据返回结果返回,如果有多个返回就抛出异常
- 真正执行的selectList,先从configuration中获取了一个MappedStatement即本次查询的全部信息,而后传入executor的query方法
- executor的query方法内首先返回了BoundSql,里面封装的sql详细信息,下一步生成了一个非常长的key,用于在缓存中作为key,再转到下面的重载query
- 首先查看缓存中是否有,这里不设置缓存,转下一步调用的是SimpleExecutor的query方法
- SimpleExecutor的query方法中,还是首先查看本地缓存,没有就调用queryFromDatabase
- queryFromDatabase方法中,首先往本地缓存中放入key和一个占位符,然后尝doQuery试真正的数据库查询,查询后存入本地缓存
- doQuery方法内,首先声明一个Statement对象,他就是原生JDBC的Statement,所以MyBatis底层用的就是JDBC,然后根据MappedStatement的配置信息,创建了StatementHandler 四大对象之一
- 看newStatementHandler方法内StatementHandler的创建,在RoutingStatementHandler内会根据statementType创建不同的StatementHandler,type的设置在查询语句的statementType内,默认为PREPARED 预编译的,所以创建出了PreparedStatementHandler,返回后,还是熟悉的操作,
interceptorChain.pluginAll
用所有拦截器包装了一下
- 回到doQuery中StatementHandler创建好调用方法prepareStatement进行参数预编译,这里调用到了parameterHandler(四大对象)进行预编译
- parameterHandler在之前创建StatementHandler时就会一并创建了,就在BaseStatementHandler中。而newParameterHandler中又是熟悉的一幕,拦截器链循环包装,resultSetHandler同样
- 在doQuery方法最后一步的query方法中,最里面就是用ResultHandler查出数据处理结果。后续连接关闭,返回list
MyBatis 四大核心对象
ParameterHandler:处理SQL的参数对象
ResultSetHandler:处理SQL的返回结果集
StatementHandler:数据库的处理对象,用于执行SQL语句
Executor:MyBatis的执行器,用于执行增删改查操作
总结
1、根据配置文件(全局,sql映射)初始化出Configuration对象
2、创建一个DefaultSqlSession对象,
他里面包含Configuration以及
Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
3、DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy;
4、MapperProxy里面有(DefaultSqlSession);
5、执行增删改查方法:
1)、调用DefaultSqlSession的增删改查(Executor);
2)、会创建一个StatementHandler对象。
(同时也会创建出ParameterHandler和ResultSetHandler)
3)、调用StatementHandler预编译参数以及设置参数值;
使用ParameterHandler来给sql设置参数
4)、调用StatementHandler的增删改查方法;
5)、ResultSetHandler封装结果
注意:
四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler);
插件
插件原理
插件原理
在四大对象创建的时候
1、每个创建出来的对象不是直接返回的,而是
interceptorChain.pluginAll(parameterHandler);
2、获取到所有的Interceptor(拦截器)(插件需要实现的接口);
调用interceptor.plugin(target);返回target包装后的对象
3、插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)
我们的插件可以为四大对象创建出代理对象;
代理对象就可以拦截到四大对象的每一个执行;
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
插件编写&单个插件原理
插件编写:
1、编写Interceptor的实现类
2、使用@Intercepts注解完成插件签名
3、将写好的插件注册到全局配置文件中
/**
* 完成插件签名:
* 告诉MyBatis当前插件用来拦截哪个对象的哪个方法
*/
@Intercepts(
{
@Signature(type= StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor{
/**
* intercept:拦截:
* 拦截目标对象的目标方法的执行;
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询3号员工
Object target = invocation.getTarget();
System.out.println("当前拦截到的对象:"+target);
//拿到:StatementHandler==>ParameterHandler===>parameterObject
//拿到target的元数据
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("sql语句用的参数是:"+value);
//修改完sql语句要用的参数
metaObject.setValue("parameterHandler.parameterObject", 11);
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
/**
* plugin:
* 包装目标对象的:包装:为目标对象创建一个代理对象
*/
@Override
public Object plugin(Object target) {
// TODO Auto-generated method stub
//我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);
Object wrap = Plugin.wrap(target, this);
//返回为当前target创建的动态代理
return wrap;
}
/**
* setProperties:
* 将插件注册时 的property属性设置进来
*/
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
System.out.println("插件配置的信息:"+properties);
}
}
<!--plugins:注册插件 -->
<plugins>
<plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</plugin>
</plugins>
扩展
分页_PageHelpler分页插件使用
引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.1</version>
</dependency>
全局配置文件xml配置插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
@Test
public void test001() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
// Page<Object> page = PageHelper.startPage(2, 5);
List<Employee> emps = mapper.getEmps();
//传入要连续显示多少页
PageInfo<Employee> info = new PageInfo<>(emps, 5);
for (Employee employee : emps) {
System.out.println(employee);
}
/*System.out.println("当前页码:"+page.getPageNum());
System.out.println("总记录数:"+page.getTotal());
System.out.println("每页的记录数:"+page.getPageSize());
System.out.println("总页码:"+page.getPages());*/
///xxx
System.out.println("当前页码:"+info.getPageNum());
System.out.println("总记录数:"+info.getTotal());
System.out.println("每页的记录数:"+info.getPageSize());
System.out.println("总页码:"+info.getPages());
System.out.println("是否第一页:"+info.isIsFirstPage());
System.out.println("连续显示的页码:");
int[] nums = info.getNavigatepageNums();
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
} finally {
openSession.close();
}
}
批量_BatchExecutor&Spring中配置批量sqlSession
演示,因为传入了ExecutorType.BATCH类型的执行解析器所以能批量执行
@Test
public void testBatch() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//可以执行批量操作的sqlSession
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
long start = System.currentTimeMillis();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
for (int i = 0; i < 10000; i++) {
mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
}
openSession.commit();
long end = System.currentTimeMillis();
//批量:(预编译sql一次==>设置参数===>10000次===>执行(1次))
//Parameters: 616c1(String), b(String), 1(String)==>4598
//非批量:(预编译sql=设置参数=执行)==》10000 10200
System.out.println("执行时长:"+(end-start));
}finally{
openSession.close();
}
}
推荐写法,整合spring时配置一个BATCH类型的executor,通过自动注入,在需要用到的地方调用就行
<!--配置一个可以进行批量执行的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
<constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>
@Autowired
private SqlSession sqlSession;