动态 SQL if <if test=""></if>
通过 test 表达式,拼接 SQL语句。
EmpMapper 接口类中新建 getEmpListByMultipleCondition 方法,该方法用于执行多条件查询。1 2 3 4 5 6 7 8 9 10 11 package com.atguigu.mapper;import java.util.List;import com.atguigu.bean.Emp;public interface EmpMapper { List<Emp> getEmpListByMultipleCondition (Emp emp) ; }
EmpMapper.xml 映射文件中新增 if 标签,用于判断值是否为null 或者为空字符串。进行 SQL 的拼接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><mapper namespace="com.atguigu.mapper.EmpMapper" > <!--List<Emp> getEmpListByMultipleCondition () ;--> <select id="getEmpListByMultipleCondition" resultType="Emp" > select eid, ename, age, sex, did from emp where 1 =1 <if test="eid != null" > and eid = #{eid} </if> <if test="ename != null and ename!=''" > and ename = #{ename} </if> <if test="age != null" > and age = #{age} </if> <if test="sex == 1 or sex == 0" > and sex = #{sex} </if> </select> </mapper>
注意⚠️:上面加上 where 1=1 的目的是防止后面条件所有为空(也就是一个条件也没有)或者其他情况下导致 SQL 的拼接不完成。
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.atguigu.test;import static org.junit.Assert.*;import java.io.IOException;import java.io.InputStream;import java.util.List;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.junit.Test;import com.atguigu.bean.Emp;import com.atguigu.mapper.EmpMapper;public class TestSQL { @Test public void testIf () throws IOException { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession openSession = sqlSessionFactory.openSession(true ); EmpMapper mapper = openSession.getMapper(EmpMapper.class ) ; Emp emp = new Emp(); emp.setEname("zhangsansan" ); emp.setAge(33 ); List<Emp> emps = mapper.getEmpListByMultipleCondition(emp); System.out.println(emps); } public SqlSessionFactory getSqlSessionFactory () throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-conf.xml" ); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); return sqlSessionFactory; } }
where where
标签的作用是添加 where 关键字,并且解决条件中第一个 and 或者 or 的问题。上面 if 中,我们为不让 SQL 条件拼接出错,我们在 SQL 中 where 的后面添加了 1=1 的恒等式,且不影响 SQL 的查询结果。除了上面在 where 后面添加 1=1 外,我们还可以用 where 标签来实现,他会帮我们解决第一个多的 and。(例如下面 eid != null 时,SQL 的拼接会在 where 后面多一个 and,这个 and where 标签就会帮我们自动去掉。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><mapper namespace="com.atguigu.mapper.EmpMapper" > <!--List<Emp> getEmpListByMultipleCondition () ;--> <select id="getEmpListByMultipleCondition" resultType="Emp" > select eid, ename, age, sex, did from emp <where> <if test="eid != null" > and eid = #{eid} </if> <if test="ename != null and ename!=''" > and ename = #{ename} </if> <if test="age != null" > and age = #{age} </if> <if test="sex == 1 or sex == 0" > and sex = #{sex} </if> </where> </select> </mapper>
trim trim 可以在条件判断完的SQL语句前后 添加或者去掉指定的字符, prefix: 添加前缀,prefixOverrides: 去掉前缀suffix: 添加后缀,suffixOverrides: 去掉后缀。
例如下面的情况下,可能会在最后多出一个 or 或者 and 导致 SQL 语法错误,且前面差一个 where,所以可以使用 trim 标签在 prefix 中写入 where,suffixOverrides 写入 and|or。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <select id ="getEmpListByMultipleCondition2" resultType ="Emp" > select eid, ename, age, sex, did from emp <trim prefix ="where" suffixOverrides ="and|or" > <if test ="eid != null" > eid = #{eid} and </if > <if test ="ename != null and ename!=''" > ename = #{ename} and </if > <if test ="age != null" > age = #{age} or </if > <if test ="sex == 1 or sex == 0" > sex = #{sex} </if > </trim > </select >
set set 的作用是添加 set 标签,并且解决修改操作中SQL语句中可能多出逗号的问题。(用得较少,因为 trim 也解决逗号多余的问题)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <update id ="updateEmpByMutipleCondition" > update emp <set > <if test ="ename != null and ename!=''" > ename=#{ename}, </if > <if test ="age != null" > age = #{age}, </if > <if test ="sex == 1 or sex == 0" > sex = #{sex} </if > </set > where eid = #{eid} </update >
choose choose 主要是用于分支判断,类似于 if…else if …结构。只会满足所有分支中的一个,如果 when 中都不满足,还可以设置 otherwise。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <select id ="getEmpListByChoose" resultType ="Emp" > select eid, ename, age, sex from emp where <choose > <when test ="eid != null" > eid=#{eid} </when > <when test ="ename != null and ename!=''" > ename=#{ename} </when > <when test ="age != null" > age=#{age} </when > <otherwise > sex = #{sex} </otherwise > </choose > </select >
或者在插入数据的时候,对某些值进行判断转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <insert id ="insertEmp" > insert into emp values( null, #{ename}, #{age}, <choose > <when test ="sex == 0" > 'nv', </when > <when test ="sex == 1" > 'nan', </when > <otherwise > 'unknow', </otherwise > </choose > #{did} ) </insert >
1 2 void insertEmp (Map<String, Object> emp) ;
1 2 3 4 5 6 HashMap<String,Object> hashMap = new HashMap<>(); hashMap.put("age" , 90 ); hashMap.put("sex" , "0" ); hashMap.put("ename" , "aa" ); hashMap.put("did" , 9 ); mapper.insertEmp(hashMap);
因为在 emp 表中 did 是部门 id,而在实体类中的表现方式是 dept 的实体类属性,所以我们在设计 insertEmp 方法的时候就最好不要传入 Emp 对象。因为 Emp 实体里面不是 did 属性,而是 dept 类型信息。所以这里采用传入 一个 Map 的方式更好些。
foreach 批量删除方式1 这里不实用 foreach 进行批量删除,采用将要删除的 id 进行用 ,进行拼接。
1 2 void deleteMutipleEmp (String eids) ;
1 2 3 4 <delete id ="deleteMutipleEmp" > delete from emp where eid in (${value}) </delete >
注意⚠️:因为这里传入的参数只有一个,且是批量删除,所以不能使用 #{}
来获取值,要改为 ${}
, ${}
在一个参数的情况下只支持 ${value}
和 ${_parameter}
两种写法。
测试删除
1 2 String eids = "1,3,4" ; mapper.deleteMutipleEmp(eids);
批量删除方式2 使用 foreach 实现批量删除。foreach 标签的作用就是对一个数组或者集合进行遍历。这种情况下就不用担心 #{}
和 ${}
的区别,因为不是直接将传递进来的参数进行赋值,而是使用 foreach 进行了拼接。
foreach 形式如下:
1 2 3 <foreach collection ="" close ="" index ="" item ="" open ="" separator ="" > ... </foreach >
collection: 用于指定要遍历的集合或者数组。 item: 当前从集合中迭代出的元素。 close: 结束字符。 open: 开始字符。 separator: 用于设置每次循环之间的分隔符。即元素与元素之间的分隔符 若遍历的是 List 集合 index 代表索引下标,若遍历的是 map 集合,则 index 代表当前元素的 key。
EmpMapper 接口种方法如下:
1 2 void deleteMutipleEmpByList (List<Integer> eids) ;
EmpMapper.xml 种配置如下:
1 2 3 4 5 6 7 8 9 <delete id ="deleteMutipleEmpByList" > delete from emp where eid in ( <foreach collection ="list" item ="eid" separator ="," > #{eid} </foreach > ) </delete >
注意⚠️:因为接口方法 deleteMutipleEmpByList 中传递的参数是一个 list,MyBatis 会将 List 或 Array 放在 map 中,List 以 list 为键,Array 以 array 为键。所以这里 collection 中使用 list 才能取到传递过来的值。item 是每次迭代出的元素,separator 是每次迭代元素与元素之间的分隔符。
或者上面的 EmpMapper.xml 可以改写成这样,设置 close 结束字符和 open 开始字符。
1 2 3 4 5 6 <delete id ="deleteMutipleEmpByList" > delete from emp where eid in <foreach collection ="list" item ="eid" separator ="," open ="(" close =")" > #{eid} </foreach > </delete >
批量删除方式3 第三种方式与上面两种相比,就是 SQL 语句不一样而已。上面的 SQL 语句是 delete from emp where eid in ()
的方式,但批量删除其实还可以这样写 delete from emp where eid=1 or eid=2 or eid=3
。这样的话,xml 中 SQL 拼接方式如下:
1 2 3 4 5 6 7 <delete id ="deleteMutipleEmpByList" > delete from emp where <foreach collection ="list" item ="eid" separator ="or" > eid = #{eid} </foreach > </delete >
SQL 设置一个 SQL 片段,即公共的 SQL,可以被当前的映射文件中所有的 SQL 语句所访问。例如以前 xml 中某个 SQL 如下:
1 2 3 4 5 6 <insert id ="insertMutipleEmp" > insert into emp value <foreach collection ="array" item ="empItem" separator ="," > (null, #{empItem.ename}, #{empItem.age}, #{empItem.sex}, 1) </foreach > </insert >
可以使用 SQL 标签将 emp value
字段进行封装成一个片段 (这里封装 emp value 字段可能不太合适,应该封装得更完整和全面些,这里只是举例说明)。然后使用 include 标签进行包含。
1 2 3 4 5 6 7 8 9 10 11 12 <sql id ="insertMutipleEmpColumns" > emp value </sql > <insert id ="insertMutipleEmp" > insert into <include refid ="insertMutipleEmpColumns" > </include > <foreach collection ="array" item ="empItem" separator ="," > (null, #{empItem.ename}, #{empItem.age}, #{empItem.sex}, 1) </foreach > </insert >
批量操作 批量新增 批量新增的 SQL 格式为 insert into emp values(), (), ()
所以我们也需要使用 foreach 进行拼接。接口中的方法如下:
1 2 void insertMutipleEmp (Emp[] emps) ;
映射文件中内容如下:
1 2 3 4 5 6 7 <insert id ="insertMutipleEmp" > insert into emp value <foreach collection ="array" item ="empItem" separator ="," > (null, #{empItem.ename}, #{empItem.age}, #{empItem.sex}, 1) </foreach > </insert >
注意⚠️:接口中的 insertMutipleEmp 方法是使用数组进行传值过来的,所以这里 collection 中需要使用 array 进行接收,迭代的时候使用 .
就可以获取里面的值。
测试方法:
1 2 3 4 5 6 7 Emp emp2 = new Emp(null , "tiantian" , 22 , "1" ); Emp emp3 = new Emp(null , "lanlan" , 45 , "0" ); Emp emp4 = new Emp(null , "wangwang" , 34 , "1" ); Emp emp5 = new Emp(null , "liiu" , 65 , "0" ); Emp[] emps = {emp2, emp3, emp4, emp5}; mapper.insertMutipleEmp(emps);
批量修改 如果批量修改修改的内容是一致的,那么 SQL 语句的写法和第一种批量删除的方式是一样的,都是使用 in。这种方式就需要注意 #{}
和 #()
的区别。
但是这里每条数据修改的是不一样的内容,所以这里要修改多条数据,就要写多条 SQL 语句。因为是一次性执行多条 SQL 所以必须在 jdbc 的 url 地址后面添加参数 allowMultiQueries=true
,即 jdbc.url = jdbc:mysql://localhost:3306/ssm?allowMultiQueries=true
。这里也就不必考虑 #{}
和 #()
的区别。
对应的 SQL 格式大致如下:
1 2 3 update emp set ... where ein = 1; update emp set ... where ein = 2; update emp set ... where ein = 3;
接口中的 updateMutipleEmp 方法
1 2 void updateMutipleEmp (Emp[] emps) ;
xml 映射文件内容如下:
1 2 3 4 5 6 <update id ="updateMutipleEmp" > <foreach collection ="array" item ="empItem" > update emp set ename=#{empItem.ename}, age=#{empItem.age}, sex=#{empItem.sex} where eid = #{empItem.eid}; </foreach > </update >
测试内容如下:
1 2 3 4 5 6 Emp emp2 = new Emp(15 , "zhenzhen1" , 22 , "1" ); Emp emp3 = new Emp(16 , "zhenzhen2" , 23 , "0" ); Emp emp4 = new Emp(17 , "zhenzhen3" , 24 , "1" ); Emp emp5 = new Emp(18 , "zhenzhen4" , 25 , "0" ); Emp[] emps2 = {emp2, emp3, emp4, emp5}; mapper.updateMutipleEmp(emps2);
Mybatis 的缓存 一级缓存测试 Mybatis 中的一级缓存默认是开启 的,是 SqlSession 级别的。即同一个 SqlSession 对于一个 sql 语句,执行之后就会存储在缓存中,下次执行相同的 SQL 的时候直接从缓存中取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void test () throws IOException { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession openSession = sqlSessionFactory.openSession(true ); EmpMapper mapper = openSession.getMapper(EmpMapper.class ) ; System.out.println("第一次执行会显示出 SQL 语句" ); Emp empByEid1 = mapper.getEmpByEid("10" ); System.out.println(empByEid1); System.out.println("-------------------------------" ); System.out.println("执行相同的查询,不会再次出现 SQL 语句,因为被缓存了所以直接从缓存中读取" ); Emp empByEid2 = mapper.getEmpByEid("10" ); System.out.println(empByEid2);
上面查询示例中都是查询 id 为 10 的 emp 信息,且都是同一个 SqlSession,所以第二次查询的时候不会再次显示 SQL 了。
一级缓存失效的几种情况
不同的 SqlSession 对应不同的一级缓存
同一个 SqlSession 但是查询条件不同
同一个 SqlSession 两次查询期间执行了任何一次增删改操作
同一个 SqlSession 两次查询期间手动清空了缓存。使用 openSession.clearCache();
方法进行清空
二级缓存 二级缓存默认不开启,需要手动配置
在 mybits 核心配置文件中新增标签 <setting name="cacheEnabled" value="true"/>
POJO 中实现 Serializable 接口。
需要在 xml 映射文件中配置 <cache>
标签。
二级缓存需要在 Sqlsession 关闭或者提交后才能生效。
<cache>
标签中可以进行一些属性的设置
eviction=“FIFO”: 缓存回收策略(默认的是 LRU): LRU – 最近最少使用的:移除最长时间不被使用的对象。 FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。 WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval:刷新间隔,单位毫秒 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
size:引用数目,正整数 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readOnly:只读,true/false true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。 false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
EmpMapper.xml 中配置 <cache>
标签
POJO 中实现 Serializable 接口
1 2 3 4 public class Emp implements Serializable { private static final long serialVersionUID = 1L ; ...... }
测试缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void testSecondCache () throws IOException { SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); SqlSession openSession = sqlSessionFactory.openSession(true ); EmpMapper mapper = openSession.getMapper(EmpMapper.class ) ; Emp empByEid1 = mapper.getEmpByEid("10" ); System.out.println(empByEid1); openSession.commit(); System.out.println("-------------------------------" ); EmpMapper mapper2 = openSession.getMapper(EmpMapper.class ) ; Emp empByEid2 = mapper2.getEmpByEid("10" ); System.out.println(empByEid2); }
二级缓存的注意点
需要在全局 setting 中设置 cacheEnable ,配置二级缓存的开关。并在 xml 文件中使用 <cache />
标签来应用,这样这个 xml 中所有的 select 查询都开启了二级缓存。一级缓存一直是打开的,无法关闭。
如果某个查询不想使用二级缓存,那么我们可以在 select 标签中加上 useCache 属性来设置是否使用。
1 <select id ="getEmpListByMultipleCondition" resultType ="Emp" useCache ="false" >
增删改操作的标签后面都可以配置 flushCache 属性,默认为 true。表示增删改操作后是否刷新缓存。默认增删改后都是会清空一级和二级的。不建议将其设置为 false。
1 <update id ="updateEmpByMutipleCondition" flushCache ="true" >
openSession.clearCache(); 方法只能清除一级缓存。
整合第三方缓存 这里使用的是第三方的 EhCache 缓存。EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate 中默认的 CacheProvider。
整合步骤:
导入 jar 包
1 2 3 4 ehcache-core-2.6.8.jar mybatis-ehcache-1.0.3.jar slf4j-api-1.6.1.jar slf4j-log4j12-1.6.2.jar
编写ehcache.xml配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation ="../config/ehcache.xsd" > <diskStore path ="D:\atguigu\ehcache" /> <defaultCache maxElementsInMemory ="1000" maxElementsOnDisk ="10000000" eternal ="false" overflowToDisk ="true" timeToIdleSeconds ="120" timeToLiveSeconds ="120" diskExpiryThreadIntervalSeconds ="120" memoryStoreEvictionPolicy ="LRU" > </defaultCache > </ehcache >
配置cache标签
1 <cache type ="org.mybatis.caches.ehcache.EhcacheCache" > </cache >
代码地址
逆向工程 MyBatis Generator: 简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及bean类。支持基本的增删改查,以及QBC风格的条件查询。但是表连接、存储过程等这些复杂sql的定义需要我们手工编写。官方文档地址 http://www.mybatis.org/generator/ 官方工程地址 https://github.com/mybatis/generator/releases
导入逆向工程的jar包
1 mybatis-generator-core-1.3.2.jar
编写 MBG 的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration > <context id ="DB2Tables" targetRuntime ="MyBatis3Simple" > <jdbcConnection driverClass ="com.mysql.jdbc.Driver" connectionURL ="jdbc:mysql://localhost:3306/ssm" userId ="root" password ="123456" > </jdbcConnection > <javaTypeResolver > <property name ="forceBigDecimals" value ="false" /> </javaTypeResolver > <javaModelGenerator targetPackage ="com.atguigu.bean" targetProject ="./src" > <property name ="enableSubPackages" value ="true" /> <property name ="trimStrings" value ="true" /> </javaModelGenerator > <sqlMapGenerator targetPackage ="com.atguigu.mapper" targetProject ="./conf" > <property name ="enableSubPackages" value ="true" /> </sqlMapGenerator > <javaClientGenerator type ="XMLMAPPER" targetPackage ="com.atguigu.mapper" targetProject ="./src" > <property name ="enableSubPackages" value ="true" /> </javaClientGenerator > <table tableName ="emp" domainObjectName ="Emp" > </table > <table tableName ="dept" domainObjectName ="Dept" > </table > </context > </generatorConfiguration >
自动生成信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.atguigu.test;import java.io.File;import java.util.ArrayList;import java.util.List;import org.mybatis.generator.api.MyBatisGenerator;import org.mybatis.generator.config.Configuration;import org.mybatis.generator.config.xml.ConfigurationParser;import org.mybatis.generator.internal.DefaultShellCallback;public class TestSQL { public static void main (String[] args) throws Exception { List<String> warnings = new ArrayList<String>(); boolean overwrite = true ; File configFile = new File("mbg.xml" ); ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration(configFile); DefaultShellCallback callback = new DefaultShellCallback(overwrite); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null ); } }
运行之后就能自动生成 bean,mapper 接口类,mapper.xml 中的信息。
代码地址