MyBatis

MyBatis


环境搭建

配置文件(application.properties)

# ========== 数据源 ==========
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/stock?useSSL=false
spring.datasource.username=root
spring.datasource.password=123456

# ========== MyBatis 配置 ==========
# 打印 SQL 日志(开发调试用)
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

# 开启驼峰命名自动映射(如 user_name → userName)
mybatis.configuration.map-underscore-to-camel-case=true

# 实体类别名包(设置后 XML 中可直接用类名,无需写全限定名)
mybatis.type-aliases-package=org.example.bean

# Mapper XML 文件路径(如果 XML 与接口同包同名,可省略此配置)
# mybatis.mapper-locations=classpath:mapper/*Mapper.xml
配置项 作用
mybatis.configuration.log-impl 指定日志实现,StdOutImpl 将 SQL 打印到控制台
mybatis.configuration.map-underscore-to-camel-case 自动将数据库下划线命名映射为 Java 驼峰命名
mybatis.type-aliases-package 扫描指定包下的实体类,自动注册别名(类名首字母小写)
mybatis.mapper-locations 指定 Mapper XML 文件的路径(支持 Ant 风格通配符)

Mapper XML 基础规则

MyBatis 通过 Mapper 接口 + XML 映射文件 实现 SQL 与 Java 代码的解耦。

规则 说明 示例
namespace 必须等于 Mapper 接口的全限定名 org.example.mapper.UserMapper
id 必须等于 Mapper 接口的方法名 selectById
parameterType 传入参数的类型(全限定名或别名) User、int、map
resultType 返回值的类型(全限定名或别名) User、org.example.bean.User
XML 存放位置 resources 下的路径必须与 Mapper 接口的包路径一致 org/example/mapper/UserMapper.xml

完整示例对照

Java 接口:

package org.example.mapper;

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

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="org.example.mapper.UserMapper">
    <!-- id = 方法名,resultType = 返回值类型(配置了别名包,直接用 User) -->
    <select id="selectById" resultType="User">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>

参数绑定

MyBatis 通过 #{} 占位符来绑定参数(底层使用 PreparedStatement,防止 SQL 注入)。

注意:#{} 和 ${} 的区别 —— #{} 是预编译占位符(安全),${} 是字符串拼接(有 SQL 注入风险,仅在动态表名/列名等特殊场景使用)。

单参数(简单类型)

当方法只有一个简单类型参数(如 int、long、String)时,#{} 中的名称可以任意写:

// Mapper 接口
User selectById(long id);
<!-- #{} 中的名称任意,这里写 abc 也能正确取值 -->
<select id="selectById" resultType="User">
    SELECT * FROM user WHERE id = #{abc}
</select>

多参数 — 序号绑定

MyBatis 内部使用 arg0, arg1, ... 或 param1, param2, ... 来引用参数:

// Mapper 接口(不使用 @Param)
User selectByNamepwd(String name, String pwd);
<!-- 方式一:arg 序号 -->
<select id="selectByNamepwd" resultType="User">
    SELECT * FROM user WHERE userName = #{arg0} AND pwd = #{arg1}
</select>

<!-- 方式二:param 序号 -->
<select id="selectByNamepwd" resultType="User">
    SELECT * FROM user WHERE userName = #{param1} AND pwd = #{param2}
</select>

不推荐:序号绑定可读性差,参数顺序一旦改变就会出错。

多参数 — @Param 注解绑定(推荐)

// Mapper 接口
import org.apache.ibatis.annotations.Param;

User selectByNamepwd(
    @Param("userName") String name,
    @Param("password") String pwd
);
<!-- #{}中使用 @Param 指定的名称 -->
<select id="selectByNamepwd" resultType="User">
    SELECT * FROM user WHERE userName = #{userName} AND pwd = #{password}
</select>

推荐:@Param 明确指定参数名,可读性好,维护方便。

Map 参数绑定

参数较多且不固定时,可以使用 Map 传参,#{} 中的名称与 Map 的 key 对应:

// Mapper 接口
List<User> selectByAgeSexMap(Map<String, Object> map);
// 调用
HashMap<String, Object> map = new HashMap<>();
map.put("age", 30);
map.put("sex", 1);
userMapper.selectByAgeSexMap(map).forEach(System.out::println);
<select id="selectByAgeSexMap" resultType="User" parameterType="map">
    SELECT * FROM user WHERE age = #{age} AND sex = #{sex}
</select>

模糊查询

使用 MySQL 的 CONCAT 函数拼接 %,可以防止 SQL 注入:

// Mapper 接口
List<User> selectByUsername(String name);
<select id="selectByUsername" resultType="User">
    SELECT * FROM user WHERE userName LIKE CONCAT('%', #{username}, '%')
</select>
// 调用
userMapper.selectByUsername("张").forEach(System.out::println);
// 实际执行 SQL:SELECT * FROM user WHERE userName LIKE '%张%'

主键回填

插入数据后,需要获取数据库生成的主键值。MyBatis 提供 3 种方式。

方式一:useGeneratedKeys(自增主键,最常用)

适用于 MySQL 自增主键(AUTO_INCREMENT),插入后自动回填:

<insert id="insert" parameterType="user"
        useGeneratedKeys="true"
        keyColumn="id"
        keyProperty="id">
    INSERT INTO user(userNo, userName, pwd, sex, age, hometown, telphone)
    VALUES (#{userNo}, #{userName}, #{pwd}, #{sex}, #{age}, #{hometown}, #{telphone})
</insert>
属性 作用
useGeneratedKeys="true" 启用 JDBC 的 getGeneratedKeys() 获取自增主键
keyColumn 数据库中的主键列名
keyProperty Java 实体类中对应的属性名
// 调用后 user.getId() 即为新生成的主键值
User user = new User(null, "zhangsan", ...);
userMapper.insert(user);
System.out.println(user.getId());  // 输出:新插入记录的自增 ID

方式二:<selectKey> + LAST_INSERT_ID()(自增主键)

与方式一等效,通过 <selectKey> 在插入后查询 last_insert_id():

<insert id="insert" parameterType="user">
    <!-- order="AFTER" 表示在 INSERT 之后执行 -->
    <selectKey order="AFTER" keyColumn="id" keyProperty="id" resultType="integer">
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO user(userNo, userName, pwd, sex, age, hometown, telphone)
    VALUES (#{userNo}, #{userName}, #{pwd}, #{sex}, #{age}, #{hometown}, #{telphone})
</insert>

方式三:<selectKey> + UUID(自定义主键,插入前生成)

适用于主键是字符串(如 UUID),在插入前生成主键值:

<insert id="insert" parameterType="product">
    <!-- order="BEFORE" 表示在 INSERT 之前执行 -->
    <selectKey order="BEFORE" resultType="string" keyColumn="id" keyProperty="id">
        SELECT REPLACE(UUID(), '-', '')
    </selectKey>
    INSERT INTO product(id, name) VALUES (#{id}, #{name})
</insert>

执行流程:先执行 <selectKey> 子查询生成 UUID → 将结果赋给 product.id → 再执行 INSERT,#{id} 取到的就是刚生成的 UUID。

// 调用
Product product = new Product(null, "鼠标");
productMapper.insert(product);
System.out.println(product.getId());  // 输出:自动生成的 UUID(32位无横线)

<selectKey> 属性说明

属性 可选值 作用
order BEFORE / AFTER BEFORE:插入前执行(UUID/雪花ID);AFTER:插入后执行(自增ID)
keyColumn 数据库列名 主键列
keyProperty Java 属性名 将生成的主键值赋给实体类的哪个属性
resultType 类型 主键值的 Java 类型

SQL 片段 <sql>

将重复使用的 SQL 片段抽取出来,通过 <include> 引用,减少代码冗余:

<!-- 定义 SQL 片段 -->
<sql id="selectAll">
    SELECT * FROM user
</sql>

<!-- 引用 SQL 片段 -->
<select id="selectById" resultType="User">
    <include refid="selectAll"/> WHERE id = #{id}
</select>

<select id="selectByCondition" resultType="User">
    <include refid="selectAll"/> WHERE age = #{age}
</select>

好处:如果表名变了或需要统一加字段,只需修改一处。


动态 SQL

根据传入参数动态拼接 SQL 语句,是 MyBatis 最强大的特性之一。

<if> + <where> — 多条件查询

<where> 标签会自动处理 多余的 AND / OR(至少有一个条件成立时才插入 WHERE 关键字):

<select id="selectByCondition" resultType="User">
    SELECT * FROM user
    <where>
        <if test="userName != null and userName.length > 0">
            AND userName = #{userName}
        </if>
        <if test="pwd != null and pwd.length > 0">
            AND pwd = #{pwd}
        </if>
        <if test="sex != null and sex > 0">
            AND sex = #{sex}
        </if>
        <if test="hometown != null and hometown.length > 0">
            AND hometown = #{hometown}
        </if>
    </where>
</select>

<if> 标签的 test 属性:写 OGNL 表达式,判断条件是否成立。

<where> 行为示例:

  • 所有条件都不满足 → 生成 SELECT * FROM user(没有 WHERE 子句)
  • 只有 userName 满足 → 生成 SELECT * FROM user WHERE userName = ?(自动去掉 AND)

<choose>、<when>、<otherwise>

if—-else if—-else if —-else 只选一个进行查询

如果用户填写了名字,这个搜索就按照名字来搜索,你即使填写了别的条件也不去拼接,

如果没有填写名字,填写了年龄,就按照年龄来搜索,

如果名字和年龄都没有填写,但是填写了性别就按照性别来查找

name、age、gender搜索时候只能按照其中一个来搜索,但是优先级name>age>gender

List selectByCondition(Student student);

<select id="selectByCondition2" parameterType="Student" resultMap="studentMap">
   SELECT <include refid="studentColumns"/>
   FROM  student
   WHERE
      <choose>
         <when test="name!=null and name!=''">
            name LIKE concat('%', #{name}, '%')
         </when>
         <when test="age!=null"> 
            age=#{age}
         </when>
         <when test="gender!=null and gender!=''">
            gender=#{gender}
         </when>
         <otherwise>
         </otherwise>
      </choose>
</select>

<trim> 代替 <where>

<trim> 更灵活,可以自定义前缀和要去除的前缀字符:

<select id="selectByCondition" resultType="User">
    SELECT * FROM user
    <trim prefix="where" prefixOverrides="and|or">
        <if test="userName != null and userName.length > 0">
            AND userName = #{userName}
        </if>
        <if test="pwd != null and pwd.length > 0">
            AND pwd = #{pwd}
        </if>
    </trim>
</select>

<set> — 动态更新

<set> 标签自动添加 SET 关键字,并去除尾部多余的逗号:

<update id="update" parameterType="user">
    UPDATE user
    <set>
        <if test="userName != null and userName.length > 0">
            userName = #{userName},
        </if>
        <if test="pwd != null and pwd.length > 0">
            pwd = #{pwd},
        </if>
        <if test="hometown != null and hometown.length > 0">
            hometown = #{hometown},
        </if>
    </set>
    WHERE id = #{id}
</update>

注意:<set> 会自动处理末尾逗号,但你需要确保至少有一个条件成立,否则会生成错误的 SQL UPDATE user SET WHERE id = ?。

<trim> 代替 <set>

<update id="update" parameterType="user">
    UPDATE user
    <trim prefix="set" suffixOverrides=",">
        <if test="userName != null and userName.length > 0">
            userName = #{userName},
        </if>
        <if test="pwd != null and pwd.length > 0">
            pwd = #{pwd},
        </if>
        <if test="hometown != null and hometown.length > 0">
            hometown = #{hometown}
        </if>
    </trim>
    WHERE id = #{id}
</update>

<foreach> — 批量操作

批量插入

// Mapper 接口
int insert1(List<User> users);
<insert id="insert1" parameterType="user">
    INSERT INTO user(userNo, userName, pwd, sex, age, hometown, telphone) VALUES
    <foreach collection="list" item="u" separator=",">
        (#{u.userNo}, #{u.userName}, #{u.pwd}, #{u.sex}, #{u.age}, #{u.hometown}, #{u.telphone})
    </foreach>
</insert>
// 调用
List<User> userList = Arrays.asList(user1, user2, user3);
userMapper.insert1(userList);
// 生成 SQL:
// INSERT INTO user(...) VALUES (?,?,...), (?,?,...), (?,?,...)

IN 查询

<select id="seletByids" resultType="User">
    SELECT * FROM user
    <where>
        <foreach collection="list" item="id" open="id IN (" close=")" separator=",">
            #{id}
        </foreach>
    </where>
</select>
// 调用后生成:SELECT * FROM user WHERE id IN (?, ?, ?)

<foreach> 属性详解

属性 含义 示例
collection 传入的集合。默认可用 list(List 类型)、array(数组类型)、collection(通用),或 @Param 指定的名称 list、arg0、ids
item 每次迭代的元素变量名,在循环体内通过 #{item名.属性} 取值 u → #{u.userName}
open 整个循环结果开头拼接的字符串 "id IN ("
close 整个循环结果结尾拼接的字符串 ")"
separator 每次迭代之间的分隔符 ","

resultMap — 列名与属性名映射

当数据库列名(如 deptNo)与 Java 属性名(如 no)不一致时,有两种解决方案。

方式一:SQL 起别名(简单场景)

<select id="selectAll" resultType="Dept">
    SELECT id, deptNo no, deptName name FROM dept
</select>

通过 deptNo no 别名,将列 deptNo 映射为属性 no。

方式二:<resultMap>(推荐,功能更强)

<!-- 定义 resultMap -->
<resultMap id="deptMap" type="dept">
    <!-- 主键列用 <id> 标签(可优化性能) -->
    <id column="id" property="id"/>
    <!-- 普通列用 <result> 标签 -->
    <result column="deptNo" property="no"/>
    <result column="deptName" property="name"/>
</resultMap>

<!-- 使用 resultMap -->
<select id="selectAll1" resultMap="deptMap">
    SELECT id, deptNo, deptName FROM dept
</select>

<resultMap> 子元素说明

子标签 用途 属性
<id> 标记主键字段映射(MyBatis 用主键做缓存优化) column(列名)、property(属性名)
<result> 标记普通字段映射 column(列名)、property(属性名)
<association> 一对一关联映射 见第十章
<collection> 一对多关联映射 见第十一章

一对一关联查询 — <association>

场景:一个订单(Order)对应一个用户(AUser),Order 中包含 AUser 对象。

数据库关系:Order 表有外键 uid 指向 AUser 表的主键 id。

实体类

// Order.java — 多方,持有 AUser 引用
@Data
public class Order {
    private Integer id;
    private Date ordertime;
    private double total;
    private Integer uid;       // 外键
    private AUser aUser;       // 一对一关联对象
}

// AUser.java — 一方
@Data
public class AUser {
    private Integer id;
    private String username;
    private String password;
    private List<Order> orderList;  // 一对多(见第十一章)
}

方式一:直接映射嵌套属性

直接在 <resultMap> 中用 . 语法映射嵌套对象的属性:

<resultMap id="orderMap" type="order">
    <id column="id" property="id"/>
    <result column="ordertime" property="ordertime"/>
    <result column="total" property="total"/>
    <result column="uid" property="uid"/>
    <!-- 直接用 property="aUser.xxx" 映射嵌套属性 -->
    <result column="uid" property="aUser.id"/>
    <result column="username" property="aUser.username"/>
    <result column="password" property="aUser.password"/>
</resultMap>

<select id="selectALL" resultMap="orderMap">
    SELECT o.*, a.username, a.password
    FROM `order` o, auser a
    WHERE o.uid = a.id
</select>

方式二:<association> 嵌套结果映射

将关联对象单独映射,结构更清晰:

<resultMap id="orderMap1" type="order">
    <id column="id" property="id"/>
    <result column="ordertime" property="ordertime"/>
    <result column="total" property="total"/>
    <result column="uid" property="uid"/>
    <!-- association 映射一对一关联 -->
    <association property="aUser" javaType="aUser">
        <id column="uid" property="id"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
    </association>
</resultMap>

<select id="selectAll1" resultMap="orderMap1">
    SELECT o.*, a.username, a.password
    FROM `order` o, auser a
    WHERE o.uid = a.id
</select>

方式三:<association> 嵌套查询(延迟加载 / 分步查询)

将一个 SQL 拆成两步(查 Order → 查 AUser),支持按需加载:

<!-- 第一步:查所有 Order -->
<resultMap id="orderMap2" type="order">
    <id column="id" property="id"/>
    <result column="ordertime" property="ordertime"/>
    <result column="total" property="total"/>
    <result column="uid" property="uid"/>
    <!-- column="uid" 作为参数传给 select 指定的查询 -->
    <!-- select 指定另一个 Mapper 的查询方法(全限定名.方法名) -->
    <association column="uid" property="aUser" javaType="aUser"
                 select="org.example.mapper.AUserMapper.selectById"/>
</resultMap>

<select id="selectAll2" resultMap="orderMap2">
    SELECT * FROM `order`
</select>
<!-- 第二步:被嵌套调用的 AUserMapper.selectById -->
<select id="selectById" resultType="AUser">
    SELECT * FROM auser WHERE id = #{id}
</select>

执行流程:先执行 selectAll2 查询所有 Order → 对每条 Order 结果,用 uid 列的值作为参数,调用 AUserMapper.selectById 查询对应的 AUser → 将结果赋给 Order.aUser。

N+1 问题:如果查询了 N 条 Order,会额外执行 N 次 selectById。在批量查询场景下应优先使用方式二(JOIN 查询)。


一对多关联查询 — <collection>

场景:一个用户(AUser)拥有多个订单(Order),AUser 中持有 List<Order>。

方式一:嵌套结果映射

通过 SQL JOIN 一次性查出所有数据,用 <collection> 将结果组装成一对多结构:

<resultMap id="aUserMap" type="aUser">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="password" property="password"/>
    <!-- collection 映射一对多关联 -->
    <!-- ofType:集合中元素的类型(不是 javaType!) -->
    <collection property="orderList" ofType="order">
        <id column="oid" property="id"/>        <!-- 注意:oid 是别名,与用户 id 区分 -->
        <result column="ordertime" property="ordertime"/>
        <result column="total" property="total"/>
        <result column="id" property="uid"/>
    </collection>
</resultMap>

<select id="selectAll" resultMap="aUserMap">
    SELECT a.*, o.id oid, o.ordertime, o.total
    FROM auser a, `order` o
    WHERE o.uid = a.id
</select>

关键点:ofType 指定的是集合中元素的类型(order),不是 javaType="list"。

方式二:嵌套查询(分步查询)

<resultMap id="aUserMap1" type="aUser">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="password" property="password"/>
    <!-- column="id" 作为参数传给 select 指定的查询 -->
    <collection column="id" property="orderList" ofType="order"
                select="org.example.mapper.OrderMapper.selectByID"/>
</resultMap>

<select id="selectAll1" resultMap="aUserMap1">
    SELECT * FROM auser
</select>
<!-- OrderMapper 中对应的查询 -->
<select id="selectByID" resultType="Order">
    SELECT * FROM `order` WHERE uid = #{uid}
</select>

<association> vs <collection> 对比

<association> <collection>
对应关系 一对一(多方持有一方引用) 一对多(一方持有多方集合)
Java 属性类型 单个对象(如 AUser aUser) 集合类型(如 List<Order> orderList)
类型属性 javaType — 指定对象类型 ofType — 指定集合元素类型
column 传给嵌套查询的参数列 传给嵌套查询的参数列
select 嵌套调用的查询 ID 嵌套调用的查询 ID

MyBatis 各标签/属性速查表

XML 映射文件顶层标签

标签 作用
<mapper> 根标签,namespace 对应 Mapper 接口全限定名
<select> 定义查询语句
<insert> 定义插入语句
<update> 定义更新语句
<delete> 定义删除语句
<sql> 定义可复用的 SQL 片段
<resultMap> 定义结果集映射规则

SQL 语句标签通用属性

属性 作用 示例
id 唯一标识,对应 Mapper 接口的方法名 "selectById"
parameterType 参数类型(全限定名或别名),可省略让 MyBatis 自动推断 "User"、"map"、"int"
resultType 返回值类型(用于简单映射,列名须与属性名一致) "User"、"int"、"org.example.bean.User"
resultMap 引用 <resultMap> 定义的映射规则(与 resultType 互斥) "userMap"
useGeneratedKeys (仅 <insert>)启用 JDBC 自增主键回填 "true" / "false"
keyColumn (仅 <insert>)数据库主键列名 "id"
keyProperty (仅 <insert>)主键对应的 Java 属性名 "id"
flushCache 执行后是否清空缓存 "true" / "false"
statementType Statement 类型 STATEMENT / PREPARED(默认) / CALLABLE
timeout 超时时间(秒) "30"

动态 SQL 标签

标签 作用 关键属性
<if> 条件判断 test:OGNL 表达式,如 "userName != null"
<where> 自动添加 WHERE,去除开头多余的 AND/OR 无
<set> 自动添加 SET,去除尾部多余的逗号 无
<trim> 通用的字符串修剪(可代替 <where>/<set>) prefix、suffix、prefixOverrides、suffixOverrides
<foreach> 遍历集合生成批量 SQL collection、item、open、close、separator
<choose> 多条件分支(类似 Java switch) 包含 <when> 和 <otherwise>
<when> <choose> 中的条件分支 test
<otherwise> <choose> 中的默认分支 无
<bind> 创建变量绑定到上下文 name、value

<trim> 属性

属性 作用 示例
prefix 在内容前添加的前缀 "where"、"set"
suffix 在内容后添加的后缀 ")"
prefixOverrides 去除内容开头的多余字符(`\ ` 分隔多个) `”and\ or”`
suffixOverrides 去除内容末尾的多余字符(`\ ` 分隔多个) ","

<foreach> 属性

属性 含义 默认可用值
collection 要遍历的集合 list(List)、array(数组)、collection(通用)、@Param 别名
item 当前迭代元素的变量名 自定义,如 "u"、"id"
index 当前迭代的索引(可选) 自定义,如 "idx"
open 拼接在整个循环结果的开头 "("、"id IN ("
close 拼接在整个循环结果的结尾 ")"
separator 每次迭代之间的分隔符 ","

<resultMap> 子标签

子标签 用途 关键属性
<id> 主键列映射(底层用于缓存优化) column(列名)、property(属性名)
<result> 普通列映射 column、property
<association> 一对一关联映射 property、javaType、column(传给嵌套查询)、select(嵌套查询 ID)
<collection> 一对多关联映射 property、ofType(元素类型)、column、select

<selectKey> 属性

属性 可选值/说明
order BEFORE(插入前生成主键) / AFTER(插入后获取自增主键)
keyColumn 数据库中的主键列名
keyProperty Java 实体类中的主键属性名
resultType 主键的 Java 类型,如 "integer"、"string"

MyBatis 配置项(application.properties)

配置项 作用
mybatis.configuration.log-impl 指定 SQL 日志输出实现
mybatis.configuration.map-underscore-to-camel-case 自动驼峰命名映射(user_name → userName)
mybatis.type-aliases-package 实体类别名包扫描路径
mybatis.mapper-locations Mapper XML 文件路径
mybatis.configuration.cache-enabled 是否开启二级缓存
mybatis.configuration.lazy-loading-enabled 是否开启延迟加载
mybatis.configuration.aggressive-lazy-loading 是否激进加载(true 则访问任一属性即加载全部延迟属性)

Lombok 注解

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Data                    // 等价于:@Getter + @Setter + @ToString + @EqualsAndHashCode
@NoArgsConstructor       // 生成无参构造方法
@AllArgsConstructor      // 生成全参构造方法
public class User {
    private Integer id;
    private String userName;
    // ...
}
注解 作用
@Data 自动生成 getter/setter、toString()、equals()、hashCode()
@NoArgsConstructor 生成无参构造器(MyBatis 反射创建对象时必须)
@AllArgsConstructor 生成包含所有字段的构造器
@Getter / @Setter 单独生成 getter/setter
@ToString 单独生成 toString()
@Builder 生成建造者模式的构建器

重要:MyBatis 通过反射创建实体对象时需要无参构造器,所以 @NoArgsConstructor 是必须的(如果用 @Data 但未显式声明,Lombok 默认不会生成无参构造——@Data 只含 @RequiredArgsConstructor,不含 @NoArgsConstructor)。


附录:常见问题

Q1:#{} vs ${}

#{} ${}
机制 预编译占位符 ?(PreparedStatement) 字符串直接拼接
SQL 注入 安全,自动转义 不安全,不转义
使用场景 普通参数值 动态表名、列名、ORDER BY 字段(必须自行校验)

Q2:resultType vs resultMap

resultType resultMap
适用场景 列名与属性名一致(或通过别名一致) 列名与属性名不一致、关联查询、嵌套映射
配置复杂度 简单 需要定义 <resultMap>
关联映射 不支持 支持 <association> / <collection>

Q3:Mapper 注入失败的常见原因

  1. 未添加 @MapperScan 注解或包路径写错
  2. XML 的 namespace 与 Mapper 接口全限定名不一致
  3. XML 文件放在 resources 下的路径与接口包路径不一致
上一篇
下一篇