人人都会AI编程

13.3 ORM 框架整合:MyBatis / MyBatis-Plus 最佳实践

更新时间:2026-07-10

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 会自动配置 SqlSessionFactorySqlSessionTemplate,无需手动编码。

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 层立即拥有了 saveBatchpagelist 等数十个方法,并且支持链式调用。

13.3.3 条件构造器与分页插件

1. 条件构造器

MyBatis-Plus 的核心利器之一就是条件构造器 QueryWrapperUpdateWrapper,可以用 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”的组合,兼顾效率与灵活性。掌握本节最佳实践,能让你在数据库交互层获得配置即用、可扩展、高性能的开发体验。