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;每次查询之前都会清空缓存,缓存是没有被使用!

总结

以上所述是小编给大家介绍的MyBatis 动态SQL和缓存机制实例详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 详解Java的MyBatis框架中动态SQL的基本用法

    有些时候,sql语句where条件中,需要一些安全判断,例如按某一条件查询时如果传入的参数是空,此时查询出的结果很可能是空的,也许我们需要参数为空时,是查出全部的信息.使用Oracle的序列.mysql的函数生成Id.这时我们可以使用动态sql.下文均采用mysql语法和函数(例如字符串链接函数CONCAT). selectKey 标签 在insert语句中,在Oracle经常使用序列.在MySQL中使用函数来自动生成插入表的主键,而且需要方法能返回这个生成主键.使用myBatis的select

  • Mybatis动态SQL之if、choose、where、set、trim、foreach标记实例详解

    动态SQL就是动态的生成SQL. if标记 假设有这样一种需求:查询用户,当用户名不等于"admin"的时候,我们还需要密码为123456. 数据库中的数据为: MyBatisConfig.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

  • MyBatis 动态拼接Sql字符串的问题

    MyBatis 的一个强大的特性之一通常是它的动态 SQL 能力.如果你有使用 JDBC 或其他 相似框架的经验,你就明白条件地串联 SQL 字符串在一起是多么的痛苦,确保不能忘了空格或在列表的最后省略逗号.动态 SQL 可以彻底处理这种痛苦. 动态SQL MyBatis的动态SQL,解决了SQL字符串拼接的痛苦. 1.if <select id="findActiveBlogWithTitleLike" parameterType="Blog" result

  • MyBatis 执行动态 SQL语句详解

    大家基本上都知道如何使用 MyBatis 执行任意 SQL,使用方法很简单,例如在一个 XXMapper.xml 中: <select id="executeSql" resultType="map"> ${_parameter} </select> 你可以如下调用: sqlSession.selectList("executeSql", "select * from sysuser where enabled

  • MyBatis使用动态SQL标签的小陷阱

    MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录. 现在MyBatis越来越受大家的喜爱了,它的优势大家都知道,我就不多说了,直接说重点. MyBatis中提供动态SQL功能,我们可以使用<if><when&

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

    有的时候需要根据要查询的参数动态的拼接SQL语句 常用标签: - if:字符判断 - choose[when...otherwise]:分支选择 - trim[where,set]:字符串截取,其中where标签封装查询条件,set标签封装修改条件 - foreach: if案例 1)在EmployeeMapper接口文件添加一个方法 public Student getStudent(Student student); 2)如果要写下列的SQL语句,只要是不为空,就作为查询条件,如下所示,这样

  • mybatis防止SQL注入的方法实例详解

    SQL注入是一种很简单的攻击手段,但直到今天仍然十分常见.究其原因不外乎:No patch for stupid.为什么这么说,下面就以JAVA为例进行说明: 假设数据库中存在这样的表: table user( id varchar(20) PRIMARY KEY , name varchar(20) , age varchar(20) ); 然后使用JDBC操作表: private String getNameByUserId(String userId) { Connection conn

  • mybatis的动态SQL和模糊查询实例详解

    现在以一个例子来介绍mybatis的动态SQL和模糊查询:通过多条件查询用户记录,条件为姓名模糊匹配,并且年龄在某两个值之间. 新建表d_user: create table d_user( id int primary key auto_increment, name varchar(10), age int(3) ); insert into d_user(name,age) values('Tom',12); insert into d_user(name,age) values('Bob

  • Smarty缓存机制实例详解【三种缓存方式】

    本文实例讲述了Smarty缓存机制.分享给大家供大家参考,具体如下: Smarty模板引擎中强大的缓存机制,缓存机制有效减少了系统对服务器的压力,而这也是很多开发者喜欢Smarty的原因之一,附录中讲解了设置缓存及清除缓存的技巧方法(其中包含缓存集合方法). 一.Smarty缓存的几种方式 缓存机制中,分为全局缓存.部分缓存.局部缓存三种方式,后面会一一讲述,下面是缓存设置前,Smarty类方法基本目录设置如下: $smarty->Smarty(); $smarty->template_dir

  • Java 反射机制实例详解

    Java 反射机制实例详解 一.JAVA是动态语言吗? 一般而言,说到动态言,都是指在程序运行时允许改变程序结构或者变量类型,从这个观点看,Java和C++一样,都不是动态语言. 但JAVA它却有着一个非常突出的动态相关机制:反射.通过反射,Java可以于运行时加载.探知和使用编译期间完全求和的类.生成其对象实体,调用其方法或者对属性设值.所以Java算是一个半动态的语言吧. 反射的概念: 在Java中的反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法; 对于任意一个对

  • MyBatis输入映射和输出映射实例详解

    什么是 MyBatis ? MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录. 我们知道,mapper.xml是我们配置操作数据库的sql语句的地方.其中每个sql语句对应着一个方法,每个方法都有自己的

  • Android启动内置APK和动态发送接收自定义广播实例详解

    Android启动内置APK和动态发送接收自定义广播实例详解 工作中遇到这样一个需求,需要为按键添加一个亲情号,提供一个接口启动内置的APK,思考再三决定更改Framework,利用广播机制去实现. 一.代码动态自主启动内置APK 我们都知道Android系统为我们提供了很多服务管理类,PackageManager主要是管理应用程序包,通过它就可以获取应用程序信息并构建Intent,启动对应的应用.除此之外Android还未我们提供了一些对应的类来管理相关的xml文件,比如说可以通过Packag

  • 微信小程序之数据缓存的实例详解

    微信小程序之数据缓存的实例详解 前言: 在H5之前,缓存一般都是用cookie,但是cookie的存储空间太小.于是,H5增加了新的缓存机制,即localstorage 和 sessionstorage,具体的介绍就不在多说.在微信小程序中,数据缓存其实就和localstorage 的原理差不多,所以理解起来并不难.下面我们来一起实现一下. 效果图展示: 我们在index页面存入数字11,然后在跳转到新页面,在将缓存中的11取出渲染到当前页面.具体代码如下: index页面: <span sty

  • LRU LFU TinyLFU缓存算法实例详解

    目录 简介 一.LRU和LFU算法 LRU算法 LFU算法 小结: 二.TinyLFU 三.Window-TinyLFU 简介 前置知识 知道什么是缓存 听完本节公开课,你可以收获 掌握朴素LRU.LFU算法的思想以及源码 掌握一种流式计数的算法 Count-Min Sketch 手撕TinyLFU算法.分析Window-TinyLFU源码 一.LRU和LFU算法 LRU算法 LRU Least Recently Used 最近最少使用算法 LRU 算法的思想是如果一个数据在最近一段时间没有被访

  • Java实现LRU缓存的实例详解

    Java实现LRU缓存的实例详解 1.Cache Cache对于代码系统的加速与优化具有极大的作用,对于码农来说是一个很熟悉的概念.可以说,你在内存中new 了一个一段空间(比方说数组,list)存放一些冗余的结果数据,并利用这些数据完成了以空间换时间的优化目的,你就已经使用了cache. 有服务级的缓存框架,如memcache,Redis等.其实,很多时候,我们在自己同一个服务内,或者单个进程内也需要缓存,例如,lucene就对搜索做了缓存,而无须依赖外界.那么,我们如何实现我们自己的缓存?还

随机推荐