mybatis入门篇3 ---- 动态sql,缓存,以及分页jar包的使用
首先我们来看一下动态sql,动态sql就是传递的参数不确定的时候,使用if,where,select,choose,set等标签,先来看一下
lib,rescources,以及utils里面文件不变,直接来看使用
直接看if跟where,if使用比较简单,就是if会有一个条件判断,如果条件满足,就会把if里面的sql语句块加入slq语句,where就是帮助我们加载一个where条件判断,并且会把拼接语句中的第一个and删除掉,接下来看一下例子
看一下UserMapper
public interface UserMapper { List<User> getUsers(@Param("username") String username, @Param("password") String password); }
看一下UserMapper.xml
<mapper namespace="com.yang.mapper.UserMapper"> <!-- 这种情况下必须传递username以及password,否则就会报错--> <!-- <select id="getUsers" resultType="com.yang.domain.User">--> <!-- select * from `user` where username=#{username} and password=#{password}--> <!-- </select>--> <!-- 因此可以使用下述来进行处理--> <!-- if标签,如果test里面为真,那么会把if包括的sql语句块拼接到sql查询语句中--> <!-- where标签会自动生成跟删除where语句,并且还可以把第一个语句中and删除掉,如下面中,只会把第一条if判断为真的语句中的and删除--> <select id="getUsers" resultType="com.yang.domain.User"> select * from `user` <where> <if test="username != null and username != ‘‘"> and username=#{username} </if> <if test="password != null and password != ‘‘"> and password=#{password} </if> </where> </select> </mapper>
看一下测试
@Test public void testWhere(){ SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 调用这个封装的where标签,password传入为空,可以看出,执行的sql语句就会不带后面的password List<User> userList = userMapper.getUsers("yang", null); // ==> Preparing: select * from `user` WHERE username=? // 如果全部为空,那么就会忽略所有的if字段 List<User> userList2 = userMapper.getUsers(null, null); // ==> ==> Preparing: select * from `user` // 第一个if条件我们的是带有if的,而sql语句中不存在,因此可以确认,where确实是把第一个语句中的开头的and删除了。 List<User> users = userMapper.getUsers("shi", "5678"); // ==> Preparing: select * from `user` WHERE username=? and password=? for (User user : users) { System.out.println(user); } sqlSession.close(); }
where可以删除第一个语句的前置and,但是无法删除结尾的and,接下来看一下trim标签,该标签可以删除前置或后置的指定的字符串
UserMapper
// trim List<User> getUserList(@Param("username") String username, @Param("password") String password);
UserMapper.xml
<!-- trim标签 prefix:设置前缀,在第一个条件之间加一个前缀,在这里面也就是加一个where prefixOverrides:条件前缀覆盖,把第一个条件之前的指定字符串覆盖,也就是and suffixOverrides:条件后缀覆盖,把最后一个条件之后的指定字符串覆盖,也就是and --> <select id="getUserList" resultType="com.yang.domain.User"> select * from `user` <trim prefix="where" prefixOverrides="and" suffixOverrides="and"> <if test="username != null and username != ‘‘"> and username=#{username} </if> <if test="password != null and password != ‘‘"> and password=#{password} and </if> </trim> </select>
测试类
@Test public void testTrim(){ SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 调用trim标签的映射,在if判断中第一个前置有and,最后一个语句后置and,利用trim的删除,删除掉了 List<User> users = userMapper.getUserList("shi", "5678"); // ==> Preparing: select * from `user` where username=? and password=? for (User user : users) { System.out.println(user); // User{id=2, username=‘shi‘, password=‘5678‘} } // 两个传入都是空值,可以看出不符合if,不执行 List<User> users2 = userMapper.getUserList("", ""); // ==> Preparing: select * from `user` sqlSession.close(); }
接下来看一下choose标签,这个标签的作用就是只要满足一个条件不执行其他条件了,相当于select标签
看一下mapper文件
// choose List<User> getUserChoose(@Param("username") String username, @Param("password") String password);
看一下UserMapper.xml文件
<!--choose标签 判断语句使用when,如果条件满足,就会直接执行这个sql并且后续语句不再执行 如果都不满足,则会执行otherwise语句,相当于java中select的default --> <select id="getUserChoose" resultType="com.yang.domain.User"> select * from `user` <where> <choose> <when test="username != null and username != ‘‘"> username=#{username} </when> <when test="password != null and password != ‘‘"> password=#{password} </when> <otherwise> 1 = 1 </otherwise> </choose> </where> </select>
测试一下
@Test public void testChoose(){ SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 我们可以看出这个两个参数均满足之前所说的条件,但是最终只是执行了最上面的满足条件的语句 List<User> users = userMapper.getUserChoose("shi", "5678"); // ==> Preparing: select * from `user` WHERE username=? for (User user : users) { System.out.println(user); } // 两个都不满足,就会执行otherwise List<User> users2 = userMapper.getUserList("", ""); // ==> Preparing: select * from `user` }
在实际应用时,我们会遇到传入一个主键数组列表,返回对应对象,sql语句中使用的是in(?),不能直接传入数组,因此这时候可以使用forEach标签
看一下接口文件
// forEach, 这个可以传入数组,列表,pojo对象也可以,只要是列表类的就行 List<User> getUserByIds(@Param("idList") Integer[] ids);
映射文件
<!--使用foreach,循环, open就是在前置加一个( close就是在后置加一个) separator就是在两个元素中间使用的分隔符 --> <select id="getUserByIds" resultType="com.yang.domain.User"> select * from `user` where id in <foreach collection="idList" open="(" close=")" separator="," item="ids"> #{ids} </foreach> </select>
看一下测试
@Test public void testForEach() { SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 使用数组或者列表都可以,主要看java中的映射是如何定义的,可以看出,foreach会帮助我们循环传入的数据并格式化为sql需求的样子 List<User> users = userMapper.getUserByIds(new Integer[]{11, 12, 13, 14, 15}); // ==> Preparing: select * from `user` where id in ( ? , ? , ? , ? , ? ) ==> Parameters: 11(Integer), 12(Integer), 13(Integer), 14(Integer), 15(Integer) for (User user : users) { System.out.println(user); } // User{id=11, username=‘mark‘, password=‘1111‘} // User{id=12, username=‘mark‘, password=‘1111‘} // User{id=13, username=‘mark‘, password=‘1111‘} // User{id=14, username=‘mark‘, password=‘1111‘} // User{id=15, username=‘mark‘, password=‘1111‘} }
最后我们看一下bind标签,set标签,bind标签可以取出传入的值,并进行重新处理,赋值给另外一个值,set标签会把最后一个,号去掉
看一下mapper文件
// set 与 bind void updateUser(User user);
看一下我们的映射文件
<!-- bind标签可以取出传入的值,并且进行重新赋值 set可以把更新语句中的最后一个,删除掉 --> <update id="updateUser"> <bind name="username" value="username+‘bind‘" /> update `user` <set> <if test="username != null and username != ‘‘"> username=#{username}, </if> <if test="password != null and password != ‘‘"> password=#{password}, </if> </set> where id=#{id} </update>
看一下测试类
@Test public void update() { SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setUsername("yang"); user.setPassword("1234"); user.setId(2); // 通过sql语句可以看出,虽然名称设置的是yang,但是我们在bind标签中拦截了username并且在最后加了一个bind,最终他们会添加上。 userMapper.updateUser(user); // ==> Preparing: update `user` SET username=?, password=? where id=? ==> Parameters: yangbind(String), 1234(String), 2(Integer) sqlSession.commit(); sqlSession.close(); }
我们来看一下include,sql标签,对于重复性语句,我们可以提出封装成一个sql语句块,然后使用include进行引用,并且可以传值
看一下mapper,定义了两个接口
// sql 与include语句 User getUserById(@Param("id") Integer id); User getUser(@Param("id") Integer id);
看一下UserMapper.xml
<!--使用include来引用我们之前定义的sql语句块,并且可以使用property进行传值--> <select id="getUserById" resultType="com.yang.domain.User"> <include refid="selectUser"> <property name="lk" value="2" /> </include> where id=#{id} </select> <select id="getUser" resultType="com.yang.domain.User"> <include refid="selectUser" /> where id=#{id} </select> <!--使用sql语句来进行封装相同的sql语句块,并且里面可以进行判断执行情况我们使用${}来获取传入的对象--> <sql id="selectUser"> <choose> <when test="${lk} ==2"> select `username` from `user` </when> <otherwise> select * from `user` </otherwise> </choose> </sql>
看一下测试类
@Test public void getUser() { SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 可以看出使用这个之后就会把之前的定义的sql进行拼接,并且通过property进行传值,sql语句块也接受到了,只查询username User user2 = userMapper.getUserById(2); // ==> Preparing: select `username` from `user` where id=? System.out.println(user2); // User{id=null, username=‘yangbind‘, password=‘null‘} }
基本动态语句够用了,接下来看一下mybatis的缓存,mybatis缓存分为
一级缓存,只存在同一个sqlSession,对于用一个sqlsession,如果参数和sql完全一样的情况下,并且中间没有增删改,没有关闭sqlSession,没有删除缓存,并且没有超时,那么同一个sqlsession调用一个mappper对象,只会执行一次sql,剩余会走缓存取,并不会再次查询数据库,一级缓存默认就是开启的。
二级缓存,是mapper级别的缓存,顾名思义缓存只存在与yigemapper中,并且二级缓存就在命名空间中命名,二级缓存默认是不开启的,二级缓存需要配置,并且二级缓存中实现返回的PoJo必须是可序列化,也就是必须实现Serializable接口。二级缓存我们一般使用第三方,因为mybatis并不是专业做缓存的。
首先我们先来配置一下,把一级缓存,二级缓存的配置一下
先看一下配置文件,直接在settings李 main开启二级缓存,一级缓存是开启的
<settings> ... <!--二级缓存开关 默认是true 全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存--> <setting name="cacheEnabled" value="true" /> <!-- 一级缓存 MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。这个设置值就相当于关闭了一级缓存,因为不同调用也会不共享数据 --> <setting name="localCacheScope" value="SESSION" /> </settings>
看一下pojo类,如果需要开启二级缓存,这个类需要实现接口implements
public class User implements Serializable { private Integer id; private String username; private String password; ..... }
接下来看一下我我们的marpper
package com.yang.mapper; import com.yang.domain.User; public interface UserMapper2 { User getUserById(Integer id); void insertUser(User user); }
<?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.yang.mapper.UserMapper2"> <!-- 开启该mapper的namespace下的二级缓存,直接使用cache eviction代表缓存回收策略,目前有如下策略: LRU:最近最少使用,最长时间不用的对象,默认是这个 FIFO:先进先出,按照对象进入缓存的顺序进行移除 SOFT:软引用,移除基于垃圾回收状态和软引用规则的对象 WEAK:弱引用,移除基于垃圾收集器状态和弱引用规则的对像 flushInterval:刷新间隔时间,单位是毫秒,如果不配置,那么在sql执行的时候才会去刷新 readOnly:是否只读,意味着缓存是否可以被修改,设置为true这样可以快速读取缓存,但是不能进行修改 size:引用书目,一个正整数,代表缓存最多可以存储多少个对象,如设置过大,内存容易益处 --> <cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024" /> <!--userCache是开启二级缓存的开关,默认试开启的--> <select id="getUserById" resultType="com.yang.domain.User" useCache="true"> select * from `user` where id=#{id} </select> <insert id="insertUser"> insert into `user`(username, password) values (#{username}, #{password}) </insert> </mapper>
首先我们做一下一级缓存的测试
/** * 测试一级缓存 * 从这个测试缓存可以看出两次查询相同的mapper对象, * 只查询了一次数据库,并且两个对象是相同的,这个是以及缓存 */ @Test public void testCache(){ SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper2 userMapper = sqlSession.getMapper(UserMapper2.class); User user1 = userMapper.getUserById(2); System.out.println(user1); // User{id=2, username=‘yangbind‘, password=‘1234‘} User user2 = userMapper.getUserById(2); System.out.println(user2); // User{id=2, username=‘yangbind‘, password=‘1234‘} System.out.println(user1 == user2); // true /* Setting autocommit to false on JDBC Connection [] ==> Preparing: select * from `user` where id=? ==> Parameters: 2(Integer) <== Columns: id, username, password <== Row: 2, yangbind, 1234 <== Total: 1 User{id=2, username=‘yangbind‘, password=‘1234‘} Cache Hit Ratio [com.yang.mapper.UserMapper2]: 0.0 User{id=2, username=‘yangbind‘, password=‘1234‘} true */ }
接下来看一下中间插入数据,一级缓存会失效,执行了两次查询数据库操作
/** * 通过打印结果可以看出,如果中间执行了一次数据库改变操作,那么就是是一级缓存失效 * 并且同时我们也可以发现,二级缓存也没有作用,这是因为,只有关闭sqlSEssion之后,一级缓存才会将内容写入二级缓存 */ @Test public void testCache2(){ SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper2 userMapper = sqlSession.getMapper(UserMapper2.class); User user1 = userMapper.getUserById(2); System.out.println(user1); // User{id=2, username=‘yangbind‘, password=‘1234‘} User user = new User(); user.setUsername("yang"); user.setPassword("13456"); userMapper.insertUser(user); User user2 = userMapper.getUserById(2); System.out.println(user2); // User{id=2, username=‘yangbind‘, password=‘1234‘} System.out.println(user1 == user2); // false /* ==> Preparing: select * from `user` where id=? ==> Parameters: 2(Integer) <== Columns: id, username, password <== Row: 2, yangbind, 1234 <== Total: 1 User{id=2, username=‘yangbind‘, password=‘1234‘} ==> Preparing: insert into `user`(username, password) values (?, ?) ==> Parameters: yang(String), 13456(String) <== Updates: 1 Cache Hit Ratio [com.yang.mapper.UserMapper2]: 0.0 ==> Preparing: select * from `user` where id=? ==> Parameters: 2(Integer) <== Columns: id, username, password <== Row: 2, yangbind, 1234 <== Total: 1 User{id=2, username=‘yangbind‘, password=‘1234‘} false */ }
通过上述例子,大体可以分析出缓存的查询顺序,先查询一级缓存,在查询二级缓存。
并且一开始二级缓存是没有东西的,只有关闭sqlSession之后,才会将一级缓存对象存入二级缓存,接下来看一下二级缓存例子
/** * 这个是关闭了一级缓存,这个我们没有关闭sqlSession,使用同一个sqlSession, * 执行结果显示是查询了两次数据库 */ @Test public void testSecond(){ SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper2 userMapper = sqlSession.getMapper(UserMapper2.class); User user1 = userMapper.getUserById(2); System.out.println(user1); // User{id=2, username=‘yangbind‘, password=‘1234‘} User user2 = userMapper.getUserById(2); System.out.println(user2); // User{id=2, username=‘yangbind‘, password=‘1234‘} System.out.println(user1 == user2); // false sqlSession.close(); /* Setting autocommit to false on JDBC Connection [] ==> Preparing: select * from `user` where id=? ==> Parameters: 2(Integer) <== Columns: id, username, password <== Row: 2, yangbind, 1234 <== Total: 1 User{id=2, username=‘yangbind‘, password=‘1234‘} Cache Hit Ratio [com.yang.mapper.UserMapper2]: 0.0 ==> Preparing: select * from `user` where id=? ==> Parameters: 2(Integer) <== Columns: id, username, password <== Row: 2, yangbind, 1234 <== Total: 1 User{id=2, username=‘yangbind‘, password=‘1234‘} false */ } /** * 这个是关闭了一级缓存,这个我们关闭sqlSession,使用同一个sqlSession, * 执行结果显示是查询了一次数据库,并且两个对象都是一样的 */ @Test public void testSecond2(){ SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper2 userMapper = sqlSession.getMapper(UserMapper2.class); User user1 = userMapper.getUserById(2); System.out.println(user1); // User{id=2, username=‘yangbind‘, password=‘1234‘} sqlSession.close(); SqlSession sqlSession2 = MyBatisUtils.openSession(); UserMapper2 userMapper2 = sqlSession2.getMapper(UserMapper2.class); User user2 = userMapper2.getUserById(2); System.out.println(user2); // User{id=2, username=‘yangbind‘, password=‘1234‘} System.out.println(user1 == user2); // true sqlSession2.close(); /* Setting autocommit to false on JDBC Connection [] ==> Preparing: select * from `user` where id=? ==> Parameters: 2(Integer) <== Columns: id, username, password <== Row: 2, yangbind, 1234 <== Total: 1 User{id=2, username=‘yangbind‘, password=‘1234‘} Resetting autocommit to true on JDBC Connection [] Closing JDBC Connection [] Returned connection 2038522556 to pool. Cache Hit Ratio [com.yang.mapper.UserMapper2]: 0.5 User{id=2, username=‘yangbind‘, password=‘1234‘} true */ }
这个大体上就是缓存,最终看一下分页插件的使用
先看一下引用的jar包
我们之前在看mybats的配置文件是,看到了plugins这个配置,我们就是在这里面进行引用第三方插件,看一下引用
<plugins> <!--引用分页插件--> <plugin interceptor="com.github.pagehelper.PageInterceptor" /> </plugins>
接下来看一下使用方法
@Test public void testPage() { SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper2 userMapper = sqlSession.getMapper(UserMapper2.class); // 这个就是指定使用分页器,在查询之前声明,否则不起作用,第一个参数是第几页,第二个参数是一页几条 Page<User> page = PageHelper.startPage(1,2); List<User> users = userMapper.getUsers(); for (User user : users) { System.out.println(user); /* User{id=2, username=‘yangbind‘, password=‘1234‘} User{id=3, username=‘xiong‘, password=‘9012‘} */ } System.out.println(page.getPageNum()); // 1 获取当前页吗 System.out.println(page.getPageSize()); // 2 获取当前页的条数 System.out.println(page.getPages()); // 6 获取总页数 System.out.println(page.getTotal()); // 11 获取总条数 } @Test public void testPage2() { SqlSession sqlSession = MyBatisUtils.openSession(); UserMapper2 userMapper = sqlSession.getMapper(UserMapper2.class); // 这个就是指定使用分页器,在查询之前声明,否则不起作用,第一个参数是第几页,第二个参数是一页几条 Page<User> page = PageHelper.startPage(1,2); List<User> users = userMapper.getUsers(); // 将查询的结果做进一步封装,可以获取是否有下一页以及是否有上一页,并且可以返回页码,第二个参数是指定页码 PageInfo<User> pageInfo = new PageInfo<>(users, 2); for (User user : users) { System.out.println(user); /* User{id=2, username=‘yangbind‘, password=‘1234‘} User{id=3, username=‘xiong‘, password=‘9012‘} */ } for (User user : pageInfo.getList()) { System.out.println(user); /* User{id=2, username=‘yangbind‘, password=‘1234‘} User{id=3, username=‘xiong‘, password=‘9012‘} */ } System.out.println(pageInfo.getPageNum()); // 1 获取当前页吗 System.out.println(pageInfo.getPageSize()); // 2 获取当前页的条数 System.out.println(pageInfo.getPages()); // 6 获取总页数 System.out.println(pageInfo.getTotal()); // 11 获取总条数 System.out.println(pageInfo.isHasPreviousPage()); // false 获取是否有上一页 System.out.println(pageInfo.isHasNextPage()); // true 获取是否有下一页 System.out.println(Arrays.toString(pageInfo.getNavigatepageNums())); // [1, 2] 获取展示的页码 }
源码可以在github上看:https://github.com/yang-shixiong/springDemo
相关推荐
② Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。④ Mapper.xml文件中的namespace即是mapper接口的类路径。