mybatis篇
一级缓存的作用域是Sqlsession级别的,也就是说不同的Sqlsession是不会走一级缓存的,那么如果需要跨Sqlsession的缓存,就需要使用到二级缓存了。
二级缓存的话默认是关闭的,所以需要我们开启,开启的方式官网也有介绍,需要在mybatis-configxml核心配置文件中开启二级缓存功能,并且我们mapperxml中也需要加入<cache/>标签,二者缺一不可,后面我们看源码就能知道为啥这两个缺一不可。
先来看个例子
执行结果很意外,为什么二级缓存的功能都开启了,结果sql还是执行了2次,并没有走缓存,其实,二级缓存还有一个要注意的点那就是必须要提交事务二级缓存才会保存记录,因为已经是跨SqlSession共享缓存了,所以事务必须要提交,否则会读取到因混滚导致的错误数据。
有个地方需要注意,二级缓存的Sqlsession中的Executor实际上是CachingExecutor
我们知道getMapper最终的执行都会走到MapperProxy类中的invoker方法,具体就来分析这个类。
最后来到了重点的地方
CacheKey我们可以认为他就是每个方法对应的一个唯一标识符。
这里我们就可以看出为什么之前两者必须要配置,cacheEnable开启了才会用CachingExecutor包装一下BaseExecutor,而<cache/>标签只有配置了才会走缓存的逻辑
这里的tcm
到这,我们就差不多揭开了二级缓存的秘密,重要的还是<cache/>这个标签,因为它的存在就对应着每个mapperxml中的一个具体Cache类,而这个类在每个mapperxml中又是同一个,所以最终的值是放入了Cache类中,key为CacheKey,value就是sql执行的结果。
至于为什么需要事务提交才能命中二级缓存,我们看下put方法就知道
这里的putObject并没有真正的把值存入Cache中,而是存入了待提交的Map中,所以再来看下commit做了什么
具体看tcmcommit()
而这里可以看到此处会遍历所有的TransactionCache并执行commit方法
真相就出来了,会遍历待提交的Map然后把里面的值都存入Cache中,所以后面的查询就能直接从Cache中拿到值了。
总结
二级缓存先会把Sqlsession中的Executor包装成包装成CacheingExecutor,所有的sql都会经过这个类,而该类通过mapperxml中配置的唯一<cache/>标签生成的Cache类存放每个方法执行的结果
比如我们在引入了jdbc的配置文件使用了properties标签,引入jdbc有什么好处?,可以在配置文件中统一管理
内容而不是在很多个文件改来改去,而且在核心配置文件中把数据库连接相关的写死,显然是硬编码的所以我们用配置文件代替 nice!!!
你可能注意到了上面的写法 用前缀jdbc 可以很好地将他们与其他的变量区分开,(可以从名字很容易看出是jdbc相关的数据,不至于和同名变量搞混因为username这种可能
不止会出现在数据库的连接)
上面的代码中引入配置文件的部分为
可以从上面看到写法:
下面这段就是用来设置类的别名:
那么问题来了,为什么要有类的别名这种 *** 作??
因为在映射文件中每次都要写全类名显然有点麻烦比如下面这样:
一个项目是会有很多个映射文件的为了方便,所以类别名就出现了。可以在核心配置文件写接口类和对应的别名
这样就可以在映射文件的命名空间里可以直接写User(对大小没有要求也可以是user; 其实可以比这更加简单,也是我们在实际开发中常用的写法
就是将整个包写成别名的形式,如果不写alias属性默认为类名(不区分大小写),这样就容易多了,我们只需一行代码,便可以在所有的映射文件命名空间
中直接写对应的类名
引入核心的配置文件
首先需要思考的这里是映射文件的引入,我们正常的一个项目的数据库是有很多个表组成的那么每一张表对应一个mapper接口,每个接口对应一个映射文件,那么就需要导入大量的映射文件,还容易漏掉-->
上面这种以包的形式的导入非常方便,不用每次新建一个接口就要导入它的映射文件,但是上面这种写法需要 注意 一些问题:
如果你在映射文件中编写查询语句的sql,但是粗心的你忘记了设置返回类型会在控制台抛异常且会看到这样的说明:
It's likely that neither a Result Type nor a Result Map was specified
下面只是指定返回类型的一种方式:resultType,还有 resultMap
它们的区别:
查询的标签必须指定resultType或resultMap
comkobedumybatis 获取参数的两种方式:${} 和 #{}
上面是使用了 #{}写法相当于原生jdbc的占位符,这个前面已经提到过了所以不多赘述, 需要注意的是#{}里面的变量名可以是任意的username规范显然很好,但是aaaa也没错因为只是用来占位的;
还有就是在使用${}时注意''单引号问题,因为${}是字符拼接的方式,所以需要注意!!
传输参数时有多个参数时
在测试代码里通过传入两个参数分别为 username和password 但是在上面代码的(映射文件里的部分代码)执行失败,(sql语句未能解析)
报错:
Cause: orgapacheibatisbindingBindingException: Parameter 'username' not found Available parameters are [arg1, arg0, param1, param2]
可以从错误提示的信息不难发现我们的参数在映射文件里未能真正地接受到,可以用[arg1, arg0, param1, param2] 的方式获取,mybatis将参数放到map容器可以通过建arg0,agr1的方式
获取参数(也可以是param1,param2)
将上面的代码改动:
需要注意的是:使用${}时需要手动添加''才能正常访问,因为他的处理方式是字符串的拼接
做了改动之后结果很感人!!
User{id=6, userName='旺财', age=20, password='cwlz'}
可以直接通过键访问相对应的值(通过自己的方式访问到数据,上面的形式是mybatis默认提供的map和mybatis默认的提取指的方式 arg0,arg2)
当需要传多个参数时将他们放到一个map容器,然后将map传给对应的方法(模拟mybatis的做法,就可以在sql语句中直接通过键访问到值)代码如下:
映射文件中的部分代码 :
通过键直接获取值,注意:使用${}时不要忘了单引号!!!!
当参数以实体对象的形式传参时如何解决?
只需要通过#{}以属性名的方式访问!
所以代码的编写一定要规范,才能减少这种错误!!!
一定要和注解中的参数名一一对应!!!
如果查询的结果只有一个,也可以通过Map集合接收,字段名为键字段的值为值:{password=0000, id=3, userName=图区, age=20}
javalangIngeger --> int ,Integer
int --> _int,_Integer
Map --> map
String --> string
注意:
所以在批量删除的案例:需要注意的是不能使用#{} 因为它是会自动添加'' 所以在批量删除的语句中我们要使用${}
若字段和属性名不一致 ,则可以通过resultMap设置自定义映射
在mybatis的核心配置文件用下面的代码将 数据库中命名的规范 (user_name) 转换为 java中的命名规范 (userName)
就是手动设置属性与字段的映射关系:
如果设置了手动的设置属性和字段的映射关系,注意主键使用 id 标签,普通字段使用 result标签,就算属性和字段名一一对应,只要
用了这种方式就 必须要写全 !!!
一对多的查询:
通过分步查询实现:
多条件的查询
if 根据标签中test的属性所对应的表达式决定标签中的内容是否拼接到sql语句中
上面的where后面的 1=1 是细节,因为当where后面的条件都为空时就成了 select from t_user where
显然这种sql语句是有问题的,还有一种情况就是当userName为null时语句就成了 select from t_user where and age=#{age}
这也是错的,所以在where后加一个恒成立的条件不仅不会影响查询结果,而且没有会在特定情况时sql语句是会报错的所以很有必要
where 当where标签中有内容时,会自动生成where关键字,并且将内容前多余的and 或者or去掉
当where中没有内容时,此时where标签没有任何效果 就是不会生成关键字 注意:在写条件时不能在后面加and or 这个在下一条语句无效时mybatis不会帮你去掉!
相当于 if else
一个案例 -->就是当我们需要批量删除一些东西时(参数以数组的形式传入)
sql 片段: 在我们的查询语句不能在实际开发中也一直写 ;因为我们要按需查找,不必将不需要的也查询出来,我们可以将我们平常查询次数较多的字段
放在sql片段内,可以在需要查询时直接进行引用!
缓存,这个术语我们听过很多次,在web阶段时访问网页时有缓存机制!
现在sql的查询时也有缓存机制,有一级缓存,一级缓存是默认开启的,一级缓存的范围时sqlSession,将我们查询到的数据先进行缓存,若下次有相同的查询时不用重新
访问数据库,可以直接从缓存中取出!!!!
手动清空缓存 sqlSessionclearCache();
在mapper配置文件中添加cache标签可以设置一些属性:
逆向工程就是不难理解,我们之前都是由实体类到数据库,而逆向类就是通过数据库表生成实体类,
11、Mybatis一级缓存测试
1 package megacltest;
2
3 import megacldomainUser;
4 import megaclutilMyBatisUtil;
5 import orgapacheibatissessionSqlSession;
6 import orgjunitTest;
7
8 /
9 @author gacl
10 测试一级缓存
11 /
12 public class TestOneLevelCache {
13
14 /
15 一级缓存: 也就Session级的缓存(默认开启)
16 /
17 @Test
18 public void testCache1() {
19 SqlSession session = MyBatisUtilgetSqlSession();
20 String statement = "megaclmappinguserMappergetUser";
21 User user = sessionselectOne(statement, 1);
22 Systemoutprintln(user);
23
24 /
25 一级缓存默认就会被使用
26 /
27 user = sessionselectOne(statement, 1);
28 Systemoutprintln(user);
29 sessionclose();
30 /
31 1 必须是同一个Session,如果session对象已经close()过了就不可能用了
32 /
33 session = MyBatisUtilgetSqlSession();
34 user = sessionselectOne(statement, 1);
35 Systemoutprintln(user);
36
37 /
38 2 查询条件是一样的
39 /
40 user = sessionselectOne(statement, 2);
41 Systemoutprintln(user);
42
43 /
44 3 没有执行过sessionclearCache()清理缓存
45 /
46 //sessionclearCache();
47 user = sessionselectOne(statement, 2);
48 Systemoutprintln(user);
49
50 /
51 4 没有执行过增删改的 *** 作(这些 *** 作都会清理缓存)
52 /
53 sessionupdate("megaclmappinguserMapperupdateUser",
54 new User(2, "user", 23));
55 user = sessionselectOne(statement, 2);
56 Systemoutprintln(user);
57
58 }
59 }
12、Mybatis二级缓存测试
1、开启二级缓存,在userMapperxml文件中添加如下配置
<mapper namespace="megaclmappinguserMapper">
<!-- 开启二级缓存 -->
<cache/>
2、测试二级缓存
1 package megacltest;
2
3 import megacldomainUser;
4 import megaclutilMyBatisUtil;
5 import orgapacheibatissessionSqlSession;
6 import orgapacheibatissessionSqlSessionFactory;
7 import orgjunitTest;
8
9 /
10 @author gacl
11 测试二级缓存
12 /
13 public class TestTwoLevelCache {
14
15 /
16 测试二级缓存
17 使用两个不同的SqlSession对象去执行相同查询条件的查询,第二次查询时不会再发送SQL语句,而是直接从缓存中取出数据
18 /
19 @Test
20 public void testCache2() {
21 String statement = "megaclmappinguserMappergetUser";
22 SqlSessionFactory factory = MyBatisUtilgetSqlSessionFactory();
23 //开启两个不同的SqlSession
24 SqlSession session1 = factoryopenSession();
25 SqlSession session2 = factoryopenSession();
26 //使用二级缓存时,User类必须实现一个Serializable接口===> User implements Serializable
27 User user = session1selectOne(statement, 1);
28 session1commit();//不懂为啥,这个地方一定要提交事务之后二级缓存才会起作用
29 Systemoutprintln("user="+user);
30
31 //由于使用的是两个不同的SqlSession对象,所以即使查询条件相同,一级缓存也不会开启使用
32 user = session2selectOne(statement, 1);
33 //session2commit();
34 Systemoutprintln("user2="+user);
35 }
36 }
13、二级缓存补充说明
1 映射语句文件中的所有select语句将会被缓存。
2 映射语句文件中的所有insert,update和delete语句会刷新缓存。
3 缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。
4 缓存会根据指定的时间间隔来刷新。
5 缓存会存储1024个对象
cache标签常用属性:
<cache
eviction="FIFO" <!--回收策略为先进先出-->
flushInterval="60000" <!--自动刷新时间60s-->
size="512" <!--最多缓存512个引用对象-->
readOnly="true"/> <!--只读-->
通常为了减轻数据库的压力,我们会引入缓存。在Dao查询数据库之前,先去缓存中找是否有要找的数据,如果有则用缓存中的数据即可,就不用查询数
据库了。如果没有才去数据库中查找。这样就能分担一下数据库的压力。另外,为了让缓存中的数据与数据库同步,我们应该在该数据发生变化的地方加入更新缓存
的逻辑代码。这样无形之中增加了工作量,同时也是一种对原有代码的入侵。这对于有着代码洁癖的程序员来说,无疑是一种伤害。
MyBatis框架早就考虑到了这些问题,因此MyBatis提供了自定义的二级缓存概念,方便引入我们自己的缓存机制,而不用更改原有的业务逻辑。
MyBatis二级缓存使用的在某些场景下会出问题,来看一下为什么这么说。
假设我有一条select语句(开启了二级缓存):
selectacol1, acol2, acol3, bcol1, bcol2, bcol3fromtableA a, tableB bwhereaid= bid;
对于tableA与tableB的 *** 作定义在两个Mapper中,分别叫做MapperA与MapperB,即它们属于两个命名空间,如果此时启用缓存:
MapperA中执行上述sql语句查询这6个字段
tableB更新了col1与col2两个字段
MapperA再次执行上述sql语句查询这6个字段(前提是没有执行过任何insert、delete、update *** 作)
此时问题就来了,即使第(2)步tableB更新了col1与col2两个字段,第(3)步MapperA走二级缓存查询到的这6个字段依然是原来的这6个字段的值,因为我们从CacheKey的3组条件来看:
<select>标签所在的Mapper的Namespace+<select>标签的id属性
RowBounds的offset和limit属性,RowBounds是MyBatis用于处理分页的一个类,offset默认为0,limit默认为IntegerMAX_VALUE
<select>标签中定义的sql语句
对于MapperA来说,其中的任何一个条件都没有变化,自然会将原结果返回。
这个问题对于MyBatis的二级缓存来说是一个无解的问题,因此使用MyBatis二级缓存有一个前提: 必须保证所有的增删改查都在同一个命名空间下才行 。
以上就是关于mybatis二级缓存原理全部的内容,包括:mybatis二级缓存原理、mybatis各阶段的详解、mybatis怎么实现局部缓存等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)