mybatis入门篇3 ---- 动态sql,缓存,以及分页jar包的使用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 [com.mysql.jdbc.JDBC4Connection@66ea810]
        ==>  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 [com.mysql.jdbc.JDBC4Connection@508dec2b]
        ==>  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 [com.mysql.jdbc.JDBC4Connection@798162bc]
        ==>  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 [com.mysql.jdbc.JDBC4Connection@798162bc]
        Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@798162bc]
        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

 

以上是关于mybatis入门篇3 ---- 动态sql,缓存,以及分页jar包的使用的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis动态sql及性能优化

mybatis从入门到精通 动态SQL

Mybatis 学习笔记总结

MyBatis从入门到精通:MyBatis动态Sql之if标签的用法

MyBatis动态SQL和缓存

MyBatis案例 | 使用映射配置文件实现CRUD操作——动态SQL优化条件查询