<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      MyBatis 完整教程

      MyBatis 完整教程

      目錄

      1. MyBatis 簡(jiǎn)介
      2. 環(huán)境搭建
      3. 核心概念
      4. 注解方式開發(fā)
      5. XML方式開發(fā)
      6. 動(dòng)態(tài)SQL
      7. 結(jié)果映射
      8. 參數(shù)傳遞
      9. 完整實(shí)戰(zhàn)案例
      10. 最佳實(shí)踐

      1. MyBatis 簡(jiǎn)介

      MyBatis 是一款優(yōu)秀的持久層框架,它支持:

      • 自定義 SQL
      • 存儲(chǔ)過程
      • 高級(jí)映射

      優(yōu)勢(shì):

      • 避免了幾乎所有的 JDBC 代碼
      • 手動(dòng)設(shè)置參數(shù)和獲取結(jié)果集的工作
      • 靈活的 SQL 編寫
      • 注解和 XML 兩種配置方式

      2. 環(huán)境搭建

      2.1 Maven 依賴

      <dependencies>
          <!-- MyBatis Spring Boot Starter -->
          <dependency>
              <groupId>org.mybatis.spring.boot</groupId>
              <artifactId>mybatis-spring-boot-starter</artifactId>
              <version>3.0.3</version>
          </dependency>
      
          <!-- MySQL 驅(qū)動(dòng) -->
          <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>8.0.29</version>
          </dependency>
      
          <!-- Lombok (可選,但推薦) -->
          <dependency>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
              <optional>true</optional>
          </dependency>
      </dependencies>
      

      2.2 配置文件 (application.yml)

      spring:
        datasource:
          url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8
          username: root
          password: your_password
          driver-class-name: com.mysql.cj.jdbc.Driver
      
      # MyBatis 配置
      mybatis:
        # XML 文件位置 (classpath: = src/main/resources/)
        # 實(shí)際對(duì)應(yīng): src/main/resources/mapper/*.xml
        mapper-locations: classpath:mapper/*.xml
        
        # 實(shí)體類包路徑 (配置后XML中可直接用類名,無需寫完整包名)
        # 例如: resultType="User" 而不是 resultType="com.example.demo.domain.User"
        type-aliases-package: com.example.demo.domain
        configuration:
          # 駝峰命名自動(dòng)轉(zhuǎn)換:數(shù)據(jù)庫(kù)下劃線命名 ? Java駝峰命名
          # 數(shù)據(jù)庫(kù)字段: user_name, created_at, order_no
          # Java屬性:   userName,  createdAt,  orderNo
          # 開啟后無需手動(dòng)映射,MyBatis自動(dòng)完成轉(zhuǎn)換
          map-underscore-to-camel-case: true
          
          # SQL 日志實(shí)現(xiàn)(開發(fā)時(shí)建議開啟,生產(chǎn)環(huán)境建議關(guān)閉)
          # STDOUT_LOGGING - 標(biāo)準(zhǔn)輸出到控制臺(tái)
          # SLF4J - 使用 SLF4J 日志框架(推薦)
          # LOG4J2 - 使用 Log4j2
          # NO_LOGGING - 不輸出日志
          log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
          
          # 其他常用配置:
          # cache-enabled: true              # 開啟二級(jí)緩存(默認(rèn)true)
          # lazy-loading-enabled: false      # 延遲加載(默認(rèn)false)
          # default-executor-type: SIMPLE    # 執(zhí)行器類型:SIMPLE/REUSE/BATCH
          # default-statement-timeout: 25    # 超時(shí)時(shí)間(秒)
          # jdbc-type-for-null: NULL         # 空值對(duì)應(yīng)的JDBC類型
      

      2.3 主啟動(dòng)類

      package com.example.demo;
      
      import org.mybatis.spring.annotation.MapperScan;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      
      @SpringBootApplication
      @MapperScan("com.example.demo.mapper") // 掃描 Mapper 接口
      public class DemoApplication {
          public static void main(String[] args) {
              SpringApplication.run(DemoApplication.class, args);
          }
      }
      

      3. 核心概念

      3.1 三層架構(gòu)

      Controller (控制層)
          ↓
      Service (業(yè)務(wù)層)
          ↓
      Mapper/DAO (持久層) ← MyBatis 在這里工作
          ↓
      Database (數(shù)據(jù)庫(kù))
      

      3.2 核心組件

      組件 說明
      Domain/Entity 實(shí)體類,對(duì)應(yīng)數(shù)據(jù)庫(kù)表
      Mapper接口 定義數(shù)據(jù)庫(kù)操作方法
      Mapper.xml SQL 語句配置文件
      SqlSession 執(zhí)行SQL的會(huì)話對(duì)象(Spring Boot自動(dòng)管理)

      4. 注解方式開發(fā)

      4.1 創(chuàng)建數(shù)據(jù)庫(kù)表

      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,
          updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
      );
      
      INSERT INTO user (username, email, age) VALUES 
      ('張三', 'zhangsan@example.com', 25),
      ('李四', 'lisi@example.com', 30),
      ('王五', 'wangwu@example.com', 28);
      

      4.2 創(chuàng)建實(shí)體類

      package com.example.demo.domain;
      
      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 createdAt;
          private LocalDateTime updatedAt;
      }
      

      4.3 創(chuàng)建 Mapper 接口(注解方式)

      package com.example.demo.mapper;
      
      import com.example.demo.domain.User;
      import org.apache.ibatis.annotations.*;
      import java.util.List;
      
      @Mapper
      public interface UserMapper {
          
          // ========== 查詢操作 ==========
          
          /**
           * 查詢所有用戶
           */
          @Select("SELECT * FROM user")
          List<User> findAll();
          
          /**
           * 根據(jù)ID查詢用戶
           * @Param("id") - 指定參數(shù)在SQL中的名稱,單個(gè)參數(shù)可省略,多個(gè)參數(shù)必須加
           */
          @Select("SELECT * FROM user WHERE id = #{id}")
          User findById(@Param("id") Long id);
          
          /**
           * 根據(jù)用戶名查詢
           */
          @Select("SELECT * FROM user WHERE username = #{username}")
          User findByUsername(@Param("username") String username);
          
          /**
           * 條件查詢(多參數(shù))
           */
          @Select("SELECT * FROM user WHERE age >= #{minAge} AND age <= #{maxAge}")
          List<User> findByAgeRange(@Param("minAge") Integer minAge, 
                                     @Param("maxAge") Integer maxAge);
          
          /**
           * 模糊查詢
           */
          @Select("SELECT * FROM user WHERE username LIKE CONCAT('%', #{keyword}, '%')")
          List<User> searchByUsername(@Param("keyword") String keyword);
          
          /**
           * 統(tǒng)計(jì)數(shù)量
           */
          @Select("SELECT COUNT(*) FROM user")
          int count();
          
          /**
           * 分頁(yè)查詢
           */
          @Select("SELECT * FROM user LIMIT #{limit} OFFSET #{offset}")
          List<User> findByPage(@Param("offset") int offset, 
                                @Param("limit") int limit);
          
          // ========== 插入操作 ==========
          
          /**
           * 插入用戶(返回受影響行數(shù))
           */
          @Insert("INSERT INTO user (username, email, age) VALUES (#{username}, #{email}, #{age})")
          int insert(User user);
          
          /**
           * 插入用戶(返回自增主鍵)
           * @Options 配置項(xiàng)說明:
           * - useGeneratedKeys = true: 使用數(shù)據(jù)庫(kù)自增主鍵
           * - keyProperty = "id": 將自增的主鍵值回填到 user 對(duì)象的 id 屬性
           * 插入后,user.getId() 就能獲取到數(shù)據(jù)庫(kù)自動(dòng)生成的ID
           * 
           * 參數(shù)映射規(guī)則:#{屬性名} 會(huì)調(diào)用對(duì)象的 getter 方法
           * #{username} → user.getUsername()
           * #{email}    → user.getEmail()
           * #{age}      → user.getAge()
           */
          @Insert("INSERT INTO user (username, email, age) VALUES (#{username}, #{email}, #{age})")
          @Options(useGeneratedKeys = true, keyProperty = "id")
          int insertAndReturnId(User user);
          
          // ========== 更新操作 ==========
          
          /**
           * 更新用戶信息
           */
          @Update("UPDATE user SET username = #{username}, email = #{email}, age = #{age} WHERE id = #{id}")
          int update(User user);
          
          /**
           * 更新部分字段
           */
          @Update("UPDATE user SET email = #{email} WHERE id = #{id}")
          int updateEmail(@Param("id") Long id, @Param("email") String email);
          
          // ========== 刪除操作 ==========
          
          /**
           * 根據(jù)ID刪除
           */
          @Delete("DELETE FROM user WHERE id = #{id}")
          int deleteById(@Param("id") Long id);
          
          /**
           * 根據(jù)條件刪除
           */
          @Delete("DELETE FROM user WHERE age < #{age}")
          int deleteByAge(@Param("age") Integer age);
      }
      

      ?? 返回類型說明

      MyBatis 如何確定返回類型?

      關(guān)鍵:通過方法簽名的返回類型聲明,而不是 SQL 語句!

      MyBatis 會(huì)根據(jù)你在 Mapper 接口方法中聲明的返回類型,自動(dòng)處理查詢結(jié)果的映射。

      常見返回類型對(duì)比:

      返回類型 說明 使用場(chǎng)景 示例方法
      User 返回單個(gè)對(duì)象 按唯一鍵查詢(ID、username等),預(yù)期0或1條結(jié)果 findById, findByUsername
      List<User> 返回集合 條件查詢,可能返回0條、1條或多條結(jié)果 findAll, findByAgeRange
      int/long 返回?cái)?shù)值 統(tǒng)計(jì)查詢、增刪改操作返回受影響行數(shù) count(), insert(), update()
      Map<K,V> 返回鍵值對(duì) 返回單行的動(dòng)態(tài)列 findUserAsMap()
      List<Map> 返回Map集合 返回多行的動(dòng)態(tài)列 findAllAsMap()

      重要說明:

      // ? 返回 List<User> - 返回所有符合條件的記錄
      @Select("SELECT * FROM user WHERE age >= #{minAge} AND age <= #{maxAge}")
      List<User> findByAgeRange(@Param("minAge") Integer minAge, 
                                 @Param("maxAge") Integer maxAge);
      
      // ?? 返回 User - 只返回第一條記錄,其他記錄被忽略
      @Select("SELECT * FROM user WHERE age >= #{minAge} AND age <= #{maxAge}")
      User findByAgeRange(@Param("minAge") Integer minAge, 
                          @Param("maxAge") Integer maxAge);
      

      區(qū)別:

      • 返回 User:如果 SQL 查詢匹配了 10 條記錄,MyBatis 只會(huì)返回第一條,其他 9 條被丟棄;如果沒有結(jié)果則返回 null
      • 返回 List<User>:會(huì)返回所有匹配的記錄(空列表、1條或多條)

      選擇原則:

      1. 確定只有一條結(jié)果 → 用 User

        // ID是主鍵,結(jié)果唯一
        @Select("SELECT * FROM user WHERE id = #{id}")
        User findById(@Param("id") Long id);
        
      2. 可能有多條結(jié)果 → 用 List<User>

        // 年齡范圍查詢,可能匹配多個(gè)用戶
        @Select("SELECT * FROM user WHERE age >= #{minAge} AND age <= #{maxAge}")
        List<User> findByAgeRange(@Param("minAge") Integer minAge, 
                                   @Param("maxAge") Integer maxAge);
        
      3. 增刪改操作 → 用 int/long(返回受影響行數(shù))

        @Insert("INSERT INTO user (username, email, age) VALUES (#{username}, #{email}, #{age})")
        int insert(User user);  // 返回插入的行數(shù)(通常是1)
        
        @Update("UPDATE user SET email = #{email} WHERE id = #{id}")
        int updateEmail(@Param("id") Long id, @Param("email") String email);
        
        @Delete("DELETE FROM user WHERE age < #{age}")
        int deleteByAge(@Param("age") Integer age);  // 返回刪除的行數(shù)
        
      4. 統(tǒng)計(jì)查詢 → 用 int/long

        @Select("SELECT COUNT(*) FROM user")
        int count();
        

      實(shí)際案例:

      // ? 錯(cuò)誤示例:業(yè)務(wù)需要查詢所有符合條件的用戶,卻用了 User
      @Select("SELECT * FROM user WHERE status = 'active'")
      User findActiveUsers();  // 只會(huì)返回第1個(gè)活躍用戶,其他的丟失了!
      
      // ? 正確示例:使用 List<User>
      @Select("SELECT * FROM user WHERE status = 'active'")
      List<User> findActiveUsers();  // 返回所有活躍用戶
      

      總結(jié):

      • 返回類型是開發(fā)者根據(jù)業(yè)務(wù)需求查詢特點(diǎn)主動(dòng)聲明的
      • MyBatis 不會(huì)自動(dòng)判斷結(jié)果數(shù)量來改變返回類型
      • 選擇錯(cuò)誤的返回類型可能導(dǎo)致數(shù)據(jù)丟失或空指針異常

      4.4 Service 層

      package com.example.demo.service;
      
      import com.example.demo.domain.User;
      import com.example.demo.mapper.UserMapper;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;
      import java.util.List;
      
      @Service
      public class UserService {
          
          @Autowired
          private UserMapper userMapper;
          
          public List<User> getAllUsers() {
              return userMapper.findAll();
          }
          
          public User getUserById(Long id) {
              return userMapper.findById(id);
          }
          
          /**
           * @Transactional 事務(wù)管理注解
           * 作用:保證方法內(nèi)的所有數(shù)據(jù)庫(kù)操作要么全部成功,要么全部回滾
           * - 方法執(zhí)行成功 → 自動(dòng)提交事務(wù)(commit)
           * - 方法拋出異常 → 自動(dòng)回滾事務(wù)(rollback)
           */
          @Transactional
          public User createUser(User user) {
              userMapper.insertAndReturnId(user);
              return user; // id 已經(jīng)被自動(dòng)填充
          }
          
          @Transactional
          public boolean updateUser(User user) {
              return userMapper.update(user) > 0;
          }
          
          @Transactional
          public boolean deleteUser(Long id) {
              return userMapper.deleteById(id) > 0;
          }
      }
      

      4.5 Controller 層

      package com.example.demo.controller;
      
      import com.example.demo.domain.User;
      import com.example.demo.service.UserService;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.*;
      import java.util.List;
      
      @RestController
      @RequestMapping("/api/users")
      public class UserController {
          
          @Autowired
          private UserService userService;
          
          @GetMapping
          public List<User> getAllUsers() {
              return userService.getAllUsers();
          }
          
          @GetMapping("/{id}")
          public User getUserById(@PathVariable Long id) {
              return userService.getUserById(id);
          }
          
          @PostMapping
          public User createUser(@RequestBody User user) {
              return userService.createUser(user);
          }
          
          @PutMapping("/{id}")
          public boolean updateUser(@PathVariable Long id, @RequestBody User user) {
              user.setId(id);
              return userService.updateUser(user);
          }
          
          @DeleteMapping("/{id}")
          public boolean deleteUser(@PathVariable Long id) {
              return userService.deleteUser(id);
          }
      }
      

      5. XML方式開發(fā)

      5.1 數(shù)據(jù)庫(kù)表(訂單示例)

      CREATE TABLE `order` (
          id BIGINT PRIMARY KEY AUTO_INCREMENT,
          order_no VARCHAR(50) NOT NULL UNIQUE,
          user_id BIGINT NOT NULL,
          total_amount DECIMAL(10, 2),
          status VARCHAR(20),
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
          updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
      );
      
      CREATE TABLE order_item (
          id BIGINT PRIMARY KEY AUTO_INCREMENT,
          order_id BIGINT NOT NULL,
          product_name VARCHAR(100),
          quantity INT,
          price DECIMAL(10, 2),
          FOREIGN KEY (order_id) REFERENCES `order`(id)
      );
      

      5.2 實(shí)體類

      package com.example.demo.domain;
      
      import lombok.Data;
      import java.math.BigDecimal;
      import java.time.LocalDateTime;
      import java.util.List;
      
      @Data
      public class Order {
          private Long id;
          private String orderNo;
          private Long userId;
          private BigDecimal totalAmount;
          private String status;
          private LocalDateTime createdAt;
          private LocalDateTime updatedAt;
          
          // 一對(duì)多關(guān)聯(lián)
          private List<OrderItem> items;
      }
      
      @Data
      public class OrderItem {
          private Long id;
          private Long orderId;
          private String productName;
          private Integer quantity;
          private BigDecimal price;
      }
      

      5.3 Mapper 接口

      package com.example.demo.mapper;
      
      import com.example.demo.domain.Order;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Param;
      import java.util.List;
      
      @Mapper
      public interface OrderMapper {
          
          // 所有方法都在 XML 中實(shí)現(xiàn)
          Order findById(@Param("id") Long id);
          
          List<Order> findByUserId(@Param("userId") Long userId);
          
          List<Order> findByCondition(@Param("status") String status, 
                                      @Param("minAmount") BigDecimal minAmount);
          
          int insert(Order order);
          
          int update(Order order);
          
          int deleteById(@Param("id") Long id);
          
          // 關(guān)聯(lián)查詢:查詢訂單及其所有訂單項(xiàng)
          Order findByIdWithItems(@Param("id") Long id);
      }
      

      5.4 Mapper XML 配置

      創(chuàng)建文件:src/main/resources/mapper/OrderMapper.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.demo.mapper.OrderMapper">
      
          <!-- ========== ResultMap 結(jié)果映射 ========== -->
          
          <!-- 基礎(chǔ)結(jié)果映射 -->
          <resultMap id="BaseResultMap" type="com.example.demo.domain.Order">
              <id property="id" column="id"/>
              <result property="orderNo" column="order_no"/>
              <result property="userId" column="user_id"/>
              <result property="totalAmount" column="total_amount"/>
              <result property="status" column="status"/>
              <result property="createdAt" column="created_at"/>
              <result property="updatedAt" column="updated_at"/>
          </resultMap>
          
          <!-- 帶關(guān)聯(lián)的結(jié)果映射 -->
          <resultMap id="OrderWithItemsMap" type="com.example.demo.domain.Order" extends="BaseResultMap">
              <!-- collection: 一對(duì)多關(guān)聯(lián) -->
              <collection property="items" ofType="com.example.demo.domain.OrderItem">
                  <id property="id" column="item_id"/>
                  <result property="orderId" column="order_id"/>
                  <result property="productName" column="product_name"/>
                  <result property="quantity" column="quantity"/>
                  <result property="price" column="price"/>
              </collection>
          </resultMap>
          
          <!-- ========== 字段映射說明 ========== -->
          <!--
          ?? 如果 ResultMap 中有些字段沒有映射會(huì)怎么樣?
          
          **規(guī)則:**
          1. ? 已映射的字段:按照 ResultMap 配置進(jìn)行映射
          2. ? 未映射的字段:該屬性值為 null(不會(huì)自動(dòng)映射,即使字段名一致)
          
          **重要特性:一旦使用 ResultMap,自動(dòng)映射將失效!**
          
          示例:假設(shè) Order 類有 7 個(gè)屬性
          public class Order {
              private Long id;
              private String orderNo;
              private Long userId;
              private BigDecimal totalAmount;
              private String status;
              private LocalDateTime createdAt;
              private LocalDateTime updatedAt;  // ← 假設(shè)這個(gè)字段沒有在 ResultMap 中映射
          }
          
          如果 ResultMap 中沒有映射 updatedAt:
          <resultMap id="IncompleteMap" type="Order">
              <id property="id" column="id"/>
              <result property="orderNo" column="order_no"/>
              <result property="userId" column="user_id"/>
              <result property="totalAmount" column="total_amount"/>
              <result property="status" column="status"/>
              <result property="createdAt" column="created_at"/>
              <!-- updatedAt 沒有映射! -->
          </resultMap>
          
          結(jié)果:
          - order.getId()         → 有值 ?
          - order.getOrderNo()    → 有值 ?
          - order.getUserId()     → 有值 ?
          - order.getTotalAmount()→ 有值 ?
          - order.getStatus()     → 有值 ?
          - order.getCreatedAt()  → 有值 ?
          - order.getUpdatedAt()  → null ?(即使數(shù)據(jù)庫(kù)有值,也不會(huì)自動(dòng)映射)
          
          **三種解決方案:**
          
          方案1:補(bǔ)全所有字段映射(推薦用于字段名不一致的情況)
          <resultMap id="CompleteMap" type="Order">
              <id property="id" column="id"/>
              <result property="orderNo" column="order_no"/>
              <result property="userId" column="user_id"/>
              <result property="totalAmount" column="total_amount"/>
              <result property="status" column="status"/>
              <result property="createdAt" column="created_at"/>
              <result property="updatedAt" column="updated_at"/>  ← 補(bǔ)全
          </resultMap>
          
          方案2:開啟 autoMappingBehavior(推薦,最常用)
          <resultMap id="BaseResultMap" type="Order" autoMapping="true">
              <!-- 只映射特殊字段(如字段名不一致的) -->
              <id property="id" column="id"/>
              <result property="orderNo" column="order_no"/>  <!-- 數(shù)據(jù)庫(kù)是 order_no,需要映射 -->
              <!-- 其他字段名一致的字段會(huì)自動(dòng)映射 -->
          </resultMap>
          
          方案3:使用 resultType 而不是 resultMap(適合簡(jiǎn)單場(chǎng)景)
          <!-- 當(dāng)字段名一致(或開啟了駝峰轉(zhuǎn)換)時(shí),直接用 resultType -->
          <select id="findById" resultType="Order">
              SELECT id, order_no, user_id, total_amount, status, created_at, updated_at
              FROM `order`
              WHERE id = #{id}
          </select>
          
          **?? 什么叫"字段名一致"?**
          
          "一致"的定義取決于是否開啟了駝峰命名轉(zhuǎn)換(map-underscore-to-camel-case):
          
          情況1:未開啟駝峰轉(zhuǎn)換(map-underscore-to-camel-case: false)
          ┌─────────────────┬──────────────┬────────┐
          │ 數(shù)據(jù)庫(kù)字段名     │ Java屬性名   │ 是否一致│
          ├─────────────────┼──────────────┼────────┤
          │ id              │ id           │ ? 一致 │
          │ username        │ username     │ ? 一致 │
          │ order_no        │ orderNo      │ ? 不一致(需手動(dòng)映射)│
          │ created_at      │ createdAt    │ ? 不一致(需手動(dòng)映射)│
          └─────────────────┴──────────────┴────────┘
          結(jié)論:必須完全相同才算一致(包括大小寫、下劃線)
          
          情況2:開啟駝峰轉(zhuǎn)換(map-underscore-to-camel-case: true)← 推薦配置
          ┌─────────────────┬──────────────┬────────┐
          │ 數(shù)據(jù)庫(kù)字段名     │ Java屬性名   │ 是否一致│
          ├─────────────────┼──────────────┼────────┤
          │ id              │ id           │ ? 一致 │
          │ username        │ username     │ ? 一致 │
          │ order_no        │ orderNo      │ ? 一致(自動(dòng)轉(zhuǎn)換)│
          │ created_at      │ createdAt    │ ? 一致(自動(dòng)轉(zhuǎn)換)│
          │ user_id         │ userId       │ ? 一致(自動(dòng)轉(zhuǎn)換)│
          │ gmt_create      │ createdAt    │ ? 不一致(語義不同,需手動(dòng)映射)│
          └─────────────────┴──────────────┴────────┘
          結(jié)論:下劃線命名 ? 駝峰命名 自動(dòng)轉(zhuǎn)換,也算一致
          
          **轉(zhuǎn)換規(guī)則詳解:**
          order_no     → orderNo      (o_n → On)
          user_name    → userName     (u_n → Un)
          created_at   → createdAt    (c_a → Ca)
          is_deleted   → isDeleted    (i_d → Id)
          
          **實(shí)際示例對(duì)比:**
          
          // 未開啟駝峰轉(zhuǎn)換時(shí)
          <resultMap id="UserMap" type="User">
              <result property="userName" column="user_name"/>  <!-- 必須手動(dòng)映射 -->
              <result property="createdAt" column="created_at"/> <!-- 必須手動(dòng)映射 -->
          </resultMap>
          
          // 開啟駝峰轉(zhuǎn)換后(推薦!)
          <select id="findById" resultType="User">
              SELECT id, user_name, created_at FROM user WHERE id = #{id}
              <!-- user_name 自動(dòng)映射到 userName -->
              <!-- created_at 自動(dòng)映射到 createdAt -->
          </select>
          
          **配置駝峰轉(zhuǎn)換:**
          # application.yml
          mybatis:
            configuration:
              map-underscore-to-camel-case: true  ← 開啟此配置
          
          開啟后的效果:
          ? 數(shù)據(jù)庫(kù)用下劃線命名(user_name, order_no)
          ? Java用駝峰命名(userName, orderNo)
          ? MyBatis自動(dòng)轉(zhuǎn)換,無需手動(dòng)映射
          
          **最佳實(shí)踐:**
          1. 如果所有字段都需要自定義映射 → 使用 <resultMap> 并映射所有字段
          2. 如果只有部分字段需要映射 → 使用 <resultMap autoMapping="true">
          3. 如果字段名一致(或已開啟駝峰轉(zhuǎn)換)→ 直接使用 resultType
          4. 配置文件已開啟駝峰轉(zhuǎn)換時(shí)(map-underscore-to-camel-case: true):
             - 數(shù)據(jù)庫(kù)字段 order_no → Java屬性 orderNo(自動(dòng)轉(zhuǎn)換)
             - 數(shù)據(jù)庫(kù)字段 created_at → Java屬性 createdAt(自動(dòng)轉(zhuǎn)換)
          
          **對(duì)比表:**
          | 場(chǎng)景 | 使用方式 | 是否自動(dòng)映射 | 示例 |
          |------|---------|-------------|------|
          | 字段名完全一致 | resultType | ? 自動(dòng) | 數(shù)據(jù)庫(kù) id → Java id |
          | 開啟駝峰轉(zhuǎn)換 | resultType | ? 自動(dòng) | 數(shù)據(jù)庫(kù) user_name → Java userName |
          | 字段名不一致 | resultMap | ? 手動(dòng) | 數(shù)據(jù)庫(kù) gmt_create → Java createdAt |
          | 復(fù)雜關(guān)聯(lián)查詢 | resultMap | ? 手動(dòng) | 一對(duì)多、多對(duì)多關(guān)聯(lián) |
          | resultMap + autoMapping | resultMap autoMapping="true" | ?? 部分自動(dòng) | 特殊字段手動(dòng),其他自動(dòng) |
          -->
          
          <!-- ========== SQL 片段(可復(fù)用) ========== -->
          
          <sql id="baseColumns">
              id, order_no, user_id, total_amount, status, created_at, updated_at
          </sql>
          
          <sql id="whereCondition">
              <where>
                  <if test="status != null and status != ''">
                      AND status = #{status}
                  </if>
                  <if test="minAmount != null">
                      AND total_amount >= #{minAmount}
                  </if>
              </where>
          </sql>
          
          <!-- ========== 查詢操作 ========== -->
          
          <!-- 根據(jù)ID查詢 -->
          <select id="findById" parameterType="long" resultMap="BaseResultMap">
              SELECT <include refid="baseColumns"/>
              FROM `order`
              WHERE id = #{id}
          </select>
          
          <!-- 根據(jù)用戶ID查詢 -->
          <select id="findByUserId" parameterType="long" resultMap="BaseResultMap">
              SELECT <include refid="baseColumns"/>
              FROM `order`
              WHERE user_id = #{userId}
              ORDER BY created_at DESC
          </select>
          
          <!-- 條件查詢 -->
          <select id="findByCondition" resultMap="BaseResultMap">
              SELECT <include refid="baseColumns"/>
              FROM `order`
              <include refid="whereCondition"/>
              ORDER BY created_at DESC
          </select>
          
          <!-- 關(guān)聯(lián)查詢:訂單 + 訂單項(xiàng) -->
          <select id="findByIdWithItems" parameterType="long" resultMap="OrderWithItemsMap">
              SELECT 
                  o.id, o.order_no, o.user_id, o.total_amount, o.status, 
                  o.created_at, o.updated_at,
                  oi.id AS item_id, oi.order_id, oi.product_name, 
                  oi.quantity, oi.price
              FROM `order` o
              LEFT JOIN order_item oi ON o.id = oi.order_id
              WHERE o.id = #{id}
          </select>
          
          <!-- ========== 插入操作 ========== -->
          
          <insert id="insert" parameterType="com.example.demo.domain.Order" 
                  useGeneratedKeys="true" keyProperty="id">
              INSERT INTO `order` (order_no, user_id, total_amount, status)
              VALUES (#{orderNo}, #{userId}, #{totalAmount}, #{status})
          </insert>
          
          <!-- ========== 更新操作 ========== -->
          
          <update id="update" parameterType="com.example.demo.domain.Order">
              UPDATE `order`
              <set>
                  <if test="orderNo != null">order_no = #{orderNo},</if>
                  <if test="totalAmount != null">total_amount = #{totalAmount},</if>
                  <if test="status != null">status = #{status},</if>
              </set>
              WHERE id = #{id}
          </update>
          
          <!-- ========== 刪除操作 ========== -->
          
          <delete id="deleteById" parameterType="long">
              DELETE FROM `order` WHERE id = #{id}
          </delete>
      
      </mapper>
      

      6. 動(dòng)態(tài)SQL

      動(dòng)態(tài)SQL是MyBatis的強(qiáng)大特性之一,可以根據(jù)不同條件動(dòng)態(tài)拼接SQL語句,避免編寫大量的if-else邏輯。

      6.1 if 標(biāo)簽

      作用: 根據(jù)條件判斷是否包含某段SQL

      基本語法:

      <if test="條件表達(dá)式">
          SQL片段
      </if>
      

      完整示例:

      Mapper 接口定義:

      // 方式1:使用 @Param 注解(推薦)
      List<User> findUsers(@Param("username") String username, 
                           @Param("minAge") Integer minAge, 
                           @Param("maxAge") Integer maxAge);
      

      XML 配置:

      <select id="findUsers" resultType="User">
          SELECT * FROM user
          <where>
              <if test="username != null and username != ''">
                  AND username LIKE CONCAT('%', #{username}, '%')
              </if>
              <if test="minAge != null">
                  AND age >= #{minAge}
              </if>
              <if test="maxAge != null">
                  AND age &lt;= #{maxAge}
              </if>
          </where>
      </select>
      

      ?? 參數(shù)是如何傳入的?

      MyBatis 通過以下方式獲取參數(shù):

      1. 使用 @Param 注解指定參數(shù)名(推薦)

      // Mapper 接口
      List<User> findUsers(@Param("username") String username, 
                           @Param("minAge") Integer minAge, 
                           @Param("maxAge") Integer maxAge);
      
      // Service 層調(diào)用
      List<User> users = userMapper.findUsers("張三", 20, 30);
      

      工作原理:

      • @Param("username") 告訴 MyBatis:這個(gè)參數(shù)在 XML 中叫 username
      • XML 中的 #{username} 就能獲取到傳入的值 "張三"
      • test="username != null" 也是通過參數(shù)名 username 來判斷

      2. 不使用 @Param 的默認(rèn)規(guī)則

      // ? 不推薦:不加 @Param
      List<User> findUsers(String username, Integer minAge, Integer maxAge);
      
      // XML 中需要使用默認(rèn)參數(shù)名(arg0, arg1 或 param1, param2)
      <if test="arg0 != null">  <!-- 或者 param1 -->
          AND username = #{arg0}  <!-- 或者 #{param1} -->
      </if>
      

      3. parameterType 屬性(可選)

      <!-- parameterType 是可選的,MyBatis 可以自動(dòng)推斷 -->
      <select id="findUsers" parameterType="map" resultType="User">
          SELECT * FROM user WHERE username = #{username}
      </select>
      

      parameterType 說明:

      • ? 可以省略:MyBatis 會(huì)自動(dòng)推斷參數(shù)類型
      • ?? 一般不寫:現(xiàn)代項(xiàng)目很少使用,容易造成混淆
      • ?? 如果要寫:只在參數(shù)是復(fù)雜對(duì)象時(shí)才可能用到

      4. 參數(shù)傳遞的幾種方式對(duì)比

      方式1:多個(gè)基本參數(shù)(使用 @Param)

      // Mapper 接口
      List<User> findUsers(@Param("username") String username, 
                           @Param("minAge") Integer minAge);
      
      // XML 配置
      <select id="findUsers" resultType="User">
          SELECT * FROM user 
          WHERE username = #{username} AND age >= #{minAge}
      </select>
      
      // 調(diào)用
      List<User> users = userMapper.findUsers("張三", 20);
      

      方式2:?jiǎn)蝹€(gè)對(duì)象參數(shù)

      // 定義查詢條件對(duì)象
      public class UserQuery {
          private String username;
          private Integer minAge;
          private Integer maxAge;
          // getters and setters
      }
      
      // Mapper 接口
      List<User> findUsers(UserQuery query);
      
      // XML 配置(直接使用對(duì)象屬性名)
      <select id="findUsers" resultType="User">
          SELECT * FROM user
          <where>
              <if test="username != null">
                  AND username = #{username}  <!-- 自動(dòng)調(diào)用 query.getUsername() -->
              </if>
              <if test="minAge != null">
                  AND age >= #{minAge}  <!-- 自動(dòng)調(diào)用 query.getMinAge() -->
              </if>
          </where>
      </select>
      
      // 調(diào)用
      UserQuery query = new UserQuery();
      query.setUsername("張三");
      query.setMinAge(20);
      List<User> users = userMapper.findUsers(query);
      

      方式3:Map 參數(shù)

      // Mapper 接口
      List<User> findUsers(Map<String, Object> params);
      
      // XML 配置(使用 Map 的 key)
      <select id="findUsers" resultType="User">
          SELECT * FROM user
          WHERE username = #{username} AND age >= #{minAge}
      </select>
      
      // 調(diào)用
      Map<String, Object> params = new HashMap<>();
      params.put("username", "張三");
      params.put("minAge", 20);
      List<User> users = userMapper.findUsers(params);
      

      參數(shù)獲取總結(jié)表:

      參數(shù)方式 Mapper 接口 XML 中如何獲取 是否需要 @Param
      單個(gè)基本參數(shù) findById(Long id) #{id}#{任意名} ? 不需要
      多個(gè)基本參數(shù) find(String name, int age) #{param1}, #{param2} ? 建議加
      多個(gè) @Param find(@Param("name") String name) #{name} ? 推薦
      對(duì)象參數(shù) findUsers(UserQuery query) #{username} (屬性名) ? 不需要
      Map 參數(shù) findUsers(Map params) #{username} (key名) ? 不需要

      最佳實(shí)踐:

      1. ? 多個(gè)參數(shù)時(shí),總是使用 @Param(清晰明了)
      2. ? 復(fù)雜查詢條件,使用對(duì)象封裝(可維護(hù)性好)
      3. ? 不要使用 Map 傳參(類型不安全,難以維護(hù))
      4. ? parameterType 一般省略(MyBatis 自動(dòng)推斷)

      詳細(xì)解釋:

      1. <where> 標(biāo)簽的作用:

        • 自動(dòng)處理 WHERE 關(guān)鍵字
        • 自動(dòng)去除第一個(gè)條件前多余的 AND 或 OR
        • 如果沒有任何條件成立,不會(huì)生成 WHERE 子句
      2. <if test="條件"> 判斷規(guī)則:

        • username != null:判斷參數(shù)是否為null
        • username != '':判斷字符串是否為空串
        • 兩個(gè)條件用 and 連接(注意是小寫)
        • 也可以用 or 連接多個(gè)條件
      3. XML 中的特殊字符轉(zhuǎn)義:

      核心問題:為什么 >= 可以直接寫,而 <= 要轉(zhuǎn)義?

      <if test="minAge != null">
          AND age >= #{minAge}      <!-- ? >= 可以直接寫 -->
      </if>
      <if test="maxAge != null">
          AND age &lt;= #{maxAge}   <!-- ?? <= 必須轉(zhuǎn)義成 &lt;= -->
      </if>
      

      原因:

      • < 是 XML 標(biāo)簽的開始符號(hào)(如 <if><select>),必須轉(zhuǎn)義
      • > 是 XML 標(biāo)簽的結(jié)束符號(hào),可以直接使用(但建議也轉(zhuǎn)義)

      XML 特殊字符對(duì)比:

      符號(hào) 含義 XML轉(zhuǎn)義 是否必須轉(zhuǎn)義 示例
      < 小于 &lt; ? 必須 age &lt; 18
      <= 小于等于 &lt;= ? 必須 age &lt;= 18
      > 大于 &gt; ?? 建議(可不轉(zhuǎn)義) age > 18age &gt; 18
      >= 大于等于 &gt;= ?? 建議(可不轉(zhuǎn)義) age >= 18age &gt;= 18
      & 與符號(hào) &amp; ? 必須 if (a &amp;&amp; b)
      " 雙引號(hào) &quot; ?? 屬性值中建議 name="value"
      ' 單引號(hào) &apos; ?? 屬性值中建議 name='value'

      為什么 < 必須轉(zhuǎn)義?

      <!-- ? 錯(cuò)誤示例:XML解析器會(huì)認(rèn)為 "age < 18" 中的 < 是一個(gè)標(biāo)簽開始 -->
      <if test="age != null">
          AND age < #{age}  <!-- 報(bào)錯(cuò)!XML解析器混亂了 -->
      </if>
      
      <!-- ? 正確示例1:轉(zhuǎn)義 -->
      <if test="age != null">
          AND age &lt; #{age}  <!-- 正確 -->
      </if>
      
      <!-- ? 正確示例2:使用 CDATA -->
      <if test="age != null">
          <![CDATA[ AND age < #{age} ]]>  <!-- 正確 -->
      </if>
      

      為什么 > 可以不轉(zhuǎn)義?

      <!-- ? 這樣寫不會(huì)報(bào)錯(cuò)(雖然不推薦)-->
      <if test="age != null">
          AND age > #{age}  <!-- 可以,但不規(guī)范 -->
      </if>
      
      <!-- ? 更規(guī)范的寫法 -->
      <if test="age != null">
          AND age &gt; #{age}  <!-- 推薦 -->
      </if>
      

      最佳實(shí)踐:

      方式1:使用轉(zhuǎn)義字符(推薦,清晰明了)

      <if test="minAge != null">AND age &gt;= #{minAge}</if>
      <if test="maxAge != null">AND age &lt;= #{maxAge}</if>
      <if test="age != null">AND age &lt; #{age}</if>
      <if test="age != null">AND age &gt; #{age}</if>
      

      方式2:使用 CDATA(推薦,復(fù)雜SQL時(shí)使用)

      <if test="minAge != null and maxAge != null">
          <![CDATA[
              AND age >= #{minAge} AND age <= #{maxAge}
          ]]>
      </if>
      
      <!-- CDATA 區(qū)域內(nèi)的所有內(nèi)容都被視為純文本,不會(huì)被XML解析 -->
      <if test="formula != null">
          <![CDATA[
              AND (price * quantity < 1000 OR discount > 0.5)
          ]]>
      </if>
      

      CDATA 說明:

      • <![CDATA[ 開始,]]> 結(jié)束
      • CDATA 區(qū)域內(nèi)可以隨意使用 <>& 等特殊字符
      • 適合包含多個(gè)比較運(yùn)算符的復(fù)雜SQL

      實(shí)際對(duì)比:

      <!-- 方式1:全部轉(zhuǎn)義(啰嗦但安全)-->
      <select id="findUsers1" resultType="User">
          SELECT * FROM user
          WHERE age &gt;= 18 AND age &lt;= 60 AND score &gt; 80
      </select>
      
      <!-- 方式2:使用 CDATA(簡(jiǎn)潔清晰)-->
      <select id="findUsers2" resultType="User">
          <![CDATA[
              SELECT * FROM user
              WHERE age >= 18 AND age <= 60 AND score > 80
          ]]>
      </select>
      
      <!-- 方式3:混合使用(不推薦,容易出錯(cuò))-->
      <select id="findUsers3" resultType="User">
          SELECT * FROM user
          WHERE age >= 18 AND age &lt;= 60 AND score > 80  <!-- 不一致,容易混淆 -->
      </select>
      

      總結(jié):

      1. < 必須轉(zhuǎn)義成 &lt;,否則XML解析報(bào)錯(cuò)
      2. > 可以不轉(zhuǎn)義,但建議轉(zhuǎn)義成 &gt;(統(tǒng)一規(guī)范)
      3. 簡(jiǎn)單SQL用轉(zhuǎn)義,復(fù)雜SQL用CDATA
      4. 團(tuán)隊(duì)開發(fā)要統(tǒng)一規(guī)范(要么全轉(zhuǎn)義,要么全用CDATA)

      補(bǔ)充:屬性值中的轉(zhuǎn)義規(guī)則

      在 MyBatis XML 中,屬性值(如 test="...")也需要注意轉(zhuǎn)義規(guī)則。

      場(chǎng)景1:test 屬性中的比較運(yùn)算符

      <!-- ? 正確:test 屬性值中可以直接用 < 和 > -->
      <if test="age != null and age > 18">
          AND age > 18
      </if>
      
      <if test="age != null and age < 60">
          AND age < 60
      </if>
      
      <if test="minAge != null and maxAge != null and minAge < maxAge">
          AND age BETWEEN #{minAge} AND #{maxAge}
      </if>
      

      重要:test 屬性值中的 <> 不需要轉(zhuǎn)義!

      原因:

      • test 屬性的值已經(jīng)被引號(hào)包裹(test="..."
      • XML 解析器知道引號(hào)內(nèi)的內(nèi)容是屬性值,不會(huì)當(dāng)作標(biāo)簽解析
      • MyBatis 會(huì)正確處理屬性值中的比較運(yùn)算符

      場(chǎng)景2:屬性值中包含引號(hào)

      <!-- ? 錯(cuò)誤:屬性值用雙引號(hào)包裹,內(nèi)部又有雙引號(hào) -->
      <if test="username != null and username == "admin"">
          AND role = 'admin'
      </if>
      
      <!-- ? 方式1:外層用雙引號(hào),內(nèi)層用單引號(hào) -->
      <if test="username != null and username == 'admin'">
          AND role = 'admin'
      </if>
      
      <!-- ? 方式2:使用轉(zhuǎn)義 -->
      <if test="username != null and username == &quot;admin&quot;">
          AND role = 'admin'
      </if>
      
      <!-- ? 方式3:外層用單引號(hào),內(nèi)層用雙引號(hào) -->
      <if test='username != null and username == "admin"'>
          AND role = 'admin'
      </if>
      

      場(chǎng)景3:屬性值中包含 & 符號(hào)

      <!-- ? 錯(cuò)誤:& 符號(hào)沒有轉(zhuǎn)義 -->
      <if test="status != null && isActive">
          AND status = #{status}
      </if>
      
      <!-- ? 方式1:使用 and 代替 && -->
      <if test="status != null and isActive">
          AND status = #{status}
      </if>
      
      <!-- ? 方式2:轉(zhuǎn)義 & 符號(hào) -->
      <if test="status != null &amp;&amp; isActive">
          AND status = #{status}
      </if>
      

      屬性值中的轉(zhuǎn)義規(guī)則總結(jié):

      位置 符號(hào) 是否需要轉(zhuǎn)義 說明
      test 屬性值中 < ? 不需要 引號(hào)內(nèi)可以直接使用
      test 屬性值中 > ? 不需要 引號(hào)內(nèi)可以直接使用
      test 屬性值中 & ? 需要(或用 and) &amp;and
      test 屬性值中 " ? 需要(或換引號(hào)) 外雙內(nèi)單,或轉(zhuǎn)義 &quot;
      test 屬性值中 ' ? 需要(或換引號(hào)) 外單內(nèi)雙,或轉(zhuǎn)義 &apos;
      SQL 內(nèi)容中 < ? 必須 &lt; 或 CDATA
      SQL 內(nèi)容中 > ?? 建議 &gt; 或 CDATA

      完整示例對(duì)比:

      <!-- ========== test 屬性值中的比較運(yùn)算符 ========== -->
      
      <!-- ? 正確:test 屬性中的 < 和 > 不需要轉(zhuǎn)義 -->
      <select id="findUsers" resultType="User">
          SELECT * FROM user
          <where>
              <if test="minAge != null and minAge > 0">
                  AND age >= #{minAge}
              </if>
              <if test="maxAge != null and maxAge < 150">
                  AND age &lt;= #{maxAge}  <!-- SQL內(nèi)容中的 < 需要轉(zhuǎn)義 -->
              </if>
          </where>
      </select>
      
      <!-- ========== 復(fù)雜條件示例 ========== -->
      
      <!-- ? 推薦:使用 and/or 代替 &&/|| -->
      <if test="username != null and username != '' and age > 18">
          AND username = #{username} AND age > 18
      </if>
      
      <!-- ? 也可以:使用轉(zhuǎn)義的 && -->
      <if test="username != null &amp;&amp; username != '' &amp;&amp; age > 18">
          AND username = #{username} AND age > 18
      </if>
      
      <!-- ========== 字符串比較示例 ========== -->
      
      <!-- ? 推薦:外雙內(nèi)單 -->
      <if test="status != null and status == 'active'">
          AND status = 'active'
      </if>
      
      <!-- ? 也可以:外單內(nèi)雙 -->
      <if test='status != null and status == "active"'>
          AND status = 'active'
      </if>
      
      <!-- ? 也可以:使用轉(zhuǎn)義 -->
      <if test="status != null and status == &quot;active&quot;">
          AND status = 'active'
      </if>
      

      最佳實(shí)踐建議:

      1. test 屬性中的比較運(yùn)算符:直接使用 <>

        <if test="age > 18 and age < 60">...</if>
        
      2. test 屬性中的邏輯運(yùn)算符:使用 andor,不用 &&||

        <!-- ? 推薦 -->
        <if test="a != null and b != null or c > 0">...</if>
        
        <!-- ? 不推薦(需要轉(zhuǎn)義,麻煩)-->
        <if test="a != null &amp;&amp; b != null || c > 0">...</if>
        
      3. test 屬性中的字符串比較:外雙內(nèi)單

        <if test="status == 'active'">...</if>
        
      4. SQL 內(nèi)容中的比較運(yùn)算符:< 必須轉(zhuǎn)義,> 建議轉(zhuǎn)義

        AND age &lt; 60 AND score &gt; 80
        
      5. 復(fù)雜SQL:使用 CDATA

        <![CDATA[ AND age < 60 AND score > 80 ]]>
        

      執(zhí)行場(chǎng)景演示:

      場(chǎng)景1:傳入所有參數(shù)

      // Mapper調(diào)用
      findUsers("張三", 20, 30);
      
      // 生成的SQL
      SELECT * FROM user
      WHERE username LIKE CONCAT('%', '張三', '%')
        AND age >= 20
        AND age <= 30
      

      場(chǎng)景2:只傳入 username

      // Mapper調(diào)用
      findUsers("李四", null, null);
      
      // 生成的SQL
      SELECT * FROM user
      WHERE username LIKE CONCAT('%', '李四', '%')
      

      場(chǎng)景3:只傳入年齡范圍

      // Mapper調(diào)用
      findUsers(null, 18, 60);
      
      // 生成的SQL
      SELECT * FROM user
      WHERE age >= 18
        AND age <= 60
      

      場(chǎng)景4:不傳任何參數(shù)

      // Mapper調(diào)用
      findUsers(null, null, null);
      
      // 生成的SQL
      SELECT * FROM user
      -- 注意:<where>標(biāo)簽智能處理,沒有WHERE子句
      

      常見判斷條件:

      <!-- 判斷是否為null -->
      <if test="id != null">
          AND id = #{id}
      </if>
      
      <!-- 判斷字符串非空 -->
      <if test="username != null and username != ''">
          AND username = #{username}
      </if>
      
      <!-- 判斷數(shù)字大于0 -->
      <if test="age != null and age > 0">
          AND age = #{age}
      </if>
      
      <!-- 判斷集合非空 -->
      <if test="ids != null and ids.size() > 0">
          AND id IN
          <foreach collection="ids" item="id" open="(" close=")" separator=",">
              #{id}
          </foreach>
      </if>
      
      <!-- 判斷布爾值 -->
      <if test="isDeleted != null and isDeleted">
          AND is_deleted = 1
      </if>
      

      6.2 choose-when-otherwise(類似switch)

      作用: 多個(gè)條件中只選擇一個(gè)執(zhí)行(類似Java的switch-case)

      基本語法:

      <choose>
          <when test="條件1">SQL片段1</when>
          <when test="條件2">SQL片段2</when>
          <when test="條件3">SQL片段3</when>
          <otherwise>默認(rèn)SQL片段</otherwise>
      </choose>
      

      完整示例:

      <select id="findUsersByCondition" resultType="User">
          SELECT * FROM user
          <where>
              <choose>
                  <when test="id != null">
                      AND id = #{id}
                  </when>
                  <when test="username != null">
                      AND username = #{username}
                  </when>
                  <when test="email != null">
                      AND email = #{email}
                  </when>
                  <otherwise>
                      AND status = 'active'
                  </otherwise>
              </choose>
          </where>
      </select>
      

      詳細(xì)解釋:

      1. 執(zhí)行規(guī)則:

        • 從上到下依次判斷 <when> 條件
        • 只要有一個(gè)條件成立,執(zhí)行對(duì)應(yīng)的SQL,然后跳出(不再判斷后續(xù)條件)
        • 如果所有 <when> 都不成立,執(zhí)行 <otherwise>
        • <otherwise> 可以省略(相當(dāng)于沒有default分支)
      2. <if> 的區(qū)別:

        • <if>:可以同時(shí)滿足多個(gè)條件,全部執(zhí)行
        • <choose>:只執(zhí)行第一個(gè)滿足的條件,其他忽略

      執(zhí)行場(chǎng)景演示:

      場(chǎng)景1:傳入 id(優(yōu)先級(jí)最高)

      // Mapper調(diào)用
      findUsersByCondition(1L, "張三", "test@example.com");
      
      // 生成的SQL(只匹配id,忽略u(píng)sername和email)
      SELECT * FROM user
      WHERE id = 1
      

      場(chǎng)景2:不傳id,傳入 username

      // Mapper調(diào)用
      findUsersByCondition(null, "張三", "test@example.com");
      
      // 生成的SQL(匹配username,忽略email)
      SELECT * FROM user
      WHERE username = '張三'
      

      場(chǎng)景3:只傳入 email

      // Mapper調(diào)用
      findUsersByCondition(null, null, "test@example.com");
      
      // 生成的SQL
      SELECT * FROM user
      WHERE email = 'test@example.com'
      

      場(chǎng)景4:什么都不傳(執(zhí)行 otherwise)

      // Mapper調(diào)用
      findUsersByCondition(null, null, null);
      
      // 生成的SQL
      SELECT * FROM user
      WHERE status = 'active'
      

      實(shí)際應(yīng)用場(chǎng)景:

      示例1:按優(yōu)先級(jí)排序

      <!-- 優(yōu)先級(jí):精確ID > 模糊用戶名 > 模糊郵箱 > 查詢所有活躍用戶 -->
      <select id="searchUsers" resultType="User">
          SELECT * FROM user
          <where>
              <choose>
                  <when test="id != null">
                      id = #{id}  <!-- 最精確 -->
                  </when>
                  <when test="username != null and username != ''">
                      username LIKE CONCAT('%', #{username}, '%')
                  </when>
                  <when test="email != null and email != ''">
                      email LIKE CONCAT('%', #{email}, '%')
                  </when>
                  <otherwise>
                      status = 'active'  <!-- 默認(rèn)查詢 -->
                  </otherwise>
              </choose>
          </where>
      </select>
      

      示例2:不同排序策略

      <select id="findUsersWithSort" resultType="User">
          SELECT * FROM user
          ORDER BY
          <choose>
              <when test="sortBy == 'age'">
                  age DESC
              </when>
              <when test="sortBy == 'name'">
                  username ASC
              </when>
              <when test="sortBy == 'createTime'">
                  created_at DESC
              </when>
              <otherwise>
                  id ASC  <!-- 默認(rèn)按ID排序 -->
              </otherwise>
          </choose>
      </select>
      

      對(duì)比 if 和 choose:

      使用 <if>(多個(gè)條件可同時(shí)生效):

      <where>
          <if test="id != null">AND id = #{id}</if>
          <if test="username != null">AND username = #{username}</if>
      </where>
      <!-- 如果兩個(gè)都傳,生成:WHERE id = 1 AND username = '張三' -->
      

      使用 <choose>(只生效一個(gè)):

      <where>
          <choose>
              <when test="id != null">AND id = #{id}</when>
              <when test="username != null">AND username = #{username}</when>
          </choose>
      </where>
      <!-- 如果兩個(gè)都傳,生成:WHERE id = 1(只用id,忽略u(píng)sername)-->
      

      6.3 set 標(biāo)簽(動(dòng)態(tài)更新)

      作用: 動(dòng)態(tài)生成UPDATE語句的SET子句,自動(dòng)處理逗號(hào)

      基本語法:

      <update id="updateXxx">
          UPDATE 表名
          <set>
              <if test="條件1">字段1 = #{值1},</if>
              <if test="條件2">字段2 = #{值2},</if>
          </set>
          WHERE id = #{id}
      </update>
      

      完整示例:

      <update id="updateUser" 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>
      

      詳細(xì)解釋:

      1. <set> 標(biāo)簽的作用:

        • 自動(dòng)添加 SET 關(guān)鍵字
        • 自動(dòng)去除最后一個(gè)多余的逗號(hào)(這是核心功能)
        • 如果所有條件都不成立,不會(huì)生成SET子句(避免SQL錯(cuò)誤)
      2. 為什么需要 <set> 標(biāo)簽?

      不使用 <set> 的問題:

      <!-- ? 錯(cuò)誤示例 -->
      <update id="updateUser">
          UPDATE user SET
          <if test="username != null">username = #{username},</if>
          <if test="email != null">email = #{email},</if>
          <if test="age != null">age = #{age},</if>  <!-- 最后一個(gè)逗號(hào)怎么處理? -->
          WHERE id = #{id}
      </update>
      
      <!-- 如果只傳age,生成的SQL:-->
      UPDATE user SET age = 25,  WHERE id = 1
                            ↑ 這個(gè)逗號(hào)會(huì)導(dǎo)致SQL語法錯(cuò)誤!
      

      使用 <set> 后:

      <!-- ? 正確示例 -->
      <update id="updateUser">
          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>
      
      <!-- 生成的SQL(<set>自動(dòng)去除最后的逗號(hào)):-->
      UPDATE user SET age = 25 WHERE id = 1
                              ↑ 逗號(hào)被自動(dòng)去除了!
      

      執(zhí)行場(chǎng)景演示:

      場(chǎng)景1:更新所有字段

      // Mapper調(diào)用
      User user = new User();
      user.setId(1L);
      user.setUsername("張三");
      user.setEmail("zhangsan@example.com");
      user.setAge(25);
      updateUser(user);
      
      // 生成的SQL
      UPDATE user 
      SET username = '張三', 
          email = 'zhangsan@example.com', 
          age = 25 
      WHERE id = 1
      

      場(chǎng)景2:只更新部分字段

      // Mapper調(diào)用(只更新email)
      User user = new User();
      user.setId(1L);
      user.setEmail("newemail@example.com");  // 只設(shè)置email
      updateUser(user);
      
      // 生成的SQL
      UPDATE user 
      SET email = 'newemail@example.com' 
      WHERE id = 1
      

      場(chǎng)景3:只更新一個(gè)字段

      // Mapper調(diào)用(只更新age)
      User user = new User();
      user.setId(1L);
      user.setAge(30);
      updateUser(user);
      
      // 生成的SQL(注意:最后沒有逗號(hào))
      UPDATE user 
      SET age = 30 
      WHERE id = 1
      

      實(shí)際應(yīng)用場(chǎng)景:

      示例1:復(fù)雜的動(dòng)態(tài)更新

      <update id="updateUserSelective">
          UPDATE user
          <set>
              <!-- 字符串字段:判斷非空 -->
              <if test="username != null and username != ''">
                  username = #{username},
              </if>
              <if test="email != null and email != ''">
                  email = #{email},
              </if>
              
              <!-- 數(shù)字字段:判斷不為null -->
              <if test="age != null">
                  age = #{age},
              </if>
              
              <!-- 布爾字段 -->
              <if test="isVip != null">
                  is_vip = #{isVip},
              </if>
              
              <!-- 更新時(shí)間:總是更新 -->
              updated_at = NOW(),
          </set>
          WHERE id = #{id}
      </update>
      

      示例2:批量更新某些字段

      <update id="batchUpdateStatus">
          UPDATE user
          <set>
              status = #{status},
              updated_at = NOW()
          </set>
          WHERE id IN
          <foreach collection="ids" item="id" open="(" close=")" separator=",">
              #{id}
          </foreach>
      </update>
      

      示例3:根據(jù)條件決定更新哪些字段

      <update id="updateUserByRole">
          UPDATE user
          <set>
              <!-- 管理員可以更新所有字段 -->
              <if test="role == 'admin'">
                  <if test="username != null">username = #{username},</if>
                  <if test="email != null">email = #{email},</if>
                  <if test="status != null">status = #{status},</if>
              </if>
              
              <!-- 普通用戶只能更新部分字段 -->
              <if test="role == 'user'">
                  <if test="email != null">email = #{email},</if>
              </if>
              
              updated_at = NOW()
          </set>
          WHERE id = #{id}
      </update>
      

      與 trim 標(biāo)簽的對(duì)比:

      使用 <set> 標(biāo)簽(推薦,簡(jiǎn)潔):

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

      使用 <trim> 標(biāo)簽實(shí)現(xiàn)相同效果(更靈活但復(fù)雜):

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

      注意事項(xiàng):

      1. 每個(gè)字段賦值后都要加逗號(hào),<set> 會(huì)自動(dòng)去除最后的逗號(hào)
      2. 如果所有 <if> 條件都不滿足,UPDATE語句可能無效,建議至少保留一個(gè)必更新字段(如 updated_at
      3. WHERE條件必須寫在 <set> 標(biāo)簽外面

      6.4 foreach 標(biāo)簽(批量操作)

      作用: 遍歷集合或數(shù)組,生成重復(fù)的SQL片段(如批量插入、IN查詢)

      基本語法:

      <foreach collection="集合名稱" item="每個(gè)元素的變量名" 
               separator="分隔符" open="開始符號(hào)" close="結(jié)束符號(hào)" index="索引變量">
          SQL片段(可使用 #{item} 訪問當(dāng)前元素)
      </foreach>
      

      屬性說明:

      屬性 必填 說明 示例
      collection ? 是 要遍歷的集合名稱 listarrayids(參數(shù)名)
      item ? 是 當(dāng)前元素的變量名 useriditem
      separator ? 否 每個(gè)元素之間的分隔符 ,ORAND
      open ? 否 整個(gè)循環(huán)開始前添加的字符 (VALUES
      close ? 否 整個(gè)循環(huán)結(jié)束后添加的字符 );
      index ? 否 當(dāng)前元素的索引(從0開始) iidx

      示例1:批量插入

      <!-- 批量插入 -->
      <insert id="batchInsert" parameterType="list">
          INSERT INTO user (username, email, age)
          VALUES
          <foreach collection="list" item="user" separator=",">
              (#{user.username}, #{user.email}, #{user.age})
          </foreach>
      </insert>
      

      詳細(xì)解釋:

      • collection="list":遍歷參數(shù)中的 list 集合
      • item="user":每次遍歷的元素命名為 user
      • separator=",":每個(gè)元素之間用逗號(hào)分隔
      • #{user.username}:訪問當(dāng)前 user 對(duì)象的 username 屬性

      執(zhí)行場(chǎng)景:

      // Mapper調(diào)用
      List<User> users = Arrays.asList(
          new User("張三", "zhangsan@example.com", 25),
          new User("李四", "lisi@example.com", 30),
          new User("王五", "wangwu@example.com", 28)
      );
      batchInsert(users);
      
      // 生成的SQL
      INSERT INTO user (username, email, age)
      VALUES
          ('張三', 'zhangsan@example.com', 25),
          ('李四', 'lisi@example.com', 30),
          ('王五', 'wangwu@example.com', 28)
      

      示例2:IN 查詢

      <!-- IN 查詢 -->
      <select id="findByIds" resultType="User">
          SELECT * FROM user
          WHERE id IN
          <foreach collection="ids" item="id" open="(" separator="," close=")">
              #{id}
          </foreach>
      </select>
      

      詳細(xì)解釋:

      • collection="ids":遍歷參數(shù)名為 ids 的集合
      • item="id":每個(gè)元素命名為 id
      • open="(":循環(huán)開始前加左括號(hào)
      • close=")":循環(huán)結(jié)束后加右括號(hào)
      • separator=",":元素之間用逗號(hào)分隔

      執(zhí)行場(chǎng)景:

      // Mapper接口定義
      List<User> findByIds(@Param("ids") List<Long> ids);
      
      // 調(diào)用
      List<Long> ids = Arrays.asList(1L, 3L, 5L, 7L);
      List<User> users = findByIds(ids);
      
      // 生成的SQL
      SELECT * FROM user
      WHERE id IN (1, 3, 5, 7)
                    ↑      ↑
                  open   close
      

      示例3:批量刪除

      <!-- 批量刪除 -->
      <delete id="batchDelete">
          DELETE FROM user WHERE id IN
          <foreach collection="array" item="id" open="(" separator="," close=")">
              #{id}
          </foreach>
      </delete>
      

      詳細(xì)解釋:

      • collection="array":遍歷數(shù)組類型的參數(shù)

      執(zhí)行場(chǎng)景:

      // Mapper接口定義(參數(shù)是數(shù)組)
      int batchDelete(Long[] ids);
      
      // 調(diào)用
      Long[] ids = {1L, 2L, 3L};
      batchDelete(ids);
      
      // 生成的SQL
      DELETE FROM user WHERE id IN (1, 2, 3)
      

      示例4:動(dòng)態(tài)拼接多個(gè)OR條件

      <select id="searchUsers" resultType="User">
          SELECT * FROM user
          <where>
              <foreach collection="keywords" item="keyword" separator="OR">
                  username LIKE CONCAT('%', #{keyword}, '%')
              </foreach>
          </where>
      </select>
      

      執(zhí)行場(chǎng)景:

      // Mapper調(diào)用
      List<String> keywords = Arrays.asList("張", "李", "王");
      List<User> users = searchUsers(keywords);
      
      // 生成的SQL
      SELECT * FROM user
      WHERE username LIKE CONCAT('%', '張', '%')
         OR username LIKE CONCAT('%', '李', '%')
         OR username LIKE CONCAT('%', '王', '%')
      

      示例5:使用 index 屬性

      Mapper 接口:

      // 方式1:不使用 @Param(單個(gè)參數(shù))
      int batchInsertWithIndex(List<User> users);
      
      // 方式2:使用 @Param(推薦)
      int batchInsertWithIndex(@Param("userList") List<User> users);
      

      XML 配置:

      <!-- 方式1:不使用 @Param 時(shí),collection 必須寫 "list" -->
      <insert id="batchInsertWithIndex">
          INSERT INTO user (username, email, age, sort_order)
          VALUES
          <foreach collection="list" item="user" index="i" separator=",">
              (#{user.username}, #{user.email}, #{user.age}, #{i})
          </foreach>
      </insert>
      
      <!-- 方式2:使用 @Param 時(shí),collection 寫 @Param 指定的名稱 -->
      <insert id="batchInsertWithIndex">
          INSERT INTO user (username, email, age, sort_order)
          VALUES
          <foreach collection="userList" item="user" index="i" separator=",">
              (#{user.username}, #{user.email}, #{user.age}, #{i})
          </foreach>
      </insert>
      

      ?? 重要區(qū)別:Java 的 List 和 collection="list"

      這兩個(gè) list 不是同一個(gè)含義

      1. Java 中的 List<User>

      List<User> users = Arrays.asList(user1, user2, user3);
      //  ↑
      // 這是 Java 的集合類型,表示一個(gè) User 對(duì)象的列表
      
      • 這是 Java 的類型聲明
      • Listjava.util.List 接口
      • <User> 是泛型,表示列表中元素的類型

      2. XML 中的 collection="list"

      <foreach collection="list" item="user">
      <!--              ↑
           這是 MyBatis 的默認(rèn)參數(shù)名,不是 Java 類型
      -->
      
      • 這是 MyBatis 的參數(shù)名
      • 當(dāng)方法參數(shù)是單個(gè) List 類型且沒有 @Param 時(shí),MyBatis 默認(rèn)把它命名為 "list"
      • 這是一個(gè)字符串,用來引用參數(shù)

      詳細(xì)對(duì)比:

      Java 接口 參數(shù)類型 XML collection 屬性 說明
      batchInsert(List<User> users) List<User> collection="list" 單個(gè)List參數(shù),默認(rèn)名 "list"
      batchInsert(@Param("users") List<User> users) List<User> collection="users" 用 @Param 指定名稱
      batchInsert(User[] users) User[] collection="array" 數(shù)組參數(shù),默認(rèn)名 "array"
      batchInsert(@Param("ids") List<Long> ids) List<Long> collection="ids" 用 @Param 指定名稱

      工作流程圖解:

      Java 代碼調(diào)用:
      userMapper.batchInsert(Arrays.asList(user1, user2));
                             ↓
                      傳入一個(gè) List<User> 類型的參數(shù)
                             ↓
      MyBatis 內(nèi)部處理:
      - 檢測(cè)到參數(shù)類型是 List
      - 沒有 @Param 注解
      - 自動(dòng)給這個(gè)參數(shù)命名為 "list"
                             ↓
      XML 中通過名稱引用:
      <foreach collection="list">  ← 這里的 "list" 是參數(shù)的名字
               ↑
          通過名字 "list" 找到傳入的 List<User> 對(duì)象
      

      常見錯(cuò)誤示例:

      // Mapper 接口(使用了 @Param)
      int batchInsert(@Param("users") List<User> users);
      
      <!-- ? 錯(cuò)誤:使用了 @Param("users"),但 collection 還寫 "list" -->
      <foreach collection="list" item="user">
          <!-- 報(bào)錯(cuò):Parameter 'list' not found -->
      </foreach>
      
      <!-- ? 正確:collection 要和 @Param 的值一致 -->
      <foreach collection="users" item="user">
          (#{user.username}, #{user.email})
      </foreach>
      

      執(zhí)行場(chǎng)景:

      // Mapper調(diào)用
      List<User> users = Arrays.asList(user1, user2, user3);
      //  ↑ 這是 Java 的 List 類型
      batchInsertWithIndex(users);
      
      // 在 MyBatis 內(nèi)部,這個(gè) List 被命名為 "list"(如果沒有 @Param)
      // 所以 XML 中 collection="list" 才能找到這個(gè)參數(shù)
      
      // 生成的SQL(index從0開始)
      INSERT INTO user (username, email, age, sort_order)
      VALUES
          ('張三', 'zhangsan@example.com', 25, 0),
          ('李四', 'lisi@example.com', 30, 1),
          ('王五', 'wangwu@example.com', 28, 2)
      

      MyBatis 默認(rèn)參數(shù)名規(guī)則:

      參數(shù)類型 沒有 @Param 時(shí)的默認(rèn)名 示例
      List "list" collection="list"
      數(shù)組 "array" collection="array"
      Map "map" 鍵名直接訪問
      其他單個(gè)對(duì)象 對(duì)象的屬性名 #{username}
      多個(gè)參數(shù)(無@Param) param1, param2, arg0, arg1 #{param1}, #{arg0}

      最佳實(shí)踐建議:

      // ? 推薦:總是使用 @Param,清晰明了
      int batchInsert(@Param("users") List<User> users);
      int batchInsert(@Param("ids") List<Long> ids);
      
      <!-- ? 推薦:collection 使用有意義的名稱 -->
      <foreach collection="users" item="user">
          ...
      </foreach>
      
      <foreach collection="ids" item="id">
          ...
      </foreach>
      
      // ? 不推薦:依賴默認(rèn)名稱 "list",不夠清晰
      int batchInsert(List<User> users);
      
      <!-- ? 不推薦:collection="list" 不夠語義化 -->
      <foreach collection="list" item="user">
          ...
      </foreach>
      

      總結(jié):

      1. Java 的 List<User> = 類型聲明
      2. XML 的 collection="list" = 參數(shù)名(MyBatis 的默認(rèn)命名)
      3. 它們是兩個(gè)不同層面的概念
      4. 建議使用 @Param 明確指定參數(shù)名,避免混淆

      示例6:遍歷Map

      <select id="findByMap" resultType="User">
          SELECT * FROM user
          <where>
              <foreach collection="params" item="value" index="key" separator="AND">
                  ${key} = #{value}
              </foreach>
          </where>
      </select>
      

      ?? 為什么這里使用 ${key} 而不是 #{key}

      這是一個(gè)關(guān)鍵問題,涉及 MyBatis 中 #{}${} 的核心區(qū)別!

      答案:key 是字段名,必須直接拼接到 SQL 中,不能用預(yù)編譯參數(shù)。

      #{} vs ${} 的本質(zhì)區(qū)別:

      特性 #{}(推薦) ${}(謹(jǐn)慎使用)
      實(shí)現(xiàn)方式 預(yù)編譯(PreparedStatement) 字符串拼接
      SQL注入 ? 安全(自動(dòng)轉(zhuǎn)義) ? 不安全(可能注入)
      使用場(chǎng)景 參數(shù)值(WHERE條件、INSERT值等) 表名、字段名、ORDER BY
      生成SQL 使用 ? 占位符 直接替換為實(shí)際值
      類型處理 自動(dòng)處理類型轉(zhuǎn)換 原樣拼接

      詳細(xì)對(duì)比:

      1. #{} - 預(yù)編譯參數(shù)(安全,推薦)

      <!-- 使用 #{} -->
      <select id="findByUsername" resultType="User">
          SELECT * FROM user WHERE username = #{username}
      </select>
      
      // Java 調(diào)用
      findByUsername("張三");
      
      // 生成的預(yù)編譯SQL(使用 ? 占位符)
      SELECT * FROM user WHERE username = ?
      
      // 執(zhí)行時(shí) MyBatis 會(huì):
      // 1. 準(zhǔn)備 PreparedStatement
      // 2. 設(shè)置參數(shù):setString(1, "張三")
      // 3. 執(zhí)行SQL
      // 結(jié)果:安全,防止SQL注入
      

      2. ${} - 字符串替換(不安全,慎用)

      <!-- 使用 ${} -->
      <select id="findByUsername" resultType="User">
          SELECT * FROM user WHERE username = '${username}'
      </select>
      
      // Java 調(diào)用
      findByUsername("張三");
      
      // 生成的SQL(直接字符串替換)
      SELECT * FROM user WHERE username = '張三'
      
      // ?? 如果傳入惡意參數(shù)
      findByUsername("' OR '1'='1");
      
      // 生成的SQL(SQL注入!)
      SELECT * FROM user WHERE username = '' OR '1'='1'
      -- 這會(huì)返回所有用戶!
      

      為什么示例6必須用 ${key}

      <foreach collection="params" item="value" index="key">
          ${key} = #{value}
          <!-- ↑字段名    ↑參數(shù)值 -->
      </foreach>
      

      原因分析:

      // Map 的內(nèi)容
      params.put("username", "張三");
      params.put("age", 25);
      

      期望生成的SQL:

      WHERE username = '張三' AND age = 25
            ↑字段名       ↑值      ↑字段名 ↑值
      

      如果使用 #{key}(錯(cuò)誤):

      -- 錯(cuò)誤的SQL
      WHERE ? = '張三' AND ? = 25
      --    ↑ 字段名不能是占位符!
      
      -- 這是無效的SQL,數(shù)據(jù)庫(kù)會(huì)報(bào)錯(cuò)
      -- 字段名必須是明確的標(biāo)識(shí)符,不能是參數(shù)
      

      如果使用 ${key}(正確):

      -- 正確的SQL
      WHERE username = '張三' AND age = 25
      --    ↑ 字段名直接拼接進(jìn)SQL
      

      完整對(duì)比示例:

      <!-- 示例1:字段名用 ${},值用 #{} -->
      <foreach collection="params" item="value" index="key">
          ${key} = #{value}  <!-- ? 正確 -->
      </foreach>
      
      <!-- 示例2:都用 #{} -->
      <foreach collection="params" item="value" index="key">
          #{key} = #{value}  <!-- ? 錯(cuò)誤:字段名不能是 ? -->
      </foreach>
      
      <!-- 示例3:都用 ${} -->
      <foreach collection="params" item="value" index="key">
          ${key} = '${value}'  <!-- ?? 不安全:值有SQL注入風(fēng)險(xiǎn) -->
      </foreach>
      

      執(zhí)行場(chǎng)景:

      // Mapper調(diào)用
      Map<String, Object> params = new HashMap<>();
      params.put("username", "張三");
      params.put("age", 25);
      params.put("status", "active");
      findByMap(params);
      
      // 生成的SQL(${key} 被替換為字段名,#{value} 被替換為 ?)
      SELECT * FROM user
      WHERE username = ?    -- 參數(shù)1: "張三"
        AND age = ?         -- 參數(shù)2: 25
        AND status = ?      -- 參數(shù)3: "active"
      

      什么時(shí)候必須用 ${}

      場(chǎng)景 示例 原因
      動(dòng)態(tài)表名 SELECT * FROM ${tableName} 表名不能是參數(shù)
      動(dòng)態(tài)字段名 ORDER BY ${orderColumn} 字段名不能是參數(shù)
      動(dòng)態(tài)排序 ORDER BY id ${sortOrder} ASC/DESC不能是參數(shù)
      IN 子句(舊版) WHERE id IN (${ids}) 現(xiàn)代應(yīng)用用 <foreach> 代替

      ?? 使用 ${} 的安全建議:

      1. 嚴(yán)格驗(yàn)證輸入
      // ? 推薦:白名單驗(yàn)證
      public List<User> findByColumn(String column, Object value) {
          // 驗(yàn)證字段名是否在允許的列表中
          if (!Arrays.asList("username", "age", "status").contains(column)) {
              throw new IllegalArgumentException("Invalid column: " + column);
          }
          return userMapper.findByColumn(column, value);
      }
      
      1. 避免用戶直接輸入
      // ? 危險(xiǎn):直接使用用戶輸入
      String userInput = request.getParameter("column");
      findByColumn(userInput, value);  // SQL注入風(fēng)險(xiǎn)!
      
      // ? 安全:使用枚舉或映射
      Map<String, String> columnMap = Map.of(
          "name", "username",
          "userAge", "age"
      );
      String column = columnMap.get(request.getParameter("field"));
      if (column != null) {
          findByColumn(column, value);
      }
      
      1. 優(yōu)先使用其他方案
      <!-- ? 不推薦:動(dòng)態(tài)字段名 -->
      <select id="findByColumn" resultType="User">
          SELECT * FROM user WHERE ${column} = #{value}
      </select>
      
      <!-- ? 推薦:使用 <choose> 明確指定 -->
      <select id="findByColumn" resultType="User">
          SELECT * FROM user
          <where>
              <choose>
                  <when test="column == 'username'">
                      AND username = #{value}
                  </when>
                  <when test="column == 'age'">
                      AND age = #{value}
                  </when>
                  <when test="column == 'status'">
                      AND status = #{value}
                  </when>
              </choose>
          </where>
      </select>
      

      總結(jié):

      1. ${key} 用于字段名:因?yàn)樽侄蚊仨毷荢QL標(biāo)識(shí)符,不能是參數(shù)
      2. #{value} 用于參數(shù)值:安全的預(yù)編譯方式,防止SQL注入
      3. 原則:能用 #{} 就不用 ${}
      4. 必須用 ${} 時(shí),要嚴(yán)格驗(yàn)證輸入,防止SQL注入

      collection 參數(shù)的不同取值:

      參數(shù)類型 collection 取值 說明
      List<User> list list 單參數(shù)List,默認(rèn)名稱是 list
      User[] array array 單參數(shù)數(shù)組,默認(rèn)名稱是 array
      @Param("ids") List<Long> ids ids 使用 @Param 指定的名稱
      Map<String, Object> map map 或 map 的key 遍歷Map

      完整示例對(duì)比:

      不使用 @Param

      // Mapper接口
      List<User> findByIds(List<Long> ids);
      
      // XML配置
      <foreach collection="list" item="id">  <!-- 必須用 list -->
          #{id}
      </foreach>
      

      使用 @Param(推薦):

      // Mapper接口
      List<User> findByIds(@Param("ids") List<Long> ids);
      
      // XML配置
      <foreach collection="ids" item="id">  <!-- 使用參數(shù)名 ids -->
          #{id}
      </foreach>
      

      性能注意事項(xiàng):

      1. 批量插入優(yōu)化:
      <!-- ? 推薦:一次性插入多條(高效)-->
      INSERT INTO user (username, email) VALUES
          ('張三', 'a@example.com'),
          ('李四', 'b@example.com')
      
      <!-- ? 不推薦:多次單條插入(效率低)-->
      INSERT INTO user (username, email) VALUES ('張三', 'a@example.com');
      INSERT INTO user (username, email) VALUES ('李四', 'b@example.com');
      
      1. IN 查詢數(shù)量限制:
      // ?? 警告:IN 的參數(shù)不宜過多(建議 < 1000)
      // WHERE id IN (1, 2, 3, ..., 10000)  ← 可能導(dǎo)致SQL過長(zhǎng)或性能問題
      
      // ? 建議:分批查詢
      List<Long> ids = ...; // 10000個(gè)ID
      List<User> allUsers = new ArrayList<>();
      for (int i = 0; i < ids.size(); i += 500) {
          List<Long> batch = ids.subList(i, Math.min(i + 500, ids.size()));
          allUsers.addAll(findByIds(batch));
      }
      
      1. 批量插入數(shù)量限制:
      // ?? 批量插入建議每次 < 500 條,避免SQL過長(zhǎng)
      if (users.size() > 500) {
          // 分批插入
          for (int i = 0; i < users.size(); i += 500) {
              List<User> batch = users.subList(i, Math.min(i + 500, users.size()));
              batchInsert(batch);
          }
      }
      

      6.5 trim 標(biāo)簽(更靈活的拼接)

      作用: 靈活處理SQL片段的前綴、后綴,以及去除多余的分隔符(最強(qiáng)大的拼接標(biāo)簽)

      基本語法:

      <trim prefix="前綴" suffix="后綴" 
            prefixOverrides="要去除的前綴" suffixOverrides="要去除的后綴">
          SQL片段
      </trim>
      

      屬性說明:

      屬性 說明 示例
      prefix 在整個(gè)SQL片段前添加的內(nèi)容 WHERESET(
      suffix 在整個(gè)SQL片段后添加的內(nèi)容 );
      prefixOverrides 去除SQL片段開頭的指定字符 AND OR ,
      suffixOverrides 去除SQL片段結(jié)尾的指定字符 ,AND OR

      注意: prefixOverridessuffixOverrides 中的空格和豎線 | 都有意義!

      完整示例:

      <select id="findUsers" resultType="User">
          SELECT * FROM user
          <trim prefix="WHERE" prefixOverrides="AND |OR ">
              <if test="username != null">
                  AND username = #{username}
              </if>
              <if test="email != null">
                  AND email = #{email}
              </if>
          </trim>
      </select>
      

      詳細(xì)解釋:

      1. prefix="WHERE":如果 trim 內(nèi)容不為空,在前面加 WHERE
      2. prefixOverrides="AND |OR ":去除開頭的 AND OR (注意有空格)
        • AND |OR 表示:AND OR (豎線 | 是"或"的意思)
        • 空格很重要:AND 會(huì)匹配 "AND " 但不會(huì)匹配 "AND"

      執(zhí)行場(chǎng)景演示:

      場(chǎng)景1:傳入兩個(gè)條件

      // Mapper調(diào)用
      findUsers("張三", "test@example.com");
      
      // 生成的SQL
      SELECT * FROM user
      WHERE username = '張三'  -- 開頭的 "AND " 被去除了
        AND email = 'test@example.com'
      

      場(chǎng)景2:只傳入第二個(gè)條件

      // Mapper調(diào)用
      findUsers(null, "test@example.com");
      
      // 生成的SQL
      SELECT * FROM user
      WHERE email = 'test@example.com'  -- 開頭的 "AND " 被去除
      

      場(chǎng)景3:不傳任何條件

      // Mapper調(diào)用
      findUsers(null, null);
      
      // 生成的SQL
      SELECT * FROM user
      -- 沒有 WHERE 子句(trim內(nèi)容為空)
      

      實(shí)際應(yīng)用場(chǎng)景:

      示例1:模擬 <where> 標(biāo)簽

      <!-- 使用 <where> 標(biāo)簽(簡(jiǎn)潔)-->
      <select id="findUsers1" resultType="User">
          SELECT * FROM user
          <where>
              <if test="username != null">AND username = #{username}</if>
              <if test="email != null">AND email = #{email}</if>
          </where>
      </select>
      
      <!-- 使用 <trim> 實(shí)現(xiàn)相同效果(更靈活)-->
      <select id="findUsers2" resultType="User">
          SELECT * FROM user
          <trim prefix="WHERE" prefixOverrides="AND |OR ">
              <if test="username != null">AND username = #{username}</if>
              <if test="email != null">AND email = #{email}</if>
          </trim>
      </select>
      

      示例2:模擬 <set> 標(biāo)簽

      <!-- 使用 <set> 標(biāo)簽(簡(jiǎn)潔)-->
      <update id="updateUser1">
          UPDATE user
          <set>
              <if test="username != null">username = #{username},</if>
              <if test="email != null">email = #{email},</if>
          </set>
          WHERE id = #{id}
      </update>
      
      <!-- 使用 <trim> 實(shí)現(xiàn)相同效果(更靈活)-->
      <update id="updateUser2">
          UPDATE user
          <trim prefix="SET" suffixOverrides=",">
              <if test="username != null">username = #{username},</if>
              <if test="email != null">email = #{email},</if>
          </trim>
          WHERE id = #{id}
      </update>
      

      示例3:動(dòng)態(tài)生成 IN 子句

      <select id="findByConditions" resultType="User">
          SELECT * FROM user
          WHERE status = 'active'
          <trim prefix="AND id IN" prefixOverrides="," suffix=")">
              <if test="ids != null and ids.size() > 0">
                  (
                  <foreach collection="ids" item="id" separator=",">
                      #{id}
                  </foreach>
              </if>
          </trim>
      </select>
      

      執(zhí)行場(chǎng)景:

      // 傳入ID列表
      findByConditions(Arrays.asList(1L, 2L, 3L));
      
      // 生成的SQL
      SELECT * FROM user
      WHERE status = 'active'
        AND id IN (1, 2, 3)
      
      // 不傳ID
      findByConditions(null);
      
      // 生成的SQL
      SELECT * FROM user
      WHERE status = 'active'
      -- 沒有 AND id IN 部分
      

      示例4:復(fù)雜的 OR 條件

      <select id="complexSearch" resultType="User">
          SELECT * FROM user
          <trim prefix="WHERE" prefixOverrides="AND |OR ">
              <if test="id != null">
                  AND id = #{id}
              </if>
              <trim prefix="AND (" suffix=")" prefixOverrides="OR ">
                  <if test="username != null">
                      OR username LIKE CONCAT('%', #{username}, '%')
                  </if>
                  <if test="email != null">
                      OR email LIKE CONCAT('%', #{email}, '%')
                  </if>
              </trim>
          </trim>
      </select>
      

      執(zhí)行場(chǎng)景:

      // Mapper調(diào)用
      complexSearch(null, "張三", "test@example.com");
      
      // 生成的SQL
      SELECT * FROM user
      WHERE (
          username LIKE CONCAT('%', '張三', '%')  -- 開頭的 OR 被去除
          OR email LIKE CONCAT('%', 'test@example.com', '%')
      )
      

      prefixOverridessuffixOverrides 的匹配規(guī)則:

      <!-- 示例1:去除 "AND " 和 "OR "(注意空格)-->
      <trim prefixOverrides="AND |OR ">
          AND username = 'test'  <!-- 匹配成功,去除 "AND " -->
          OR email = 'test'      <!-- 匹配成功,去除 "OR " -->
          ANDOR status = 'active' <!-- 不匹配(沒有空格)-->
      </trim>
      
      <!-- 示例2:去除逗號(hào) -->
      <trim suffixOverrides=",">
          username = 'test',
          email = 'test',  <!-- 最后的逗號(hào)會(huì)被去除 -->
      </trim>
      
      <!-- 示例3:去除多個(gè)可能的后綴 -->
      <trim suffixOverrides=", |AND |OR ">
          username = 'test',   <!-- 去除逗號(hào)+空格 -->
          email = 'test' AND   <!-- 去除 " AND" -->
          age = 25 OR          <!-- 去除 " OR" -->
      </trim>
      

      標(biāo)簽對(duì)比總結(jié):

      標(biāo)簽 適用場(chǎng)景 靈活性 推薦度
      <where> 動(dòng)態(tài)WHERE條件 低(固定功能) ?????(最常用)
      <set> 動(dòng)態(tài)UPDATE 低(固定功能) ?????(最常用)
      <trim> 任意動(dòng)態(tài)拼接 高(完全自定義) ???(復(fù)雜場(chǎng)景)

      選擇建議:

      • ? 優(yōu)先使用 <where><set>(簡(jiǎn)潔明了)
      • ? 需要自定義前綴/后綴時(shí)才用 <trim>(更靈活但復(fù)雜)
      • ? <trim> 可以實(shí)現(xiàn) <where><set> 的所有功能,但代碼可讀性較差

      完整示例:綜合使用

      <select id="advancedSearch" resultType="User">
          SELECT * FROM user
          <trim prefix="WHERE" prefixOverrides="AND |OR ">
              <!-- 基礎(chǔ)條件 -->
              <if test="status != null">
                  AND status = #{status}
              </if>
              
              <!-- 復(fù)雜OR條件組 -->
              <if test="keyword != null and keyword != ''">
                  AND (
                      username LIKE CONCAT('%', #{keyword}, '%')
                      OR email LIKE CONCAT('%', #{keyword}, '%')
                  )
              </if>
              
              <!-- 時(shí)間范圍 -->
              <if test="startDate != null">
                  AND created_at >= #{startDate}
              </if>
              <if test="endDate != null">
                  AND created_at &lt;= #{endDate}
              </if>
              
              <!-- 年齡范圍 -->
              <trim prefix="AND age" prefixOverrides="IN ">
                  <if test="ages != null and ages.size() > 0">
                      IN
                      <foreach collection="ages" item="age" open="(" close=")" separator=",">
                          #{age}
                      </foreach>
                  </if>
              </trim>
          </trim>
          ORDER BY created_at DESC
      </select>
      

      總結(jié)要點(diǎn):

      1. <trim> 是最靈活的動(dòng)態(tài)SQL標(biāo)簽,可以替代 <where><set>
      2. prefixOverrides 去除開頭多余內(nèi)容,suffixOverrides 去除結(jié)尾多余內(nèi)容
      3. 豎線 | 表示"或"關(guān)系,可以匹配多個(gè)可能的字符
      4. 空格很重要"AND " 只匹配 "AND加空格"
      5. 優(yōu)先使用專用標(biāo)簽<where><set>),復(fù)雜場(chǎng)景再考慮 <trim>

      7. 結(jié)果映射

      ?? 如果不加 resultType 或 resultMap 會(huì)怎么樣?

      核心規(guī)則:

      • <select> 語句:必須指定 resultType 或 resultMap(否則報(bào)錯(cuò))
      • <insert><update><delete> 語句:不需要 resultType/resultMap(返回受影響行數(shù))

      情況1:<select> 不加 resultType/resultMap ?

      <!-- ? 錯(cuò)誤示例 -->
      <select id="findById">
          SELECT * FROM user WHERE id = #{id}
      </select>
      

      結(jié)果:編譯或運(yùn)行時(shí)報(bào)錯(cuò)!

      org.apache.ibatis.executor.ExecutorException: 
      A query was run and no Result Maps were found for the Mapped Statement 
      'com.example.demo.mapper.UserMapper.findById'.
      

      原因:

      • MyBatis 不知道如何將查詢結(jié)果映射到 Java 對(duì)象
      • 必須明確指定返回類型

      正確做法:

      <!-- ? 方式1:使用 resultType -->
      <select id="findById" resultType="User">
          SELECT * FROM user WHERE id = #{id}
      </select>
      
      <!-- ? 方式2:使用 resultMap -->
      <select id="findById" resultMap="UserResultMap">
          SELECT * FROM user WHERE id = #{id}
      </select>
      

      情況2:<insert><update><delete> 不加 resultType/resultMap ?

      <!-- ? 正確:增刪改操作不需要 resultType/resultMap -->
      <insert id="insert">
          INSERT INTO user (username, email, age) 
          VALUES (#{username}, #{email}, #{age})
      </insert>
      
      <update id="update">
          UPDATE user SET username = #{username} WHERE id = #{id}
      </update>
      
      <delete id="deleteById">
          DELETE FROM user WHERE id = #{id}
      </delete>
      

      返回值:

      • 默認(rèn)返回 intlong(受影響的行數(shù))
      • Mapper 接口方法的返回類型通常是 int
      // Mapper 接口
      public interface UserMapper {
          int insert(User user);      // 返回插入的行數(shù)(通常是1)
          int update(User user);      // 返回更新的行數(shù)
          int deleteById(Long id);    // 返回刪除的行數(shù)
      }
      

      執(zhí)行示例:

      // 插入1條記錄
      int rows = userMapper.insert(user);
      System.out.println(rows);  // 輸出:1
      
      // 批量刪除3條記錄
      int deletedRows = userMapper.deleteByStatus("inactive");
      System.out.println(deletedRows);  // 輸出:3
      
      // 更新操作,但沒有記錄被更新(WHERE條件不匹配)
      int updatedRows = userMapper.updateById(999L);
      System.out.println(updatedRows);  // 輸出:0
      

      情況3:<select> 返回值類型與 resultType 不匹配

      <!-- Mapper 接口定義 -->
      List<User> findAll();
      
      <!-- ? 錯(cuò)誤:返回類型應(yīng)該是集合,但 resultType 寫的是 List -->
      <select id="findAll" resultType="List">
          SELECT * FROM user
      </select>
      
      <!-- ? 正確:resultType 指定集合的元素類型,不是集合本身 -->
      <select id="findAll" resultType="User">
          SELECT * FROM user
      </select>
      

      重要規(guī)則:

      • Mapper 接口方法返回 List<User>
      • XML 中 resultType 寫 User(元素類型),不是 List
      • MyBatis 會(huì)自動(dòng)根據(jù)方法簽名判斷是返回單個(gè)對(duì)象還是集合

      對(duì)應(yīng)關(guān)系:

      Mapper 方法返回類型 XML resultType 說明
      User findById(Long id) User 返回單個(gè)對(duì)象
      List<User> findAll() User 返回集合,resultType寫元素類型
      Map<String, Object> findAsMap() maphashmap 返回Map
      List<Map<String, Object>> findAllAsMap() map 返回Map集合
      int count() int 或不寫 返回基本類型
      Long countUsers() long 或不寫 返回基本類型

      情況4:特殊的返回類型

      返回 Map:

      <!-- Mapper 接口 -->
      Map<String, Object> findByIdAsMap(@Param("id") Long id);
      
      <!-- XML 配置 -->
      <select id="findByIdAsMap" resultType="map">
          SELECT id, username, email, age FROM user WHERE id = #{id}
      </select>
      

      返回基本類型:

      <!-- Mapper 接口 -->
      int count();
      Long maxId();
      String findUsernameById(@Param("id") Long id);
      
      <!-- XML 配置 -->
      <select id="count" resultType="int">
          SELECT COUNT(*) FROM user
      </select>
      
      <select id="maxId" resultType="long">
          SELECT MAX(id) FROM user
      </select>
      
      <select id="findUsernameById" resultType="string">
          SELECT username FROM user WHERE id = #{id}
      </select>
      

      MyBatis 內(nèi)置的類型別名:

      Java 類型 MyBatis 別名
      int / Integer int, integer
      long / Long long
      String string
      boolean / Boolean boolean
      float / Float float
      double / Double double
      BigDecimal bigdecimal
      Date date
      Map map, hashmap
      List list, arraylist

      情況5:同時(shí)指定 resultType 和 resultMap ?

      <!-- ? 錯(cuò)誤:不能同時(shí)使用 -->
      <select id="findById" resultType="User" resultMap="UserResultMap">
          SELECT * FROM user WHERE id = #{id}
      </select>
      

      結(jié)果:報(bào)錯(cuò)!

      Cannot specify both resultType and resultMap in the same statement
      

      正確做法:只能二選一

      <!-- ? 方式1:簡(jiǎn)單映射用 resultType -->
      <select id="findById" resultType="User">
          SELECT * FROM user WHERE id = #{id}
      </select>
      
      <!-- ? 方式2:復(fù)雜映射用 resultMap -->
      <select id="findById" resultMap="UserResultMap">
          SELECT * FROM user WHERE id = #{id}
      </select>
      

      總結(jié)對(duì)比表:

      操作類型 是否需要 resultType/resultMap 返回值 示例
      <select> ? 必須 對(duì)象、集合、基本類型、Map resultType="User"
      <insert> ? 不需要 受影響行數(shù)(int) 插入1條返回1
      <update> ? 不需要 受影響行數(shù)(int) 更新3條返回3
      <delete> ? 不需要 受影響行數(shù)(int) 刪除5條返回5

      選擇 resultType 還是 resultMap?

      場(chǎng)景 推薦使用 原因
      字段名一致(或開啟駝峰轉(zhuǎn)換) resultType 簡(jiǎn)單自動(dòng)映射
      字段名不一致 resultMap 手動(dòng)指定映射關(guān)系
      一對(duì)一關(guān)聯(lián) resultMap + <association> 需要嵌套映射
      一對(duì)多關(guān)聯(lián) resultMap + <collection> 需要集合映射
      多對(duì)多關(guān)聯(lián) resultMap + <collection> 需要復(fù)雜映射
      返回 Map resultType="map" 動(dòng)態(tài)列

      7.1 自動(dòng)映射(字段名一致)

      <!-- 當(dāng)數(shù)據(jù)庫(kù)字段和實(shí)體類屬性完全一致時(shí) -->
      <select id="findById" resultType="User">
          SELECT id, username, email, age FROM user WHERE id = #{id}
      </select>
      

      7.2 ResultMap(自定義映射)

      <resultMap id="UserResultMap" type="com.example.demo.domain.User">
          <!-- id: 主鍵映射 -->
          <id property="id" column="user_id"/>
          
          <!-- result: 普通字段映射 -->
          <result property="username" column="user_name"/>
          <result property="email" column="user_email"/>
          <result property="age" column="user_age"/>
          <result property="createdAt" column="gmt_create"/>
      </resultMap>
      
      <select id="findById" resultMap="UserResultMap">
          SELECT user_id, user_name, user_email, user_age, gmt_create
          FROM t_user
          WHERE user_id = #{id}
      </select>
      

      7.3 一對(duì)一映射(association)

      <!-- 用戶信息表 -->
      <resultMap id="UserDetailMap" type="com.example.demo.domain.User">
          <id property="id" column="id"/>
          <result property="username" column="username"/>
          
          <!-- association: 一對(duì)一關(guān)聯(lián) -->
          <association property="profile" javaType="com.example.demo.domain.UserProfile">
              <id property="id" column="profile_id"/>
              <result property="realName" column="real_name"/>
              <result property="phone" column="phone"/>
              <result property="address" column="address"/>
          </association>
      </resultMap>
      
      <select id="findUserWithProfile" resultMap="UserDetailMap">
          SELECT 
              u.id, u.username,
              p.id AS profile_id, p.real_name, p.phone, p.address
          FROM user u
          LEFT JOIN user_profile p ON u.id = p.user_id
          WHERE u.id = #{id}
      </select>
      

      7.4 一對(duì)多映射(collection)

      <resultMap id="UserWithOrdersMap" type="com.example.demo.domain.User">
          <id property="id" column="id"/>
          <result property="username" column="username"/>
          
          <!-- collection: 一對(duì)多關(guān)聯(lián) -->
          <collection property="orders" ofType="com.example.demo.domain.Order">
              <id property="id" column="order_id"/>
              <result property="orderNo" column="order_no"/>
              <result property="totalAmount" column="total_amount"/>
              <result property="status" column="status"/>
          </collection>
      </resultMap>
      
      <select id="findUserWithOrders" resultMap="UserWithOrdersMap">
          SELECT 
              u.id, u.username,
              o.id AS order_id, o.order_no, o.total_amount, o.status
          FROM user u
          LEFT JOIN `order` o ON u.id = o.user_id
          WHERE u.id = #{id}
      </select>
      

      7.5 多對(duì)多映射(collection + 中間表)

      場(chǎng)景:學(xué)生和課程的關(guān)系

      • 一個(gè)學(xué)生可以選多門課程
      • 一門課程可以被多個(gè)學(xué)生選擇

      數(shù)據(jù)庫(kù)設(shè)計(jì)(三張表):

      -- 學(xué)生表
      CREATE TABLE student (
          id BIGINT PRIMARY KEY,
          name VARCHAR(50)
      );
      
      -- 課程表
      CREATE TABLE course (
          id BIGINT PRIMARY KEY,
          name VARCHAR(50)
      );
      
      -- 中間表(關(guān)聯(lián)表)
      CREATE TABLE student_course (
          student_id BIGINT,
          course_id BIGINT,
          PRIMARY KEY (student_id, course_id)
      );
      

      Java 實(shí)體類:

      // 學(xué)生類
      public class Student {
          private Long id;
          private String name;
          private List<Course> courses;  // ← 多對(duì)多:集合
      }
      
      // 課程類
      public class Course {
          private Long id;
          private String name;
          private List<Student> students;  // ← 反向關(guān)系(可選)
      }
      

      MyBatis 配置:

      <resultMap id="StudentWithCoursesMap" type="com.example.demo.domain.Student">
          <id property="id" column="id"/>
          <result property="name" column="name"/>
          
          <!-- collection: 多對(duì)多也用 collection -->
          <collection property="courses" ofType="com.example.demo.domain.Course">
              <id property="id" column="course_id"/>
              <result property="name" column="course_name"/>
          </collection>
      </resultMap>
      
      <select id="findStudentWithCourses" resultMap="StudentWithCoursesMap">
          SELECT 
              s.id, s.name,
              c.id AS course_id, c.name AS course_name
          FROM student s
          LEFT JOIN student_course sc ON s.id = sc.student_id  -- ← 第一次 JOIN:連接中間表
          LEFT JOIN course c ON sc.course_id = c.id            -- ← 第二次 JOIN:連接目標(biāo)表
          WHERE s.id = #{id}
      </select>
      

      ?? 關(guān)系映射完整規(guī)律總結(jié)

      ?? 一、從數(shù)據(jù)庫(kù)角度理解

      關(guān)系類型 外鍵位置 表的數(shù)量 示例
      一對(duì)一 A表或B表(任一方) 2張表 用戶 ? 用戶檔案
      一對(duì)多 多的一方(B表) 2張表 用戶 ? 訂單
      多對(duì)多 獨(dú)立的中間表 3張表 學(xué)生 ? 課程

      規(guī)律:

      • 一對(duì)一/一對(duì)多:2張表,直接 JOIN
      • 多對(duì)多:3張表(需要中間表),JOIN 兩次

      ?? 二、從 Java 實(shí)體類角度理解

      關(guān)系類型 屬性類型 記憶技巧
      一對(duì)一 Profile profile; 單個(gè)對(duì)象 - 一個(gè)人一張身份證
      一對(duì)多 List<Order> orders; 集合 - 一個(gè)人多個(gè)訂單
      多對(duì)多 List<Course> courses; 集合 - 一個(gè)學(xué)生多門課

      規(guī)律:

      // 看屬性類型就知道用什么標(biāo)簽
      private Profile profile;        // ← 單個(gè)對(duì)象 → association
      private List<Order> orders;     // ← 集合    → collection
      private List<Course> courses;   // ← 集合    → collection
      

      ?? 三、從 MyBatis 配置角度理解

      關(guān)系類型 標(biāo)簽 類型屬性 完整寫法
      一對(duì)一 <association> javaType="對(duì)象類" <association property="profile" javaType="Profile">
      一對(duì)多 <collection> ofType="元素類" <collection property="orders" ofType="Order">
      多對(duì)多 <collection> ofType="元素類" <collection property="courses" ofType="Course">

      規(guī)律:

      1. association = 單個(gè)關(guān)聯(lián) → 用 javaType(Java類型)
      2. collection = 集合 → 用 ofType(集合里元素的類型)

      記憶口訣:

      一對(duì)一 → association → javaType    (單個(gè)Java對(duì)象)
      一對(duì)多 → collection  → ofType      (集合Of某類型)
      多對(duì)多 → collection  → ofType      (集合Of某類型)
      

      ?? 四、從 SQL 角度理解

      關(guān)系類型 JOIN 次數(shù) SQL 模式
      一對(duì)一 1次 JOIN FROM A LEFT JOIN B ON A.id = B.a_id
      一對(duì)多 1次 JOIN FROM A LEFT JOIN B ON A.id = B.a_id
      多對(duì)多 2次 JOIN FROM A LEFT JOIN AB ON A.id = AB.a_id
      LEFT JOIN B ON AB.b_id = B.id

      規(guī)律:

      • 一對(duì)一/一對(duì)多:只連接目標(biāo)表(1次 JOIN)
      • 多對(duì)多:先連中間表,再連目標(biāo)表(2次 JOIN)

      ?? 五、完整對(duì)比示例

      1?? 一對(duì)一(User → Profile)

      // 實(shí)體類
      public class User {
          private Long id;
          private String username;
          private Profile profile;  // ← 單個(gè)對(duì)象
      }
      
      <!-- MyBatis 配置 -->
      <resultMap id="UserDetailMap" type="User">
          <id property="id" column="id"/>
          <result property="username" column="username"/>
          <association property="profile" javaType="Profile">  <!-- ← association + javaType -->
              <id property="id" column="profile_id"/>
              <result property="phone" column="phone"/>
          </association>
      </resultMap>
      
      <!-- SQL:1次 JOIN -->
      <select id="findUserWithProfile" resultMap="UserDetailMap">
          SELECT u.id, u.username, p.id AS profile_id, p.phone
          FROM user u
          LEFT JOIN user_profile p ON u.id = p.user_id  -- ← 直接連目標(biāo)表
          WHERE u.id = #{id}
      </select>
      

      2?? 一對(duì)多(User → Orders)

      // 實(shí)體類
      public class User {
          private Long id;
          private String username;
          private List<Order> orders;  // ← 集合
      }
      
      <!-- MyBatis 配置 -->
      <resultMap id="UserWithOrdersMap" type="User">
          <id property="id" column="id"/>
          <result property="username" column="username"/>
          <collection property="orders" ofType="Order">  <!-- ← collection + ofType -->
              <id property="id" column="order_id"/>
              <result property="orderNo" column="order_no"/>
          </collection>
      </resultMap>
      
      <!-- SQL:1次 JOIN -->
      <select id="findUserWithOrders" resultMap="UserWithOrdersMap">
          SELECT u.id, u.username, o.id AS order_id, o.order_no
          FROM user u
          LEFT JOIN `order` o ON u.id = o.user_id  -- ← 直接連目標(biāo)表
          WHERE u.id = #{id}
      </select>
      

      3?? 多對(duì)多(Student → Courses)

      // 實(shí)體類
      public class Student {
          private Long id;
          private String name;
          private List<Course> courses;  // ← 集合
      }
      
      <!-- MyBatis 配置 -->
      <resultMap id="StudentWithCoursesMap" type="Student">
          <id property="id" column="id"/>
          <result property="name" column="name"/>
          <collection property="courses" ofType="Course">  <!-- ← collection + ofType -->
              <id property="id" column="course_id"/>
              <result property="name" column="course_name"/>
          </collection>
      </resultMap>
      
      <!-- SQL:2次 JOIN(關(guān)鍵區(qū)別!)-->
      <select id="findStudentWithCourses" resultMap="StudentWithCoursesMap">
          SELECT s.id, s.name, c.id AS course_id, c.name AS course_name
          FROM student s
          LEFT JOIN student_course sc ON s.id = sc.student_id  -- ← 第1次:連中間表
          LEFT JOIN course c ON sc.course_id = c.id            -- ← 第2次:連目標(biāo)表
          WHERE s.id = #{id}
      </select>
      

      ?? 六、終極記憶法

      ?? 方法1:看Java屬性類型

      private Profile profile;        → association + javaType
      private List<Order> orders;     → collection + ofType
      private List<Course> courses;   → collection + ofType
      

      ?? 方法2:看表的數(shù)量

      2張表(一對(duì)一/一對(duì)多)→ 1次 JOIN
      3張表(多對(duì)多)        → 2次 JOIN(中間表是核心)
      

      ?? 方法3:看外鍵位置

      一對(duì)一 → 外鍵在任一表     → 直接 JOIN
      一對(duì)多 → 外鍵在"多"的表   → 直接 JOIN
      多對(duì)多 → 外鍵在中間表     → 2次 JOIN
      

      ?? 方法4:口訣

      單個(gè)對(duì)象 association,多個(gè)對(duì)象 collection
      javaType 定整體,ofType 定元素
      一二直連目標(biāo)表,多多要過中間表
      

      ?? 七、常見錯(cuò)誤對(duì)比

      錯(cuò)誤寫法 正確寫法 說明
      <association ofType="Order"> <collection ofType="Order"> 集合不能用 association
      <collection javaType="List"> <collection ofType="Order"> collection 用 ofType
      <association javaType="List"> <association javaType="Profile"> association 指定具體類

      ?? 八、快速判斷流程圖

      看 Java 實(shí)體類屬性
             ↓
         是集合嗎?
            /  \
          是    否
          ↓     ↓
      collection  association
          ↓           ↓
       ofType    javaType
          ↓           ↓
      看表數(shù)量      1次JOIN
        /  \
      2張  3張
       ↓    ↓
      一對(duì)多 多對(duì)多
      1次   2次
      JOIN  JOIN
      

      ?? 九、實(shí)戰(zhàn)對(duì)照表

      業(yè)務(wù)場(chǎng)景 關(guān)系 Java類型 標(biāo)簽 JOIN次數(shù)
      用戶-檔案 一對(duì)一 Profile association 1次
      用戶-訂單 一對(duì)多 List<Order> collection 1次
      學(xué)生-課程 多對(duì)多 List<Course> collection 2次
      訂單-訂單詳情 一對(duì)多 List<OrderItem> collection 1次
      文章-標(biāo)簽 多對(duì)多 List<Tag> collection 2次
      用戶-錢包 一對(duì)一 Wallet association 1次

      ?? 十、單向關(guān)聯(lián) vs 雙向關(guān)聯(lián)

      什么是單向關(guān)聯(lián)和雙向關(guān)聯(lián)?

      在關(guān)系映射中,可以選擇單向關(guān)聯(lián)(只能從一方訪問另一方)或雙向關(guān)聯(lián)(互相訪問)。


      10.1 一對(duì)一關(guān)系的單向 vs 雙向

      單向關(guān)聯(lián)(User → Profile)
      // 用戶類
      public class User {
          private Long id;
          private String username;
          private UserProfile profile;  // ← 可以訪問檔案
      }
      
      // 檔案類
      public class UserProfile {
          private Long id;
          private Long userId;      // ← 只有外鍵ID
          private String realName;
          // 沒有 User 對(duì)象
      }
      

      特點(diǎn):

      • 只能從 User 訪問 Profile
      • Profile 不能直接訪問 User(只有 userId

      使用:

      User user = userMapper.findUserWithProfile(1L);
      System.out.println(user.getProfile().getPhone());  // ? 可以
      
      UserProfile profile = profileMapper.findById(1L);
      System.out.println(profile.getUser().getUsername());  // ? 不行!沒有 user 對(duì)象
      

      雙向關(guān)聯(lián)(User ? Profile)
      // 用戶類
      public class User {
          private Long id;
          private String username;
          private UserProfile profile;  // ← 可以訪問檔案
      }
      
      // 檔案類
      public class UserProfile {
          private Long id;
          private Long userId;      // ← 保留外鍵ID
          private String realName;
          private User user;        // ← 反向引用:可以訪問用戶
      }
      

      特點(diǎn):

      • User 可以訪問 Profile
      • Profile 也可以訪問 User(雙向)

      MyBatis 配置(反向查詢):

      <!-- 查詢檔案及其用戶 -->
      <resultMap id="ProfileWithUserMap" type="UserProfile">
          <id property="id" column="id"/>
          <result property="userId" column="user_id"/>
          <result property="realName" column="real_name"/>
          
          <!-- 反向關(guān)聯(lián)用戶 -->
          <association property="user" javaType="User">
              <id property="id" column="user_id"/>
              <result property="username" column="username"/>
          </association>
      </resultMap>
      
      <select id="findProfileWithUser" resultMap="ProfileWithUserMap">
          SELECT 
              p.id, p.user_id, p.real_name,
              u.username
          FROM user_profile p
          LEFT JOIN user u ON p.user_id = u.id
          WHERE p.id = #{id}
      </select>
      

      使用:

      // 正向查詢
      User user = userMapper.findUserWithProfile(1L);
      System.out.println(user.getProfile().getPhone());  // ? 可以
      
      // 反向查詢
      UserProfile profile = profileMapper.findProfileWithUser(1L);
      System.out.println(profile.getUser().getUsername());  // ? 可以!
      

      10.2 一對(duì)多關(guān)系的單向 vs 雙向

      單向關(guān)聯(lián)(User → Orders)
      // 用戶類
      public class User {
          private Long id;
          private String username;
          private List<Order> orders;  // ← 可以訪問訂單列表
      }
      
      // 訂單類
      public class Order {
          private Long id;
          private Long userId;     // ← 只有外鍵ID
          private String orderNo;
          // 沒有 User 對(duì)象
      }
      

      特點(diǎn):

      • 只能從 User 訪問 Orders
      • Order 不能直接訪問 User

      雙向關(guān)聯(lián)(User ? Orders)
      // 用戶類
      public class User {
          private Long id;
          private String username;
          private List<Order> orders;  // ← 可以訪問訂單列表
      }
      
      // 訂單類
      public class Order {
          private Long id;
          private Long userId;     // ← 保留外鍵ID
          private String orderNo;
          private User user;       // ← 反向引用:可以訪問用戶
      }
      

      MyBatis 配置(反向查詢):

      <!-- 查詢訂單及其用戶 -->
      <resultMap id="OrderWithUserMap" type="Order">
          <id property="id" column="id"/>
          <result property="userId" column="user_id"/>
          <result property="orderNo" column="order_no"/>
          
          <!-- 多對(duì)一:反向關(guān)聯(lián)用戶(注意用 association,不是 collection)-->
          <association property="user" javaType="User">
              <id property="id" column="user_id"/>
              <result property="username" column="username"/>
          </association>
      </resultMap>
      
      <select id="findOrderWithUser" resultMap="OrderWithUserMap">
          SELECT 
              o.id, o.user_id, o.order_no,
              u.username
          FROM `order` o
          LEFT JOIN user u ON o.user_id = u.id
          WHERE o.id = #{id}
      </select>
      

      關(guān)鍵點(diǎn): 反向查詢(多 → 一)用的是 <association>,不是 <collection>

      使用場(chǎng)景:

      // 訂單列表:顯示每個(gè)訂單的下單用戶
      List<Order> orders = orderMapper.findAllOrdersWithUser();
      for (Order order : orders) {
          System.out.println(order.getOrderNo() + " - " + order.getUser().getUsername());
      }
      
      // 輸出:
      // ORD001 - zhangsan
      // ORD002 - lisi
      

      10.3 多對(duì)多關(guān)系的單向 vs 雙向

      單向關(guān)聯(lián)(Student → Courses)
      // 學(xué)生類
      public class Student {
          private Long id;
          private String name;
          private List<Course> courses;  // ← 可以訪問課程列表
      }
      
      // 課程類
      public class Course {
          private Long id;
          private String name;
          // 沒有 students 列表
      }
      

      雙向關(guān)聯(lián)(Student ? Courses)
      // 學(xué)生類
      public class Student {
          private Long id;
          private String name;
          private List<Course> courses;  // ← 學(xué)生的課程列表
      }
      
      // 課程類
      public class Course {
          private Long id;
          private String name;
          private List<Student> students;  // ← 反向:選這門課的學(xué)生列表
      }
      

      MyBatis 配置(反向查詢):

      <!-- 查詢課程及選課學(xué)生 -->
      <resultMap id="CourseWithStudentsMap" type="Course">
          <id property="id" column="id"/>
          <result property="name" column="name"/>
          
          <!-- 反向:課程的學(xué)生列表 -->
          <collection property="students" ofType="Student">
              <id property="id" column="student_id"/>
              <result property="name" column="student_name"/>
          </collection>
      </resultMap>
      
      <select id="findCourseWithStudents" resultMap="CourseWithStudentsMap">
          SELECT 
              c.id, c.name,
              s.id AS student_id, s.name AS student_name
          FROM course c
          LEFT JOIN student_course sc ON c.id = sc.course_id
          LEFT JOIN student s ON sc.student_id = s.id
          WHERE c.id = #{id}
      </select>
      

      10.4 雙向關(guān)聯(lián)的循環(huán)引用問題

      問題:JSON 序列化死循環(huán)
      // 雙向關(guān)聯(lián)
      public class User {
          private List<Order> orders;
      }
      
      public class Order {
          private User user;
      }
      
      // 查詢并返回給前端
      @GetMapping("/users/{id}")
      public Result getUser(@PathVariable Long id) {
          User user = userMapper.findUserWithOrders(id);
          return Result.success(user);  // ? 會(huì)死循環(huán)!
      }
      

      循環(huán)引用結(jié)構(gòu):

      user {
          orders: [
              order1 {
                  user: {
                      orders: [
                          order1 { user: { ... } }  // 無限循環(huán)
                      ]
                  }
              }
          ]
      }
      

      解決方案1:使用 @JsonIgnore
      public class Order {
          private Long id;
          private String orderNo;
          
          @JsonIgnore  // ← 序列化時(shí)忽略 user 字段
          private User user;
      }
      

      序列化結(jié)果:

      {
        "id": 1,
        "username": "zhangsan",
        "orders": [
          {
            "id": 101,
            "orderNo": "ORD001"
          }
        ]
      }
      

      解決方案2:使用 @JsonManagedReference 和 @JsonBackReference
      public class User {
          @JsonManagedReference  // ← 主引用(會(huì)序列化)
          private List<Order> orders;
      }
      
      public class Order {
          @JsonBackReference  // ← 反向引用(會(huì)被忽略)
          private User user;
      }
      

      解決方案3:使用 DTO(最佳實(shí)踐)
      // 實(shí)體類(內(nèi)部使用,可以雙向)
      public class User {
          private List<Order> orders;
      }
      
      public class Order {
          private User user;
      }
      
      // DTO 類(返回給前端,單向)
      public class UserDTO {
          private Long id;
          private String username;
          private List<OrderDTO> orders;
      }
      
      public class OrderDTO {
          private Long id;
          private String orderNo;
          // 沒有 user 字段,打斷循環(huán)
      }
      
      // Controller
      @GetMapping("/users/{id}")
      public Result getUser(@PathVariable Long id) {
          User user = userMapper.findUserWithOrders(id);
          UserDTO dto = convertToDTO(user);  // 轉(zhuǎn)換
          return Result.success(dto);
      }
      

      10.6 何時(shí)使用單向,何時(shí)使用雙向?

      推薦使用單向關(guān)聯(lián):

      ? 大多數(shù)情況(默認(rèn)選擇)
      ? 訪問方向明確(總是從主表查從表)
      ? 反向查詢需求少
      ? 需要返回 JSON API(避免循環(huán)引用)
      ? 追求簡(jiǎn)單性

      示例場(chǎng)景:

      • 用戶 → 檔案(總是查用戶順便看檔案)
      • 文章 → 作者(總是看文章是誰寫的)

      推薦使用雙向關(guān)聯(lián):

      ? 雙向訪問需求頻繁
      ? 兩個(gè)方向同樣重要
      ? 內(nèi)部業(yè)務(wù)邏輯(不直接返回給前端)
      ? 有完善的循環(huán)引用處理機(jī)制

      示例場(chǎng)景:

      • 訂單 ? 用戶(既查用戶的訂單,也查訂單的用戶)
      • 評(píng)論 ? 用戶(既查用戶的評(píng)論,也查評(píng)論的作者)
      • 員工 ? 部門(既查員工的部門,也查部門的員工)

      10.7 關(guān)系映射方向總結(jié)

      一對(duì)一關(guān)系:
      方向 User 類 UserProfile 類 MyBatis 標(biāo)簽
      單向 UserProfile profile Long userId <association>
      雙向 UserProfile profile Long userId + User user 兩邊都配 <association>

      一對(duì)多關(guān)系:
      方向 User 類 Order 類 MyBatis 標(biāo)簽
      單向 List<Order> orders Long userId <collection>
      雙向 List<Order> orders Long userId + User user User 用 <collection>
      Order 用 <association>

      關(guān)鍵: 反向(多 → 一)用 <association>,不是 <collection>


      多對(duì)多關(guān)系:
      方向 Student 類 Course 類 MyBatis 標(biāo)簽
      單向 List<Course> courses <collection>
      雙向 List<Course> courses List<Student> students 兩邊都用 <collection>

      10.8 最佳實(shí)踐建議

      1. 默認(rèn)使用單向關(guān)聯(lián)

        // 簡(jiǎn)單、安全、推薦
        public class Order {
            private Long userId;  // 只存外鍵ID
        }
        
      2. 需要雙向時(shí)添加反向引用

        // 按需添加
        public class Order {
            private Long userId;
            private User user;  // 反向引用
        }
        
      3. 雙向關(guān)聯(lián)必須處理循環(huán)引用

        // 使用 @JsonIgnore 或 DTO
        @JsonIgnore
        private User user;
        
      4. 按查詢需求定義 ResultMap

        <!-- 查詢1:只查用戶和訂單號(hào)(不包含 user) -->
        <resultMap id="UserWithOrdersSimple" type="User">
            <collection property="orders" ofType="Order">
                <result property="orderNo" column="order_no"/>
            </collection>
        </resultMap>
        
        <!-- 查詢2:查訂單和用戶信息(包含 user) -->
        <resultMap id="OrderWithUser" type="Order">
            <association property="user" javaType="User">
                <result property="username" column="username"/>
            </association>
        </resultMap>
        
      5. 記憶口訣

        單向簡(jiǎn)單又安全,雙向功能更強(qiáng)大
        默認(rèn)單向是首選,需要雙向再添加
        雙向一定防循環(huán),@JsonIgnore 或 DTO
        多對(duì)一用 association,一對(duì)多用 collection
        

      8. 參數(shù)傳遞

      8.1 單個(gè)參數(shù)

      // Mapper 接口
      User findById(Long id);
      String findUsername(Long id);
      
      // XML
      <select id="findById" parameterType="long" resultType="User">
          SELECT * FROM user WHERE id = #{id}
      </select>
      

      8.2 多個(gè)參數(shù)(@Param)

      // Mapper 接口
      List<User> findByCondition(@Param("username") String username, 
                                 @Param("minAge") Integer minAge);
      
      // XML
      <select id="findByCondition" resultType="User">
          SELECT * FROM user 
          WHERE username = #{username} AND age >= #{minAge}
      </select>
      

      @Param 注解詳解:

      @Param 用于指定參數(shù)在 SQL 中的名稱,讓 MyBatis 知道如何映射參數(shù)。

      ?? 什么時(shí)候必須使用 @Param?

      1. 多個(gè)參數(shù)時(shí)(推薦總是加)
      // ? 不加 @Param - 會(huì)報(bào)錯(cuò)或無法識(shí)別
      User findByUsernameAndAge(String username, Integer age);
      
      // ? 加 @Param - 明確指定參數(shù)名
      User findByUsernameAndAge(@Param("username") String username, 
                                @Param("age") Integer age);
      

      對(duì)應(yīng) XML:

      <select id="findByUsernameAndAge" resultType="User">
          SELECT * FROM user 
          WHERE username = #{username} AND age = #{age}
      </select>
      
      1. 動(dòng)態(tài) SQL 中需要引用參數(shù)名
      List<User> findUsers(@Param("username") String username, 
                           @Param("minAge") Integer minAge);
      
      <select id="findUsers" resultType="User">
          SELECT * FROM user
          <where>
              <if test="username != null">
                  AND username = #{username}
              </if>
              <if test="minAge != null">
                  AND age >= #{minAge}
              </if>
          </where>
      </select>
      

      ?? 什么時(shí)候可以省略 @Param?

      1. 單個(gè)參數(shù)(簡(jiǎn)單類型)
      // 可以省略 @Param
      User findById(Long id);
      List<User> findByAge(Integer age);
      
      // XML 中參數(shù)名可以隨意寫(建議保持一致)
      <select id="findById" resultType="User">
          SELECT * FROM user WHERE id = #{id}
          <!-- 也可以寫 #{userId}、#{value}、#{_parameter} -->
      </select>
      
      1. 單個(gè)參數(shù)(對(duì)象類型)
      // 可以省略 @Param
      int insert(User user);
      
      <insert id="insert">
          INSERT INTO user (username, email, age)
          VALUES (#{username}, #{email}, #{age})
          <!-- 直接使用對(duì)象的屬性名 -->
      </insert>
      

      ?? @Param 使用對(duì)比表

      場(chǎng)景 是否需要 @Param 示例
      單個(gè)簡(jiǎn)單參數(shù) ? 不需要 findById(Long id)
      單個(gè)對(duì)象參數(shù) ? 不需要 insert(User user)
      多個(gè)參數(shù) ? 必須 find(@Param("name") String name, @Param("age") int age)
      動(dòng)態(tài)SQL引用參數(shù) ? 必須 <if test="name != null">
      集合參數(shù) ?? 建議加 findByIds(@Param("ids") List<Long> ids)

      ?? 不加 @Param 的默認(rèn)規(guī)則

      MyBatis 會(huì)使用以下默認(rèn)參數(shù)名:

      // 方法定義
      List<User> findByCondition(String username, Integer age);
      
      // 不加 @Param 時(shí),MyBatis 使用默認(rèn)名稱
      // param1, param2, param3... 或 arg0, arg1, arg2...
      

      對(duì)應(yīng) XML(不推薦):

      <select id="findByCondition" resultType="User">
          SELECT * FROM user 
          WHERE username = #{param1} AND age = #{param2}
          <!-- 或者 #{arg0} 和 #{arg1} -->
      </select>
      

      ? 這種方式可讀性差,強(qiáng)烈不推薦!

      ?? 最佳實(shí)踐

      建議:只要是 Mapper 方法,都加上 @Param

      @Mapper
      public interface UserMapper {
          
          // ? 單個(gè)參數(shù)也加,更清晰
          User findById(@Param("id") Long id);
          
          // ? 多個(gè)參數(shù)必須加
          List<User> findByCondition(@Param("username") String username, 
                                     @Param("age") Integer age);
          
          // ? 對(duì)象參數(shù)可以不加,但加了更明確
          int insert(@Param("user") User user);
          
          // ? 集合參數(shù)建議加
          int batchInsert(@Param("users") List<User> users);
          List<User> findByIds(@Param("ids") List<Long> ids);
      }
      

      原因:

      • ? 代碼可讀性更好
      • ? XML 中參數(shù)名更明確
      • ? 避免升級(jí) MyBatis 版本導(dǎo)致的兼容問題
      • ? 團(tuán)隊(duì)開發(fā)統(tǒng)一規(guī)范

      8.3 對(duì)象參數(shù)

      // Mapper 接口
      int insert(User user);
      
      // XML
      <insert id="insert" parameterType="User">
          INSERT INTO user (username, email, age)
          VALUES (#{username}, #{email}, #{age})
      </insert>
      

      8.4 Map參數(shù)

      // Mapper 接口
      List<User> findByMap(Map<String, Object> params);
      
      // XML
      <select id="findByMap" parameterType="map" resultType="User">
          SELECT * FROM user
          WHERE username = #{username} AND age = #{age}
      </select>
      
      // 調(diào)用
      Map<String, Object> params = new HashMap<>();
      params.put("username", "張三");
      params.put("age", 25);
      List<User> users = userMapper.findByMap(params);
      

      8.5 集合參數(shù)

      // Mapper 接口
      int batchInsert(List<User> users);
      List<User> findByIds(@Param("ids") List<Long> ids);
      
      // XML
      <insert id="batchInsert">
          INSERT INTO user (username, email, age) VALUES
          <foreach collection="list" item="user" separator=",">
              (#{user.username}, #{user.email}, #{user.age})
          </foreach>
      </insert>
      
      <select id="findByIds" resultType="User">
          SELECT * FROM user WHERE id IN
          <foreach collection="ids" item="id" open="(" close=")" separator=",">
              #{id}
          </foreach>
      </select>
      

      9. 完整實(shí)戰(zhàn)案例

      9.1 場(chǎng)景:電商訂單系統(tǒng)

      數(shù)據(jù)庫(kù)設(shè)計(jì)

      -- 用戶表
      CREATE TABLE user (
          id BIGINT PRIMARY KEY AUTO_INCREMENT,
          username VARCHAR(50) NOT NULL UNIQUE,
          email VARCHAR(100),
          password VARCHAR(100),
          balance DECIMAL(10, 2) DEFAULT 0.00,
          status VARCHAR(20) DEFAULT 'active',
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      );
      
      -- 商品表
      CREATE TABLE product (
          id BIGINT PRIMARY KEY AUTO_INCREMENT,
          name VARCHAR(100) NOT NULL,
          price DECIMAL(10, 2) NOT NULL,
          stock INT NOT NULL DEFAULT 0,
          category VARCHAR(50)
      );
      
      -- 訂單表
      CREATE TABLE `order` (
          id BIGINT PRIMARY KEY AUTO_INCREMENT,
          order_no VARCHAR(50) NOT NULL UNIQUE,
          user_id BIGINT NOT NULL,
          total_amount DECIMAL(10, 2) NOT NULL,
          status VARCHAR(20) DEFAULT 'pending',
          created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
          FOREIGN KEY (user_id) REFERENCES user(id)
      );
      
      -- 訂單明細(xì)表
      CREATE TABLE order_item (
          id BIGINT PRIMARY KEY AUTO_INCREMENT,
          order_id BIGINT NOT NULL,
          product_id BIGINT NOT NULL,
          product_name VARCHAR(100),
          price DECIMAL(10, 2) NOT NULL,
          quantity INT NOT NULL,
          subtotal DECIMAL(10, 2) NOT NULL,
          FOREIGN KEY (order_id) REFERENCES `order`(id),
          FOREIGN KEY (product_id) REFERENCES product(id)
      );
      
      -- 插入測(cè)試數(shù)據(jù)
      INSERT INTO user (username, email, password, balance) VALUES
      ('張三', 'zhangsan@test.com', '123456', 1000.00),
      ('李四', 'lisi@test.com', '123456', 500.00);
      
      INSERT INTO product (name, price, stock, category) VALUES
      ('iPhone 15', 5999.00, 100, '手機(jī)'),
      ('MacBook Pro', 12999.00, 50, '電腦'),
      ('AirPods', 1299.00, 200, '耳機(jī)');
      

      實(shí)體類

      // User.java
      package com.example.demo.domain;
      
      import lombok.Data;
      import java.math.BigDecimal;
      import java.time.LocalDateTime;
      import java.util.List;
      
      @Data
      public class User {
          private Long id;
          private String username;
          private String email;
          private String password;
          private BigDecimal balance;
          private String status;
          private LocalDateTime createdAt;
      }
      
      // Product.java
      @Data
      public class Product {
          private Long id;
          private String name;
          private BigDecimal price;
          private Integer stock;
          private String category;
      }
      
      // Order.java
      @Data
      public class Order {
          private Long id;
          private String orderNo;
          private Long userId;
          private BigDecimal totalAmount;
          private String status;
          private LocalDateTime createdAt;
          
          // 關(guān)聯(lián)數(shù)據(jù)
          private User user;
          private List<OrderItem> items;
      }
      
      // OrderItem.java
      @Data
      public class OrderItem {
          private Long id;
          private Long orderId;
          private Long productId;
          private String productName;
          private BigDecimal price;
          private Integer quantity;
          private BigDecimal subtotal;
      }
      

      Mapper 接口

      // ProductMapper.java
      package com.example.demo.mapper;
      
      import com.example.demo.domain.Product;
      import org.apache.ibatis.annotations.*;
      import java.util.List;
      
      @Mapper
      public interface ProductMapper {
          
          @Select("SELECT * FROM product WHERE id = #{id}")
          Product findById(@Param("id") Long id);
          
          @Select("SELECT * FROM product WHERE category = #{category}")
          List<Product> findByCategory(@Param("category") String category);
          
          @Update("UPDATE product SET stock = stock - #{quantity} WHERE id = #{id} AND stock >= #{quantity}")
          int reduceStock(@Param("id") Long id, @Param("quantity") Integer quantity);
          
          @Update("UPDATE product SET stock = stock + #{quantity} WHERE id = #{id}")
          int increaseStock(@Param("id") Long id, @Param("quantity") Integer quantity);
      }
      
      // OrderMapper.java
      @Mapper
      public interface OrderMapper {
          
          // XML 實(shí)現(xiàn)
          int insert(Order order);
          
          Order findById(@Param("id") Long id);
          
          Order findByIdWithDetails(@Param("id") Long id);
          
          List<Order> findByUserId(@Param("userId") Long userId);
          
          int updateStatus(@Param("id") Long id, @Param("status") String status);
      }
      
      // OrderItemMapper.java
      @Mapper
      public interface OrderItemMapper {
          
          @Insert("INSERT INTO order_item (order_id, product_id, product_name, price, quantity, subtotal) " +
                  "VALUES (#{orderId}, #{productId}, #{productName}, #{price}, #{quantity}, #{subtotal})")
          @Options(useGeneratedKeys = true, keyProperty = "id")
          int insert(OrderItem item);
          
          @Select("SELECT * FROM order_item WHERE order_id = #{orderId}")
          List<OrderItem> findByOrderId(@Param("orderId") Long orderId);
          
          int batchInsert(@Param("items") List<OrderItem> items);
      }
      

      OrderMapper.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.demo.mapper.OrderMapper">
      
          <!-- 基礎(chǔ)結(jié)果映射 -->
          <resultMap id="BaseResultMap" type="com.example.demo.domain.Order">
              <id property="id" column="id"/>
              <result property="orderNo" column="order_no"/>
              <result property="userId" column="user_id"/>
              <result property="totalAmount" column="total_amount"/>
              <result property="status" column="status"/>
              <result property="createdAt" column="created_at"/>
          </resultMap>
          
          <!-- 完整結(jié)果映射:訂單 + 用戶 + 訂單項(xiàng) -->
          <resultMap id="OrderDetailMap" type="com.example.demo.domain.Order" extends="BaseResultMap">
              <!-- 關(guān)聯(lián)用戶信息 -->
              <association property="user" javaType="com.example.demo.domain.User">
                  <id property="id" column="user_id"/>
                  <result property="username" column="username"/>
                  <result property="email" column="email"/>
              </association>
              
              <!-- 關(guān)聯(lián)訂單項(xiàng)列表 -->
              <collection property="items" ofType="com.example.demo.domain.OrderItem">
                  <id property="id" column="item_id"/>
                  <result property="orderId" column="order_id"/>
                  <result property="productId" column="product_id"/>
                  <result property="productName" column="product_name"/>
                  <result property="price" column="price"/>
                  <result property="quantity" column="quantity"/>
                  <result property="subtotal" column="subtotal"/>
              </collection>
          </resultMap>
          
          <!-- 插入訂單 -->
          <insert id="insert" parameterType="com.example.demo.domain.Order" 
                  useGeneratedKeys="true" keyProperty="id">
              INSERT INTO `order` (order_no, user_id, total_amount, status)
              VALUES (#{orderNo}, #{userId}, #{totalAmount}, #{status})
          </insert>
          
          <!-- 根據(jù)ID查詢訂單 -->
          <select id="findById" parameterType="long" resultMap="BaseResultMap">
              SELECT * FROM `order` WHERE id = #{id}
          </select>
          
          <!-- 查詢訂單詳情(包含用戶和訂單項(xiàng)) -->
          <select id="findByIdWithDetails" parameterType="long" resultMap="OrderDetailMap">
              SELECT 
                  o.id, o.order_no, o.user_id, o.total_amount, o.status, o.created_at,
                  u.username, u.email,
                  oi.id AS item_id, oi.order_id, oi.product_id, 
                  oi.product_name, oi.price, oi.quantity, oi.subtotal
              FROM `order` o
              LEFT JOIN user u ON o.user_id = u.id
              LEFT JOIN order_item oi ON o.id = oi.order_id
              WHERE o.id = #{id}
          </select>
          
          <!-- 根據(jù)用戶ID查詢訂單列表 -->
          <select id="findByUserId" parameterType="long" resultMap="BaseResultMap">
              SELECT * FROM `order` 
              WHERE user_id = #{userId}
              ORDER BY created_at DESC
          </select>
          
          <!-- 更新訂單狀態(tài) -->
          <update id="updateStatus">
              UPDATE `order` 
              SET status = #{status}
              WHERE id = #{id}
          </update>
      
      </mapper>
      

      OrderItemMapper.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.demo.mapper.OrderItemMapper">
      
          <!-- 批量插入訂單項(xiàng) -->
          <insert id="batchInsert">
              INSERT INTO order_item (order_id, product_id, product_name, price, quantity, subtotal)
              VALUES
              <foreach collection="items" item="item" separator=",">
                  (#{item.orderId}, #{item.productId}, #{item.productName}, 
                   #{item.price}, #{item.quantity}, #{item.subtotal})
              </foreach>
          </insert>
      
      </mapper>
      

      Service 層

      package com.example.demo.service;
      
      import com.example.demo.domain.*;
      import com.example.demo.mapper.*;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;
      
      import java.math.BigDecimal;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.UUID;
      
      @Service
      public class OrderService {
          
          @Autowired
          private OrderMapper orderMapper;
          
          @Autowired
          private OrderItemMapper orderItemMapper;
          
          @Autowired
          private ProductMapper productMapper;
          
          @Autowired
          private UserMapper userMapper;
          
          /**
           * 創(chuàng)建訂單(事務(wù)管理)
           */
          @Transactional(rollbackFor = Exception.class)
          public Order createOrder(Long userId, List<OrderItemRequest> itemRequests) {
              // 1. 計(jì)算訂單總額
              BigDecimal totalAmount = BigDecimal.ZERO;
              List<OrderItem> items = new ArrayList<>();
              
              for (OrderItemRequest request : itemRequests) {
                  // 查詢商品
                  Product product = productMapper.findById(request.getProductId());
                  if (product == null) {
                      throw new RuntimeException("商品不存在: " + request.getProductId());
                  }
                  
                  // 檢查庫(kù)存
                  if (product.getStock() < request.getQuantity()) {
                      throw new RuntimeException("庫(kù)存不足: " + product.getName());
                  }
                  
                  // 扣減庫(kù)存
                  int updated = productMapper.reduceStock(product.getId(), request.getQuantity());
                  if (updated == 0) {
                      throw new RuntimeException("庫(kù)存扣減失敗: " + product.getName());
                  }
                  
                  // 計(jì)算小計(jì)
                  BigDecimal subtotal = product.getPrice().multiply(new BigDecimal(request.getQuantity()));
                  totalAmount = totalAmount.add(subtotal);
                  
                  // 構(gòu)建訂單項(xiàng)
                  OrderItem item = new OrderItem();
                  item.setProductId(product.getId());
                  item.setProductName(product.getName());
                  item.setPrice(product.getPrice());
                  item.setQuantity(request.getQuantity());
                  item.setSubtotal(subtotal);
                  items.add(item);
              }
              
              // 2. 創(chuàng)建訂單
              Order order = new Order();
              order.setOrderNo("ORD" + UUID.randomUUID().toString().substring(0, 8).toUpperCase());
              order.setUserId(userId);
              order.setTotalAmount(totalAmount);
              order.setStatus("pending");
              
              orderMapper.insert(order);
              
              // 3. 創(chuàng)建訂單項(xiàng)
              for (OrderItem item : items) {
                  item.setOrderId(order.getId());
              }
              orderItemMapper.batchInsert(items);
              
              // 4. 返回完整訂單信息
              return orderMapper.findByIdWithDetails(order.getId());
          }
          
          /**
           * 查詢訂單詳情
           */
          public Order getOrderDetail(Long orderId) {
              return orderMapper.findByIdWithDetails(orderId);
          }
          
          /**
           * 查詢用戶訂單列表
           */
          public List<Order> getUserOrders(Long userId) {
              return orderMapper.findByUserId(userId);
          }
          
          /**
           * 取消訂單
           */
          @Transactional(rollbackFor = Exception.class)
          public void cancelOrder(Long orderId) {
              Order order = orderMapper.findById(orderId);
              if (order == null) {
                  throw new RuntimeException("訂單不存在");
              }
              
              if (!"pending".equals(order.getStatus())) {
                  throw new RuntimeException("訂單狀態(tài)不允許取消");
              }
              
              // 恢復(fù)庫(kù)存
              List<OrderItem> items = orderItemMapper.findByOrderId(orderId);
              for (OrderItem item : items) {
                  productMapper.increaseStock(item.getProductId(), item.getQuantity());
              }
              
              // 更新訂單狀態(tài)
              orderMapper.updateStatus(orderId, "cancelled");
          }
          
          // DTO 類
          @lombok.Data
          public static class OrderItemRequest {
              private Long productId;
              private Integer quantity;
          }
      }
      

      Controller 層

      package com.example.demo.controller;
      
      import com.example.demo.domain.Order;
      import com.example.demo.service.OrderService;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.*;
      
      import java.util.List;
      
      @RestController
      @RequestMapping("/api/orders")
      public class OrderController {
          
          @Autowired
          private OrderService orderService;
          
          /**
           * 創(chuàng)建訂單
           */
          @PostMapping
          public Order createOrder(@RequestBody CreateOrderRequest request) {
              return orderService.createOrder(request.getUserId(), request.getItems());
          }
          
          /**
           * 查詢訂單詳情
           */
          @GetMapping("/{orderId}")
          public Order getOrderDetail(@PathVariable Long orderId) {
              return orderService.getOrderDetail(orderId);
          }
          
          /**
           * 查詢用戶訂單列表
           */
          @GetMapping("/user/{userId}")
          public List<Order> getUserOrders(@PathVariable Long userId) {
              return orderService.getUserOrders(userId);
          }
          
          /**
           * 取消訂單
           */
          @PostMapping("/{orderId}/cancel")
          public void cancelOrder(@PathVariable Long orderId) {
              orderService.cancelOrder(orderId);
          }
          
          @lombok.Data
          public static class CreateOrderRequest {
              private Long userId;
              private List<OrderService.OrderItemRequest> items;
          }
      }
      

      9.2 測(cè)試示例

      package com.example.demo;
      
      import com.example.demo.domain.*;
      import com.example.demo.mapper.*;
      import com.example.demo.service.OrderService;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      
      import java.util.ArrayList;
      import java.util.List;
      
      @SpringBootTest
      public class MyBatisTests {
          
          @Autowired
          private UserMapper userMapper;
          
          @Autowired
          private ProductMapper productMapper;
          
          @Autowired
          private OrderService orderService;
          
          @Test
          public void testFindUser() {
              User user = userMapper.findById(1L);
              System.out.println("用戶信息: " + user);
          }
          
          @Test
          public void testCreateOrder() {
              // 構(gòu)建訂單請(qǐng)求
              List<OrderService.OrderItemRequest> items = new ArrayList<>();
              
              OrderService.OrderItemRequest item1 = new OrderService.OrderItemRequest();
              item1.setProductId(1L);
              item1.setQuantity(2);
              items.add(item1);
              
              OrderService.OrderItemRequest item2 = new OrderService.OrderItemRequest();
              item2.setProductId(3L);
              item2.setQuantity(1);
              items.add(item2);
              
              // 創(chuàng)建訂單
              Order order = orderService.createOrder(1L, items);
              System.out.println("訂單創(chuàng)建成功: " + order);
          }
          
          @Test
          public void testQueryOrderDetail() {
              Order order = orderService.getOrderDetail(1L);
              System.out.println("訂單詳情: " + order);
              System.out.println("訂單項(xiàng)數(shù)量: " + order.getItems().size());
          }
      }
      

      10. 最佳實(shí)踐

      10.1 注解 vs XML 如何選擇?

      場(chǎng)景 推薦方式 原因
      簡(jiǎn)單CRUD 注解 代碼簡(jiǎn)潔,易于維護(hù)
      復(fù)雜SQL XML 可讀性好,易于調(diào)試
      動(dòng)態(tài)SQL XML 功能更強(qiáng)大
      多表關(guān)聯(lián) XML 結(jié)果映射更清晰
      項(xiàng)目規(guī)范 混合使用 根據(jù)實(shí)際情況選擇

      10.2 命名規(guī)范

      // Mapper 接口方法命名
      findById()          // 查詢單個(gè)
      findAll()           // 查詢所有
      findByXxx()         // 條件查詢
      insert()            // 插入
      update()            // 更新
      deleteById()        // 刪除
      count()             // 統(tǒng)計(jì)
      batchInsert()       // 批量操作
      

      10.3 性能優(yōu)化

      1. 避免 N+1 查詢問題
      <!-- 不推薦:會(huì)產(chǎn)生 N+1 查詢 -->
      <select id="findOrders" resultMap="OrderMap">
          SELECT * FROM order
      </select>
      <select id="findItems" resultType="OrderItem">
          SELECT * FROM order_item WHERE order_id = #{orderId}
      </select>
      
      <!-- 推薦:使用 JOIN 一次查詢 -->
      <select id="findOrdersWithItems" resultMap="OrderWithItemsMap">
          SELECT o.*, oi.* 
          FROM order o
          LEFT JOIN order_item oi ON o.id = oi.order_id
      </select>
      
      1. 使用分頁(yè)
      <select id="findByPage" resultType="User">
          SELECT * FROM user
          LIMIT #{limit} OFFSET #{offset}
      </select>
      
      1. 只查詢需要的字段
      <!-- 不推薦 -->
      <select id="findUsers" resultType="User">
          SELECT * FROM user
      </select>
      
      <!-- 推薦 -->
      <select id="findUsers" resultType="User">
          SELECT id, username, email FROM user
      </select>
      

      10.4 事務(wù)管理

      @Service
      public class UserService {
          
          @Autowired
          private UserMapper userMapper;
          
          @Autowired
          private AccountMapper accountMapper;
          
          /**
           * 使用 @Transactional 確保數(shù)據(jù)一致性
           */
          @Transactional(rollbackFor = Exception.class)
          public void registerUser(User user) {
              // 1. 創(chuàng)建用戶
              userMapper.insert(user);
              
              // 2. 創(chuàng)建賬戶
              Account account = new Account();
              account.setUserId(user.getId());
              account.setBalance(BigDecimal.ZERO);
              accountMapper.insert(account);
              
              // 如果發(fā)生異常,以上操作都會(huì)回滾
          }
      }
      

      10.5 SQL 注入防護(hù)

      <!-- 推薦:使用 #{} 預(yù)編譯 -->
      <select id="findByUsername" resultType="User">
          SELECT * FROM user WHERE username = #{username}
      </select>
      
      <!-- 危險(xiǎn):使用 ${} 字符串拼接,可能 SQL 注入 -->
      <select id="findByUsername" resultType="User">
          SELECT * FROM user WHERE username = '${username}'
      </select>
      
      <!-- ${} 適用場(chǎng)景:動(dòng)態(tài)表名、列名 -->
      <select id="findFromTable" resultType="User">
          SELECT * FROM ${tableName} WHERE id = #{id}
      </select>
      

      10.6 日志配置

      # application.yml
      logging:
        level:
          # 打印 MyBatis SQL 日志
          com.example.demo.mapper: DEBUG
      
      mybatis:
        configuration:
          # 使用標(biāo)準(zhǔn)輸出打印 SQL
          log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      

      10.7 常見問題

      1. 自增主鍵回填
      <insert id="insert" useGeneratedKeys="true" keyProperty="id">
          INSERT INTO user (username) VALUES (#{username})
      </insert>
      
      1. 枚舉類型處理
      // 實(shí)體類
      public class Order {
          private OrderStatus status; // 枚舉
      }
      
      public enum OrderStatus {
          PENDING, PAID, SHIPPED, COMPLETED, CANCELLED
      }
      
      // Mapper
      @Update("UPDATE `order` SET status = #{status} WHERE id = #{id}")
      int updateStatus(@Param("id") Long id, @Param("status") OrderStatus status);
      
      1. 日期時(shí)間處理
      // 使用 Java 8 時(shí)間類型
      @Data
      public class User {
          private LocalDateTime createdAt;
          private LocalDate birthday;
          private LocalTime loginTime;
      }
      

      總結(jié)

      快速上手步驟

      1. ? 添加依賴:mybatis-spring-boot-starter + 數(shù)據(jù)庫(kù)驅(qū)動(dòng)
      2. ? 配置數(shù)據(jù)源:application.yml 配置數(shù)據(jù)庫(kù)連接
      3. ? 創(chuàng)建實(shí)體類:對(duì)應(yīng)數(shù)據(jù)庫(kù)表
      4. ? 創(chuàng)建Mapper接口:定義數(shù)據(jù)庫(kù)操作方法
      5. ? 選擇實(shí)現(xiàn)方式
        • 簡(jiǎn)單操作 → 注解(@Select/@Insert/@Update/@Delete)
        • 復(fù)雜操作 → XML 配置文件
      6. ? Service層調(diào)用:注入Mapper,編寫業(yè)務(wù)邏輯
      7. ? Controller層暴露:提供REST API

      核心要點(diǎn)

      • @Mapper 注解標(biāo)記接口
      • @MapperScan 掃描包路徑
      • #{參數(shù)} 預(yù)編譯參數(shù)(防SQL注入)
      • ${參數(shù)} 字符串替換(動(dòng)態(tài)表名/列名)
      • resultMap 自定義結(jié)果映射
      • 動(dòng)態(tài)SQL 使用 if/choose/foreach/set 等標(biāo)簽
      • @Transactional 事務(wù)管理

      參考資源


      現(xiàn)在你已經(jīng)掌握了 MyBatis 的核心知識(shí),可以開始在項(xiàng)目中實(shí)踐了!

      建議從簡(jiǎn)單的 CRUD 操作開始,逐步嘗試復(fù)雜的關(guān)聯(lián)查詢和動(dòng)態(tài) SQL。遇到問題時(shí),多查看日志中的 SQL 語句,理解 MyBatis 的執(zhí)行過程。

      posted @ 2025-10-07 16:19  逝雪  閱讀(9)  評(píng)論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 老司机精品影院一区二区三区| 内射老妇bbwx0c0ck| 一二三三免费观看视频| 久久精品中文字幕免费| 天天综合色一区二区三区| 最新国产精品中文字幕| 中文字幕av国产精品| 国产精品亚洲av三区色| 婷婷丁香五月激情综合| 亚洲乱码中文字幕综合| 国产97人人超碰caoprom| 国产av亚洲精品ai换脸电影| 亚洲欧美激情在线一区| 亚洲一区二区偷拍精品| 久久99精品久久久久麻豆| 亚洲av无码国产在丝袜线观看| 久久这里都是精品一区| 18禁视频一区二区三区| 无码熟妇αⅴ人妻又粗又大| 激情综合色综合久久丁香| 亚洲精品国产av成人网| 欧美肥妇毛多水多bbxx| 亚洲精品综合久中文字幕| 国产第一页浮力影院入口| 什邡市| 国内精品视频区在线2021| 免费视频成人片在线观看| 国产人妻人伦精品婷婷| 午夜久久水蜜桃一区二区| 疯狂做受xxxx高潮欧美日本| 日韩一区二区三区理伦片| 日日躁夜夜躁狠狠久久av| 久久狠狠高潮亚洲精品夜色 | 国产极品美女高潮无套| 人妻 日韩精品 中文字幕| 亚洲成av人片不卡无码手机版| 亚洲精品综合第一国产综合| 日韩精品人妻中文字幕| 久久夜色精品亚洲国产av| 久久国产精品精品国产色婷婷| 欧美大bbbb流白水|