• 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

  • 相关阅读:
    Swift中字符串转化为Class的方法
    React Native安装
    关于error:Cannot assign to 'self' outside of a method in the init family
    Xcode6中Swift没有智能提示和自动补全功能
    关于消息推送的补充,主要介绍服务端的实现,包含object c 版本 c 版本 java 版本 php 版本 (转)
    Git和GitHub在线学习资源整理(转)
    HTTP协议详解(转)
    iOS开发之GCD基础
    一步一步实现消息推送 2014-06-19
    理解Certificate、App Id、Identifiers 和 Provisioning Profile
  • 原文地址:https://www.cnblogs.com/yangshixiong/p/12250709.html
Copyright © 2020-2023  润新知