共1026行

SpringBoot+Mybaties攻略

2026-07-07 09:03:04

➣ SpringBoot + MyBatis 完整讲解(背景+介绍+实战示例)

一、技术背景

1. MyBatis 诞生背景

早期 Java 持久层方案:

  1. JDBC:原生代码,大量重复模板(连接、关闭资源、硬编码SQL),维护繁琐;
  2. Hibernate:全ORM框架,屏蔽SQL,自动生成语句,复杂多表、性能调优困难,SQL可控性差。

MyBatis 2010年由iBatis更名而来,半自动ORM

2. SpringBoot 诞生背景

传统SSM(Spring+SpringMVC+MyBatis)痛点:

SpringBoot 基于Spring,核心思想约定优于配置

3. SpringBoot + MyBatis 组合优势

  1. 轻量高效:MyBatis灵活控SQL,适合复杂业务、大数据查询;
  2. 开发极速:Boot自动管理数据源、事务、MyBatis会话工厂;
  3. 解耦:SQL与Java代码分离(XML),DBA可单独维护SQL;
  4. 生态完善:分页插件PageHelper、动态数据源、代码生成器配套成熟;
  5. 企业主流:中小型后台、ERP、管理系统、微服务持久层标准搭配。

二、核心组件介绍

1. SpringBoot Starter

mybatis-spring-boot-starter:官方整合包,自动完成:

2. MyBatis核心概念

三、完整应用示例(MySQL8 + SpringBoot2.7 + MyBatis)

步骤1:Maven依赖 pom.xml

<!-- SpringBoot父工程 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.15</version>
</parent>

<dependencies>
    <!-- web 可选,后台接口必备 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- MyBatis整合Boot -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.3.0</version>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- 连接池HikariCP(boot自带,无需额外引入) -->
    <!-- lombok简化实体get/set -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

步骤2:数据库建表语句

CREATE DATABASE IF NOT EXISTS boot_mybatis;
USE boot_mybatis;

CREATE TABLE t_user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(32) NOT NULL,
    age INT,
    email VARCHAR(50)
);

INSERT INTO t_user(username,age,email) VALUES 
('张三',20,'zhangsan@qq.com'),
('李四',22,'lisi@163.com');

步骤3:application.yml 核心配置

# 数据源配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/boot_mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 你的数据库密码

# MyBatis配置
mybatis:
  # mapper xml文件存放路径
  mapper-locations: classpath:mapper/*.xml
  # 实体类别名包,xml中可直接写类名不用全限定名
  type-aliases-package: com.demo.entity
  configuration:
    map-underscore-to-camel-case: true # 开启下划线转驼峰 t_user -> tUser
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印执行SQL日志

步骤4:启动类(核心注解 @MapperScan)

package com.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 扫描Mapper接口包,所有Mapper自动注册Bean
@MapperScan("com.demo.mapper")
@SpringBootApplication
public class MyBatisDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBatisDemoApplication.class, args);
    }
}

步骤5:实体类 User.java

package com.demo.entity;

import lombok.Data;

@Data
public class User {
    private Integer id;
    private String username;
    private Integer age;
    private String email;
}

步骤6:Mapper接口 UserMapper.java

package com.demo.mapper;

import com.demo.entity.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface UserMapper {
    // 查询所有用户
    List<User> selectAll();
    // 根据id查询
    User selectById(@Param("uid") Integer id);
    // 新增用户
    int insert(User user);
    // 修改
    int update(User user);
    // 删除
    int deleteById(Integer id);
}

步骤7:Mapper XML 文件 resources/mapper/UserMapper.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">
<!-- namespace绑定对应Mapper接口全类名 -->
<mapper namespace="com.demo.mapper.UserMapper">

    <select id="selectAll" resultType="User">
        select id, username, age, email from t_user
    </select>

    <select id="selectById" resultType="User">
        select * from t_user where id = #{uid}
    </select>

    <insert id="insert">
        insert into t_user(username, age, email)
        values(#{username}, #{age}, #{email})
    </insert>

    <update id="update">
        update t_user
        set username=#{username}, age=#{age}, email=#{email}
        where id=#{id}
    </update>

    <delete id="deleteById">
        delete from t_user where id = #{id}
    </delete>
</mapper>

步骤8:Service层(业务层)

UserService

package com.demo.service;
import com.demo.entity.User;
import java.util.List;

public interface UserService {
    List<User> getAllUser();
    User getUserById(Integer id);
    int addUser(User user);
}

UserServiceImpl

package com.demo.service.impl;

import com.demo.entity.User;
import com.demo.mapper.UserMapper;
import com.demo.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public List<User> getAllUser() {
        return userMapper.selectAll();
    }

    @Override
    public User getUserById(Integer id) {
        return userMapper.selectById(id);
    }

    @Override
    public int addUser(User user) {
        return userMapper.insert(user);
    }
}

步骤9:Controller 测试接口

package com.demo.controller;

import com.demo.entity.User;
import com.demo.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    @GetMapping("/list")
    public List<User> list() {
        return userService.getAllUser();
    }

    @GetMapping("/{id}")
    public User getById(@PathVariable Integer id) {
        return userService.getUserById(id);
    }

    @PostMapping("/add")
    public String add(@RequestBody User user) {
        int rows = userService.addUser(user);
        return rows > 0 ? "新增成功" : "新增失败";
    }
}

四、运行测试

  1. 启动主程序,内置Tomcat端口默认8080;
  2. 接口访问:
{
    "username":"王五",
    "age":25,
    "email":"wangwu@qq.com"
}

控制台会自动打印完整执行SQL、参数、返回行数。

五、拓展使用场景

  1. 复杂多表联查:MyBatis XML支持left join、子查询,自定义ResultMap映射关联实体;
  2. 分页查询:整合PageHelper插件,一行代码实现分页;
  3. 注解版MyBatis:简单SQL可直接写在Mapper接口方法上,无需XML;
  4. 多数据源:SpringBoot配置多套DataSource,不同Mapper绑定不同库;
  5. 代码生成:MyBatis Generator自动生成Entity、Mapper、XML,减少手写CRUD;
  6. 微服务持久层:配合SpringCloud,作为各业务模块数据访问层。

➣ PageHelper-SpringBoot-Starter 完整讲解

一、背景介绍

1. 原生MyBatis分页痛点

单纯使用SpringBoot+MyBatis做分页有两种原生方案,都有明显缺陷:

  1. 手动写分页SQL
    每条查询都要加 limit offset,size,还要单独写一条count统计总数;
    多表联查、复杂条件查询时,count语句需要重复复制业务条件,代码冗余、极易出错,维护成本高。
  2. 自定义拦截器分页
    需要自己实现MyBatis拦截器,解析SQL、拼接limit、处理count,开发门槛高,容易出现SQL注入、分页逻辑bug。

2. PageHelper 诞生作用

PageHelper 是国内开源的MyBatis分页插件,专门解决MyBatis分页繁琐问题:

3. 和MyBatis配合原理

  1. 执行Mapper查询前,调用 PageHelper.startPage(pageNum, pageSize) 设置分页参数;
  2. PageHelper通过MyBatis拦截器 PageInterceptor 拦截查询SQL;
  3. 插件自动生成两条SQL:
  4. 封装分页结果到PageInfo对象,包含:当前页、每页条数、总条数、总页数、是否有上/下一页等分页全部信息;
  5. 查询完成自动清理ThreadLocal分页参数,避免线程污染(SpringBoot内置自动清理)。

二、整合依赖与配置

1. Maven pom.xml 引入starter

配合上文SpringBoot+MyBatis项目,新增依赖:

<!-- pagehelper 分页插件 starter -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.6</version>
</dependency>

2. application.yml 分页配置(可选,推荐配置)

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/boot_mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: root

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.demo.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# PageHelper分页配置
pagehelper:
  # 数据库方言,自动识别也可手动指定 mysql/oracle
  helper-dialect: mysql
  # 开启合理化分页:pageNum<=1 自动查第一页,pageNum>总页数 查询最后一页
  reasonable: true
  # 支持分页参数传递:pageNum/pageSize 从请求参数自动获取
  support-methods-arguments: true
  # 开启count统计,默认true
  count: true
  # 分页参数名,前端可传 pageNum pageSize
  params: pageNum=pageNum;pageSize=pageSize

三、完整代码示例(复用之前User模块)

1. Mapper 层(SQL完全不用改,普通查询即可)

UserMapper.java

package com.demo.mapper;
import com.demo.entity.User;
import java.util.List;

public interface UserMapper {
    // 普通全量查询,无需手动加limit
    List<User> selectAll();
    // 带条件查询分页(示例:根据用户名模糊查询)
    List<User> selectByUsername(String username);
}

UserMapper.xml

<mapper namespace="com.demo.mapper.UserMapper">
    <select id="selectAll" resultType="User">
        select id, username, age, email from t_user
    </select>

    <select id="selectByUsername" resultType="User">
        select * from t_user
        <where>
            <if test="username != null and username != ''">
                username like concat('%',#{username},'%')
            </if>
        </where>
    </select>
</mapper>

2. Service层 分页逻辑(核心 PageHelper.startPage)

UserService 接口

package com.demo.service;
import com.demo.entity.User;
import com.github.pagehelper.PageInfo;

public interface UserService {
    // 无条件分页
    PageInfo<User> pageUser(Integer pageNum, Integer pageSize);
    // 带条件分页
    PageInfo<User> pageUserByUsername(Integer pageNum, Integer pageSize, String username);
}

UserServiceImpl 实现类

package com.demo.service.impl;

import com.demo.entity.User;
import com.demo.mapper.UserMapper;
import com.demo.service.UserService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public PageInfo<User> pageUser(Integer pageNum, Integer pageSize) {
        // 核心一行:开启分页,参数1=当前页码,参数2=每页条数
        PageHelper.startPage(pageNum, pageSize);
        // 紧随其后的第一条Mapper查询会自动分页
        List<User> userList = userMapper.selectAll();
        // 封装分页全部数据,返回PageInfo
        return new PageInfo<>(userList);
    }

    @Override
    public PageInfo<User> pageUserByUsername(Integer pageNum, Integer pageSize, String username) {
        PageHelper.startPage(pageNum, pageSize);
        List<User> list = userMapper.selectByUsername(username);
        return new PageInfo<>(list);
    }
}

3. Controller 接口测试

package com.demo.controller;

import com.demo.entity.User;
import com.demo.service.UserService;
import com.github.pagehelper.PageInfo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.annotation.Resource;

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    /**
     * 无条件分页接口
     * 示例地址:http://localhost:8080/user/page?pageNum=1&pageSize=2
     */
    @GetMapping("/page")
    public PageInfo<User> page(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "2") Integer pageSize
    ){
        return userService.pageUser(pageNum, pageSize);
    }

    /**
     * 条件分页接口
     * 示例地址:http://localhost:8080/user/page/search?pageNum=1&pageSize=2&username=张
     */
    @GetMapping("/page/search")
    public PageInfo<User> pageSearch(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "2") Integer pageSize,
            @RequestParam(required = false) String username
    ){
        return userService.pageUserByUsername(pageNum, pageSize, username);
    }
}

四、返回结果说明(PageInfo结构)

访问接口后返回JSON示例:

{
    "pageNum": 1,        // 当前页码
    "pageSize": 2,       // 每页条数
    "size": 2,           // 当前页实际数据条数
    "startRow": 1,
    "endRow": 2,
    "total": 10,         // 总记录数
    "pages": 5,          // 总页数
    "list": [            // 当前页数据集合
        {"id":1,"username":"张三","age":20,"email":"zhangsan@qq.com"},
        {"id":2,"username":"李四","age":22,"email":"lisi@163.com"}
    ],
    "prePage": 0,        // 上一页页码
    "nextPage": 2,       // 下一页页码
    "isFirstPage": true, // 是否第一页
    "isLastPage": false, // 是否最后一页
    "hasPreviousPage": false, // 是否有上一页
    "hasNextPage": true       // 是否有下一页
}

五、重要使用规范(避坑)

  1. PageHelper.startPage 只对紧跟的第一条查询生效
    startPage后不能写其他Mapper查询,否则分页失效;
    错误示例:
    PageHelper.startPage(1,2);
    userMapper.selectById(1); // 这条会分页
    List<User> list = userMapper.selectAll(); // 这条不分页
  2. 开启 reasonable: true 合理化分页
    前端传pageNum=0自动查第一页,传超大页码自动查最后一页,不用手动做参数校验。
  3. 多线程场景安全
    分页参数存在ThreadLocal,查询完PageInfo会自动清空,不会线程串参。
  4. 关闭count场景
    大数据量查询不需要总条数时,PageHelper.startPage(1,10,false) 第三个参数关闭count,提升查询性能。

六、拓展高级用法

1. 不统计总数(大数据优化)

// 第三个参数false:不执行count查询,只分页数据
PageHelper.startPage(pageNum, pageSize, false);

2. 自定义分页参数(自动从请求读取)

配置文件开启 support-methods-arguments: true 后,Controller不用手动接收pageNum/pageSize,直接传递到Service:

// Service层方法
PageInfo<User> page(String username, @Param("pageNum") Integer pageNum, @Param("pageSize") Integer pageSize);

3. 多表复杂联查完全兼容

Mapper XML中的left join、子查询、多条件动态SQL无需任何修改,插件自动解析拼接limit。


➣ Mapper.xml 完整结构、标签详解与实战用法

Mapper.xml 是 MyBatis 核心 SQL 映射文件,作用:绑定 Mapper 接口、编写 SQL、处理参数、封装返回结果、动态拼接 SQL,配合 SpringBoot+MyBatis 使用。

一、完整文件整体结构

1. 文件头部固定声明(DTD约束)

每个 xml 第一行必须写,用于 IDE 提示、语法校验

<?xml version="1.0" encoding="UTF-8"?>
<!-- MyBatis 3.x DTD约束 -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

2. 根标签 <mapper>

整个文件只有一个根标签,核心属性:

<mapper namespace="com.demo.mapper.UserMapper">
    <!-- 所有SQL标签写在这里 -->
</mapper>

3. 内部所有子标签分类

  1. 增删改查:<select> / <insert> / <update> / <delete>
  2. 结果映射:<resultMap>(自定义字段映射、多表关联)
  3. 可复用SQL片段:<sql> + <include>
  4. 动态SQL:<if> <where> <set> <foreach> <choose> <trim>
  5. 参数转换:<parameterMap>(极少用,淘汰)

完整标准结构模板:

<?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.demo.mapper.UserMapper">
    <!-- 1. 可复用SQL片段 -->
    <sql id="userColumn">id,username,age,email</sql>

    <!-- 2. 自定义结果映射(复杂场景用) -->
    <resultMap id="UserResultMap" type="User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
    </resultMap>

    <!-- 3. 查询语句 -->
    <select id="selectById" resultType="User">
        select <include refid="userColumn"/> from t_user where id = #{id}
    </select>

    <!-- 4. 新增 -->
    <insert id="insert">...</insert>
    <!-- 5. 修改 -->
    <update id="update">...</update>
    <!-- 6. 删除 -->
    <delete id="deleteById">...</delete>
</mapper>

二、根标签 详解

属性只有一个核心:namespace
规则:

  1. 必须和 Mapper 接口完整类名完全一致;
  2. 一个 xml 文件只能对应一个 Mapper 接口;
  3. 多个 xml 不能同名 namespace。

示例:
接口:com.demo.mapper.UserMapper
xml:<mapper namespace="com.demo.mapper.UserMapper">


三、CRUD 四大核心标签(select/insert/update/delete)通用属性

通用公共属性

属性 作用
id 对应 Mapper 接口方法名,必须唯一
parameterType 传入参数类型(实体、Integer、String、Map,可省略自动推断)
resultType 返回单行/多行数据类型(简单场景,实体/基本类型)
resultMap 复杂返回映射,优先级高于 resultType,和 <resultMap> id 绑定
useCache 是否开启二级缓存,默认true
flushCache 执行后清空缓存,增删改默认true

1. <select> 查询标签(最常用)

基础示例

<!-- resultType:别名User,对应type-aliases-package下的实体 -->
<select id="selectAll" resultType="User">
    select id, username, age, email from t_user
</select>

特殊属性

参数取值两种语法

  1. #{变量}:预编译占位符,防SQL注入(推荐)
  2. ${变量}:字符串直接拼接,存在注入风险,仅用于表名、排序字段
<!-- 单参数 -->
<select id="selectById" resultType="User">
    select * from t_user where id = #{id}
</select>

<!-- 多参数:接口使用@Param("name") -->
<select id="selectByNameAge" resultType="User">
    select * from t_user where username = #{name} and age = #{age}
</select>

2. <insert> 新增标签

专属属性:

<!-- 新增并自动回填主键id到User实体 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert into t_user(username, age, email)
    values(#{username}, #{age}, #{email})
</insert>

3. <update> 更新标签

<update id="update">
    update t_user
    set username=#{username}, age=#{age}, email=#{email}
    where id=#{id}
</update>

4. <delete> 删除标签

<delete id="deleteById">
    delete from t_user where id = #{id}
</delete>

四、 + 复用SQL片段

作用

抽取重复列、重复查询条件,多处复用,减少冗余代码。

用法

  1. <sql id="片段ID"> 定义公共片段
  2. <include refid="片段ID"/> 引入片段
<!-- 抽取查询列 -->
<sql id="userCols">
    id, username, age, email
</sql>

<!-- 抽取查询条件 -->
<sql id="userWhere">
    <if test="username != null">
        and username like concat('%',#{username},'%')
    </if>
</sql>

<select id="listUser" resultType="User">
    select <include refid="userCols"/>
    from t_user
    where 1=1
    <include refid="userWhere"/>
</select>

五、 自定义结果映射(重点难点)

适用场景

  1. 数据库字段名和实体属性名不一致(不开启驼峰转换时)
  2. 多表一对一、一对多关联查询
  3. 日期、枚举、复杂类型转换

标签结构

示例1:字段映射(数据库user_name,实体userName)

<resultMap id="UserMap" type="User">
    <!-- column:数据库字段,property:实体属性 -->
    <id column="id" property="id"/>
    <result column="user_name" property="username"/>
    <result column="user_age" property="age"/>
</resultMap>

<!-- 使用resultMap代替resultType -->
<select id="getUser" resultMap="UserMap">
    select id,user_name,user_age from t_user where id=#{id}
</select>

示例2:一对一关联(用户+部门)

<resultMap id="UserDeptMap" type="User">
    <id column="u_id" property="id"/>
    <result column="username" property="username"/>
    <!-- 一对一关联Dept实体 -->
    <association property="dept" javaType="Dept">
        <id column="d_id" property="deptId"/>
        <result column="dept_name" property="deptName"/>
    </association>
</resultMap>

示例3:一对多关联(用户+多个订单)

<resultMap id="UserOrderMap" type="User">
    <id column="id" property="id"/>
    <!-- 一对多,集合类型List<Order> -->
    <collection property="orderList" ofType="Order">
        <id column="o_id" property="orderId"/>
        <result column="order_no" property="orderNo"/>
    </collection>
</resultMap>

六、动态SQL全套标签(开发高频)

1. 条件判断

test 属性写 OGNL 表达式,判断参数是否存在、值是否相等

<select id="listByCondition" resultType="User">
    select * from t_user
    where 1=1
    <if test="username != null and username != ''">
        and username like concat('%',#{username},'%')
    </if>
    <if test="age != null">
        and age = #{age}
    </if>
</select>

2. 智能处理where关键字

自动处理:

<select id="listByWhere" resultType="User">
    select * from t_user
    <where>
        <if test="username != null">
            username like concat('%',#{username},'%')
        </if>
        <if test="age != null">
            and age = #{age}
        </if>
    </where>
</select>

3. 更新语句智能去逗号

更新时多个字段可选更新,自动删除最后多余逗号

<update id="updateSelective">
    update t_user
    <set>
        <if test="username != null">username=#{username},</if>
        <if test="age != null">age=#{age},</if>
        <if test="email != null">email=#{email}</if>
    </set>
    where id=#{id}
</update>

4. 循环(in批量查询、批量插入)

属性:

批量删除 id in (1,2,3)

<delete id="deleteBatch">
    delete from t_user
    where id in
    <foreach collection="ids" item="uid" open="(" close=")" separator=",">
        #{uid}
    </foreach>
</delete>

批量插入多条数据

<insert id="insertBatch">
    insert into t_user(username,age) values
    <foreach collection="userList" item="u" separator=",">
        (#{u.username},#{u.age})
    </foreach>
</insert>

5. 多分支判断(if-else if-else)

<select id="listChoose" resultType="User">
    select * from t_user
    <where>
        <choose>
            <when test="id != null">
                id = #{id}
            </when>
            <when test="username != null">
                username = #{username}
            </when>
            <otherwise>
                age >= 18
            </otherwise>
        </choose>
    </where>
</select>

6. 自定义截取前缀后缀(通用替代where/set)

prefix:开头添加字符串
prefixOverrides:去掉开头多余and
suffixOverrides:去掉末尾逗号

<!-- 模拟where标签 -->
<trim prefix="where" prefixOverrides="and|or">
    <if test="name != null">and name=#{name}</if>
</trim>

<!-- 模拟set标签 -->
<trim prefix="set" suffixOverrides=",">
    <if test="name != null">name=#{name},</if>
</trim>

七、#{} 和 ${} 核心区别(必掌握)

  1. #{param}

  2. ${param}

<!-- 动态排序只能用${} -->
<select id="listOrder" resultType="User">
    select * from t_user
    order by ${sortColumn} ${sortType}
</select>

八、Mapper.xml 使用规范与避坑

  1. 文件存放路径
    yml 配置 mapper-locations: classpath:mapper/*.xml,xml 统一放在 resources/mapper 下;
  2. namespace 必须和 Mapper 接口全类名完全一致;
  3. id 值必须和接口方法名一模一样,重载方法不支持;
  4. 开启 map-underscore-to-camel-case: true 可省去大量 resultMap 字段映射;
  5. 动态SQL多条件优先使用 <where>,不要手写 where 1=1
  6. 更新语句用 <set> 避免逗号语法错误;
  7. 批量操作统一使用 <foreach>,不要循环单条操作;
  8. 禁止业务SQL直接拼接字符串,优先 #{}
  9. 自增主键新增必须加 useGeneratedKeys="true" keyProperty="id" 才能回填ID。

九、完整可运行综合示例(整合全部标签)

<?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.demo.mapper.UserMapper">
    <!-- 复用列片段 -->
    <sql id="userCol">id,username,age,email</sql>

    <!-- 自定义结果映射 -->
    <resultMap id="UserResult" type="User">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="age" property="age"/>
        <result column="email" property="email"/>
    </resultMap>

    <!-- 条件分页查询 -->
    <select id="listUser" resultMap="UserResult">
        select <include refid="userCol"/>
        from t_user
        <where>
            <if test="username != null and username != ''">
                username like concat('%',#{username},'%')
            </if>
            <if test="minAge != null">
                age >= #{minAge}
            </if>
        </where>
        order by id desc
    </select>

    <!-- 新增用户回填主键 -->
    <insert id="addUser" useGeneratedKeys="true" keyProperty="id">
        insert into t_user(username,age,email)
        values(#{username},#{age},#{email})
    </insert>

    <!-- 动态更新 -->
    <update id="updateUser">
        update t_user
        <set>
            <if test="username != null">username=#{username},</if>
            <if test="age != null">age=#{age},</if>
            <if test="email != null">email=#{email}</if>
        </set>
        where id=#{id}
    </update>

    <!-- 批量删除 -->
    <delete id="batchDelete">
        delete from t_user
        where id in
        <foreach collection="ids" item="uid" open="(" close=")" separator=",">
            #{uid}
        </foreach>
    </delete>
</mapper>