AI 摘要
AI
正在生成摘要...

一、MyBatis环境搭建

1. 引入相关Maven依赖

pom.xml 中引入 MyBatis + 数据库驱动(本文使用MySQL为例):

XML
<dependencies>
    <!-- MyBatis 核心依赖 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.15</version>
    </dependency>

    <!-- MyBatis 与 SpringBoot 整合 -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>

    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>

    <!-- Lombok(简化实体类) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.32</version>
    </dependency>
</dependencies>

2. 数据库连接配置(application.yml)

YAML
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.demo.domain

二、准备测试表(单表 & 多表)

以下建表语句和相关的测试数据由ChatGPT5生成。

SQL
-- 建库
CREATE DATABASE IF NOT EXISTS mybatis_demo DEFAULT CHARSET utf8mb4;
USE mybatis_demo;

-- 用户表
DROP TABLE IF EXISTS user;
CREATE TABLE user (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL,
  email VARCHAR(100),
  age INT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 订单表
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(50) NOT NULL,
  amount DECIMAL(10,2),
  status TINYINT DEFAULT 0,      -- 0:待支付 1:已支付 2:已取消
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES user(id)
);

-- 产品表
DROP TABLE IF EXISTS product;
CREATE TABLE product (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  price DECIMAL(10,2) NOT NULL,
  category VARCHAR(50)
);

-- 订单明细表
DROP TABLE IF EXISTS order_item;
CREATE TABLE order_item (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  order_id BIGINT NOT NULL,
  product_id BIGINT NOT NULL,
  quantity INT NOT NULL,
  item_amount DECIMAL(10,2) GENERATED ALWAYS AS (quantity * 1.0) VIRTUAL,
  FOREIGN KEY (order_id) REFERENCES orders(id),
  FOREIGN KEY (product_id) REFERENCES product(id)
);

-- 测试数据
INSERT INTO user (username,email,age) VALUES
(\'alice\',\'alice@example.com\',23),
(\'bob\',\'bob@example.com\',28),
(\'charlie\',\'charlie@example.com\',31),
(\'dora\',\'dora@example.com\',26);

INSERT INTO product (name,price,category) VALUES
(\'iPhone 15\', 8999.00, \'phone\'),
(\'iPad Air\',  4999.00, \'tablet\'),
(\'ThinkPad X1\', 12999.00, \'laptop\'),
(\'AirPods Pro\', 1999.00, \'audio\'),
(\'Kindle Scribe\', 2999.00, \'reader\');

INSERT INTO orders (user_id, order_no, amount, status) VALUES
(1, \'A202501-0001\',  1999.00, 1),
(1, \'A202501-0002\', 10998.00, 1),
(2, \'B202501-0003\',  4999.00, 0),
(3, \'C202501-0004\', 12999.00, 2),
(3, \'C202501-0005\',  1999.00, 1);

INSERT INTO order_item (order_id, product_id, quantity) VALUES
(1, 4, 1),                    -- alice 买 AirPods Pro
(2, 1, 1), (2, 2, 1),         -- alice 买 iPhone + iPad
(3, 2, 1),                    -- bob 预下 iPad
(4, 3, 1),                    -- charlie 取消 ThinkPad X1
(5, 4, 1);                    -- charlie 买 AirPods

设计以上几个表可以覆盖:

  • 单表查询user
  • 一对多userorders
  • 多对多ordersproduct 通过 order_item

三、使用MyBatis进行CRUD回顾

1. 使用Lombok快速编写实体类

JAVA
// User.java
@Data
public class User {
  private Long id;
  private String username;
  private String email;
  private Integer age;
  private LocalDateTime createdAt;
  private List<Order> orders; // 一对多
}

// Order.java
@Data
public class Order {
  private Long id;
  private Long userId;
  private String orderNo;
  private BigDecimal amount;
  private Integer status;
  private LocalDateTime createdAt;
  private User user;                 // 一对一(下单用户)
  private List<Product> products;    // 多对多(通过明细)
}

// Product.java
@Data
public class Product {
  private Long id;
  private String name;
  private BigDecimal price;
  private String category;
}

// OrderItem.java
@Data
public class OrderItem {
  private Long id;
  private Long orderId;
  private Long productId;
  private Integer quantity;
}

四、Mapper 接口定义

JAVA
public interface UserMapper {
  User selectById(Long id);
  List<User> selectAll();
  int insert(User u);
  int update(User u);
  int delete(Long id);

  // 动态查询
  List<User> query(@Param(\"username\") String username,
                   @Param(\"minAge\") Integer minAge,
                   @Param(\"maxAge\") Integer maxAge,
                   @Param(\"ids\") List<Long> ids);

  // 一对多(结果集映射)
  User selectUserWithOrders(Long id);

  // 分页(简单 LIMIT)
  List<User> page(@Param(\"offset\") int offset, @Param(\"size\") int size);
}

public interface OrderMapper {
  Order selectOrderWithUser(Long id);            // association 嵌套对象
  Order selectOrderWithProducts(Long id);        // collection 多对多
  int insert(Order o);
  int insertUseSelectKey(Order o);
}

public interface ProductMapper {
  List<Product> selectByCategory(@Param(\"category\") String category);
}

五、使用XML标签编写SQL语句

放在 resources/mapper/*.xml。以下示例以 UserMapper.xml / OrderMapper.xml 为主,每个常用标签都有最小可运行示例

0)顶层与命名空间:<mapper namespace=\"...\">

XML
<!-- UserMapper.xml -->
<mapper namespace=\"com.example.demo.mapper.UserMapper\">
  <!-- 具体语句见下 -->
</mapper>
  • namespace 必须与接口全限定名一致,方法名与子标签 id 对应。

1)基础 CRUD:<select>/<insert>/<update>/<delete>

XML
<!-- resultType 可用别名(配置了 type-aliases-package) -->
<select id=\"selectById\" parameterType=\"long\" resultType=\"User\">
  SELECT id, username, email, age, created_at
  FROM user
  WHERE id = #{id}
</select>

<select id=\"selectAll\" resultType=\"User\">
  SELECT id, username, email, age, created_at FROM user
</select>

<insert id=\"insert\" parameterType=\"User\" useGeneratedKeys=\"true\" keyProperty=\"id\">
  INSERT INTO user (username, email, age)
  VALUES (#{username}, #{email}, #{age})
</insert>

<update id=\"update\" parameterType=\"User\">
  UPDATE user
  <set>
    <if test=\"username!=null\">username=#{username},</if>
    <if test=\"email!=null\">email=#{email},</if>
    <if test=\"age!=null\">age=#{age},</if>
  </set>
  WHERE id=#{id}
</update>

<delete id=\"delete\" parameterType=\"long\">
  DELETE FROM user WHERE id=#{id}
</delete>

要点:

  • useGeneratedKeys + keyProperty:MySQL/JDBC 自动回填主键。
  • parameterType/resultType:参数/返回类型(类名或别名)。

2)结果映射:<resultMap><id><result><association><collection>ofTypepropertycolumn

XML
<!-- 一对多:用户 + 订单列表 -->
<resultMap id=\"UserWithOrdersRM\" type=\"User\">
  <id     property=\"id\"        column=\"u_id\"/>
  <result property=\"username\"  column=\"username\"/>
  <result property=\"email\"     column=\"email\"/>
  <result property=\"age\"       column=\"age\"/>
  <collection property=\"orders\" ofType=\"Order\">
    <id     property=\"id\"       column=\"o_id\"/>
    <result property=\"orderNo\"  column=\"order_no\"/>
    <result property=\"amount\"   column=\"amount\"/>
    <result property=\"status\"   column=\"status\"/>
    <result property=\"createdAt\" column=\"o_created_at\"/>
  </collection>
</resultMap>

<select id=\"selectUserWithOrders\" resultMap=\"UserWithOrdersRM\">
  SELECT
    u.id u_id, u.username, u.email, u.age,
    o.id o_id, o.order_no, o.amount, o.status, o.created_at o_created_at
  FROM user u
  LEFT JOIN orders o ON u.id = o.user_id
  WHERE u.id = #{id}
</select>

要点:

  • property:Java 字段名;column:SQL 列或别名。
  • ofType:集合元素类型(常用于 <collection>)。

3)嵌套查询(分步查询)与懒加载:association select / collection selectfetchType

XML
<!-- OrderMapper.xml -->
<resultMap id=\"OrderWithUserRM\" type=\"Order\">
  <id     property=\"id\"      column=\"id\"/>
  <result property=\"orderNo\" column=\"order_no\"/>
  <result property=\"amount\"  column=\"amount\"/>
  <association property=\"user\" javaType=\"User\"
               column=\"user_id\" select=\"com.example.demo.mapper.UserMapper.selectById\"
               fetchType=\"lazy\"/>
</resultMap>

<select id=\"selectOrderWithUser\" resultMap=\"OrderWithUserRM\">
  SELECT id, user_id, order_no, amount FROM orders WHERE id=#{id}
</select>

要点:

  • select=\"namespace.method\":把某列(通过 column 指定)作为参数传给另一个 select
  • 配合全局 lazy-loading-enabled: true 可懒加载,避免一次性拉大对象图。

4)多对多(订单 → 产品列表):<collection> + 连接查询

XML
<!-- OrderMapper.xml -->
<resultMap id=\"OrderWithProductsRM\" type=\"Order\">
  <id     property=\"id\"       column=\"o_id\"/>
  <result property=\"orderNo\"  column=\"order_no\"/>
  <result property=\"amount\"   column=\"amount\"/>
  <collection property=\"products\" ofType=\"Product\">
    <id     property=\"id\"     column=\"p_id\"/>
    <result property=\"name\"   column=\"p_name\"/>
    <result property=\"price\"  column=\"price\"/>
    <result property=\"category\" column=\"category\"/>
  </collection>
</resultMap>

<select id=\"selectOrderWithProducts\" resultMap=\"OrderWithProductsRM\">
  SELECT
    o.id o_id, o.order_no, o.amount,
    p.id p_id, p.name p_name, p.price, p.category
  FROM orders o
  LEFT JOIN order_item oi ON o.id=oi.order_id
  LEFT JOIN product p ON oi.product_id=p.id
  WHERE o.id=#{id}
</select>

5)SQL 片段复用:<sql> + <include>

XML
<sql id=\"UserColumns\">
  id, username, email, age, created_at
</sql>

<select id=\"page\" resultType=\"User\">
  SELECT <include refid=\"UserColumns\"/>
  FROM user
  ORDER BY id DESC
  LIMIT #{offset}, #{size}
</select>

6)动态 SQL(开发中使用较多)

6.1 <if> + <where>:自动处理 AND/OR

XML
<select id=\"query\" resultType=\"User\">
  SELECT id, username, email, age, created_at
  FROM user
  <where>
    <if test=\"username != null and username != \'\'\">
      AND username LIKE CONCAT(\'%\', #{username}, \'%\')
    </if>
    <if test=\"minAge != null\"> AND age &gt;= #{minAge}</if>
    <if test=\"maxAge != null\"> AND age &lt;= #{maxAge}</if>
    <if test=\"ids != null and ids.size() > 0\">
      AND id IN
      <foreach collection=\"ids\" item=\"id\" open=\"(\" separator=\",\" close=\")\">
        #{id}
      </foreach>
    </if>
  </where>
</select>

6.2 <choose>/<when>/<otherwise>:分支

XML
<select id=\"chooseExample\" resultType=\"User\">
  SELECT id, username, email, age, created_at
  FROM user
  <where>
    <choose>
      <when test=\"username != null\"> username = #{username} </when>
      <when test=\"minAge != null\"> age &gt;= #{minAge} </when>
      <otherwise> age IS NOT NULL </otherwise>
    </choose>
  </where>
</select>

6.3 <foreach>:遍历 List / Map

XML
<!-- List 参数:ids -->
<select id=\"selectByIds\" resultType=\"User\">
  SELECT id, username, email, age, created_at
  FROM user
  WHERE id IN
  <foreach collection=\"ids\" item=\"id\" open=\"(\" separator=\",\" close=\")\">
    #{id}
  </foreach>
</select>

<!-- Map 参数:map.put(\"k1\",\"v1\") -->
<select id=\"foreachMap\" resultType=\"map\">
  SELECT #{map[\'k1\']} AS k1_value
</select>

6.4 <set>:动态 UPDATE 字段

XML
<update id=\"dynamicUpdate\" parameterType=\"User\">
  UPDATE user
  <set>
    <if test=\"username!=null\"> username=#{username},</if>
    <if test=\"email!=null\"> email=#{email},</if>
    <if test=\"age!=null\"> age=#{age},</if>
  </set>
  WHERE id=#{id}
</update>

6.5 <trim>:灵活控制前后缀

XML
<update id=\"updateWithTrim\" parameterType=\"User\">
  UPDATE user
  <trim prefix=\"SET\" suffixOverrides=\",\">
    <if test=\"username!=null\"> username=#{username},</if>
    <if test=\"email!=null\"> email=#{email},</if>
    <if test=\"age!=null\"> age=#{age},</if>
  </trim>
  WHERE id=#{id}
</update>

6.6 <bind>:绑定变量(常用于 LIKE)

XML
<select id=\"likeQuery\" resultType=\"User\">
  <bind name=\"kw\" value=\"\'%\' + _parameter + \'%\'\" />
  SELECT id, username, email, age, created_at
  FROM user
  WHERE username LIKE #{kw}
</select>

传入参数是一个字符串时,_parameter 指它本身。

7)主键策略:useGeneratedKeys<selectKey>

XML
<!-- MySQL 推荐:useGeneratedKeys -->
<insert id=\"insert\" parameterType=\"User\" useGeneratedKeys=\"true\" keyProperty=\"id\">
  INSERT INTO user (username,email,age) VALUES (#{username},#{email},#{age})
</insert>

<!-- 兼容序列的写法(例如 Oracle / PG 有 sequence) -->
<insert id=\"insertUseSelectKey\" parameterType=\"Order\">
  <selectKey keyProperty=\"id\" resultType=\"long\" order=\"BEFORE\">
    SELECT nextval(\'order_seq\')
  </selectKey>
  INSERT INTO orders (id, user_id, order_no, amount, status)
  VALUES (#{id}, #{userId}, #{orderNo}, #{amount}, #{status})
</insert>

8)构造器映射 & 鉴别器:<constructor><discriminator>

XML
<!-- 用构造器给不可变对象赋值 -->
<resultMap id=\"UserCtorRM\" type=\"User\">
  <constructor>
    <idArg    column=\"id\"        javaType=\"long\"/>
    <arg      column=\"username\"  javaType=\"string\"/>
    <arg      column=\"email\"     javaType=\"string\"/>
    <arg      column=\"age\"       javaType=\"int\"/>
  </constructor>
</resultMap>

<!-- discriminator:根据列值映射不同结构(示例性) -->
<resultMap id=\"OrderWithStatusRM\" type=\"Order\">
  <id property=\"id\" column=\"id\"/>
  <result property=\"orderNo\" column=\"order_no\"/>
  <result property=\"status\"  column=\"status\"/>
  <discriminator javaType=\"int\" column=\"status\">
    <case value=\"0\">
      <result property=\"amount\" column=\"amount\"/>
    </case>
    <case value=\"1\">
      <result property=\"amount\" column=\"amount\"/>
    </case>
    <case value=\"2\">
      <result property=\"amount\" column=\"amount\"/>
    </case>
  </discriminator>
</resultMap>

<select id=\"selectOrderDiscriminator\" resultMap=\"OrderWithStatusRM\">
  SELECT id, order_no, amount, status FROM orders WHERE id=#{id}
</select>

9)存储过程 / 多结果集:statementType=\"CALLABLE\"resultSets(知道即可)

XML
<select id=\"callProc\" statementType=\"CALLABLE\">
  { call proc_recalc_order_amount(#{orderId, mode=IN}) }
</select>

多结果集时可用 resultSets=\"rs1,rs2\" 配合多个 resultMap(不同库支持度差异较大)。

10)缓存:<cache><cache-ref>(二级缓存)

XML
<!-- 当前 mapper 开启二级缓存 -->
<cache eviction=\"LRU\" flushInterval=\"60000\" size=\"512\" readOnly=\"false\"/>

<!-- 引用别的命名空间的缓存 -->
<!-- <cache-ref namespace=\"com.example.demo.mapper.OtherMapper\"/> -->

要点:

  • 二级缓存是跨 SqlSession 的;更新会清理对应命名空间的缓存。
  • 生产环境慎用,注意与数据一致性、事务提交时机的关系。

11)数据库差异:databaseId(同一语句不同方言)

XML
<select id=\"selectNow\" resultType=\"string\" databaseId=\"mysql\">
  SELECT DATE_FORMAT(NOW(), \'%Y-%m-%d %H:%i:%s\')
</select>

<select id=\"selectNow\" resultType=\"string\" databaseId=\"postgresql\">
  SELECT TO_CHAR(NOW(), \'YYYY-MM-DD HH24:MI:SS\')
</select>

需在全局配置里开启 databaseIdProvider(Spring Boot 多用 starter 自动识别)。

12)resultType 的多种返回:POJO / Map / 基本类型

XML
<!-- 返回 Map -->
<select id=\"countByStatus\" resultType=\"map\">
  SELECT status, COUNT(*) cnt FROM orders GROUP BY status
</select>

<!-- 返回基本类型 -->
<select id=\"countUser\" resultType=\"long\">
  SELECT COUNT(*) FROM user
</select>

六、综合实战片段(把常用点串一遍)

XML
<!-- UserMapper.xml -->
<mapper namespace=\"com.example.demo.mapper.UserMapper\">

  <cache eviction=\"LRU\" size=\"256\"/>

  <sql id=\"BaseCols\">id, username, email, age, created_at</sql>

  <select id=\"selectById\" parameterType=\"long\" resultType=\"User\">
    SELECT <include refid=\"BaseCols\"/> FROM user WHERE id=#{id}
  </select>

  <select id=\"selectAll\" resultType=\"User\">
    SELECT <include refid=\"BaseCols\"/> FROM user ORDER BY id
  </select>

  <insert id=\"insert\" parameterType=\"User\" useGeneratedKeys=\"true\" keyProperty=\"id\">
    INSERT INTO user (username,email,age) VALUES (#{username},#{email},#{age})
  </insert>

  <update id=\"dynamicUpdate\" parameterType=\"User\">
    UPDATE user
    <trim prefix=\"SET\" suffixOverrides=\",\">
      <if test=\"username!=null\">username=#{username},</if>
      <if test=\"email!=null\">email=#{email},</if>
      <if test=\"age!=null\">age=#{age},</if>
    </trim>
    WHERE id=#{id}
  </update>

  <delete id=\"delete\" parameterType=\"long\">
    DELETE FROM user WHERE id=#{id}
  </delete>

  <select id=\"query\" resultType=\"User\">
    SELECT <include refid=\"BaseCols\"/> FROM user
    <where>
      <if test=\"username!=null and username!=\'\'\">
        AND username LIKE CONCAT(\'%\', #{username}, \'%\')
      </if>
      <if test=\"minAge!=null\"> AND age &gt;= #{minAge}</if>
      <if test=\"maxAge!=null\"> AND age &lt;= #{maxAge}</if>
      <if test=\"ids!=null and ids.size()>0\">
        AND id IN
        <foreach collection=\"ids\" item=\"id\" open=\"(\" separator=\",\" close=\")\">
          #{id}
        </foreach>
      </if>
    </where>
  </select>

  <!-- 一对多 -->
  <resultMap id=\"UserWithOrdersRM\" type=\"User\">
    <id     property=\"id\"        column=\"u_id\"/>
    <result property=\"username\"  column=\"username\"/>
    <result property=\"email\"     column=\"email\"/>
    <result property=\"age\"       column=\"age\"/>
    <collection property=\"orders\" ofType=\"Order\">
      <id     property=\"id\"       column=\"o_id\"/>
      <result property=\"orderNo\"  column=\"order_no\"/>
      <result property=\"amount\"   column=\"amount\"/>
      <result property=\"status\"   column=\"status\"/>
      <result property=\"createdAt\" column=\"o_created_at\"/>
    </collection>
  </resultMap>

  <select id=\"selectUserWithOrders\" resultMap=\"UserWithOrdersRM\">
    SELECT
      u.id u_id, u.username, u.email, u.age,
      o.id o_id, o.order_no, o.amount, o.status, o.created_at o_created_at
    FROM user u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.id = #{id}
  </select>

  <!-- 分页 -->
  <select id=\"page\" resultType=\"User\">
    SELECT <include refid=\"BaseCols\"/> FROM user
    ORDER BY id DESC
    LIMIT #{offset}, #{size}
  </select>

</mapper>
XML
<!-- OrderMapper.xml -->
<mapper namespace=\"com.example.demo.mapper.OrderMapper\">

  <!-- association:订单带用户(分步 + 懒加载) -->
  <resultMap id=\"OrderWithUserRM\" type=\"Order\">
    <id     property=\"id\"      column=\"id\"/>
    <result property=\"orderNo\" column=\"order_no\"/>
    <result property=\"amount\"  column=\"amount\"/>
    <association property=\"user\" javaType=\"User\"
                 column=\"user_id\" select=\"com.example.demo.mapper.UserMapper.selectById\"
                 fetchType=\"lazy\"/>
  </resultMap>

  <select id=\"selectOrderWithUser\" resultMap=\"OrderWithUserRM\">
    SELECT id, user_id, order_no, amount FROM orders WHERE id=#{id}
  </select>

  <!-- 多对多:订单带产品列表 -->
  <resultMap id=\"OrderWithProductsRM\" type=\"Order\">
    <id     property=\"id\"       column=\"o_id\"/>
    <result property=\"orderNo\"  column=\"order_no\"/>
    <result property=\"amount\"   column=\"amount\"/>
    <collection property=\"products\" ofType=\"Product\">
      <id     property=\"id\"     column=\"p_id\"/>
      <result property=\"name\"   column=\"p_name\"/>
      <result property=\"price\"  column=\"price\"/>
      <result property=\"category\" column=\"category\"/>
    </collection>
  </resultMap>

  <select id=\"selectOrderWithProducts\" resultMap=\"OrderWithProductsRM\">
    SELECT
      o.id o_id, o.order_no, o.amount,
      p.id p_id, p.name p_name, p.price, p.category
    FROM orders o
    LEFT JOIN order_item oi ON o.id=oi.order_id
    LEFT JOIN product p ON oi.product_id=p.id
    WHERE o.id=#{id}
  </select>

  <!-- insert:主键回填 -->
  <insert id=\"insert\" parameterType=\"Order\" useGeneratedKeys=\"true\" keyProperty=\"id\">
    INSERT INTO orders (user_id, order_no, amount, status)
    VALUES (#{userId}, #{orderNo}, #{amount}, #{status})
  </insert>

  <!-- insert:selectKey 示例(非 MySQL 必要) -->
  <insert id=\"insertUseSelectKey\" parameterType=\"Order\">
    <selectKey keyProperty=\"id\" resultType=\"long\" order=\"BEFORE\">
      SELECT nextval(\'order_seq\')
    </selectKey>
    INSERT INTO orders (id, user_id, order_no, amount, status)
    VALUES (#{id}, #{userId}, #{orderNo}, #{amount}, #{status})
  </insert>

</mapper>

七、事务实战(Service 层)

JAVA
@Service
@RequiredArgsConstructor
public class OrderService {
  private final OrderMapper orderMapper;
  private final SqlSessionFactory sqlSessionFactory;

  @Transactional
  public Long createOrder(Order order, List<OrderItem> items) {
    orderMapper.insert(order); // 回填 order.id
    try (SqlSession session = sqlSessionFactory.openSession()) {
      for (OrderItem it : items) {
        session.insert(\"com.example.demo.mapper.OrderItemMapper.insert\", it);
      }
      // 模拟异常可测试回滚
      // int x = 1/0;
    }
    return order.getId();
  }
}

八、常见的坑

  • 命名参数:多参数场景务必用 @Param 指定名称,与 XML 中占位符一致。
  • 驼峰映射:开启 map-underscore-to-camel-case 或 SQL 中加别名。
  • N+1:能用联表就联表,分步查询配合懒加载谨慎使用。
  • 分页:MySQL 直接 LIMIT;生产建议用分页插件(PageHelper / MyBatis-Plus)。
  • 缓存:二级缓存注意一致性与事务提交时机。
  • 动态 SQL:优先 <where>/<set>/<trim>,避免字符串拼接。
  • 返回 MapresultType=\"map\" 可快速做统计类查询。
  • 数据库差异databaseId + 多语句兼容不同方言。

评论