尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

第32篇:MyBatis进阶:动态SQL+分页+缓存,优化数据库操作

第32篇:MyBatis进阶:动态SQL+分页+缓存,优化数据库操作
📅 发布时间:2026/7/18 21:44:21

在Java后端开发中,MyBatis作为一款优秀的持久层框架,凭借其灵活的SQL定制能力和高效的数据库交互性能,成为了开发人员的首选工具。但在实际项目开发中,单纯的基础CRUD操作远不能满足业务需求,我们需要借助动态SQL、分页查询、缓存机制等进阶特性,进一步优化数据库操作,提升系统性能和开发效率。本文将从核心进阶特性入手,结合实战案例,详细解析MyBatis优化技巧,同时补充注解开发和MyBatis-Plus入门知识,帮助大家全面掌握MyBatis进阶用法。

一、动态SQL:灵活拼接SQL,适配复杂业务场景

动态SQL是MyBatis的核心亮点之一,它允许我们根据不同的业务条件,动态拼接SQL语句,避免了编写大量重复、冗余的SQL代码。MyBatis提供了多种动态SQL标签,常用的有if、where、foreach、choose等,下面结合实战案例逐一讲解。

1. if标签:单条件判断,按需拼接SQL

if标签是最基础的动态SQL标签,用于根据条件判断是否拼接某段SQL语句。常用于多条件查询、动态更新等场景,核心属性是test,用于指定判断条件(使用OGNL表达式)。

实战案例:根据用户姓名和年龄查询用户信息,姓名和年龄为可选条件,若参数为空则不参与查询。

<!-- UserMapper.xml --> <mapper namespace="com.example.mapper.UserMapper"> <select id="selectUserByCondition" resultType="com.example.entity.User"> SELECT id, username, age, email FROM t_user WHERE 1=1 <!-- 判断姓名是否为空,不为空则拼接姓名查询条件 --> <if test="username != null and username != ''"> AND username LIKE CONCAT('%', #{username}, '%') </if> <!-- 判断年龄是否为空,不为空则拼接年龄查询条件 --> <if test="age != null"> AND age = #{age} </if> </select> </mapper>

注意:使用if标签时,通常会在WHERE后添加“1=1”作为基础条件,避免因所有if条件都不满足,导致拼接出“WHERE”后无内容的语法错误。

2. where标签:自动处理多余的AND/OR,简化条件拼接

where标签可以替代手动添加的“1=1”,它会自动判断内部拼接的SQL片段,若有内容则自动添加WHERE关键字,同时去除开头多余的AND或OR,避免SQL语法错误。

改造上述案例,使用where标签优化:

<select id="selectUserByCondition" resultType="com.example.entity.User"> SELECT id, username, age, email FROM t_user <where> <if test="username != null and username != ''"> AND username LIKE CONCAT('%', #{username}, '%') </if> <if test="age != null"> AND age = #{age} </if> </where> </select>

此时,若两个条件都为空,SQL语句会自动变为“SELECT id, username, age, email FROM t_user”,无多余的WHERE关键字;若有一个条件满足,会自动去除条件前的AND,拼接正确的SQL。

3. foreach标签:遍历集合,实现批量操作

foreach标签用于遍历集合(List、Set、Map等),常用于批量查询、批量插入、批量删除等场景。核心属性包括:collection(指定要遍历的集合)、item(遍历的元素别名)、index(索引别名,可选)、open(拼接的开头字符)、close(拼接的结尾字符)、separator(元素之间的分隔符)。

实战案例1:批量查询用户(根据用户ID集合)

<select id="selectUserByIds" resultType="com.example.entity.User"> SELECT id, username, age, email FROM t_user <where> id IN <foreach collection="ids" item="id" open="(" close=")" separator=","> #{id} </foreach> </where> </select>

对应的Mapper接口方法:

List<User> selectUserByIds(@Param("ids") List<Long> ids);

实战案例2:批量插入用户

<insert id="batchInsertUser"> INSERT INTO t_user (username, age, email) VALUES <foreach collection="users" item="user" separator=","> (#{user.username}, #{user.age}, #{user.email}) </foreach> </insert>

对应的Mapper接口方法:

int batchInsertUser(@Param("users") List<User> users);

4. choose标签:多条件分支,类似Java中的switch-case

choose标签用于实现多条件分支判断,它包含when和otherwise子标签:when标签用于指定满足的条件,otherwise标签用于指定所有when条件都不满足时的默认操作。常用于“优先按某条件查询,若不满足则按另一条件查询”的场景。

实战案例:优先按用户ID查询,若ID为空则按姓名查询,若姓名也为空则查询所有用户

<select id="selectUserByChoose" resultType="com.example.entity.User"> SELECT id, username, age, email FROM t_user <where> <choose> <when test="id != null"> AND id = #{id} </when> <when test="username != null and username != ''"> AND username LIKE CONCAT('%', #{username}, '%') </when> <otherwise> AND 1=1 </otherwise> </choose> </where> </select>

二、分页查询:高效处理大量数据,提升查询性能

在实际项目中,数据库中的数据量往往较大,若一次性查询所有数据,会导致内存占用过高、查询速度变慢,甚至引发系统异常。此时,分页查询就显得尤为重要。MyBatis实现分页的方式主要有两种:原生RowBounds和PageHelper插件,其中PageHelper插件因使用简单、功能强大,成为主流选择。

1. 原生RowBounds:简单分页,无需修改SQL

RowBounds是MyBatis提供的原生分页方式,它通过在查询时指定起始行和查询条数,实现分页效果,无需修改SQL语句。但需要注意的是,RowBounds是基于内存分页(即先查询所有数据,再在内存中截取),数据量较大时性能较差,仅适用于小数据量场景。

实战案例:使用RowBounds实现分页查询用户列表

第一步:编写Mapper接口和XML查询语句(无需添加分页条件)

// Mapper接口 List<User> selectAllUser();

二步:在Service层使用RowBounds实现分页

@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public List<User> selectUserByPage(int pageNum, int pageSize) { // pageNum:页码,pageSize:每页条数;起始行 = (页码-1)*每页条数 int offset = (pageNum - 1) * pageSize; RowBounds rowBounds = new RowBounds(offset, pageSize); // 使用SqlSession调用查询方法,传入RowBounds return userMapper.selectAllUser(rowBounds); } }

2. PageHelper插件:物理分页,高效适配大量数据

PageHelper是一款基于MyBatis的分页插件,它采用物理分页(即通过在SQL语句后添加LIMIT子句,直接从数据库中查询分页数据),性能远优于原生RowBounds,同时支持多种数据库(MySQL、Oracle、SQL Server等),使用简单、配置方便。

实战案例:使用PageHelper实现分页查询

第一步:引入PageHelper依赖(Maven)

<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.6</version> </dependency>

第二步:配置PageHelper(Spring Boot项目可省略,默认配置即可,如需自定义可在application.yml中配置)

pagehelper: helper-dialect: mysql # 指定数据库方言 reasonable: true # 分页合理化,页码小于1时查第一页,大于总页数时查最后一页 support-methods-arguments: true # 支持通过Mapper接口参数传递分页参数

第三步:在Service层使用PageHelper实现分页

@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public PageInfo<User> selectUserByPage(int pageNum, int pageSize) { // 开启分页,紧跟在该方法后的第一个查询会自动分页 PageHelper.startPage(pageNum, pageSize); // 查询所有用户(会自动添加分页条件) List<User> userList = userMapper.selectAllUser(); // 封装分页结果,包含总条数、总页数、当前页数据等信息 PageInfo<User> pageInfo = new PageInfo<>(userList); return pageInfo; } }

PageInfo封装了丰富的分页信息,常用属性有:total(总记录数)、pages(总页数)、list(当前页数据)、pageNum(当前页码)、pageSize(每页条数)等,可直接返回给前端使用。

三、MyBatis缓存机制:减少数据库查询,提升响应速度

缓存是提升系统性能的重要手段,MyBatis提供了一级缓存和二级缓存,通过缓存查询结果,避免重复查询数据库,从而减少数据库压力,提升系统响应速度。

1. 一级缓存:SqlSession级别的缓存,默认开启

一级缓存也称为本地缓存,是SqlSession级别的缓存,每个SqlSession都有自己的一级缓存,不同SqlSession之间的缓存相互独立。一级缓存的工作机制是:当通过SqlSession执行查询操作时,MyBatis会先从一级缓存中查询,若存在则直接返回结果;若不存在则查询数据库,并将结果存入一级缓存。当执行增、删、改操作或关闭SqlSession时,一级缓存会被清空。

实战案例:验证一级缓存效果

@Test public void testFirstLevelCache() { // 获取第一个SqlSession try (SqlSession session1 = MyBatisUtil.getSqlSession()) { UserMapper mapper1 = session1.getMapper(UserMapper.class); // 第一次查询,从数据库获取数据,并存入一级缓存 User user1 = mapper1.selectUserById(1L); System.out.println(user1); // 第二次查询,同一SqlSession,直接从一级缓存获取数据,不查询数据库 User user2 = mapper1.selectUserById(1L); System.out.println(user2 == user1); // 输出true,说明是同一个对象 // 执行更新操作,清空一级缓存 User user = new User(); user.setId(1L); user.setUsername("test_cache"); mapper1.updateUser(user); session1.commit(); // 第三次查询,缓存已清空,重新从数据库获取数据 User user3 = mapper1.selectUserById(1L); System.out.println(user3 == user1); // 输出false } // 获取第二个SqlSession,与第一个SqlSession的缓存相互独立 try (SqlSession session2 = MyBatisUtil.getSqlSession()) { UserMapper mapper2 = session2.getMapper(UserMapper.class); // 第四次查询,新的SqlSession,从数据库获取数据 User user4 = mapper2.selectUserById(1L); System.out.println(user4 == user1); // 输出false } }

注意:一级缓存默认开启,无需额外配置;若需要禁用一级缓存,可在查询标签中添加flushCache="true"属性。

2. 二级缓存:Mapper级别的缓存,需手动开启

二级缓存也称为全局缓存,是Mapper级别的缓存,多个SqlSession可以共享同一个Mapper的二级缓存。二级缓存的工作机制是:当SqlSession执行查询操作并提交事务后,查询结果会存入二级缓存;其他SqlSession查询相同的语句时,会先从二级缓存中查询,若存在则直接返回结果,无需查询数据库。二级缓存的作用范围比一级缓存更广,适用于查询频率高、数据变更频率低的场景。

注意:执行增、删、改操作时,会自动清空当前Mapper的二级缓存,确保缓存数据与数据库数据一致;若需要禁用某条查询的二级缓存,可在查询标签中添加useCache="false"属性。

四、MyBatis注解开发:替代XML,简化开发流程

MyBatis不仅支持XML配置开发,还提供了注解开发方式,通过在Mapper接口方法上添加注解,直接指定SQL语句,无需编写XML文件,简化了开发流程,适用于简单的CRUD操作场景。常用的注解有@Select、@Insert、@Update、@Delete、@Results等。

1. 基础CRUD注解使用

@Mapper public interface UserMapper { // 查询:根据ID查询用户 @Select("SELECT id, username, age, email FROM t_user WHERE id = #{id}") User selectUserById(Long id); // 插入:新增用户 @Insert("INSERT INTO t_user (username, age, email) VALUES (#{username}, #{age}, #{email})") @Options(useGeneratedKeys = true, keyProperty = "id") // 自动生成主键,并赋值给实体类的id属性 int insertUser(User user); // 更新:修改用户信息 @Update("UPDATE t_user SET username = #{username}, age = #{age}, email = #{email} WHERE id = #{id}") int updateUser(User user); // 删除:根据ID删除用户 @Delete("DELETE FROM t_user WHERE id = #{id}") int deleteUserById(Long id); // 多条件查询,结合动态SQL注解 @Select({ "<script>", "SELECT id, username, age, email FROM t_user", "<where>", "<if test='username != null and username != \"\"'>", "AND username LIKE CONCAT('%', #{username}, '%')", "</if>", "<if test='age != null'>", "AND age = #{age}", "</if>", "</where>", "</script>" }) List<User> selectUserByCondition(@Param("username") String username, @Param("age") Integer age); }

注意:使用注解开发动态SQL时,需要用<script>标签包裹SQL语句,内部标签用法与XML一致;对于复杂的SQL语句,建议还是使用XML开发,可读性更高。

2. 结果映射注解@Results

当实体类属性名与数据库表字段名不一致时,可使用@Results注解实现结果映射,替代XML中的resultMap标签。

@Select("SELECT id, user_name, user_age, user_email FROM t_user WHERE id = #{id}") @Results({ @Result(column = "id", property = "id"), @Result(column = "user_name", property = "username"), // 表字段user_name映射到实体类username @Result(column = "user_age", property = "age"), // 表字段user_age映射到实体类age @Result(column = "user_email", property = "email") // 表字段user_email映射到实体类email }) User selectUserById(Long id);

五、MyBatis-Plus入门:简化CRUD,提升开发效率

MyBatis-Plus(简称MP)是基于MyBatis的增强工具,在MyBatis的基础上做了无侵入增强,提供了通用的CRUD方法、分页查询、条件构造器等功能,无需编写基础CRUD的SQL语句,极大地提升了开发效率。

1. 快速入门:配置与基础使用

第一步:引入MyBatis-Plus依赖(Maven)

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>

第二步:配置数据库连接(application.yml),与MyBatis配置一致

spring: datasource: url: jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true # 开启驼峰命名自动映射 mapper-locations: classpath:mapper/*.xml # 若有自定义XML,指定路径

第三步:编写实体类,使用注解指定表名和主键

@Data @TableName("t_user") // 指定对应的数据库表名 public class User { @TableId(type = IdType.AUTO) // 指定主键,自增策略 private Long id; private String username; private Integer age; private String email; // 无参、有参构造方法可省略,使用@Data注解自动生成getter/setter、toString等方法 }

2. 核心亮点:条件构造器QueryWrapper

MyBatis-Plus提供了QueryWrapper条件构造器,用于动态拼接查询条件,替代传统的动态SQL,使用更加简洁。

实战案例:根据姓名模糊查询、年龄大于指定值的用户

@Override public List<User> selectUserByCondition(String username, Integer age) { // 创建条件构造器 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); // 姓名模糊查询 queryWrapper.like(StringUtils.isNotBlank(username), "username", username); // 年龄大于指定值 queryWrapper.gt(age != null, "age", age); // 执行查询 return userMapper.selectList(queryWrapper); }

六、MyBatis优化技巧总结

结合以上进阶特性,总结MyBatis优化数据库操作的核心技巧,帮助大家在实际项目中提升系统性能和开发效率:

  1. 合理使用动态SQL:根据业务场景选择合适的动态SQL标签(if、where、foreach等),避免编写冗余SQL,同时注意SQL拼接的语法正确性。

  2. 优先使用物理分页:对于大量数据的分页查询,优先使用PageHelper插件或MyBatis-Plus的分页功能,避免使用原生RowBounds的内存分页,提升查询性能。

  3. 合理配置缓存:一级缓存默认开启,无需额外配置;二级缓存适用于查询频率高、数据变更频率低的场景,开启后可减少数据库查询次数,但需注意缓存的清空策略,确保数据一致性。

  4. 注解与XML结合使用:简单的CRUD操作使用注解开发,简化流程;复杂的SQL语句(如多表关联查询、复杂动态SQL)使用XML开发,提升可读性和维护性。

  5. 使用MyBatis-Plus简化开发:对于基础CRUD操作,优先使用MyBatis-Plus的通用方法和条件构造器,减少重复代码,提升开发效率;同时可结合MyBatis的原生特性,实现复杂业务需求。

  6. 优化SQL语句:避免使用SELECT *,只查询需要的字段;合理使用索引,提升查询速度;减少多表关联查询的复杂度,必要时可使用分步查询。

七、总结

本文详细讲解了MyBatis的进阶特性,包括动态SQL、分页查询、缓存机制,同时补充了注解开发和MyBatis-Plus入门知识,并结合实战案例进行了演示。这些进阶特性是优化数据库操作、提升系统性能和开发效率的关键,希望大家在实际项目中灵活运用,结合业务场景选择合适的技术方案。

MyBatis的学习是一个循序渐进的过程,基础CRUD是入门,进阶特性是提升,而真正的精通需要结合实际项目,不断总结和优化。后续我将继续分享MyBatis的高级用法(如多表关联查询、存储过程调用等),欢迎大家持续关注!

相关新闻

  • Kimi阅读Word的5个隐藏功能:90%用户不知道的智能解析技巧,今天不学明天就落后
  • 2026智能制造升级:PLC控制柜与冷库冷链电控箱专业供应商解析 - 企业推荐官【官方】
  • API中转返回200仍报错?检查choices和usage字段

最新新闻

  • SQL 窗口函数性能调优:ROWS 和 RANGE 的执行代价差异
  • 2026深圳正规搬家公司全名录清单盘点:同城跨城报价明细、5大隐形收费拆解+正规服务商甄选避坑及场景适配指南 - 厚道搬家
  • 项目性能复盘方法论:从故障 Postmortem 到可持续优化的工程文化构建
  • 宝珀中国官方售后服务中心|官方热线和门店地址权威信息声明(2026年7月最新) - 宝珀官方售后服务中心
  • 天津河西私教TOP5:资深教练这样选才不踩坑 - GrowUME
  • 智慧民航升级刚需:为什么机场必须普及护照阅读器技术?

日新闻

  • 宝珀中国官方售后服务中心|官方热线和维修地址权威信息声明(2026年7月更新) - 宝珀官方售后服务中心
  • # 2026年北京知识产权律师推荐怎么选?看这五点关键不踩雷 - 本地品牌推荐
  • 2026实测教程:生成的拼豆图纸不满意怎么修改才省事 - 省事研究所

周新闻

  • IX9104 PCIe5.0 高速交换芯片@ACP#完整规格 + 应用场景总结
  • Unity游戏集成Coze智能体:实现NPC智能对话与知识库联动
  • SAP EPIC 建行回单查询:从标准类CL_EPIC_EXAMPLE_CN_CCB_GHTD到Z类的5处关键修改

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号