ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

MyBatis核心原理与Spring Boot集成实战:从JDBC痛点解析到企业级应用

MyBatis核心原理与Spring Boot集成实战:从JDBC痛点解析到企业级应用

如果你是一名Java开发者,正在为如何高效、优雅地操作数据库而烦恼,那么这篇文章就是为你准备的。你可能已经习惯了直接写JDBC代码,或者被Hibernate的复杂映射和“黑盒”行为所困扰。今天,我们要深入探讨的,正是那个在两者之间找到了绝佳平衡点的持久层框架——MyBatis。

很多人对MyBatis的第一印象是“半自动ORM”,但这四个字背后隐藏的真正价值,远不止于此。它解决的,是Java开发中一个最核心也最繁琐的痛点:如何将面向对象的Java代码与关系型数据库的SQL世界,以一种可控、清晰且高效的方式连接起来。它没有试图完全隐藏SQL,而是选择拥抱SQL的灵活性,同时帮你处理了所有重复、机械的“脏活累活”,比如参数设置、结果集映射、连接管理。

这篇文章不会只告诉你MyBatis“是什么”,我们会深入剖析它“为什么”能成为Java企业级开发的事实标准,以及你“如何”才能真正用好它。我们将从最原始的JDBC痛点出发,一步步拆解MyBatis的核心设计哲学,并通过一个从零开始的Spring Boot集成示例,让你亲手体验它的威力。更重要的是,我们会探讨那些官方文档不会明说,但在实际项目中至关重要的“坑”与最佳实践,比如一级缓存引发的数据一致性问题、动态SQL的优雅写法、以及如何防止SQL注入。

无论你是正在学习MyBatis的新手,还是希望深化理解、排查生产问题的资深开发者,这篇文章都将提供清晰的路径和可落地的代码。

1. 这篇文章真正要解决的问题

在深入技术细节之前,我们必须先回答一个根本问题:为什么我们需要MyBatis?直接使用JDBC不行吗?用全自动的Hibernate不是更省事吗?

核心痛点在于控制力与开发效率的权衡。

  • 纯JDBC:控制力满分,但开发效率极低。你需要手动管理连接、拼装SQL、设置参数、遍历ResultSet、处理异常、关闭资源。大量的样板代码(Boilerplate Code)不仅枯燥,更容易出错,尤其是资源泄露问题。
  • 全自动ORM(如早期Hibernate):开发效率高,但控制力减弱。框架自动生成SQL,对于复杂查询、性能优化或数据库特性利用,开发者往往感到束手无策,调试生成的SQL也较为困难。

MyBatis精准地切入了这个空白地带。它不替你生成SQL,你将SQL的掌控权牢牢握在手中,可以编写任何复杂、优化的SQL语句。同时,它接管了JDBC中所有重复的劳动:自动将Java对象(POJO)属性映射到SQL参数,自动将查询结果集映射回Java对象。这种“SQL与代码分离”的设计,使得SQL易于维护和优化,而Java代码则保持简洁。

因此,这篇文章要解决的,正是如何从一个JDBC的“泥潭”或全ORM的“黑盒”中解脱出来,掌握一种既高效又透明的数据库访问方式。它特别适合以下场景的开发者:

  1. 需要执行复杂SQL、存储过程或高度依赖数据库特定功能的项目。
  2. 团队对SQL性能有极高要求,希望直接优化SQL语句。
  3. 从遗留系统迁移或需要与现有复杂SQL共存的场景。
  4. 希望持久层代码清晰、易于调试和Code Review的团队。

2. 基础概念与核心原理

理解MyBatis,需要先理清几个核心概念,它们构成了MyBatis工作的骨架。

2.1 核心组件

  • SqlSessionFactory:这是MyBatis的“心脏”。每个基于MyBatis的应用都以一个SqlSessionFactory实例为中心。它负责创建SqlSession对象。通常由全局唯一的一个实例,通过读取配置文件(如mybatis-config.xml)来构建。
  • SqlSession:这是你与数据库交互的“会话”。你可以通过它执行映射的SQL语句、提交或回滚事务、获取映射器(Mapper)。重要:SqlSession不是线程安全的,因此最佳实践是在每个请求或方法作用域内创建,用完后立即关闭。
  • Mapper接口:这是一个Java接口,其方法对应Mapper XML文件或注解中的SQL语句。MyBatis会使用动态代理技术,在运行时为你生成这个接口的实现类。这是MyBatis实现“面向接口编程”的关键。
  • Mapper XML文件:这是SQL语句的“家园”。它将SQL语句与Mapper接口的方法进行绑定,并定义了详细的参数映射和结果映射规则。SQL与Java代码在此分离。

2.2 工作流程(简化版)

  1. 应用启动,加载mybatis-config.xml全局配置文件,创建SqlSessionFactory
  2. 当需要执行数据库操作时,从SqlSessionFactory中获取一个SqlSession
  3. 通过SqlSession,获取某个Mapper接口的代理对象(如UserMapper userMapper = sqlSession.getMapper(UserMapper.class))。
  4. 调用Mapper接口的方法(如userMapper.selectUserById(1))。
  5. MyBatis底层根据接口方法名,找到对应的Mapper XML中的SQL语句(如id为selectUserById<select>标签)。
  6. 框架执行SQL:将传入的Java参数转换为JDBC参数(PreparedStatement?占位符),执行SQL,获取ResultSet
  7. 核心映射:根据XML中定义的<resultMap>规则,将ResultSet中的数据自动填充到返回的Java对象(或集合)中。
  8. 返回结果,关闭SqlSession

2.3 与JDBC、Hibernate的对比为了更直观,我们用下表对比三者的核心差异:

特性维度原生 JDBCHibernate (全自动ORM)MyBatis (半自动ORM)
SQL控制完全手动编写,控制力最强。框架自动生成,控制力弱,可写HQL。手动编写,控制力强,SQL可见、可优化。
学习曲线较低(基础API),但精通难。较高,需掌握Session、缓存、HQL、对象状态等复杂概念。中等,核心是SQL和映射配置,概念更直接。
开发效率极低,大量样板代码。,CRUD操作简单。高,但需编写SQL和XML。
性能调优直接优化SQL,但需手动处理连接池等。需理解框架缓存、抓取策略,优化有门槛。直接优化SQL,性能透明,易于定位瓶颈。
适用场景极简应用、框架底层、需要绝对控制。业务模型复杂、对象关系复杂、以对象操作为主的项目。SQL复杂、需深度优化、遗留系统集成、强调DBA与开发协作的项目。

MyBatis的核心原理,可以概括为“用接口声明意图,用XML描述实现,由框架完成粘合与执行”

3. 环境准备与前置条件

接下来,我们通过一个最流行的方式——Spring Boot集成,来快速搭建一个MyBatis环境。你将看到,在现代Spring Boot生态下,集成MyBatis变得异常简单。

3.1 基础环境要求

  • JDK:1.8 或更高版本(推荐 JDK 11 或 17)。
  • 构建工具:Maven 3.6+ 或 Gradle。
  • IDE:IntelliJ IDEA、Eclipse 或 VS Code 等。
  • 数据库:MySQL 5.7 / 8.0(本文以MySQL为例,MyBatis支持所有主流数据库)。

3.2 初始化Spring Boot项目你可以通过 Spring Initializr 快速生成项目,选择以下依赖:

  • Spring Web(可选,用于构建Web应用演示)
  • MyBatis Framework
  • MySQL Driver

生成的pom.xml核心依赖部分如下:

<!-- pom.xml --> <dependencies> <!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis Spring Boot Starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> <!-- 请使用最新稳定版 --> </dependency> <!-- MySQL Connector --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- 方便测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

3.3 数据库准备在MySQL中创建一个测试数据库和表:

CREATE DATABASE IF NOT EXISTS mybatis_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE mybatis_demo; CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(30) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年龄', `email` varchar(50) DEFAULT NULL COMMENT '邮箱', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; INSERT INTO `user` (`name`, `age`, `email`) VALUES ('张三', 25, 'zhangsan@example.com'), ('李四', 30, 'lisi@example.com');

4. 核心配置与项目结构

Spring Boot极大简化了配置。我们主要关注application.yml和项目结构。

4.1 应用配置文件 (application.yml)

# src/main/resources/application.yml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root # 请替换为你的数据库用户名 password: yourpassword # 请替换为你的数据库密码 # MyBatis 配置 mybatis: # 指定Mapper XML文件的位置 mapper-locations: classpath:mapper/*.xml # 指定实体类别名包,这样在XML中可以直接写类名,而不需要写全限定名 type-aliases-package: com.example.mybatisdemo.entity # 开启驼峰命名自动映射(数据库user_name -> Java属性userName) configuration: map-underscore-to-camel-case: true # 其他可选配置,如日志实现 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 在控制台打印执行的SQL,便于调试

4.2 项目目录结构一个清晰的Maven项目结构如下:

src/main/java ├── com.example.mybatisdemo │ ├── MybatisDemoApplication.java # Spring Boot 主类 │ ├── entity │ │ └── User.java # 实体类 (POJO) │ ├── mapper │ │ └── UserMapper.java # Mapper 接口 │ └── service │ ├── UserService.java # 业务服务接口 │ └── impl │ └── UserServiceImpl.java # 业务服务实现 src/main/resources ├── application.yml # 配置文件 └── mapper └── UserMapper.xml # Mapper XML 文件

5. 从零开始:实体、Mapper接口与XML映射

让我们一步步创建核心组件。

5.1 实体类 (Entity/Pojo)这是与数据库表对应的Java对象。

// src/main/java/com/example/mybatisdemo/entity/User.java package com.example.mybatisdemo.entity; import java.time.LocalDateTime; public class User { private Long id; private String name; private Integer age; private String email; private LocalDateTime createTime; // 驼峰命名,对应数据库的create_time // 无参构造器、全参构造器、Getter/Setter、toString() 方法 // 这里使用Lombok注解可以极大简化代码,但为了清晰,我们手动写出 public User() { } public User(Long id, String name, Integer age, String email, LocalDateTime createTime) { this.id = id; this.name = name; this.age = age; this.email = email; this.createTime = createTime; } // 省略 Getter 和 Setter 方法... // 省略 toString 方法... }

5.2 Mapper接口定义我们想要执行的数据库操作。

// src/main/java/com/example/mybatisdemo/mapper/UserMapper.java package com.example.mybatisdemo.mapper; import com.example.mybatisdemo.entity.User; import org.apache.ibatis.annotations.*; import java.util.List; @Mapper // Spring Boot 需要这个注解来扫描并注册该接口为MyBatis的Mapper public interface UserMapper { /** * 根据ID查询用户 * @param id 用户ID * @return 用户对象 */ User selectUserById(Long id); /** * 查询所有用户 * @return 用户列表 */ List<User> selectAllUsers(); /** * 插入一个新用户 * @param user 用户对象 * @return 受影响的行数 */ int insertUser(User user); /** * 根据ID更新用户信息 * @param user 用户对象(包含ID) * @return 受影响的行数 */ int updateUser(User user); /** * 根据ID删除用户 * @param id 用户ID * @return 受影响的行数 */ int deleteUserById(Long id); /** * 根据姓名模糊查询 * @param name 姓名关键词 * @return 用户列表 */ List<User> selectUsersByName(String name); }

5.3 Mapper XML文件这是MyBatis的灵魂,SQL语句和映射规则在这里定义。

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace 必须对应 Mapper 接口的全限定名 --> <mapper namespace="com.example.mybatisdemo.mapper.UserMapper"> <!-- 定义可重用的结果映射。如果属性名和列名能自动映射(开启驼峰),则可不写。 但复杂映射(如关联查询)时必须定义。 --> <resultMap id="UserResultMap" type="User"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="age" column="age"/> <result property="email" column="email"/> <result property="createTime" column="create_time"/> </resultMap> <!-- id 对应 Mapper 接口中的方法名 --> <select id="selectUserById" resultMap="UserResultMap" parameterType="Long"> SELECT id, name, age, email, create_time FROM user WHERE id = #{id} </select> <select id="selectAllUsers" resultMap="UserResultMap"> SELECT id, name, age, email, create_time FROM user </select> <!-- useGeneratedKeys 和 keyProperty 用于获取自增主键并回填到实体对象中 --> <insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id"> INSERT INTO user (name, age, email) VALUES (#{name}, #{age}, #{email}) </insert> <update id="updateUser" parameterType="User"> UPDATE user SET name = #{name}, age = #{age}, email = #{email} WHERE id = #{id} </update> <delete id="deleteUserById" parameterType="Long"> DELETE FROM user WHERE id = #{id} </delete> <!-- 动态SQL示例:根据条件查询 --> <select id="selectUsersByName" resultMap="UserResultMap" parameterType="String"> SELECT id, name, age, email, create_time FROM user WHERE 1=1 <!-- test 属性中写的是OGNL表达式,判断参数是否不为空且不为空字符串 --> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <!-- 可以添加更多条件,如按年龄过滤 --> <!-- <if test="age != null"> AND age = #{age} </if> --> </select> </mapper>

关键点解析:

  • namespace:将此XML文件与UserMapper接口绑定。
  • #{id}:这是MyBatis的参数占位符,等同于JDBC的?。MyBatis会进行预编译,有效防止SQL注入。与之相对的是${},它是字符串替换,有注入风险,慎用。
  • resultMap:定义了查询结果列与Java对象属性的映射关系。虽然开启了驼峰映射,但显式定义resultMap是更清晰、更可控的做法,尤其在列名与属性名差异较大或有关联查询时。
  • useGeneratedKeys="true" keyProperty="id":这是插入后获取自增主键的经典配置,插入成功后,传入的user对象的id属性会被自动赋值。

6. 编写Service与Controller进行测试

为了验证我们的配置,我们创建Service层和一个简单的REST接口。

6.1 Service接口与实现

// src/main/java/com/example/mybatisdemo/service/UserService.java package com.example.mybatisdemo.service; import com.example.mybatisdemo.entity.User; import java.util.List; public interface UserService { User getUserById(Long id); List<User> getAllUsers(); int createUser(User user); int updateUser(User user); int deleteUser(Long id); List<User> searchUsersByName(String name); }
// src/main/java/com/example/mybatisdemo/service/impl/UserServiceImpl.java package com.example.mybatisdemo.service.impl; import com.example.mybatisdemo.entity.User; import com.example.mybatisdemo.mapper.UserMapper; import com.example.mybatisdemo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; // MyBatis会自动注入代理实现 @Override public User getUserById(Long id) { return userMapper.selectUserById(id); } @Override public List<User> getAllUsers() { return userMapper.selectAllUsers(); } @Override @Transactional // 声明事务,确保操作原子性 public int createUser(User user) { return userMapper.insertUser(user); // 插入后,user.getId() 已经包含了数据库生成的自增ID } @Override @Transactional public int updateUser(User user) { return userMapper.updateUser(user); } @Override @Transactional public int deleteUser(Long id) { return userMapper.deleteUserById(id); } @Override public List<User> searchUsersByName(String name) { return userMapper.selectUsersByName(name); } }

6.2 简单的REST Controller

// src/main/java/com/example/mybatisdemo/controller/UserController.java package com.example.mybatisdemo.controller; import com.example.mybatisdemo.entity.User; import com.example.mybatisdemo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public User getUser(@PathVariable Long id) { return userService.getUserById(id); } @GetMapping public List<User> getAllUsers() { return userService.getAllUsers(); } @PostMapping public String createUser(@RequestBody User user) { int result = userService.createUser(user); return result > 0 ? "创建成功,用户ID: " + user.getId() : "创建失败"; } @PutMapping public String updateUser(@RequestBody User user) { int result = userService.updateUser(user); return result > 0 ? "更新成功" : "更新失败(用户可能不存在)"; } @DeleteMapping("/{id}") public String deleteUser(@PathVariable Long id) { int result = userService.deleteUser(id); return result > 0 ? "删除成功" : "删除失败(用户可能不存在)"; } @GetMapping("/search") public List<User> searchUsers(@RequestParam String name) { return userService.searchUsersByName(name); } }

7. 运行结果与效果验证

7.1 启动应用运行Spring Boot主类MybatisDemoApplication。观察控制台,如果没有报错且看到Tomcat启动端口,说明MyBatis与Spring Boot集成成功。

7.2 测试接口使用Postman、curl或浏览器进行测试:

  1. 查询所有用户GET http://localhost:8080/api/users
  2. 根据ID查询GET http://localhost:8080/api/users/1
  3. 创建用户POST http://localhost:8080/api/usersBody (JSON):
    { "name": "王五", "age": 28, "email": "wangwu@example.com" }
    响应应包含新生成的ID。
  4. 模糊搜索GET http://localhost:8080/api/users/search?name=张
  5. 更新用户PUT http://localhost:8080/api/usersBody (JSON):
    { "id": 1, "name": "张三丰", "age": 100, "email": "zhangsanfeng@example.com" }
  6. 删除用户DELETE http://localhost:8080/api/users/3

7.3 查看执行的SQL如果你在配置中开启了log-impl: org.apache.ibatis.logging.stdout.StdOutImpl,在控制台可以看到MyBatis执行的每一条SQL语句及其参数,这对于调试和性能分析至关重要。

8. 深入核心特性与高级用法

掌握了基础CRUD后,我们来看看MyBatis那些让开发事半功倍的核心特性。

8.1 动态SQL这是MyBatis的王牌功能之一,用于构建灵活的SQL语句。上面的<if>标签只是开始。

<!-- 一个更复杂的动态查询示例 --> <select id="selectUsersByCondition" resultMap="UserResultMap" parameterType="map"> SELECT * FROM user <where> <!-- 避免 WHERE 关键字后直接跟 AND/OR 的语法错误 --> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="minAge != null"> AND age >= #{minAge} </if> <if test="maxAge != null"> AND age &lt;= #{maxAge} <!-- XML中需转义 < --> </if> <if test="email != null"> AND email = #{email} </if> </where> ORDER BY create_time DESC </select>

对应的Mapper接口方法:List<User> selectUsersByCondition(Map<String, Object> params);

<where>标签会智能处理:如果其内部有内容,则插入WHERE关键字,并且会去除内容开头多余的ANDOR

其他常用动态SQL标签:

  • <choose>/<when>/<otherwise>:类似Java的switch-case
  • <foreach>:用于IN查询或批量操作。
    <select id="selectUsersByIdList" resultMap="UserResultMap"> SELECT * FROM user WHERE id IN <foreach item="id" collection="list" open="(" separator="," close=")"> #{id} </foreach> </select>
  • <set>:用于动态更新,类似<where>,会去除末尾的逗号。
    <update id="updateUserSelective" parameterType="User"> UPDATE user <set> <if test="name != null">name = #{name},</if> <if test="age != null">age = #{age},</if> <if test="email != null">email = #{email},</if> </set> WHERE id = #{id} </update>

8.2 结果映射(ResultMap)高级用法处理“一对多”、“多对一”关联查询是ORM的难点。MyBatis通过<association><collection>优雅解决。

假设我们新增一个Order订单表,一个用户有多个订单。

CREATE TABLE `order` ( `id` bigint PRIMARY KEY AUTO_INCREMENT, `order_no` varchar(64) NOT NULL, `user_id` bigint NOT NULL, `amount` decimal(10,2) NOT NULL );

一对多查询(查询用户及其所有订单):

<!-- 在UserMapper.xml中 --> <resultMap id="UserWithOrdersResultMap" type="User" extends="UserResultMap"> <!-- 继承基础的UserResultMap --> <!-- collection 标签用于映射一对多关系 --> <collection property="orderList" ofType="Order" columnPrefix="order_"> <id property="id" column="id"/> <result property="orderNo" column="order_no"/> <result property="amount" column="amount"/> <!-- user_id 通常不需要再映射回Java对象 --> </collection> </resultMap> <select id="selectUserWithOrders" resultMap="UserWithOrdersResultMap"> SELECT u.id, u.name, u.age, u.email, u.create_time, o.id as order_id, o.order_no as order_order_no, o.amount as order_amount FROM user u LEFT JOIN `order` o ON u.id = o.user_id WHERE u.id = #{userId} </select>

User实体类中需要增加List<Order> orderList属性。

8.3 注解开发对于简单的SQL,可以使用注解,避免XML文件。但复杂SQL和动态SQL仍推荐XML。

@Mapper public interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") @Results(id = "userMap", value = { @Result(property = "id", column = "id", id = true), @Result(property = "createTime", column = "create_time") }) User selectUserByIdAnnotation(Long id); @Insert("INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})") @Options(useGeneratedKeys = true, keyProperty = "id") int insertUserAnnotation(User user); // 动态SQL用注解比较麻烦,可以使用@SelectProvider等,但可读性不如XML }

注解 vs XML 选择:简单、固定的SQL用注解更简洁;复杂、动态的SQL用XML更清晰、更强大。很多项目采用混合模式。

9. 必须警惕的“坑”与最佳实践

MyBatis很强大,但使用不当也会带来问题。以下是实际项目中高频出现的注意事项。

9.1 一级缓存导致的“脏读”MyBatis的一级缓存(Local Cache)默认开启,其生命周期与SqlSession相同。在同一个SqlSession内,执行两次相同的查询,第二次会直接返回缓存结果,不会访问数据库。

坑点:在开启事务(例如使用@Transactional)的场景下,Spring会保证整个方法使用同一个SqlSession。如果你在方法内先更新了某条数据,紧接着又查询同一条数据,你查到的可能还是缓存中的旧数据!

解决方案

  1. 在查询语句上设置flushCache="true",强制清空缓存并查询数据库。
    <select id="selectUserById" resultMap="UserResultMap" flushCache="true"> SELECT * FROM user WHERE id = #{id} </select>
  2. 在需要最新数据的方法中,手动控制SqlSession或避免在同一个事务方法内进行“写后读”操作。
  3. 理解并合理使用缓存,对于实时性要求高的数据,可以考虑关闭一级缓存(localCacheScope=STATEMENT)或使用二级缓存。

9.2#{}${}的正确使用

  • #{}:是预编译处理,MyBatis会将其替换为?,然后使用PreparedStatementset方法安全赋值。能有效防止SQL注入绝大多数情况都应使用它
  • ${}:是字符串替换,MyBatis会直接将参数值替换到SQL语句中。存在SQL注入风险。仅用于动态指定表名、列名等非值参数
    <!-- 错误示例:存在注入风险 --> <select id="selectByOrder" resultMap="UserResultMap"> SELECT * FROM user ORDER BY ${orderByColumn} </select> <!-- 如果orderByColumn传入 `id; DROP TABLE user--`,后果严重 --> <!-- 相对安全的用法:动态表名(但仍需在业务层严格校验参数) --> <select id="selectFromTable" resultType="map"> SELECT * FROM ${tableName} WHERE id = #{id} <!-- #{id} 仍然是安全的 --> </select>

最佳实践:永远将用户输入或外部参数用#{}。使用${}时,必须确保参数值在代码中硬编码或经过严格的白名单校验。

9.3 参数传递与@Param注解当Mapper接口方法有多个参数时,MyBatis默认无法识别它们的名字。

// 错误:XML中无法识别 #{name} 和 #{age} 对应哪个参数 List<User> selectUsers(String name, Integer age);

解决方案

  1. 使用@Param注解:
    List<User> selectUsers(@Param("name") String name, @Param("age") Integer age);
    XML中即可使用#{name}#{age}
  2. 将参数封装为Map或一个POJO对象传递。

9.4 分页查询MyBatis本身不提供物理分页,但可以通过LIMIT语句实现。更推荐使用成熟的分页插件,如PageHelper,它能无缝集成,极大简化分页逻辑。

<!-- 使用PageHelper后,Mapper接口方法无需任何改变 --> <select id="selectAllUsers" resultMap="UserResultMap"> SELECT * FROM user </select>

在Service层:

// 关键代码 PageHelper.startPage(pageNum, pageSize); // 紧跟在查询方法前 List<User> userList = userMapper.selectAllUsers(); PageInfo<User> pageInfo = new PageInfo<>(userList); // pageInfo包含了分页的所有信息(总记录数、总页数、当前页数据等)

9.5 日志与调试

  • 开启SQL日志(如配置StdOutImpl)是调试的利器。
  • 可以使用MyBatis Log Plugin等IDE插件,将MyBatis执行的SQL还原成完整的、带参数的语句,直接复制到数据库客户端执行,非常方便。

9.6 生产环境最佳实践

  1. 连接池:务必使用生产级连接池(如HikariCP,Spring Boot默认集成)。
  2. XML管理:将SQL集中放在XML中,便于DBA评审和优化。
  3. 代码生成器:对于简单的单表CRUD,可以使用MyBatis Generator或MyBatis-Plus的代码生成器,避免手动编写重复代码。
  4. MyBatis-Plus:如果你是MyBatis的重度用户,强烈建议了解MyBatis-Plus。它在MyBatis基础上做了大量增强,提供了通用的Mapper、Service封装、强大的条件构造器、分页插件等,能进一步提升开发效率,同时保持对原生MyBatis的兼容。
  5. 监控:集成Micrometer等监控组件,观察数据库连接池状态、慢SQL等。

MyBatis的成功在于它做出了一个明智的取舍:将SQL的灵活性和控制权交还给开发者,同时通过自动映射和会话管理消除了JDBC的样板代码。它不是一个试图解决所有问题的“魔法”框架,而是一个强大且透明的“助手”。理解它的核心原理——接口绑定、SQL映射、动态SQL和缓存机制——是高效使用它的关键。从今天搭建的这个Spring Boot项目开始,尝试在你的下一个需要复杂SQL或高性能数据访问的项目中应用它,亲自体会那种“SQL尽在掌握”的畅快感。对于更进阶的需求,深入探索动态SQL、插件开发(如分页、数据权限)以及MyBatis-Plus生态,你的持久层代码将会更加优雅和强大。

返回列表