Spring Boot 与 MyBatis 的整合早已成为国内开发的主流选择。相比 JPA 的全自动映射,MyBatis 提供了更灵活的 SQL 控制能力;而 MyBatis-Plus 在此基础上进一步简化了单表 CRUD,让开发效率大幅提升。本节将从环境搭建、核心用法、分页插件、代码生成器到性能优化,给出可直接落地的实践方案。
13.3.1 快速集成 MyBatis
1. 引入依赖
在 Spring Boot 项目中,只需添加 MyBatis 官方提供的 starter 和对应的数据库驱动:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
注意:MyBatis Spring Boot Starter 会自动配置
SqlSessionFactory和SqlSessionTemplate,无需手动编码。
2. 数据源与基础配置
application.yml 中配置数据源和 MyBatis 基本参数:
spring:
datasource:
url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
# 映射文件位置,classpath 下可简写
mapper-locations: classpath:mapper/*.xml
# 实体类别名包,简化 resultType 编写
type-aliases-package: com.example.entity
configuration:
# 开启驼峰命名自动映射 (order_id → orderId)
map-underscore-to-camel-case: true
# 日志输出,开发时开启便于调试
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3. 编写实体与 Mapper 接口
实体类保持简洁,字段名建议与数据库列名对应,或通过配置自动下划线转换:
public class Order {
private Long id;
private String orderNo;
private BigDecimal amount;
private LocalDateTime createTime;
// 省略 getter/setter
}
Mapper 接口使用 @Mapper 注解标记,或通过启动类 @MapperScan 扫描:
@Mapper
public interface OrderMapper {
Order selectById(Long id);
List<Order> selectByCondition(@Param("orderNo") String orderNo,
@Param("status") String status);
int insert(Order order);
int updateById(Order order);
}
对应的 XML 映射文件置于 resources/mapper/ 下:
<?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.OrderMapper">
<select id="selectById" resultType="Order">
SELECT id, order_no, amount, create_time
FROM t_order WHERE id = #{id}
</select>
<select id="selectByCondition" resultType="Order">
SELECT * FROM t_order
<where>
<if test="orderNo != null and orderNo != ''">
AND order_no = #{orderNo}
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
</where>
</select>
<insert id="insert" parameterType="Order" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_order(order_no, amount, create_time)
VALUES(#{orderNo}, #{amount}, #{createTime})
</insert>
</mapper>
4. 注解式 SQL 的适用场景
对于简单查询,可以直接在 Mapper 方法上使用注解,避免 XML 文件膨胀:
@Select("SELECT * FROM t_order WHERE order_no = #{orderNo}")
Order selectByOrderNo(String orderNo);
但复杂动态 SQL 仍建议使用 XML,以保持可读性和维护性。
13.3.2 集成 MyBatis-Plus
MyBatis-Plus 是 MyBatis 的增强工具,提供通用 CRUD、条件构造器、分页插件等功能,让开发者连基础 SQL 都能省略。
1. 依赖替换
直接使用 MyBatis-Plus 提供的 starter,它内部已包含 MyBatis 及其自动配置:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
配置与原生 MyBatis 基本兼容,额外可指定表前缀等:
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
global-config:
db-config:
# 表名前缀,自动拼接
table-prefix: t_
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2. 实体类注解增强
实体类可通过注解与数据库表建立映射,开启主键策略、自动填充等:
@TableName("t_order")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private BigDecimal amount;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
3. Mapper 继承 BaseMapper
让 Mapper 接口继承 BaseMapper<T>,即刻获得常用 CRUD 方法:
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
// 自定义方法依然可以在 XML 或注解中定义
List<Order> selectByCondition(@Param("orderNo") String orderNo,
@Param("status") String status);
}
然后在 Service 中可以直接调用 orderMapper.selectById(1)、orderMapper.insert(order) 等,无须编写 SQL。
4. Service 层继承 IService
MyBatis-Plus 同样提供了通用 Service 接口和实现类,进一步减少重复代码:
public interface OrderService extends IService<Order> {
// 自定义业务方法
boolean placeOrder(Order order);
}
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order>
implements OrderService {
@Override
public boolean placeOrder(Order order) {
// 可以直接使用 this.save(order) 等基础方法
return this.save(order);
}
}
这样,Service 层立即拥有了 saveBatch、page、list 等数十个方法,并且支持链式调用。
13.3.3 条件构造器与分页插件
1. 条件构造器
MyBatis-Plus 的核心利器之一就是条件构造器 QueryWrapper 和 UpdateWrapper,可以用 Java 链式代码构建复杂查询,避免 XML 中的 <if> 判断:
// 查询条件:订单号包含 "2024" 且金额大于 100,按创建时间倒序
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.like("order_no", "2024")
.gt("amount", 100)
.orderByDesc("create_time");
List<Order> list = orderMapper.selectList(wrapper);
Lambda 条件构造器解决字段名硬编码问题,依赖编译期检查:
LambdaQueryWrapper<Order> lambdaQuery = Wrappers.<Order>lambdaQuery();
lambdaQuery.like(Order::getOrderNo, "2024")
.gt(Order::getAmount, 100);
List<Order> orders = orderMapper.selectList(lambdaQuery);
这在重构时更加安全,IDE 也能给出提示。
2. 分页插件
MyBatis-Plus 的分页插件需要手动配置一个 MybatisPlusInterceptor 并添加 PaginationInnerInterceptor:
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置单页最大条数,防止恶意查询
pageInterceptor.setMaxLimit(500L);
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
}
使用分页查询时,直接传入 Page 对象:
Page<Order> page = new Page<>(1, 20);
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.orderByDesc(Order::getCreateTime);
Page<Order> resultPage = orderMapper.selectPage(page, wrapper);
System.out.println("总记录数:" + resultPage.getTotal());
System.out.println("当前页数据:" + resultPage.getRecords());
MyBatis-Plus 会自动在执行的 SQL 后追加 LIMIT 语句,并通过 count 查询获取总记录数。你也可以自定义 count 查询来优化性能。
3. 多表分页与自定义方法
当需要联表查询并分页时,可以在 XML 中自定义 SQL,并让方法参数接收 Page 对象,MyBatis-Plus 会自动处理分页逻辑:
// Mapper 接口
IPage<OrderVO> selectOrderWithDetail(Page<OrderVO> page, @Param("status") String status);
<select id="selectOrderWithDetail" resultType="com.example.vo.OrderVO">
SELECT o.*, d.product_name, d.quantity
FROM t_order o LEFT JOIN t_order_detail d ON o.id = d.order_id
WHERE o.status = #{status}
ORDER BY o.create_time DESC
</select>
调用时传入 Page,返回的 IPage 中即包含分页信息。
13.3.4 代码生成器:真实项目的效率加速器
MyBatis-Plus 提供了功能强大的代码生成器,能根据数据库表逆向生成 Entity、Mapper、Service、Controller 以及 XML 映射文件,且模板高度可定制。
1. 生成器快速配置
新版生成器采用编程式 FastAutoGenerator,在一个 main 方法中完成配置:
FastAutoGenerator.create("jdbc:mysql://localhost:3306/order_db",
"root", "root")
// 全局配置
.globalConfig(builder -> {
builder.author("Your Name")
.outputDir("src/main/java")
.commentDate("yyyy-MM-dd");
})
// 包配置
.packageConfig(builder -> {
builder.parent("com.example")
.entity("entity")
.mapper("mapper")
.service("service")
.serviceImpl("service.impl")
.xml("mapper.xml");
})
// 策略配置
.strategyConfig(builder -> {
builder.addInclude("t_order", "t_order_detail") // 指定表
.entityBuilder()
.enableLombok()
.enableTableFieldAnnotation()
.formatFileName("%sEntity") // 实体名后缀
.controllerBuilder()
.enableRestStyle()
.mapperBuilder()
.enableMapperAnnotation()
.formatXmlFileName("%sMapper");
})
.execute();
运行后即可生成一整套代码,开发者只需关注业务逻辑实现。
2. 模板定制与排除字段
通常我们会将公共字段(如 id、createTime、updateTime)放到基类,通过策略配置排除生成:
.entityBuilder()
.superClass(BaseEntity.class)
.addSuperEntityColumns("id", "create_time", "update_time");
模板可以通过 templateEngine(new FreemarkerTemplateEngine()) 配合自定义模板文件来实现完全控制。
13.3.5 真实项目中的最佳实践
1. 不要在 Service 层直接暴露 BaseMapper 方法
虽然继承了 BaseMapper 后可以方便调用,但应封装有业务含义的方法,避免前端或上层直接操纵数据访问细节:
// 不推荐:Controller 直接调用 orderMapper.selectList(...)
// 推荐:封装方法
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order>
implements OrderService {
public List<Order> listValidOrders() {
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Order::getStatus, "VALID");
return this.list(wrapper);
}
}
2. 分页查询注意连表时的 count 优化
默认的分页 count 查询会套用原 SQL 作为子查询,当联表复杂时性能较差。可以在自定义方法中手动指定 count 查询,或在 XML 中用 dada-sql 区分:
<select id="selectOrderWithDetail" resultType="OrderVO">
SELECT o.*, d.product_name ...
FROM t_order o LEFT JOIN t_order_detail d ...
${ew.customSqlSegment}
</select>
并使用 @Select 配合 @Result 手动映射,但更直接的是使用 MyBatis-Plus 的分页时传入 optimizeCountSql(false) 来关闭自动优化,然后自己编写轻量 count 语句。
3. 批量操作
- 批量插入:
saveBatch(Collection<T> entityList)实际上仍是逐条发出的 INSERT,数量较大时应启动 JDBC 批量模式,在数据源 URL 上添加rewriteBatchedStatements=true(MySQL),性能将提升几十倍。 - 批量更新:可使用自定义 SQL 加 foreach 拼接 CASE WHEN 语法,或使用 MyBatis-Plus 提供的
updateBatchById,注意其底层同样依赖rewriteBatchedStatements。
4. 事务支持
Spring 的事务管理与 MyBatis 天然集成,直接在 Service 方法上使用 @Transactional 即可。当同时操作多表或调用多个 Mapper 方法时,保持事务传播级别默认 REQUIRED,能保证数据一致性。
5. 日志与性能监控
开发阶段可将 SQL 日志级别设为 DEBUG,或通过 MyBatis-Plus 配置输出执行时长。生产环境建议集成 p6spy 或使用 Druid 连接池的 SQL 监控功能,便于分析慢查询。
6. 自动填充功能
利用 MyBatis-Plus 的 MetaObjectHandler 接口自动设置创建和更新时间:
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
再配合实体上的 @TableField(fill = ...) 注解,保存或更新时自动赋值,减少手动设置。
7. 逻辑删除
只需在实体字段上标注 @TableLogic,并在配置中指定删除值和未删除值,调用 deleteById 会转变为 UPDATE 语句,实现逻辑删除:
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
13.3.6 MyBatis 与 MyBatis-Plus 的选型建议
- 纯 MyBatis 适用于需要精细控制每一条 SQL、团队 SQL 能力较强的场景,以及复杂查询极多的报表类系统。
- MyBatis-Plus 适用于标准 CRUD 占比高的业务系统(如管理后台、基础微服务),它可以极大减少重复代码,提高开发效率。即使项目中有部分复杂查询,仍然可以同时使用 MyBatis 自定义 XML,两者完全兼容。
在实际项目中,常采用“MyBatis-Plus + 复杂查询走 XML”的组合,兼顾效率与灵活性。掌握本节最佳实践,能让你在数据库交互层获得配置即用、可扩展、高性能的开发体验。