动态sql
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。( 借助了OGNL 的表达式)
常用的标签:
- if
- choose(when,otherwise)
- trim(when,set)
- foreach
- where
1、if标签(常常与where标签一起配合使用)
直接上xml文件演示了
<!--测试where 和 if标签组合使用
if标签就和if条件判断语句一样一样的(若只用where标签的话,若一些值为空,可能会造成拼接麻烦,出现多余的and或or关键字)
where:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,*where* 元素也会将它们去除。
-->
<select id="selectByMoreProperties" resultType="com.kaisi.bean.Emp">
select * from emp
<where>
<if test="empno!=null and empno!=''">
empno=#{empno}
</if>
<if test="ename!=null and ename!=''">
and ename=#{ename}
</if>
</where>
</select>
2、trim标签,用来截取字符串(自定义的where)
<!--
trim截取字符串:
prefix:前缀,为sql整体添加一个前缀
prefixOverrides:去除整体字符串前面多余的字符
suffixOverrides:去除后面多余的字符串
-->
<select id="getEmpByCondition" resultType="com.kaisi.bean.Emp">
select * from emp
<trim prefix="where" prefixOverrides="and" suffixOverrides="and">
<if test="empno!=null">
empno > #{empno} and
</if>
<if test="ename!=null">
ename like #{ename} and
</if>
<if test="sal!=null">
sal > #{sal} and
</if>
</trim>
</select>
3、foreach标签
动态sql中唱用于对集合进行遍历(尤其是在构建in条件语句的时候)
<!--
这里测试foreach标签:
collection:传入的集合名
close:关闭标签
open:开始标签
item:定义下集合中的遍历到的数据叫什么,也就是遍历出来的元素值
separator:分割符
index:没什么用,就是索引标识。
注意:item中定义参数名是需要与#{}中参数一致
还需要注意一点,就是有时候会读取不到你定义的参数名,比如刚才就找不到deptnos
这个list集合,那么就去接口中加入@Param("deptnos")即可
-->
<select id="selectByArry" resultType="com.kaisi.bean.Emp">
select * from emp where deptno in
<foreach collection="deptnos" close=")" open="(" item="jqk" separator="," index="idx" >
#{jqk}
</foreach>
</select>
4、choose标签:多种条件,但是只选择其中一种就会用到choose标签
<select id="selectByChoose" resultType="com.kaisi.bean.Emp">
select * from emp
<where>
<choose>
<when test="empno!=null and empno!=''">
empno >= #{empno}
</when>
<when test="sal!=null and sal!=''">
and sal > #{sal}
</when>
<!--
下面这个我是为了防止全表查询,没别的意思
-->
<otherwise>
1!=1
</otherwise>
</choose>
</where>
</select>
5、set标签:用于动态更新语句的标签就是set,set元素可以动态的包含需要更新的列,忽略其它不更新的列。
<!--
set标签用于动态更新语句的类似解决方案叫
做 *set*。*set* 元素可以用于动态包含需要更新的列,忽略其它不更新的列。
-->
<update id="updateByEmp">
update emp
<set>
<if test="empno!=null and empno!=''">
empno=#{empno},
</if>
<if test="ename!=null and ename!=''">
ename=#{ename},
</if>
<if test="sal!=null and sal!=''">
sal=#{sal}
</if>
</set>
<where>
empno=#{empno}
</where>
</update>
Mybatis中的缓存
如果没有缓存,那么每次查询的时候都需要从数据库中加载数据,这回造成io的性能问题,所以,在很多情况下
如果连续执行两条相同的sql语句,可以直接从缓存中获取,如果获取不到,那么再去查询数据库,这意味着查询完成的结果
需要放到缓存中。
缓存分类:
1、一级缓存:表示sqlSession级别的缓存,每次查询的时候都会开启一个会话,想到于一次连接,当会话关闭,那么sqlSession中缓存的数据会失效
2、二级缓存:二级缓存是全局范围的缓存,二级缓存有个先决条件就是sqlSession关闭后才会生效。
3、第三方缓存:用第三方缓存组件,来当做缓存
一级缓存:
表示将数据存储在sqlsession中,关闭之后自动失效,默认情况下是开启的
在同一个会话之内,如果执行了多个相同的sql语句,那么除了第一个之外,所有的数据都是从缓存中进行查询的
//测试一级缓存:理想状态,emp第一次查询会出现sql语句进行去数据库查询,第二次emp2则会走默认的一级缓存
@Test
public void t10(){
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp emp = mapper.selectByEmpno(7369);
System.out.println(emp);
Emp emp2 = mapper.selectByEmpno(7369);
System.out.println(emp2);
sqlSession.close();
}
从下面运行结果就可以看出,只执行了一条sql查询语句
在某些情况下,一级缓存会失效
1、在同一个方法体中,并不是只会有一次会话,不同会话之间的缓存并不会进行共享,记住是sqlSession级别就好理解多了
//理想状态,2次会话,2次查询,出现2条sql语句
@Test
public void t11(){
//sqlSession一号准备完毕
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
//sqlSession二号准备完毕
SqlSession sqlSession1 = sqlSessionFactory.openSession();
EmpDao mapper1 = sqlSession1.getMapper(EmpDao.class);
Emp emp = mapper.selectByEmpno(7369);
Emp emp1 = mapper1.selectByEmpno(7369);
System.out.println(emp);
System.out.println(emp1);
sqlSession.close();
}
从下图不难看出 (- - 哎,是个人都猜到了,我还写出来…)
2、当传递对象的时候,如果对象中的属性值不同,也不会走缓存
(这句可以当我没说,我查的东西都不一样,缓存中怎么可能会有)
3、在多次的查询中,如果修改了数据,那么缓存会失效。
//理想状态,在一次会话中,俩次查询中间进行修改操作,第二次查询会查询数据库
@Test
public void t12 (){
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp emp = mapper.selectByEmpno(7369);
Emp empUpdate = new Emp();
empUpdate.setEmpno(7369);
empUpdate.setEname("KAISI");
int i = mapper.updateByEmp(empUpdate);
Emp emp1 = null;
if (i==1){
emp1 = mapper.selectByEmpno(7369);
}
System.out.println(emp);
System.out.println(emp1);
sqlSession.commit();
sqlSession.close();
}
发生修改后,一级缓存就会失效,第二次的查询依旧需要查询数据库
4、如果在一个会话过程中,手动清空了缓存,那么缓存也会失效(这句…各位同僚都懂,就不演示了哈)
二级缓存:
二级缓存表示全局缓存,前提的条件就是sqlSession必须关闭才会生效
默认情况下二级缓存是不开启,还需要进行以下配置才能生效
1、修改全局配置文件,在settings中添加配置
2、指定在哪个映射文件中必须添加缓存配置
3、与映射文件对应的实体类必须实现序列化接口
public class Emp implements Serializable(){…}
映射文件中标签中缓存属性:
eviction:表示缓存回收策略,默认是LRU
LRU:最近最少使用的,移除最长时间不被使用的对象
FIFO:先进先出,按照对象进入缓存的顺序来移除
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象
flushInternal:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
size:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readonly:只读,true/false
true:只读缓存,会给所有调用这返回缓存对象的相同实例,因此这些对象不能被修改。
false:读写缓存,会返回缓存对象的拷贝(序列化实现),这种方式比较安全,默认值
//开始测试2级缓存
//理想状态,mapper2进行查询,会走二级缓存,不会出现sql查询语句
@Test
public void t13 (){
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
Emp emp = mapper.selectByEmpno(7369);
System.out.println(emp);
sqlSession.close();
Emp emp2 = mapper2.selectByEmpno(7369);
System.out.println(emp2);
sqlSession2.close();
}
从上述代码中,既有一级缓存,也有二级缓存。如何确定缓存的查询是先查询一级缓存还是先查询二级缓存呢?
@Test
public void t14(){
//一号会话
SqlSession sqlSession = sqlSessionFactory.openSession();
//二号会话
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
Emp emp = mapper.selectByEmpno(7566);
System.out.println("一号会话初次"+emp);
/*
* 关闭一号会话,开启二级缓存,将7566对象数据缓存到二级缓存中去,并且查俩次
* 断个言,3次查询,2次命中,2/3的概率。既然出了3中2的概率,那么查询7566的时候
* 就可以认定走了二级缓存
* */
sqlSession.close();
Emp emp2 = mapper2.selectByEmpno(7566);
System.out.println(emp2);
Emp emp3 = mapper2.selectByEmpno(7566);
System.out.println(emp3);
//查询新的对象使之加入缓冲,既没有关闭sqlSession2,那么何来开启二级缓存
Emp emp4 = mapper2.selectByEmpno(7369);
System.out.println(emp4);
Emp emp5 = mapper2.selectByEmpno(7369);
System.out.println(emp5);
sqlSession2.close();
}
从下图中得出的信息可以看出
Mybatis中缓存先后为
二级缓存---->一级缓存----->数据库
第三方缓存
在一些情况是可以自定义实现缓存,当然了,肯定有可以直接拿来用的
下面用ehcache
<!-- https://mvnrepository.com/artifact/org.ehcache/ehcache -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.0-alpha1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.0-alpha1</version>
<scope>test</scope>
</dependency>
加入ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\ehcache" />
<defaultCache
maxElementsInMemory="1"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
最后一步,在映射配置文件中,加入type属性
哈哈哈,是不是感觉没什么特别的,但是打开D盘就会发现,缓存的数据都会存放到你指定的本地文件中