☰
MyBatis从入门到实战:半自动ORM的核心原理、动态SQL与配置排查指南
2026/10/14 11:10:33 网站建设 项目流程

1. 上手前要把这事的定位搞清楚:MyBatis 到底在解决什么问题

不少人第一次接触 MyBatis 的时候,都会有一个共同的感觉:它比 JDBC 省事,但又不像那些全自动 ORM 框架那样“啥都不用写”。这个“卡在中间”的状态,其实正是它最大的价值所在。简单说,MyBatis 是一个半自动的持久层框架,它的核心工作是帮你把 Java 方法和 SQL 语句建立起映射关系,让数据库操作变成“调一个接口方法”这么简单,同时又不夺走你对 SQL 的控制权。

我自己的体会是,用它之前必须先理解一个事实:MyBatis 不是让你完全不用写 SQL,恰恰相反,SQL 仍然要你自己写,但它替你把这些 SQL 语句从业务代码里解放出来,放在 XML 或者注解里统一管理。这样一个简单拆分,带来的收益非常可观,项目的维护成本会明显下降。

1.1 它和 JDBC 之间差了一层什么

回到最原始的 JDBC 时代,一次数据库查询大概要经历这些步骤:加载驱动、获取连接、创建 Statement、拼接 SQL、执行查询、遍历 ResultSet 取值、逐条组装成对象、关闭连接、处理异常。这套流程本身不难,但每一张表都要重复一遍,不同的地方只有 SQL 语句和结果的赋值逻辑。当项目里有几十张表、几百个查询时,重复代码就成了最大的负担。

MyBatis 解决这类问题的思路很直接:把连接管理、SQL 执行、参数设置、结果集转换这些重复劳动,全部收归框架内部。你在 Mapper 接口里定义一个方法,然后在 XML 映射文件里写对应的 SQL,框架在运行时会根据方法签名自动完成参数填充和结果映射。数据库操作的时间复杂度没有变,但代码复杂度大幅下降。

这个过程中有一件值得注意的事:MyBatis 并不会像某些框架那样,在启动时把所有 SQL 都预编译执行一遍。它的预编译是发生在首次调用某个 Mapper 方法的时候,解析 XML 里的 SQL 片段,生成对应的 MappedStatement,之后放进缓存里复用。所以你改完 XML,通常需要重启应用才能生效,这一点在开发阶段经常被忽略。

1.2 MyBatis 的“半自动”到底自动在哪

网上有个说法我是比较认同的:对比 Hibernate 这类全自动 ORM,MyBatis 更像是一把精心打磨过的“手动挡工具”。所谓“半自动”,指的是它只自动处理参数映射和结果集到对象的转换,但 SQL 语句本身由你掌控。这个特性在复杂查询场景下显得特别珍贵。

举个例子,如果业务里有一个报表查询,需要关联四五张表,还带各种聚合和子查询。全自动框架会根据实体关系尝试生成 SQL,但生成的语句往往不够精准,执行效率不可控。MyBatis 里你可以直接写一条经过优化的原生态 SQL,把它放在 XML 的某个 select 节点里,框架不会对你的 SQL 做多余改动。这种“可控性”在排查性能问题的时候极其有用。

另外,MyBatis 对 SQL 的优化支持得特别细。它可以配置超时时间、抓取数量、是否启用二级缓存、是否使用动态 SQL 拼接,几乎每个执行参数都能手动指定。这种细致的控制能力,是它能在各种复杂业务系统里长期站稳脚跟的重要原因。

1.3 适合谁、不适合谁

说清楚了定位,再聊聊适用场景。如果你所在的团队对 SQL 有一定积累,系统里存在大量复杂查询,或者需要对接老旧的数据库结构(比如那些字段命名不规范的表),MyBatis 基本就是最优解。它不会要求你修改数据库表结构,也不会强迫你按照它的理想模型重新设计业务字段。

反过来,如果系统全是简单的单表 CRUD,实体关系极度简单,团队又不希望写任何 SQL,那 MyBatis 可能不是最省事的选择。尤其是一些后台管理系统中常见的“增删改查”,用 MyBatis 也需要为每张表准备 Mapper 接口和 XML,工作量没那么少。项目选型这事没有绝对的对错,只有适合不适合。我看过不少团队把 MyBatis 用在需要强 SQL 控制的互联网后端项目里,效果很好;也见过有人把它用在纯 CRUD 的管理系统上,结果写大量重复 XML 写到手累。

2. 环境搭建与核心配置文件:按步骤走基本不会翻车

MyBatis 的搭建过程,说难不难,说简单也有不少容易忽略的细节。我以前第一次搭的时候,卡在配置文件路径问题上折腾了一个晚上,后来才明白错误是“找不到 mapper 资源”而不是“SQL 写错了”。这类坑只要按正确的流程走,完全可以避免。

2.1 依赖引入与工程结构

如果你用的是 Maven 工程,引入依赖相当简单。核心只有一个包,不过一般还会搭配数据库驱动一起引入。

<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.16</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> <scope>runtime</scope> </dependency>

依赖版本这里多说一句:MyBatis 3.5.x 系列比较稳定,Java 8 以上都能正常使用。很多人喜欢用最新版,但我建议优先选择已经在一段时间内被广泛验证的版本,踩过的坑少,网上资料也齐全。

工程结构上,我自己习惯按这种方式组织:

  • src/main/java:业务代码、Mapper 接口
  • src/main/resources:MyBatis 配置、XML 映射文件
  • src/main/resources/mapper:专门存放 Mapper XML 文件

一个容易被新手踩坑的地方是:如果 Mapper 接口放在com.example.mapper包下,XML 文件也建议放在 resources 下对应的com/example/mapper目录里,这样编译打包后,接口和 XML 恰好处于同一个包路径下,MyBatis 默认的扫描规则就能直接命中。如果把 XML 乱放一处,就得在配置里增加额外的 mapper 资源路径,麻烦不说,还容易出问题。

2.2 mybatis-config.xml 的关键节点

配置文件的顶层节点叫configuration,内部节点的顺序是固定的,这点非常重要。如果你把节点顺序写错了,解析配置文件时会直接报错。

<?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> <properties resource="db.properties"/> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <package name="com.example.mapper"/> </mappers> </configuration>

properties节点用来引入外部属性文件,数据库连接信息放这里更便于不同环境切换。settings里我强烈建议开启mapUnderscoreToCamelCase,它会自动把数据库字段user_name映射为 Java 属性的userName,省掉大量手动映射。

environments里的transactionManager用 JDBC 类型,意思是让事务由 MyBatis 自己管理,更适合集成到 Spring 事务体系里。dataSource用 POOLED 类型,这是 MyBatis 内置的连接池,开发阶段足够用了。

关于mappers节点,有两种常见方式。一种是逐个注册 XML 文件路径,比如<mapper resource="mapper/UserMapper.xml"/>。另一种是用<package name="com.example.mapper"/>指定接口包路径,MyBatis 会自动扫描包下所有接口,寻找同名同路径的 XML。第二种方式在项目规模变大后明显省事,我推荐直接用这个。

注意:配置文件中节点顺序是properties、settings、typeAliases、typeHandlers、objectFactory、objectWrapperFactory、reflectorFactory、plugins、environments、databaseIdProvider、mappers,中间任何一项都不能乱序。我见过有人把 settings 放在 environments 后面,结果启动直接抛异常。

2.3 mybatis-config.xml 之外:SqlSessionFactory 的构建

配置文件的加载本身不算难,真正写代码时还是要通过SqlSessionFactory把配置“激活”。

String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这段代码可以放在一个单例类里,也可以交给 Spring 去管理。如果你用的是纯 MyBatis 而没有引入 Spring,建议把SqlSessionFactory做成全局单例,因为每次构建都会重新解析 XML,成本很高,频繁创建会明显拖累启动速度。

配合SqlSessionFactory还要拿到SqlSession:

try (SqlSession session = sqlSessionFactory.openSession(true)) { UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.selectById(1); }

openSession(true)表示自动提交事务,适合只做查询的场景。如果有 insert、update 或 delete 操作,我一般建议用openSession(false)手动控制事务提交,避免中间出错导致数据半写入。我曾经在批量插入测试数据时图省事用了自动提交,结果有一条数据字段格式不对,前面的数据全写进去了却没法回滚,排查了很久才发现是这个原因。

3. Mapper 接口与 XML 映射:写得最勤快的一个环节

如果说配置文件是地基,那 Mapper 接口和 XML 映射文件就是每天都要打交道的主战场。这一步的体验好坏,直接决定这个框架用起来顺不顺手。

3.1 基本映射规则与方法对应

Mapper 接口是一个普通的 Java 接口,定义的方法对应 XML 里的 SQL 操作。拿最简单的用户表举例子:

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

对应的 XML:

<mapper namespace="com.example.mapper.UserMapper"> <select id="selectById" resultType="com.example.entity.User"> select id, user_name, email from user where id = #{id} </select> </mapper>

这里的对应关系有几个关键点:namespace必须和 Mapper 接口的全限定名完全一致;id必须是接口方法名;XML 文件的位置和接口的包路径对应,或者通过<package>扫描方式注册。缺了任何一个,运行时就找不到映射。

#{}是 MyBatis 中最常用的占位符。它会生成一个参数占位符?,使用PreparedStatement进行预编译,可以有效避免 SQL 注入。与之相对的是${},这个会直接做字符串替换,拼接出原始的 SQL 语句。绝大多数情况下,能用#{}就坚决别用${}。只有极端场景,比如动态表名、动态排序字段等没法用占位符的地方,才考虑用${},而且传入的参数必须做好白名单校验。

3.2 参数传递的几种姿势

方法参数到底怎么传给 SQL,是新手容易懵的地方。核心要记住一个原则:只有一个参数时,MyBatis 会把参数直接绑定;多个参数时,一定要把事情说明白。

多参数的情况我推荐使用@Param注解:

List<User> selectByStatusAndRole(@Param("status") Integer status, @Param("role") String role);

XML 里写:

<select id="selectByStatusAndRole" resultType="com.example.entity.User"> select * from user where status = #{status} and role = #{role} </select>

如果你不喜欢在接口上写注解,也可以把参数封装成一个对象:

List<User> selectByQuery(UserQuery query);

XML 里用属性名直接访问:

where status = #{status} and role = #{role}

注意,这里的status和role是 UserQuery 类的属性名。这种传参方式在参数较多(超过两个)时更清晰,也便于后续扩展查询条件。

还有一个经常会遇到的场景:传入集合参数做in查询。这时候参数类型是List或数组,命名推荐用@Param("ids"):

List<User> selectByIds(@Param("ids") List<Integer> ids);

对应 XML 里的动态 SQL 配合foreach去拼in条件,这个在下一节详细展开。

3.3 resultType 与 resultMap 的选择

查询结果的映射方式有两种:resultType和resultMap。

resultType简单直接,MyBatis 根据结果集的列名,反射到指定类型的属性上。如果你开启了mapUnderscoreToCamelCase,绝大多数场景列名和属性名能自动对上。比如数据库字段user_name自动映射到userName,不需要额外配置。这种方式的优点是省事,适合字段不多、命名规整的表。

resultMap则用于更复杂的映射。比如字段名太怪、需要做类型转换、关联查询需要嵌套对象的时候,resultMap才是真正的正确做法。举个典型例子:

<resultMap id="userWithOrdersMap" type="com.example.entity.User"> <id property="id" column="id"/> <result property="userName" column="user_name"/> <collection property="orders" ofType="com.example.entity.Order"> <id property="id" column="order_id"/> <result property="orderNo" column="order_no"/> </collection> </resultMap>

这个映射里,<collection>负责把一对多关系的结果组装进 User 对象里的 orders 列表。使用resultMap后,<select>节点通过resultMap="userWithOrdersMap"引用。

我的建议是这样:单表查询、字段规整,优先用resultType;涉及多表关联、嵌套对象、字段映射不规整,用resultMap。别一上来所有查询都配 resultMap,文件会臃肿且难维护。

4. 动态 SQL 是 MyBatis 能打的核心优势

以前用 JDBC 拼条件查询时,最痛苦的就是“拼接 WHERE 子句”。加一个条件要写if,还要小心 where 后面多个and的问题。MyBatis 的动态 SQL 专门解决这个问题,这也是它区别于很多其他半自动框架的一个明显亮点。

4.1 if + where:条件查询的正确姿势

最常见的需求是“根据多个可选条件筛选”。比如根据状态和角色查用户,但可能状态不填、角色也不填。如果自己在代码里拼 SQL,逻辑写起来很丑。MyBatis 里这样处理:

<select id="searchUsers" resultType="com.example.entity.User"> select * from user <where> <if test="status != null"> and status = #{status} </if> <if test="role != null and role != ''"> and role = #{role} </if> </where> </select>

<where>标签会自动处理“开头多了一个 and 或 or”的问题。如果没有任何条件,它不会生成 where 关键字。这一点非常贴心,省去了无数次手动 trim 的麻烦。

顺便提一下<trim>标签。如果你需要的逻辑更定制化,比如必须手动控制某些前缀或后缀,可以用 trim 实现同样的效果:

<trim prefix="WHERE" prefixOverrides="AND |OR "> <if test="status != null"> status = #{status} </if> </trim>

日常开发用<where>就够,只有特殊需求才需要 trim。

4.2 foreach:批量操作与 IN 查询

foreach是动态 SQL 里使用频率极高的一个标签。最常见的两个场景:批量插入和in条件查询。

批量插入的写法:

<insert id="batchInsert"> insert into user (user_name, email) values <foreach collection="list" item="user" separator=","> (#{user.userName}, #{user.email}) </foreach> </insert>

separator=","会在每项之间插入逗号,最终拼出来的效果就是values (...), (...), (...)。

in查询的写法:

<select id="selectByIds" resultType="com.example.entity.User"> select * from user where id in <foreach collection="ids" item="id" open="(" close=")" separator=","> #{id} </foreach> </select>

这里open和close指定了左右括号,item是遍历中的当前元素。使用foreach时要注意 collection 的名称,如果接口方法参数用@Param("ids")指定了,这里就写对应的名字;如果你直接在方法上传入List而不加注解,那么 XML 里通常用list或collection作为默认名称。这个细节经常让人栽跟头,我的经验是:多参数或 List 参数,一律用@Param显式命名,不要依赖默认值。

4.3 choose:类似于 switch 的智能分支

有时候查询逻辑不是“叠加条件”,而是“多个条件中选一个”。比如有名称就按名称查,没有名称但邮箱非空就按邮箱查,都没有就查默认列表。这种场景用<if>堆叠并不合适,<choose>更合适。

<select id="selectSmart" resultType="com.example.entity.User"> select * from user <where> <choose> <when test="userName != null and userName != ''"> and user_name = #{userName} </when> <when test="email != null and email != ''"> and email = #{email} </when> <otherwise> and status = 1 </otherwise> </choose> </where> </select>

这段逻辑和 Java 里的 switch 几乎一模一样,按顺序匹配第一个满足的<when>,都不满足就走<otherwise>。这种写法在实现“多选一”搜索条件时相当清晰,代码的可读性比在业务层写一堆 if-else 再拼不同 SQL 要好得多。

动态 SQL 还有一个常常被忽略的细节:里面的 test 表达式是基于 OGNL 的,空字符串和 null 的判断逻辑要小心。status != null看的是 Java 对象的 null,role != ''看的是非空字符串,两者不要混用。在接前端传参的时候,参数很可能是空字符串""而不是 null,做条件判断时最好两个条件都用上,避免出现“我以为没传,其实传了空字符串”的情况。

5. 常见问题与排查技巧实录

不管配置写得多熟,实际开发中总会碰到一些让人摸不着头脑的问题。这里把我踩过的坑和排查思路整理成一份速查表,希望对你有实际帮助。

5.1 高频报错与解决办法

报错 1:找不到 Mapper 映射文件

典型报错类似Invalid bound statement (not found): com.example.mapper.UserMapper.selectById。这个问题通常有三个原因:

  1. XML 文件没有放到 resources 目录下对应包路径里
  2. 配置里没有注册 mapper 或者注册的路径不对
  3. XML 文件的 namespace 写错

排查方式很直接:先看 target 目录里编译出来的 class 文件旁边有没有 XML。如果没 XML,就是资源配置的问题;如果有 XML,再看 namespace 和方法 id。

报错 2:无法确定参数类型

报错信息里经常出现Parameter 'xxx' not found。这个场景多发生在多参数方法没有加@Param注解的时候。解决办法:给每个参数加上@Param("名称"),或者封装成一个查询对象。

报错 3:返回结果集映射成 null

SQL 查询到的数据库字段明明有值,但 Java 对象的属性是 null。大概率是列名和属性名对不上,比如数据库列名是user_name,Java 属性是userName,而且你没开启mapUnderscoreToCamelCase。解决办法有两个:在配置里开启该项,或者用resultMap显式映射。

报错 4:执行 SQL 时提示无效的列类型

这种问题常见于参数类型传递不明确时,比如传入了null。MyBatis 在预编译时需要知道参数对应的 JDBC 类型,如果无法判断,就会报警。处理方法是给参数指定jdbcType:

#{status, jdbcType=INTEGER}

5.2 日志输出与 SQL 排查手段

排查 SQL 问题时,打开日志非常关键。我建议在settings里配置日志实现:

<setting name="logImpl" value="STDOUT_LOGGING"/>

这样 MyBatis 会把执行的 SQL、参数值、影响行数都打印到控制台。注意,这里打印出来的 SQL 是预编译前的占位符形式,参数值会单独再打印一遍,你在控制台看到的select * from user where id = ?和后面的 Parameters 是配套的,别误以为是完整 SQL。

如果项目已经集成 SLF4J 或 Log4j2,也可以把logImpl改为对应实现,或者在日志框架配置里把com.example.mapper包的日志级别设为 DEBUG。这种方式更推荐,因为STDOUT_LOGGING只是控制台输出,生产环境不合适。

5.3 一级缓存、二级缓存与事务的坑

MyBatis 的缓存机制简单,但坑也不少。

一级缓存是SqlSession级别的,同一个 SqlSession 内执行相同的查询,第二次会直接命中缓存。但如果你在同一个 SqlSession 里先查询、再执行了一次 update,MyBatis 会清掉一级缓存,避免脏读。这个机制在大多数情况下是好用的,但如果你希望“会话内两次查询保证最新数据”,就得注意手动清理缓存,或者干脆开启自动提交。

二级缓存是namespace级别的,跨 SqlSession 生效。默认情况是关闭的,要开启得在 XML 里加一段配置:

<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

这里每个参数都有讲究:eviction指缓存回收策略,默认 LRU 就够了;flushInterval是刷新间隔,单位毫秒;size是缓存对象数量;readOnly=true表示缓存对象只读。注意,二级缓存会把查询结果对象直接存起来,共享给所有 SqlSession,如果对象允许被修改,就可能出现并发问题。我的建议是:除非你对缓存机制非常熟悉,否则不要轻易开启二级缓存。很多系统问题都是“缓存没刷新导致数据不一致”造成的,远比那点性能提升更麻烦。

另外需要特别记住:如果手动执行了增删改操作,MyBatis 会认为整个 namespace 的缓存都可能失效,所以它会刷新二级缓存。但你通过存储过程或其他方式直接修改数据库的话,MyBatis 感知不到,就可能出现缓存数据过期。这种问题排查起来很隐蔽,我遇到过一次,查了整整一天才发现是缓存没失效,从那以后对于实时性要求高的模块,我都直接关掉二级缓存。

5.4 和 Spring Boot 集成时的注意事项

现在很多项目不再单独搭建 MyBatis,而是把它嵌入 Spring Boot。如果是 Spring Boot 项目,推荐引入官方 starter:

<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency>

引入 starter 后,mybatis-config.xml反而可以简化很多。数据源、事务管理都交给 Spring Boot 处理,你只需要在application.yml里配置对应参数:

mybatis: mapper-locations: classpath:mapper/**/*.xml type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: true

这里的mapper-locations指定了 XML 文件的通配路径,也是排查“找不到 Mapper”时第一个要检查的地方。type-aliases-package配置后,XML 里的resultType可以直接写实体类的简单名称,比如User,不需要每次写全限定名。

集成 Spring Boot 后有个细节值得注意:如果同时存在mybatis-config.xml和application.yml里的 configuration 配置,它们会合并。合并规则是,mybatis-config.xml里的配置优先级更高。所以如果你在两边配置了不同的驼峰映射开关,实际生效的可能不是你在 yml 里写的那份。建议项目里统一只在其中一个地方配置,别两头都写,免得人员调试时产生认知混乱。

说到日志,Spring Boot 环境里推荐直接把日志级别调到 DEBUG,观察 MyBatis 的执行细节。在application.yml里加上:

logging: level: com.example.mapper: debug

这样只输出 Mapper 包下的 SQL 日志,不会把整个框架的调试信息全打出来,方便得多。

6. 进阶一点:几个我很常用的配置优化

如果你已经跑通了基础使用,下面这几个配置优化点可以让开发体验再上一个台阶。

6.1 类型别名:减少全限定名的重复书写

XML 里写resultType="com.example.entity.User"写多了,确实有点烦。在配置文件里设置别名后,可以极大简化映射文件。

<typeAliases> <package name="com.example.entity"/> </typeAliases>

配置这个包扫描后,XML 里可以直接用实体类的简单名称:

<select id="selectById" resultType="User">

对于实体类特别多的项目,这个配置能明显减少重复笔误的概率。注意,如果包下有多个相同类名的实体(不同包路径),别名会冲突,这种情况要留意一下。

6.2 批量操作时的合理拆分

MyBatis 的批量 insert 虽然写起来方便,但数据量大的时候要特别小心。单条 insert 语句打包成一条超长的values (...), (...), (...),对于 MySQL 来说是能接受的,但超过几百条之后,SQL 的长度和执行效率都会受影响。我个人的经验是,每批控制在 100 到 200 条比较稳妥。

另外,批量插入时如果走的是 MySQL JDBC 驱动,可以在连接参数里加上:

rewriteBatchedStatements=true

这个参数可以显著提升批量操作的执行速度,但要注意它要求驱动版本不能太老。具体版本兼容情况,以你所用的连接驱动官方文档为准,网上有不少针对不同版本的验证结果,可以参考。

动态 SQL 的 foreach 拼接超长列表还有一个隐藏问题:生成的 SQL 里有大量占位符,解析和优化成本会上升。假如一次性传入上千个 id,MySQL 会做深度优化,但很多情况下性能并不理想。如果确实需要查大量 id,考虑分批查询再在内存合并,或者把 id 列表写入临时表再关联查询,这样经常比巨长的 in 语句更稳定。

6.3 分页查询的注意点

MyBatis 本身不带物理分页功能,你要是直接写limit的话,简单场景没问题。但一旦涉及动态排序、复杂条件、总条数统计,手写分页就比较啰嗦了。建议直接用市面上的分页插件,比如通用流行的 PageHelper 一类,引入配置很简单。使用这类插件时,需要理清它和 MyBatis 拦截器机制的关系。它本质上是注册了一个 MyBatis 插件,在 Executor 执行前改写 SQL,自动拼接 limit 语句并执行 count 查询。

使用分页插件时有两个坑一定要规避:

  1. 不要在分页查询后面再手动拼接 limit,否则和插件自动追加的 limit 冲突,SQL 直接报错。
  2. 每个请求线程的分页参数是线程隔离的,但如果你在页面渲染、循环遍历中意外触发了另一个查询,分页条件可能被带到那个查询上。这个坑很隐蔽,表现出来就是“下一个 SQL 莫名其妙多了 limit”。排查方式是确认在执行查询前,分页插件设置的 Page 对象已经被正确消费。

7. 关于“简单使用”的一点个人建议

很多人看到 “MyBatis 简单使用” 这个题目,会以为只要学会几个标签就算完事了。我的感受是,真正让 MyBatis 用得舒服的,恰恰是那些不写在官方快速上手文档里的习惯。

比如 XML 文件的目录结构,从一开始就和 Mapper 接口保持同包路径映射,后面打包部署时才不会到处找 XML。再比如,所有的 SQL 都统一放 XML,不要一部分写 XML,一部分写注解。注解虽然能减少文件数量,可一旦 SQL 复杂起来,注解里写动态 SQL 真是折磨,可读性差到让人崩溃。我见过的多数项目,最终都会摒弃注解,回到 XML。

还有一个建议是:写每个查询方法之前,先想清楚它会不会被复用。如果只是某个业务页面的一次性取数,SQL 写在 XML 里也没关系,但方法命名要尽量能表达业务意图,比如selectActiveUserCount这种,别用query01之类的名字。时间久了项目里 SQL 多起来,命名清晰与否直接影响维护效率。我接手过一个老项目,看到select1、select2这样的方法名,只能一个个点开 XML 看 SQL 内容,那种体验真的很酸爽。

最后想分享的一个小技巧是:动手搭环境时,先写最简单的一条select * from user where id = #{id},跑通后再逐步加动态 SQL、批量操作、缓存配置。这样一路下来,遇到问题可以很自然地定位范围。很多人一开始就配置一堆高级项,出错了也分不清是配置文件问题、SQL 问题还是映射问题。把复杂度往后放,把最基础的通路先打通,这个节奏对于学习任何框架都适用。

MyBatis 的“简单使用”,说到底不是框架本身有多简单,而是你把那套映射规则和约定搞清楚之后,业务代码能变简单。希望这篇分享能帮你少走一些弯路。

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

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

立即咨询