欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

MyBatis 动态SQL和缓存机制实例详解

程序员文章站 2024-02-28 14:26:46
有的时候需要根据要查询的参数动态的拼接sql语句 常用标签: - if:字符判断 - choose【when...otherwise】:分支选择 - trim【wh...

有的时候需要根据要查询的参数动态的拼接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;每次查询之前都会清空缓存,缓存是没有被使用!

总结

以上所述是小编给大家介绍的mybatis 动态sql和缓存机制实例详解,希望对大家有所帮助