47_Spring Boot整合MyBatis

Spring Boot整合MyBatis

文章目录

前言

MyBatis是Java生态中最流行的持久层框架之一,它通过简单的XML或注解来配置SQL语句,将Java对象与数据库记录进行映射。相比JPA/Hibernate的全自动ORM,MyBatis给了开发者对SQL的完全控制权,特别适合复杂查询和对SQL有精细要求的项目。

MyBatis vs JPA/Hibernate:这是面试中常被问到的对比。JPA/Hibernate是"全自动ORM"------你不需要写SQL,框架根据对象模型自动生成SQL,开发效率高但不够灵活,复杂查询时生成的SQL可能性能很差。MyBatis是"半自动ORM"------你需要手写SQL,但拥有完全的SQL控制权,适合对性能要求高、SQL逻辑复杂的场景(如报表系统、遗留数据库适配)。国内互联网公司中,MyBatis的使用率显著高于JPA。本文将手把手教你如何在Spring Boot中整合MyBatis,实现完整的CRUD操作。

一、环境准备

1.1 引入依赖

pom.xml 中添加以下依赖:

xml 复制代码
<dependencies>
    <!-- Spring Boot Web -->
    <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驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- Druid连接池 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.21</version>
    </dependency>
    
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

1.2 数据库建表

首先在MySQL中创建数据库和测试表:

sql 复制代码
CREATE DATABASE mybatis_demo DEFAULT CHARACTER SET utf8mb4;

USE mybatis_demo;

CREATE TABLE t_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
    username VARCHAR(50) NOT NULL COMMENT '用户名',
    email VARCHAR(100) COMMENT '邮箱',
    age INT COMMENT '年龄',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT '用户表';

INSERT INTO t_user (username, email, age) VALUES 
('张三', 'zhangsan@example.com', 25),
('李四', 'lisi@example.com', 30),
('王五', 'wangwu@example.com', 28);

1.3 配置文件

yaml 复制代码
# application.yml
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8mb4
    username: root
    password: 123456
    druid:
      initial-size: 5
      max-active: 20
      min-idle: 5
      max-wait: 60000

# MyBatis配置
mybatis:
  mapper-locations: classpath:mapper/*.xml        # Mapper XML文件位置
  type-aliases-package: com.example.entity         # 实体类别名
  configuration:
    map-underscore-to-camel-case: true             # 下划线转驼峰
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # SQL日志

配置项详解

  • mapper-locations:指定Mapper XML文件的位置,支持通配符。常见错误是路径写错导致MyBatis找不到XML文件,此时controller调用mapper会抛出BindingException
  • type-aliases-package :指定实体类包路径后,XML中可以直接使用类名而非全限定名。如<resultMap type="User">而不是<resultMap type="com.example.entity.User">
  • map-underscore-to-camel-case:这是非常实用的配置。数据库字段通常是下划线命名(如create_time),Java属性是驼峰命名(createTime)。开启此选项后MyBatis自动完成映射,不需要每个字段都写resultMap
  • log-impl:开发时建议开启SQL日志,可以在控制台看到实际执行的SQL语句和参数。生产环境应关闭或使用框架日志(如SLF4J),StdOutImpl性能和安全性不好

二、实体类与Mapper

2.1 实体类

java 复制代码
package com.example.entity;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class User {
    private Long id;
    private String username;
    private String email;
    private Integer age;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

2.2 Mapper接口

MyBatis的Mapper接口不需要实现类,通过动态代理自动生成:

java 复制代码
package com.example.mapper;

import com.example.entity.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper  // 或者使用MapperScan
public interface UserMapper {
    
    // 查询所有用户
    List<User> findAll();
    
    // 根据ID查询
    User findById(Long id);
    
    // 根据用户名模糊查询
    List<User> findByUsername(String username);
    
    // 新增用户,返回自增ID
    int insert(User user);
    
    // 更新用户
    int update(User user);
    
    // 删除用户
    int deleteById(Long id);
    
    // 批量删除
    int deleteByIds(@Param("ids") List<Long> ids);
    
    // 统计用户数
    int count();
}

三、Mapper XML配置

src/main/resources/mapper/ 目录下创建 UserMapper.xml

xml 复制代码
<?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">

    <!-- 结果映射 -->
    <resultMap id="BaseResultMap" type="User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="email" property="email"/>
        <result column="age" property="age"/>
        <result column="create_time" property="createTime"/>
        <result column="update_time" property="updateTime"/>
    </resultMap>

    <!-- 查询所有 -->
    <select id="findAll" resultMap="BaseResultMap">
        SELECT * FROM t_user ORDER BY id DESC
    </select>

    <!-- 根据ID查询 -->
    <select id="findById" resultMap="BaseResultMap" parameterType="long">
        SELECT * FROM t_user WHERE id = #{id}
    </select>

    <!-- 模糊查询 -->
    <select id="findByUsername" resultMap="BaseResultMap">
        SELECT * FROM t_user WHERE username LIKE CONCAT('%', #{username}, '%')
    </select>

    <!-- 新增用户 -->
    <insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO t_user (username, email, age)
        VALUES (#{username}, #{email}, #{age})
    </insert>

    <!-- 更新用户 -->
    <update id="update" parameterType="User">
        UPDATE t_user
        <set>
            <if test="username != null and username != ''">
                username = #{username},
            </if>
            <if test="email != null">
                email = #{email},
            </if>
            <if test="age != null">
                age = #{age},
            </if>
        </set>
        WHERE id = #{id}
    </update>

    <!-- 删除用户 -->
    <delete id="deleteById" parameterType="long">
        DELETE FROM t_user WHERE id = #{id}
    </delete>

    <!-- 批量删除 -->
    <delete id="deleteByIds">
        DELETE FROM t_user WHERE id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

    <!-- 统计用户数 -->
    <select id="count" resultType="int">
        SELECT COUNT(*) FROM t_user
    </select>

</mapper>

3.1 动态SQL

MyBatis的强大之处在于动态SQL,可以根据条件灵活拼接SQL语句。

动态SQL的重要性 :想象一个用户搜索功能------用户可以输入用户名搜索,也可以按年龄段筛选,还可以按邮箱查询。如果不用动态SQL,你需要为每种组合写一个不同的查询方法,代码会爆炸。动态SQL通过<if><where>等标签,让你在一个SQL中根据条件动态拼接WHERE条件,既保持SQL清晰,又减少重复代码。

xml 复制代码
<!-- 动态条件查询 -->
<select id="findByCondition" resultMap="BaseResultMap">
    SELECT * FROM t_user
    <where>
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="email != null">
            AND email = #{email}
        </if>
        <if test="minAge != null">
            AND age &gt;= #{minAge}
        </if>
        <if test="maxAge != null">
            AND age &lt;= #{maxAge}
        </if>
    </where>
    ORDER BY id DESC
</select>

常用动态SQL标签:

标签 作用
<if> 条件判断
<where> 自动处理WHERE关键字和AND前缀
<set> 自动处理SET关键字和逗号后缀
<foreach> 遍历集合
<choose> <when> <otherwise> 多项条件选择
<trim> 更灵活的前后缀处理
<sql> / <include> SQL片段复用
xml 复制代码
<!-- SQL片段 -->
<sql id="userColumns">
    id, username, email, age, create_time, update_time
</sql>

<!-- 复用 -->
<select id="findById" resultMap="BaseResultMap">
    SELECT <include refid="userColumns"/> FROM t_user WHERE id = #{id}
</select>

动态SQL的常见陷阱

  1. <if> 中不要使用中文括号或其他特殊字符,否则XML解析会出错
  2. update语句使用<set>而不是手动写SET<set>会自动处理末尾多余的逗号,避免SQL语法错误
  3. foreach中collection属性 :当参数是List时写list,当参数是数组时写array,当参数是Map时写map的key名。如果使用了@Param("ids"),则写注解指定的名称

四、注解方式SQL

MyBatis也支持使用注解直接编写SQL,适合简单的SQL语句:

java 复制代码
@Mapper
public interface UserAnnotationMapper {
    
    @Select("SELECT * FROM t_user WHERE id = #{id}")
    @Results({
        @Result(property = "createTime", column = "create_time"),
        @Result(property = "updateTime", column = "update_time")
    })
    User findById(Long id);
    
    @Insert("INSERT INTO t_user(username, email, age) VALUES(#{username}, #{email}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
    
    @Update("UPDATE t_user SET username=#{username}, email=#{email}, age=#{age} WHERE id=#{id}")
    int update(User user);
    
    @Delete("DELETE FROM t_user WHERE id = #{id}")
    int deleteById(Long id);
}

混合使用XML和注解:简单SQL用注解,复杂SQL用XML。

选择建议 :注解方式代码更集中,适合简单CRUD;XML方式支持动态SQL,适合复杂查询。实际项目中通常是XML为主、注解为辅的组合模式,甚至可以将复杂的SQL片段提取到<sql>标签中复用。

通过 @MapperScan 可以统一扫描Mapper:

java 复制代码
@SpringBootApplication
@MapperScan("com.example.mapper")  // 扫描所有Mapper接口
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

五、Service层与Controller层

5.1 Service层

java 复制代码
@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    public List<User> listAll() {
        return userMapper.findAll();
    }
    
    public User getById(Long id) {
        User user = userMapper.findById(id);
        if (user == null) {
            throw new RuntimeException("用户不存在: " + id);
        }
        return user;
    }
    
    @Transactional(rollbackFor = Exception.class)
    public User create(User user) {
        userMapper.insert(user);
        return user;  // 返回带自增ID的对象
    }
    
    @Transactional(rollbackFor = Exception.class)
    public User update(Long id, User user) {
        user.setId(id);
        userMapper.update(user);
        return userMapper.findById(id);
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id) {
        userMapper.deleteById(id);
    }
}

5.2 Controller层

java 复制代码
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    public List<User> list() {
        return userService.listAll();
    }
    
    @GetMapping("/{id}")
    public User getById(@PathVariable Long id) {
        return userService.getById(id);
    }
    
    @PostMapping
    public User create(@RequestBody User user) {
        return userService.create(user);
    }
    
    @PutMapping("/{id}")
    public User update(@PathVariable Long id, @RequestBody User user) {
        return userService.update(id, user);
    }
    
    @DeleteMapping("/{id}")
    public String delete(@PathVariable Long id) {
        userService.delete(id);
        return "删除成功";
    }
}

六、分页查询

MyBatis本身不提供分页功能,推荐使用PageHelper分页插件:

xml 复制代码
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>
java 复制代码
public PageInfo<User> listByPage(int pageNum, int pageSize) {
    PageHelper.startPage(pageNum, pageSize);
    List<User> users = userMapper.findAll();
    return new PageInfo<>(users);
}

PageInfo包含的常用信息:

java 复制代码
PageInfo<User> pageInfo = userService.listByPage(1, 10);
System.out.println("总记录数: " + pageInfo.getTotal());
System.out.println("总页数: " + pageInfo.getPages());
System.out.println("当前页: " + pageInfo.getPageNum());
System.out.println("数据: " + pageInfo.getList());

总结

本文详细介绍了Spring Boot整合MyBatis的完整流程:从依赖引入、数据库配置,到Mapper接口和XML的编写,再到Service和Controller的分层实现。MyBatis的优势在于对SQL的精细控制动态SQL的灵活性,配合PageHelper分页插件可以快速实现完整的数据访问层。

面试高频考点

  1. MyBatis vs JPA:半自动ORM(灵活SQL控制)vs 全自动ORM(开发效率高)
  2. #{}${}的区别#{}是预编译占位符(防SQL注入),${}是字符串替换(不防注入,用于表名/列名等动态场景)
  3. 动态SQL的核心标签<if>/<where>/<set>/<foreach>/<choose>
  4. resultMap的作用:解决数据库字段名与Java属性名不匹配的问题
  5. 一级缓存和二级缓存:一级缓存默认开启(SqlSession级别),二级缓存需配置(namespace级别)

✅ 亮点总结

  • mybatis-spring-boot-starter一键整合,自动配置DataSource和SqlSessionFactory
  • XML和注解双模式:复杂SQL用XML动态标签,简单SQL用@Select/@Insert注解
  • 动态SQL标签<if><where><set><foreach>实现灵活的条件拼接
  • resultMap解决数据库字段名(下划线)与Java属性名(驼峰)的自动映射
  • PageHelper分页插件:一行PageHelper.startPage()即可实现物理分页,零侵入

适用场景

  • 复杂报表查询系统:多表关联、动态条件组合、聚合统计等需要精细控制SQL的项目
  • 遗留数据库适配:表结构不规范的情况下,通过resultMap灵活映射字段
  • 高并发OLTP系统:对SQL性能有极致要求,需要手写优化SQL的场景

扩展方向

  • 学习MyBatis-Plus:封装单表CRUD、条件构造器、自动填充等,大幅减少样板代码
  • 了解MyBatis拦截器:实现SQL审计日志、分表路由、数据脱敏
  • 推荐阅读下一篇文章:Spring Boot RESTful API开发(./48_Spring Boot RESTful API开发),掌握Controller层最佳实践