MyBatis 动态SQL和缓存机制实例详解
发布时间 - 2026-01-11 03:08:52 点击率:次有的时候需要根据要查询的参数动态的拼接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从参数中取值进行判断
遇见特殊符号,应该去写转义字符:如<>分别为<,>
<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和缓存机制实例详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
# mybatis
# 动态sql
# 缓存机制
# Mybatis 动态sql的编写与开启二级缓存
# MyBatis动态SQL与缓存原理深入分析
# 的是
# 放在
# 清空
# 需要注意
# 移除
# 可以使用
# 所示
# 多出
# 就会
# 都有
# 是有
# 第一个
# 才会
# 遍历
# 开启了
# 自定义
# 序列化
# 会将
# 中就
# 小编
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?
如何用y主机助手快速搭建网站?
python中快速进行多个字符替换的方法小结
php静态变量怎么调试_php静态变量作用域调试技巧【解答】
Laravel怎么实现API接口鉴权_Laravel Sanctum令牌生成与请求验证【教程】
Laravel API路由如何设计_Laravel构建RESTful API的路由最佳实践
,网页ppt怎么弄成自己的ppt?
矢量图网站制作软件,用千图网的一张矢量图做公司app首页,该网站并未说明版权等问题,这样做算不算侵权?应该如何解决?
Laravel如何正确地在控制器和模型之间分配逻辑_Laravel代码职责分离与架构建议
Laravel如何实现图片防盗链功能_Laravel中间件验证Referer来源请求【方案】
Laravel怎么实现前端Toast弹窗提示_Laravel Session闪存数据Flash传递给前端【方法】
装修招标网站设计制作流程,装修招标流程?
如何快速上传建站程序避免常见错误?
使用spring连接及操作mongodb3.0实例
Laravel如何记录自定义日志?(Log频道配置)
高端建站三要素:定制模板、企业官网与响应式设计优化
微信小程序 canvas开发实例及注意事项
文字头像制作网站推荐软件,醒图能自动配文字吗?
Laravel如何操作JSON类型的数据库字段?(Eloquent示例)
b2c电商网站制作流程,b2c水平综合的电商平台?
微信小程序 require机制详解及实例代码
Laravel Eloquent访问器与修改器是什么_Laravel Accessors & Mutators数据处理技巧
详解Android——蓝牙技术 带你实现终端间数据传输
最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?
如何在HTML表单中获取用户输入并用JavaScript动态控制复利计算循环
如何在腾讯云服务器上快速搭建个人网站?
微信小程序 五星评分(包括半颗星评分)实例代码
Laravel如何使用Facades(门面)及其工作原理_Laravel门面模式与底层机制
Laravel怎么做缓存_Laravel Cache系统提升应用速度的策略与技巧
如何自定义建站之星网站的导航菜单样式?
手机网站制作与建设方案,手机网站如何建设?
Android中AutoCompleteTextView自动提示
Laravel如何安装使用Debugbar工具栏_Laravel性能调试与SQL监控插件【步骤】
如何在 Go 中优雅地映射具有动态字段的 JSON 对象到结构体
如何快速搭建高效服务器建站系统?
PHP 500报错的快速解决方法
如何快速搭建支持数据库操作的智能建站平台?
Laravel如何记录日志_Laravel Logging系统配置与自定义日志通道
昵图网官网入口 昵图网素材平台官方入口
Python结构化数据采集_字段抽取解析【教程】
javascript基于原型链的继承及call和apply函数用法分析
Laravel如何监控和管理失败的队列任务_Laravel失败任务处理与监控
三星、SK海力士获美批准:可向中国出口芯片制造设备
微信公众帐号开发教程之图文消息全攻略
JS中页面与页面之间超链接跳转中文乱码问题的解决办法
Claude怎样写约束型提示词_Claude约束提示词写法【教程】
CSS3怎么给轮播图加过渡动画_transition加transform实现【技巧】
Android使用GridView实现日历的简单功能
php在windows下怎么调试_phpwindows环境调试操作说明【操作】
如何基于云服务器快速搭建网站及云盘系统?

