zoukankan      html  css  js  c++  java
  • MyBatis --- 动态SQL、缓存机制

    有的时候需要根据要查询的参数动态的拼接SQL语句

    常用标签:

    - if:字符判断

    - choose【when...otherwise】:分支选择

    - trim【where,set】:字符串截取,其中where标签封装查询条件,set标签封装修改条件

    - foreach:

    if案例

    1)在EmployeeMapper接口文件添加一个方法

    public Student getStudent(Student student);
    

     2)如果要写下列的SQL语句,只要是不为空,就作为查询条件,如下所示,这样写实际上是有问题的,所以我们要写成动态SQL语句:

    <select id="getEmployeeByConditionIf" resultType="com.neuedu.entity.Employee">
           select *from tbl_employee where id = #{id} and user_name = #{userName} and email = #{email} and gender = #{gender}
    </select>
    

     3)用if标签改写为动态SQL,如下所示:

     test:判断表达式(OGNL):OGNL参照PPT或者官方文档。
     test从参数中取值进行判断
     遇见特殊符号,应该去写转义字符:如<>分别为&lt,&gt
    <select id="getStudent" resultType="com.neuedu.mybatis.entity.Student">
          SELECT *
          FROM student
           where
                <if test="id != null">
                      id=#{id}
                </if>
                <if test="name !=null and name!=''">
                      and name=#{name}
                </if>
                <if test="password !=null and password !=''">
                      and password=#{password}
                </if>
                <if test="email !=null and email !=''">
                      and email=#{email}
                </if>
    </select>
    

    4)测试代码

    @Test
    public void TestgetStudent(){
          StudentMapper bean = ioc.getBean(StudentMapper.class);
          Student student = new Student(4,"jack", "111", "jack@qq.com");
          System.out.println(student);
          Student student2 = bean.getStudent(student);
          System.out.println(student2);
    }
    

     #测试结果没问题,

    但是仔细来说,上面的sql语句是有问题的,当我们不给动态sql语句传递id值的时候,sql语句的拼装就会有问题!【name前有一个and】

    - where 标签

    解决办法
    1.给where后面加上1=1,以后的条件都可以使用and xxx了
    2.可以使用 where 标签来将所有的查询条件包括在内
       mybatis就会将where标签中拼装的sql,多出来的and或者or去掉!
    <select id="getStudent" resultType="com.neuedu.mybatis.entity.Student">
          SELECT *
          FROM student
          <where>
                <if test="id != null">
                      id=#{id}
                </if>
                <if test="name !=null and name!=''">
                      and name=#{name}
                </if>
                <if test="password !=null and password !=''">
                      and password=#{password}
                </if>
                <if test="email !=null and email !=''">
                      and email=#{email}
                </if>
          </where>
    </select>
    

    3.需要注意:where标签只会去掉第一个多出来的and或者or

       也就是说使用where标签有时候还是不能解决问题的,那怎么办呢?我们这里可以使用trim标签

    - trim标签:可以自定义字符串的截取规则 

        后面多出的and或者or where标签不能够解决
        prefix="":前缀:trim标签体是整个字符串拼串后的结果。
        prefix给拼串后的整个字符串加一个前缀
        prefixOverrides="":前缀覆盖:去掉整个字符串前面多余的字符
        suffix="":后缀
        suffix给拼串后的整个字符串加一个后缀
        suffixOverrides="":后缀覆盖:去掉整个字符串后面多余的字符
    <select id="getStudent" resultType="com.neuedu.mybatis.entity.Student">
          SELECT *
          FROM student
          <trim prefix="where" prefixOverrides="and">
                <if test="id != null">
                      id=#{id}
                </if>
                <if test="name !=null and name!=''">
                      and name=#{name}
                </if>
                <if test="password !=null and password !=''">
                      and password=#{password}
                </if>
                <if test="email !=null and email !=''">
                      and email=#{email}
                </if>
          </trim>
    </select>
    
    - choose标签:分支选择,类似于Java中的带了break的switch...case

    相当于确保了第一个case 符合之后,就跳出

    案例演示:

    1.在EmployeeMapper接口中添加一个方法

    public List<Student> getStus(Student student);
    

     2.sql映射文件

    <select id="getStus" resultType="com.neuedu.mybatis.entity.Student">
          select * from student
          <where>
                <choose>
                      <when test="id !=null">
                            id = #{id}
                      </when>
                      <when test="name !=null and name!=''">
                            name = #{name}
                      </when>
                      <when test="password !=null and password!=''">
                            password = #{password}
                      </when>
                      <when test="email !=null and email!=''">
                            email = #{email}
                      </when>
                      <otherwise>
                            1 = 1
                      </otherwise>
                </choose>
          </where>
    </select>
    

    - set标签:字符串截取,可以写在trim里面

    set元素会动态前置set关键字,同时也会消除无关的逗号

    1)在EmployeeMapper中添加一个更新的方法

    public void updateStu(Student student);
    

     2)在sql映射文件中,填写相应的sql语句,如下所示【set标签可以将字段后面的逗号去掉】

    <update id="updateStu">
          update student
          <set>
                <if test="name !=null and name!=''">
                      name=#{name},
                </if>
                <if test="password !=null and password !=''">
                      password=#{password},
                </if>
                <if test="email !=null and email !=''">
                      email=#{email}
                </if>
          </set>
                where id = #{id}
    </update>
    

     3)测试类代码为

    @Test
    public void TestUpdateStu(){
          StudentMapper bean = ioc.getBean(StudentMapper.class);
          bean.updateStu(new Student(4, "jackk", null, null));
    }
    

     将set标签用trim标签代替

    <update id="updateStu">
          update student
          <trim prefix="set" suffixOverrides=",">
                <if test="name !=null and name!=''">
                      name=#{name},
                </if>
                <if test="password !=null and password !=''">
                      password=#{password},
                </if>
                <if test="email !=null and email !=''">
                      email=#{email}
                </if>
          </trim>
                where id = #{id}
    </update>
    

    - foreach:遍历元素

    动态SQL的另一个常用的操作是需要对一个集合进行遍历,通常在构建in条件语句的时候!
    foreach元素允许指定一个集合,声明集合项和索引变量,并可以指定开闭匹配的字符串以及在迭代之间放置分隔符。
     
    案例演示:
    1.在EmployeeMapper接口中加入一个方法
    public List<Student> getStuByIdForEach(@Param("ids")List<Integer> ids);
    

    2.在MyBatis的sql映射文件中写相应的代码

    <select id="getStuByIdForEach" resultType="com.neuedu.mybatis.entity.Student">
          select * from student
          where id
          in
          <foreach collection="ids" item="id" open="(" close=")" separator=",">
                #{id}
          </foreach>
    </select>
    

     3.测试类代码

    @Test
    public void getStuByIdForEach(){
          StudentMapper bean = ioc.getBean(StudentMapper.class);
          List<Integer> list = Arrays.asList(16,17,18,19);
          List<Student> stuByIdForEachlist = bean.getStuByIdForEach(list);
          for (Student student : stuByIdForEachlist) {
                System.out.println(student);
          }
    }
    

     foreach标签还可以用于批量保存数据

    1.在EmployeeMapper接口类中添加批量插入的方法

    public void insertStus(@Param("stus")List<Student> student);
    

     2.在EmployeeMapper.xml的sql映射文件中添加响应的语句

    foreach 中用 collection,collection中是从Mapper接口传来的参数,separator是去掉中间符号

    <insert id="insertStus">
          insert into student (name,password,email) values
          <foreach collection="stus" item="stu" separator=",">
                (#{stu.name},#{stu.password},#{stu.email})
          </foreach>
    </insert>
    

     3.测试代码

    @Test
    public void TestInsertStus(){
          StudentMapper bean = ioc.getBean(StudentMapper.class);
          List<Student> list = new ArrayList<Student>();
          list.add(new Student("123","123", "123"));
          list.add(new Student("123","123", "123"));
          list.add(new Student("123","123", "123"));
          bean.insertStus(list);
    }
    

    MyBatis-缓存机制   

    MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
    只在MyBatis中,在SSM整合文件中没用,因为SqlSession 定义在 bean.xml中,无法重新定义SqlSession
     
    MyBatis系统中默认定义了两级缓存。
    一级缓存和二级缓存
         一级缓存:(本地缓存):SqlSession级别的缓存,一级缓存是一直开启的,没法关闭。方法之间不共用!
                与数据库同一次会话期间查询到的数据放在本地缓存中。
                以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;
         二级缓存(全局缓存):
     
            –1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
            –2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
            –3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
     

    一级缓存:

    案例:测试一级缓存【默认是开启的】

    将返回一条select查询语句,
    将返回true,说明emp与emp2是缓存,而不是重新查找
    @Test
    public void TestFirstCache(){
          SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
          session = sqlSessionFactory.openSession();
          mapper = session.getMapper(EmployeeMapper.class);
    
          Employee emp = mapper.getEmpInfoById(4);
          System.out.println(emp);
    
          Employee emp2 = mapper.getEmpInfoById(4);
          System.out.println(emp2);
    
          System.out.println(emp == emp2);
    
          session.commit();
          session.close();
    }
    

     一级缓存失效的情况【4种】(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询)

    1.sqlSession不同,重新定义SqlSession

    将返回两条select语句

    将返回false,说明emp2不是emp的缓存

    @Test
    public void TestFirstCache(){
          SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
          session = sqlSessionFactory.openSession();
          mapper = session.getMapper(EmployeeMapper.class);
          Employee emp = mapper.getEmpInfoById(4);
          System.out.println(emp);
    
          SqlSession session2 = sqlSessionFactory.openSession();
          EmployeeMapper mapper2 = session2.getMapper(EmployeeMapper.class);
          Employee emp2 = mapper2.getEmpInfoById(4);
          System.out.println(emp2);
    
          System.out.println(emp == emp2);
    
          session.commit();
          session.close();
    }
    

    2.SqlSession相同,但是查询条件不一样[当前缓存中还没有这个数据]

    就是相当于根据不同条件再次查找

    @Test
    public void TestFirstCache(){
          SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
          session = sqlSessionFactory.openSession();
          mapper = session.getMapper(EmployeeMapper.class);
    
          Employee emp = mapper.getEmpInfoById(4);
          System.out.println(emp);
    
          Employee emp2 = mapper.getEmpInfoById(16);
          System.out.println(emp2);
    
          System.out.println(emp == emp2);
    
          session.commit();
          session.close();
    }
    

    3.SqlSession相同,但是两次查询之间执行了增删改操作【这次增删改可能对当前数据有影响】

    因为默认自动刷新了缓存

    @Test
    public void TestFirstCache(){
          SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
          session = sqlSessionFactory.openSession();
          mapper = session.getMapper(EmployeeMapper.class);
    
          Employee emp = mapper.getEmpInfoById(4);
          System.out.println(emp);
    
          mapper.deleteEmp(16);
    
          Employee emp2 = mapper.getEmpInfoById(4);
          System.out.println(emp2);
    
          System.out.println(emp == emp2);
    
          session.commit();
          session.close();
    }
    

    4.SqlSession相同,手动清除了一级缓存[缓存清空]

    手动清除了缓存,所以得重新查找

    @Test
    public void TestFirstCache(){
          SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
          session = sqlSessionFactory.openSession();
          mapper = session.getMapper(EmployeeMapper.class);
    
          Employee emp = mapper.getEmpInfoById(4);
          System.out.println(emp);
    
          session.clearCache();
    
          Employee emp2 = mapper.getEmpInfoById(4);
          System.out.println(emp2);
    
          System.out.println(emp == emp2);
    
          session.commit();
          session.close();
    }
    

    二级缓存:

    【全局缓存】:基于namespace级别的缓存:一个namespace对应一个二级缓存。
    【一级缓存的范围还是太小了,每次SqlSession一关闭,一级缓存中的数据就消失】
    所以从这个角度讲:能跨sqlSession的缓存为二级缓存!
     
    工作机制:
    1.一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中。
    2.如果会话关闭,一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中。
    不同namespace查出的数据会放在自己对应的缓存中(map)   
    效果:数据会从二级缓存中获取
              查出的数据都会被默认先放在一级缓存中。
              只有会话提交或者关闭之后,一级缓存中的数据才会转移到二级缓存中。
              需要注意的是:在哪个Mapper.xml文件中开启了<cache>缓存标签,哪个Mapper中就开启了二级缓存。

    案例:

    1)开启全局二级缓存配置:

    <setting name="cacheEnabled" value="true"/>
    

     2)去mapper.xml中配置使用二级缓存

    <cache eviction="FIFO" size="100" readOnly="false"/>
    
    其中属性:
    eviction=“FIFO”:缓存回收策略:
    LRU –最近最少使用的:移除最长时间不被使用的对象。
    FIFO –先进先出:按对象进入缓存的顺序来移除它们。
    SOFT –软引用:移除基于垃圾回收器状态和软引用规则的对象。
    WEAK –弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
    默认的是LRU。
    flushInterval:缓存刷新间隔 ?缓存多长时间清空一次,默认不清空,设置一个毫秒值。
    size:引用数目,正整数,默认1024
          代表缓存最多可以存储多少个对象,太大容易导致内存溢出
    readOnly:是否只读,true/false
          true:只读缓存;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
                  mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快。
          false:非只读:mybatis觉得获取的数据可能会被修改。
                  mybatis会利用序列化&反序列化的技术克隆一份。安全,速度慢。
    type:指定自定义缓存的全类名 实现cache接口即可!

    3)我们的POJO需要实现序列化接口[implements Serializable]

    4)必须先关闭之前的sqlsession对象

    测试:

    可以看到只发送了一次SQL语句,第二次查询时从二级缓存中拿到的数据,并没有发送新的sql语句。

    @Test
    public void TestFirstCache(){
          SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
          session = sqlSessionFactory.openSession();
          mapper = session.getMapper(EmployeeMapper.class);
          Employee emp = mapper.getEmpInfoById(4);
          System.out.println(emp);
          session.close();
    
          SqlSession session2 = sqlSessionFactory.openSession();
          EmployeeMapper mapper2 = session2.getMapper(EmployeeMapper.class);
          Employee emp2 = mapper2.getEmpInfoById(4);
          System.out.println(emp2);
          session2.close();
    }
    

    需要注意的是:只有一级缓存中关闭的情况下,二级缓存才会被使用。

    需要注意的是:在哪个Mapper.xml文件中开启了<cache>缓存标签,哪个Mapper中就开启了二级缓存。

    和缓存有关的设置/属性:
    1)cacheEnabled="true": false:关闭缓存(二级缓存关闭)【一级缓存一直可用】
    2)每个select标签都有useCache="true";
                      false:不使用缓存(一级缓存依然使用,二级缓存不使用)
    3)每个增删改标签都有一个flushCache="true":增删改执行完成后就会清楚缓存【一级二级缓存都会被清空】
               查询标签:flushCache = "false"
               如果flushCache = true;每次查询之前都会清空缓存,缓存是没有被使用!
     
     
  • 相关阅读:
    在 json4s 中自定义CustomSerializer
    【重点】2020年宝山区义务教育阶段学校校区范围与招生计划(小学)
    2019宝山区小升初对口地段表及对口初中片区划分
    2019上海市各区重点幼儿园、小学和中学排名(建议收藏)
    转:一千行MYSQL 笔记
    基于weixin-java-mp 做微信JS签名 invalid signature签名错误 官方说明
    转 : 深入解析Java锁机制
    微服务架构转型升级
    抽奖活动 mark
    抽奖 mark
  • 原文地址:https://www.cnblogs.com/lwj-0923/p/7488091.html
Copyright © 2011-2022 走看看