MyBatis入门与实战:从JDBC痛点解析到核心机制深度应用
2026/8/7 6:21:22 网站建设 项目流程

这类工具最值得先看的不是功能列表,而是能不能在普通环境里稳定跑起来。MyBatis 就是这样一个东西:它不是一个新数据库,也不是一个独立系统,而是一个帮你把 Java 对象和数据库表记录“粘”起来的持久层框架。如果你写过原生的 JDBC 代码,就会知道从连接、拼 SQL、传参、处理结果集到关连接,每一步都是重复且容易出错的体力活。MyBatis 的核心价值就是把这些重复劳动标准化、配置化,让你能更专注在业务逻辑和 SQL 本身。

很多人第一次接触 MyBatis 会困惑,它和直接写 JDBC 有什么区别?和 Hibernate 又有什么不同?其实关键就两点:控制权开发效率。MyBatis 没有像 Hibernate 那样试图完全屏蔽 SQL,它承认 SQL 是数据库交互的核心,所以把 SQL 的编写权完全交还给你,同时帮你解决 JDBC 里那些繁琐的“脏活”。这特别适合对 SQL 有优化需求,或者数据库 Schema 比较复杂的项目。

我建议先从最小样例开始,理解 MyBatis 是怎么把一段 SQL 和一个 Java 方法绑定起来的。这比一上来就研究动态 SQL、缓存、插件这些高级特性要实在得多。下面按实际落地顺序拆一遍。

1. 先弄明白 MyBatis 到底解决了 JDBC 的哪些痛点

在讨论 MyBatis 怎么用之前,得先搞清楚我们为什么需要它。直接使用 JDBC 不是不行,但在实际项目中,尤其是需要快速迭代和维护时,原生 JDBC 的写法会暴露出几个非常具体的问题。

1.1 原生 JDBC 的样板代码太多,且容易出错

一段典型的 JDBC 查询代码,即便是最简单的根据 ID 查询用户,也需要经历以下步骤:

// 1. 加载驱动(现代 JDBC 通常省略,由 SPI 机制自动加载) // 2. 建立连接 Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; User user = null; try { conn = DriverManager.getConnection(url, username, password); // 3. 创建 PreparedStatement,编写 SQL String sql = "SELECT id, name, email FROM user WHERE id = ?"; pstmt = conn.prepareStatement(sql); // 4. 设置参数 pstmt.setInt(1, userId); // 5. 执行查询 rs = pstmt.executeQuery(); // 6. 遍历结果集,手动映射到 Java 对象 if (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setName(rs.getString("name")); user.setEmail(rs.getString("email")); // ... 更多字段 } } catch (SQLException e) { // 处理异常 e.printStackTrace(); } finally { // 7. 按顺序关闭资源,必须放在 finally 块确保执行 try { if (rs != null) rs.close(); } catch (SQLException e) { /* ignore */ } try { if (pstmt != null) pstmt.close(); } catch (SQLException e) { /* ignore */ } try { if (conn != null) conn.close(); } catch (SQLException e) { /* ignore */ } }

这段代码的问题非常明显:

  • 重复性高:每个查询、插入、更新操作,都需要重复这 7 步模板代码。
  • 资源管理繁琐:必须手动管理ConnectionPreparedStatementResultSet的创建和关闭,顺序不能错,关闭必须放在finally块,否则可能导致连接泄漏。
  • 手动映射枯燥易错:从ResultSet中一个个getXxxset到对象属性,字段多时极其枯燥,且容易因字段名拼写错误或类型不匹配而引入 Bug。
  • SQL 与 Java 代码强耦合:SQL 以字符串形式硬编码在 Java 文件中,修改 SQL 需要重新编译 Java 代码,不利于维护和 DBA 评审。

1.2 MyBatis 提供的核心解决方案

MyBatis 正是针对上述痛点设计的。它的工作模式可以概括为:

  1. SQL 与代码分离:将 SQL 语句从 Java 代码中剥离,写入 XML 配置文件或注解中。这使得 SQL 更清晰,便于管理和优化。
  2. 自动参数映射:将 Java 方法传入的参数(基本类型、Map、JavaBean)自动、安全地设置到 SQL 的占位符(#{})中,无需手动调用pstmt.setXxx
  3. 自动结果集映射:将查询返回的ResultSet自动映射成 Java 对象(单个对象、List、Map 等),省去了手动get/set的繁琐过程。
  4. 内置连接池与事务管理:MyBatis 可以集成如 HikariCP、Druid 等高性能连接池,并提供了简单易用的事务管理接口,开发者无需关心底层的连接获取与释放。

用 MyBatis 实现同样的查询,代码会简洁得多。首先,在 Mapper 接口中定义一个方法:

public interface UserMapper { User selectUserById(Integer id); }

然后,在一个对应的 XML 文件(如UserMapper.xml)中编写 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"> <mapper namespace="com.example.mapper.UserMapper"> <select id="selectUserById" resultType="com.example.model.User"> SELECT id, name, email FROM user WHERE id = #{id} </select> </mapper>

最后,在 Java 代码中通过SqlSession获取 Mapper 并调用:

try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.selectUserById(1); // 直接使用 user 对象 }

可以看到,资源管理(SqlSession实现了AutoCloseable)、参数设置、结果映射这些“脏活”都被 MyBatis 默默处理掉了。

1.3 与 Hibernate 的定位差异:为什么是“半自动化”

这也是面试常问的点。Hibernate 是一个全自动的 ORM(对象关系映射)框架,它提供了 HQL(Hibernate Query Language),目标是让开发者完全用面向对象的方式操作数据库,尽量不写 SQL。这在简单、标准的 CRUD 场景下开发效率极高。

但全自动化带来了两个问题:

  1. 对 SQL 的控制力弱:自动生成的 SQL 可能不是最优的,在复杂查询、大数据量、需要深度优化时,开发者往往感到束手束脚。
  2. 学习曲线陡峭:需要理解 Session、缓存、延迟加载、级联等复杂概念,调试问题有时比较困难。

MyBatis 则自称“半自动化” ORM。它不尝试生成或替换 SQL,而是增强 SQL。它认为 SQL 应该是开发者的核心资产,框架只负责把 SQL 执行好,并把结果方便地送回来。因此,MyBatis 的学习成本相对较低,对于熟悉 SQL 的开发者来说上手更快,在需要复杂 SQL、存储过程调用、多表关联查询等场景下更加灵活和直观。

2. 环境搭建与最小可运行示例:从零到一跑起来

理解了“为什么用”,接下来就是“怎么用”。我建议不要一开始就整合 Spring Boot,先用最核心的 MyBatis 独立环境跑通一个例子,这样能更清楚地看到它的核心组件是如何协作的。

2.1 核心依赖与项目结构

创建一个普通的 Maven 项目,在pom.xml中引入 MyBatis 核心依赖和数据库驱动(以 MySQL 为例):

<dependencies> <!-- MyBatis 核心 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> <!-- 请使用当时最新稳定版 --> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <!-- 日志,方便查看执行的 SQL --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.9</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.11</version> </dependency> </dependencies>

项目目录结构建议如下:

mybatis-demo/ ├── pom.xml ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ ├── model/ │ │ │ │ └── User.java # 实体类 │ │ │ ├── mapper/ │ │ │ │ └── UserMapper.java # Mapper 接口 │ │ │ └── App.java # 主类,包含测试代码 │ │ └── resources/ │ │ ├── mybatis-config.xml # MyBatis 全局配置文件 │ │ └── com/example/mapper/ │ │ └── UserMapper.xml # SQL 映射文件 │ └── test/ │ └── ... # 测试代码

2.2 编写核心组件

1. 实体类 (User.java)这是一个简单的 POJO (Plain Old Java Object),属性对应数据库表的字段。

package com.example.model; public class User { private Integer id; private String name; private String email; // 省略 getter, setter, toString 方法。实际项目中请使用 Lombok 或手动生成。 }

2. Mapper 接口 (UserMapper.java)定义数据操作的方法签名。方法名与后续 XML 中的id对应。

package com.example.mapper; import com.example.model.User; public interface UserMapper { User selectUserById(Integer id); }

3. SQL 映射文件 (UserMapper.xml)这是 MyBatis 的灵魂。namespace属性必须对应 Mapper 接口的全限定名,<select>标签的id对应接口方法名,resultType指定返回结果的 Java 类型。

<?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"> <mapper namespace="com.example.mapper.UserMapper"> <!-- 查询语句 --> <select id="selectUserById" resultType="com.example.model.User"> SELECT id, name, email FROM user WHERE id = #{id} </select> </mapper>

注意#{id},这是 MyBatis 的参数占位符,它会安全地进行预编译参数设置,能有效防止 SQL 注入。

4. MyBatis 全局配置文件 (mybatis-config.xml)这个文件配置数据源、事务管理器、以及告诉 MyBatis 去哪里找 SQL 映射文件。

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!-- 开启下划线转驼峰命名映射,如果数据库字段是 user_name,Java 属性是 userName --> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 打印查询语句到日志,调试非常有用 --> <setting name="logImpl" value="SLF4J"/> </settings> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <!-- 配置数据源,这里使用最基础的 POOLED --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="yourpassword"/> </dataSource> </environment> </environments> <!-- 告诉 MyBatis Mapper 映射文件的位置 --> <mappers> <mapper resource="com/example/mapper/UserMapper.xml"/> </mappers> </configuration>

2.3 编写测试代码并运行

App.java中,我们按步骤加载配置、创建SqlSessionFactory、获取SqlSessionMapper,最后执行查询。

package com.example; import com.example.mapper.UserMapper; import com.example.model.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; public class App { public static void main(String[] args) { // 1. 加载 MyBatis 全局配置文件 String resource = "mybatis-config.xml"; InputStream inputStream = null; SqlSession sqlSession = null; try { inputStream = Resources.getResourceAsStream(resource); // 2. 构建 SqlSessionFactory(相当于数据库连接池工厂) SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 3. 获取 SqlSession(代表一次数据库会话) sqlSession = sqlSessionFactory.openSession(); // 4. 获取 Mapper 接口的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 5. 调用接口方法,执行 SQL User user = userMapper.selectUserById(1); System.out.println("查询到的用户: " + user); // 6. 提交事务(如果是查询,非必须。增删改需要) sqlSession.commit(); } catch (IOException e) { e.printStackTrace(); } finally { // 7. 关闭会话,释放连接回连接池 if (sqlSession != null) { sqlSession.close(); } } } }

运行这段代码,如果控制台打印出用户信息,并且日志中能看到执行的 SQL 语句(类似==> Preparing: SELECT id, name, email FROM user WHERE id = ?),那么恭喜你,第一个 MyBatis 程序就跑通了。

注意:这里最容易忽略的是 XML 文件的路径和namespace的对应关系。Resources.getResourceAsStream是从类路径(classpath)的根目录开始找文件,所以配置文件要放在resources目录下,并且路径要写对。namespace必须是接口的全限定名,一个字母都不能错。

3. 核心机制深度解析:参数、结果映射与动态 SQL

跑通 Demo 只是第一步。要让 MyBatis 在真实项目中发挥作用,必须理解它的几个核心工作机制。很多人卡壳,问题都出在这些细节上。

3.1 参数传递的多种姿势

#{}是 MyBatis 的参数占位符,它底层使用PreparedStatement,能防止 SQL 注入。参数可以来自多种类型:

1. 基本类型和 String

// Mapper 接口 User selectByAge(int age); // XML <select id="selectByAge" resultType="User"> SELECT * FROM user WHERE age = #{age} </select>

#{age}里的名字age可以任意写,对于单个基本类型参数,MyBatis 直接使用。

2. 多个参数:使用@Param注解当方法有多个参数时,MyBatis 默认会按param1, param2, ...或者arg0, arg1, ...来识别,但这可读性差。推荐使用@Param注解。

List<User> selectByCondition(@Param("minAge") Integer minAge, @Param("nameLike") String nameLike);
<select id="selectByCondition" resultType="User"> SELECT * FROM user WHERE age >= #{minAge} AND name LIKE CONCAT('%', #{nameLike}, '%') </select>

3. 传入 JavaBean 对象这是最常见的方式,属性名直接作为参数名。

// 假设有一个 UserQuery 对象,有 minAge, maxAge, name 属性 List<User> selectByUserQuery(UserQuery query);
<select id="selectByUserQuery" resultType="User"> SELECT * FROM user WHERE age BETWEEN #{minAge} AND #{maxAge} AND name = #{name} </select>

4. 传入 MapMap 的 key 就是参数名。

List<User> selectByMap(Map<String, Object> params);
Map<String, Object> params = new HashMap<>(); params.put("status", 1); params.put("role", "admin"); List<User> users = userMapper.selectByMap(params);
<select id="selectByMap" resultType="User"> SELECT * FROM user WHERE status = #{status} AND role = #{role} </select>

#{}${}的本质区别这是面试必问,也是安全关键点。

  • #{}:是预编译处理。MyBatis 会将其替换为?,然后使用PreparedStatementset方法来安全赋值。能防止 SQL 注入。
  • ${}:是字符串替换。MyBatis 会直接将${}中的内容替换到 SQL 中,不做任何处理。存在 SQL 注入风险${}一般用于动态指定表名、列名等无法使用预编译的场景。
<!-- 安全,使用 #{} --> SELECT * FROM user WHERE name = #{name} <!-- 有风险,如果 orderBy 来自用户输入,可能被注入 --> SELECT * FROM user ORDER BY ${orderBy} <!-- 合理使用 ${} 的场景:动态表名(但表名通常不应来自用户输入) --> SELECT * FROM ${tableName} WHERE id = #{id}

原则:能用#{}绝不用${}

3.2 结果映射的几种方式

将查询结果的列映射到 Java 对象的属性,是 MyBatis 的另一个核心。

1. 自动映射(Auto-Mapping)当数据库列名(如user_name)和 Java 属性名(如userName)遵循一定规则时,MyBatis 可以自动完成映射。通过全局配置mapUnderscoreToCamelCase可以开启下划线到驼峰的自动转换。

<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>

这样,SQL 查询返回的user_name列会自动映射到userName属性。

2. 使用resultType指定返回结果类型的全限定类名或别名(在配置文件中定义)。适用于属性名和列名能自动映射的简单场景。

<select id="selectUserById" resultType="com.example.model.User"> SELECT id, user_name, email FROM user WHERE id = #{id} </select>

3. 使用resultMap(推荐用于复杂场景)当自动映射不满足需求时(如列名和属性名差异巨大、存在一对一、一对多关联查询),就需要定义resultMap

<resultMap id="userResultMap" type="User"> <!-- id 标签指定主键列,提高性能 --> <id property="id" column="user_id"/> <!-- result 标签映射普通列 --> <result property="userName" column="user_name"/> <result property="email" column="email_address"/> <!-- 关联对象映射 --> <association property="department" javaType="Department"> <id property="id" column="dept_id"/> <result property="name" column="dept_name"/> </association> <!-- 集合映射(一对多) --> <collection property="roles" ofType="Role"> <id property="id" column="role_id"/> <result property="name" column="role_name"/> </collection> </resultMap> <select id="selectUserWithDetails" resultMap="userResultMap"> SELECT u.id as user_id, u.name as user_name, u.email as email_address, d.id as dept_id, d.name as dept_name, r.id as role_id, r.name as role_name FROM user u LEFT JOIN department d ON u.dept_id = d.id LEFT JOIN user_role ur ON u.id = ur.user_id LEFT JOIN role r ON ur.role_id = r.id WHERE u.id = #{id} </select>

resultMap提供了最精细的映射控制,是处理复杂对象关系的利器。

3.3 动态 SQL:让 SQL 活起来

这是 MyBatis 最强大的特性之一。它允许你在 XML 中编写条件判断、循环等逻辑,根据传入参数动态生成不同的 SQL 片段,避免了在 Java 代码中拼接 SQL 字符串的丑陋和风险。

核心标签:

  • <if>:条件判断。
  • <choose>,<when>,<otherwise>:多路选择,类似 Java 的switch-case
  • <where>:智能添加WHERE关键字,并处理多余的ANDOR
  • <set>:智能处理UPDATE语句中的SET子句,去掉多余的逗号。
  • <foreach>:遍历集合,常用于IN查询或批量插入。
  • <trim>:更通用的字符串修剪功能。

示例:一个多条件查询

<select id="selectUsersDynamic" resultType="User"> SELECT * FROM user <where> <!-- 如果 name 不为 null 且不为空字符串,则添加条件 --> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <!-- 如果 minAge 不为 null,则添加条件 --> <if test="minAge != null"> AND age >= #{minAge} </if> <if test="maxAge != null"> AND age &lt;= #{maxAge} <!-- XML 中 < 需要转义为 &lt; --> </if> <!-- 如果 roles 集合不为空,则生成 IN 子句 --> <if test="roles != null and roles.size() > 0"> AND role IN <foreach collection="roles" item="role" open="(" separator="," close=")"> #{role} </foreach> </if> </where> ORDER BY id </select>

<where>标签会智能处理:如果所有<if>条件都不成立,则不会生成WHERE关键字;如果成立,它会自动去掉第一个条件前的AND(或OR)。

批量插入示例:

<insert id="batchInsertUsers"> INSERT INTO user (name, email) VALUES <foreach collection="list" item="user" separator=","> (#{user.name}, #{user.email}) </foreach> </insert>

这样,传入一个List<User>,就能生成一条包含多组值的INSERT语句,比循环执行单条INSERT效率高得多。

4. 进阶话题与生产实践:缓存、插件、整合与排错

当基本 CRUD 满足后,项目规模扩大,就会遇到性能、可维护性、与其他框架整合等更深层次的问题。

4.1 缓存机制:一级缓存与二级缓存

MyBatis 内置了缓存机制来提升查询性能,理解它们的工作原理和失效场景至关重要。

一级缓存(本地缓存)

  • 范围SqlSession级别。在同一个SqlSession中执行相同的查询,第二次会直接从缓存返回结果,不会访问数据库。
  • 生命周期:与SqlSession绑定。SqlSession关闭,缓存清空。
  • 失效条件
    1. 执行了 INSERT、UPDATE、DELETE 操作(无论是否影响当前缓存的数据)。
    2. 手动调用sqlSession.clearCache()
    3. 对 Mapper 执行了flushCache属性为true的查询(如某些需要实时性的统计查询)。
  • 注意:在 Spring 整合 MyBatis 时,通常将SqlSession的生命周期与一个事务绑定。这意味着在同一个事务中,一级缓存生效;跨事务,即使方法调用相同,也会创建新的SqlSession,缓存失效。这就是为什么有时“开启事务后 MyBatis 一级缓存导致数据查询不到”会成为问题——因为你可能在一个事务中修改了数据,然后期望在另一个新事务中立即查到最新数据,但一级缓存帮不上忙。

二级缓存

  • 范围Mapper级别(命名空间级别)。多个SqlSession可以共享同一个 Mapper 的二级缓存。
  • 开启方式
    1. 在全局配置中设置<setting name="cacheEnabled" value="true"/>(默认就是true)。
    2. 在需要开启的 Mapper XML 文件中添加<cache/>标签。
  • 工作机制:当一个SqlSession查询数据后,结果会被放到二级缓存中。另一个SqlSession执行相同查询时,如果缓存命中,则直接返回。提交 (commit) 是关键:只有SqlSession执行了commit()(如执行了增删改并提交),它查询的数据才会被真正刷入二级缓存;同时,commit()也会清空该 Mapper 命名空间下的所有二级缓存数据,以保证数据一致性。
  • 使用建议
    • 二级缓存适用于读远多于写,且数据实时性要求不高的场景,如配置表、历史数据查询。
    • 对于读写频繁或对一致性要求高的数据(如账户余额、库存),不建议开启二级缓存,或需要精心设计缓存策略(如使用更专业的 Redis)。
    • 注意序列化:缓存的对象需要实现Serializable接口。

4.2 插件(Interceptor):扩展 MyBatis 的利器

MyBatis 允许你编写插件来拦截其核心组件的执行过程,主要是Executor(执行器)、StatementHandler(语句处理器)、ParameterHandler(参数处理器)、ResultSetHandler(结果集处理器)。这是实现自定义功能(如分页、SQL 执行时间监控、数据加解密、多租户数据过滤)的底层手段。

一个简单的 SQL 执行时间监控插件示例:

@Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class SqlCostTimeInterceptor implements Interceptor { private static final Logger log = LoggerFactory.getLogger(SqlCostTimeInterceptor.class); @Override public Object intercept(Invocation invocation) throws Throwable { long startTime = System.currentTimeMillis(); try { // 执行原方法 return invocation.proceed(); } finally { long costTime = System.currentTimeMillis() - startTime; MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; String sqlId = mappedStatement.getId(); log.info("执行 SQL [{}] 耗时 {} ms", sqlId, costTime); // 可以在这里添加慢 SQL 告警逻辑 if (costTime > 1000) { log.warn("慢 SQL 警告: {}, 耗时 {} ms", sqlId, costTime); } } } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可以读取配置参数 } }

然后在mybatis-config.xml中配置该插件:

<plugins> <plugin interceptor="com.example.plugin.SqlCostTimeInterceptor"/> </plugins>

像 PageHelper 这样的知名分页插件,就是基于此机制实现的。开发插件需要对 MyBatis 运行原理有较深理解,谨慎使用。

4.3 与 Spring/Spring Boot 整合

在实际企业开发中,几乎不会单独使用 MyBatis,而是与 Spring 框架整合,让 Spring 的 IOC 容器来管理 MyBatis 的组件(SqlSessionFactoryMapper接口代理等)和事务。

Spring Boot 整合(最简方式)

  1. 添加依赖:
    <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
  2. application.yml中配置数据源和 MyBatis:
    spring: datasource: url: jdbc:mysql://localhost:3306/demo username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver hikari: # 使用 HikariCP 连接池 connection-timeout: 30000 maximum-pool-size: 10 mybatis: # mapper.xml 文件位置 mapper-locations: classpath:mapper/*.xml # 实体类所在包,用于配置 type-aliases-package type-aliases-package: com.example.model configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印 SQL
  3. 在 Mapper 接口上添加@Mapper注解,或者在主类上加@MapperScan("com.example.mapper")扫描整个包。
  4. 在 Service 中直接@Autowired注入 Mapper 接口使用即可。事务由 Spring 的@Transactional注解管理。

这种整合方式极大简化了配置,是当前的主流做法。

4.4 常见问题与排查链路

遇到 MyBatis 相关问题时,建议按以下顺序排查:

  1. “BindingException: Invalid bound statement (not found)”

    • 现象:最常见的启动错误,提示找不到 Mapper 方法对应的 SQL 语句。
    • 原因:MyBatis 找不到namespace.id对应的 SQL。
    • 排查
      • 检查 Mapper 接口的namespace(全限定名)和 XML 中的namespace是否完全一致(包括大小写)。
      • 检查方法名和 XML 中 SQL 标签的id是否一致。
      • 检查mapper-locations配置的路径是否包含了你的 XML 文件。
      • 检查 Maven 构建时,XML 文件是否被正确复制到了target/classes目录下(检查pom.xml中是否有<resources>配置)。
  2. SQL 执行报错,但 SQL 在数据库客户端执行正常

    • 原因:往往是参数映射或 SQL 语法在 MyBatis 动态处理后与预期不符。
    • 排查
      • 开启日志:在配置中设置log-implStdOutImplSLF4J,查看 MyBatis 实际发送到数据库的 SQL 和参数。这是最有效的调试手段。
      • 检查动态 SQL 中的条件判断(<if>)是否因参数为null或空导致 SQL 结构异常(如WHERE后直接跟AND)。
      • 检查#{}${}的使用是否正确。
  3. 查询结果映射失败,部分属性为 null

    • 原因:列名和属性名无法自动映射。
    • 排查
      • 确认是否开启了mapUnderscoreToCamelCase
      • 查看日志中的 SQL,确认 SELECT 的列名是什么。
      • 使用resultMap进行显式、精确的映射。
      • 检查数据库返回的列名是否包含空格等特殊字符。
  4. 性能问题

    • 现象:查询慢。
    • 排查
      • 通过日志查看 SQL 执行时间(可用插件)。
      • 检查是否产生了 N+1 查询问题(在循环中查询关联数据)。应使用<association><collection>进行连接查询一次性获取。
      • 检查二级缓存是否配置合理,是否因频繁更新导致缓存频繁失效。
      • 分析 SQL 本身,在数据库端使用EXPLAIN查看执行计划。
  5. 事务不生效

    • 现象:在 Spring 中,@Transactional标注的方法内,前面的更新操作失败,但后面的操作没回滚。
    • 排查
      • 确认方法是否是public的(Spring AOP 基于代理,对非 public 方法无效)。
      • 确认异常是否被捕获且未抛出。默认只对RuntimeExceptionError回滚,检查是否抛出了受检异常(Exception)且未在@Transactional中声明rollbackFor
      • 确认是否在同一个类中方法 A 调用了方法 B(B 有@Transactional),这会导致 B 的事务注解失效(代理对象问题)。可通过将 B 方法移到另一个 Service,或使用AopContext.currentProxy()解决。

我个人更建议先把单任务跑稳,再考虑批量和接口。对于 MyBatis,真正落地时最该盯住的不是它有多少炫酷的功能,而是 XML 文件的管理、SQL 的质量、以及如何与项目中的连接池、事务管理器、监控体系顺畅协作。很多项目后期的问题,根源都在初期对这些“基础设施”的随意处理上。如果只是学习,默认配置够用;如果要长期使用,就要把日志规范、SQL 评审流程和性能监控提前规划好。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询