動態

詳情 返回 返回

深入理解 MyBatis 延遲加載機制與實現原理 - 動態 詳情

作為 Java 後端開發,你是否曾經糾結過:查詢用户信息時,要不要把用户關聯的訂單、地址一起查出來?全部查詢性能肯定受影響,可不查又怕後面用到時反覆訪問數據庫。這種"查不查"的兩難抉擇,其實可以通過 MyBatis 的延遲加載機制漂亮解決。那麼問題來了,MyBatis 到底支持延遲加載嗎?它背後的實現原理又是什麼?

MyBatis 的延遲加載支持情況

MyBatis 確實支持延遲加載(Lazy Loading)功能,這是一種按需加載的策略,可以有效減輕系統負擔,提高查詢效率。

簡單來説,當我們查詢一個實體時,對於它的關聯對象,不立即從數據庫中加載,而是在第一次真正使用到關聯對象時才去數據庫查詢。這樣做可以避免一次性加載過多數據,尤其是在關聯關係較多或數據量較大的情況下。

延遲加載的配置方式

MyBatis 提供了兩個全局參數來控制延遲加載:

<settings>
    <!-- 開啓延遲加載功能 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 設置激進延遲加載策略 -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
  • lazyLoadingEnabled:設置為 true 時開啓延遲加載功能
  • aggressiveLazyLoading:設置為 false 時,按需加載對象屬性(只有當調用該屬性的 getter 方法時才加載);設置為 true 時,任何對對象方法的調用都會觸發所有標記為延遲加載的屬性加載

舉個簡單例子,當aggressiveLazyLoading=true時:

User user = userMapper.getUserById(1);
user.getUsername(); // 僅想獲取用户名,但會觸發orderList等所有延遲加載屬性的加載
// 或者
System.out.println(user); // 調用toString()方法,卻觸發了所有延遲屬性的加載

因此,生產環境中通常建議保持aggressiveLazyLoading=false,避免不必要的性能損耗。

除了全局配置外,還可以在關聯查詢中單獨設置:

<!-- association關聯查詢時使用延遲加載 -->
<association property="author" column="author_id" select="selectAuthor" fetchType="lazy"/>

<!-- collection集合查詢時使用延遲加載 -->
<collection property="posts" ofType="Post" column="id" select="selectPostsForBlog" fetchType="lazy"/>

通過fetchType屬性可以覆蓋全局的延遲加載設置,值為lazy表示使用延遲加載,eager表示立即加載。

延遲加載的觸發條件

延遲加載並非任何操作都會觸發,具體的觸發條件包括:

  1. 調用延遲屬性的 getter 方法:如user.getOrderList()
  2. 對延遲集合屬性進行操作:如orderList.size()orderList.isEmpty()、遍歷操作等
  3. 僅獲取代理對象引用不會觸發加載:必須調用其方法才會觸發
User user = userMapper.getUserById(1);
// 以下操作不會觸發延遲加載
List<Order> orderList = null;
orderList = user.getOrderList(); // 僅獲取引用,不會觸發加載

// 以下操作會觸發延遲加載
int size = user.getOrderList().size(); // 調用size()方法觸發加載
boolean isEmpty = user.getOrderList().isEmpty(); // 調用isEmpty()方法觸發加載
for (Order order : user.getOrderList()) { // 遍歷觸發加載
    // 處理訂單
}

延遲加載的實現原理

MyBatis 的延遲加載主要是通過動態代理實現的。這裏涉及兩種代理模式:

  1. JDK 動態代理
  2. CGLIB 動態代理
graph TD
    A[查詢主對象] --> B{是否配置延遲加載?}
    B -->|是| C[創建代理對象]
    B -->|否| D[直接關聯查詢]
    C --> E[返回代理對象包裝真實對象]
    E --> F{是否訪問關聯屬性getter方法?}
    F -->|是| G[觸發SQL查詢加載關聯對象]
    F -->|否| H[不執行額外查詢]
    D --> I[返回完整對象]

字節碼層面的代理原理

理解代理選擇的核心,需要了解底層實現原理:

  • JDK 動態代理:基於接口實現,通過java.lang.reflect.Proxy類在運行時生成接口的代理類。它要求目標類必須實現至少一個接口。
  • CGLIB 動態代理:基於字節碼生成技術,通過創建目標類的子類來實現代理。CGLIB 在運行時動態修改字節碼,重寫目標類的方法以插入延遲加載邏輯。

簡單理解:JDK 代理是"實現接口",CGLIB 代理是"繼承類"。這就是為什麼實現了接口的類優先使用 JDK 代理,而普通類只能用 CGLIB 代理。

代理機制的選擇

MyBatis 會根據目標類是否實現接口選擇使用不同的代理機制:

// MyBatis ProxyFactory選擇邏輯(簡化版)
public class ProxyFactory {
    private ProxyFactory() {
        // Prevent Instantiation
    }

    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration,
                                    ObjectFactory objectFactory, List<Class<?>> constructorArgTypes,
                                    List<Object> constructorArgs) {
        // target: 真實對象(如User實例)
        // lazyLoader: 存儲延遲加載任務的映射(屬性名→加載器)

        // 判斷目標類是否為接口或者代理類
        boolean isJdkProxy = target.getClass().getInterfaces().length > 0
            && !Proxy.isProxyClass(target.getClass());

        if (isJdkProxy) {
            // 使用JDK動態代理(優先選擇,性能略優且符合Java標準)
            return JdkProxyFactory.createProxy(target, lazyLoader, configuration, objectFactory,
                                              constructorArgTypes, constructorArgs);
        } else {
            // 使用CGLIB動態代理(目標是非接口的普通類時)
            return CglibProxyFactory.createProxy(target, lazyLoader, configuration, objectFactory,
                                               constructorArgTypes, constructorArgs);
        }
    }
}
  • 如果目標類實現了接口,MyBatis 會優先使用 JDK 動態代理(性能更好且符合 Java 標準)
  • 如果目標類沒有實現接口,則使用 CGLIB 動態代理
注意:MyBatis 3.2.8+完全支持 JDK/CGLIB 代理自動切換,早期版本可能需要手動配置代理工廠。MyBatis 自 3.3.0 起,若檢測到 classpath 中無 CGLIB 依賴,會自動引入mybatis-cglib-proxy模塊(基於 CGLIB 3.2.5),因此 Maven 項目通常無需額外配置。若使用 Gradle 或手動管理依賴,需確保相關 jar 包存在。

動態代理實現優化

JDK 和 CGLIB 代理處理邏輯中有很多相似部分,可以抽取公共方法處理:

// 公共方法處理邏輯
private Object handleSpecialMethods(Object target, Method method, Object[] args) throws Throwable {
    final String methodName = method.getName();

    if (methodName.equals("equals")) {
        return target.equals(args[0]);
    } else if (methodName.equals("hashCode")) {
        return target.hashCode();
    } else if (methodName.equals("toString")) {
        return target.toString();
    }
    return null; // 不是特殊方法,返回null
}

// 然後在代理處理器中調用
Object result = handleSpecialMethods(target, method, args);
if (result != null) {
    return result;
}
// 處理其他方法...

ResultLoaderMap:延遲加載的核心容器

ResultLoaderMap是 MyBatis 用於管理延遲加載任務的容器,它存儲了屬性名與對應的ResultLoader的映射關係。每個延遲屬性對應一個ResultLoader,當屬性被訪問時,通過ResultLoader執行對應的子查詢並填充數據。

ResultLoaderMap是會話級(SqlSession)容器,線程安全由SqlSession的線程隔離性保證,無需額外同步。在高併發場景下,每個請求使用獨立SqlSession,避免線程間數據污染。

// ResultLoaderMap簡化概念示意
public class ResultLoaderMap {
    // 存儲屬性名到ResultLoader的映射
    private final Map<String, LoadPair> loaderMap = new HashMap<>();

    // 檢查是否有指定屬性的加載器
    public boolean hasLoader(String property) {
        return loaderMap.containsKey(property);
    }

    // 觸發指定屬性的加載
    public void load(String property) throws SQLException {
        LoadPair pair = loaderMap.get(property);
        if (pair != null) {
            pair.load(); // 執行SQL查詢並填充結果
            loaderMap.remove(property); // 加載後移除該加載器
        }
    }
}

// 加載器,包含了執行查詢所需的全部信息
class LoadPair {
    private final String property;
    private final MetaObject metaResultObject;
    private final ResultLoader resultLoader;

    public void load() throws SQLException {
        // 執行SQL查詢獲取結果
        Object value = resultLoader.loadResult();
        // 將結果設置到目標對象的屬性上
        metaResultObject.setValue(property, value);
    }
}

延遲加載的實際案例

讓我們通過一個用户(User)和訂單(Order)的例子來看看延遲加載如何工作:

實體類定義

public class User implements Serializable { // 實現Serializable接口避免序列化問題
    private Integer id;
    private String username;
    private List<Order> orderList;

    // getter和setter方法
}

public class Order implements Serializable {
    private Integer id;
    private String orderNo;
    private Double amount;
    private Integer userId;

    // getter和setter方法
}

MyBatis 配置

  1. 首先在 MyBatis 全局配置中啓用延遲加載:
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
  1. 然後在 Mapper 文件中配置:
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 查詢用户,延遲加載訂單信息 -->
    <select id="getUserById" resultMap="userResultMap" parameterType="int">
        SELECT id, username FROM user WHERE id = #{id}
    </select>

    <!-- 根據用户ID查詢訂單列表 -->
    <select id="getOrdersByUserId" resultType="com.example.entity.Order" parameterType="int">
        SELECT id, order_no, amount, user_id FROM orders WHERE user_id = #{userId}
    </select>

    <resultMap id="userResultMap" type="com.example.entity.User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <!-- 配置延遲加載 -->
        <collection property="orderList" ofType="com.example.entity.Order"
                    column="id" select="getOrdersByUserId" fetchType="lazy"/>
    </resultMap>
</mapper>

執行過程與事務

工具類及代碼演示

首先,需要一個 MyBatis 工具類來獲取 SqlSession:

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyBatisUtil {
    private static final Logger log = LoggerFactory.getLogger(MyBatisUtil.class);
    private static final SqlSessionFactory sqlSessionFactory;

    static {
        try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            log.error("MyBatis配置加載失敗", e);
            throw new RuntimeException("MyBatis配置加載失敗", e);
        }
    }

    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}
注意:需要在類路徑下添加mybatis-config.xml配置文件,配置數據源和 Mapper 掃描。

然後,使用這個工具類編寫延遲加載示例:

public class LazyLoadingDemo {
    public static void main(String[] args) {
        // 使用try-with-resources確保SqlSession正確關閉
        try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

            // 查詢用户信息
            User user = userMapper.getUserById(1);
            System.out.println("用户名: " + user.getUsername());

            // 此時還沒有執行訂單查詢的SQL
            System.out.println("=== 分割線,以上SQL不包含訂單查詢 ===");

            // 訪問訂單信息時,才會觸發延遲加載,執行訂單查詢SQL
            // 注意:延遲加載依賴活動的SqlSession,建議在會話關閉前完成所有延遲屬性的訪問
            List<Order> orderList = user.getOrderList();
            System.out.println("訂單數量: " + orderList.size());

            // 後續再次訪問不會觸發SQL查詢,因為已緩存在一級緩存中
            System.out.println("再次訪問訂單: " + user.getOrderList().size());
        } // SqlSession自動關閉

        // 注意:在此處訪問user.getOrderList()會拋出異常
        // 因為延遲加載依賴活動的SqlSession
    }
}

延遲加載的優缺點

優點

  1. 性能提升:避免一次性加載過多不必要的數據,減少內存佔用
  2. 按需加載:只有真正需要使用關聯數據時才會查詢,減少不必要的 IO 操作
  3. 降低系統壓力:特別是在複雜關聯關係或大數據量場景下,可以顯著降低系統負擔

缺點

  1. N+1 問題:當需要遍歷一個集合並訪問每個元素的延遲加載屬性時,會導致主查詢 1 次+每個對象的延遲查詢 N 次,總共 N+1 次查詢
  2. 代理對象序列化問題:延遲加載的代理對象序列化時可能會出現問題,尤其是 CGLIB 代理對象
  3. 會話關閉後無法加載:延遲加載依賴活動的數據庫會話,SqlSession 關閉後無法再加載
graph LR
    A[延遲加載] --> B[優點]
    A --> C[缺點]

    B --> D[性能提升]
    B --> E[按需加載]
    B --> F[降低系統壓力]

    C --> G[N+1問題]
    C --> H[序列化問題]
    C --> I[會話依賴]

解決 N+1 問題的方法

延遲加載可能導致的 N+1 問題可以通過以下方式解決:

1. 使用顯式即時加載

在明確需要關聯數據的場景下,可以顯式指定即時加載:

<collection property="orderList" ofType="Order" column="id"
           select="getOrdersByUserId" fetchType="eager"/>

需要注意的是,fetchType="eager"並不是在 SQL 層面使用 JOIN 查詢,而是在主查詢完成後立即執行關聯查詢。本質上是"分步加載",但不需要等到屬性被訪問時才加載。

2. 使用 MyBatis 的批量查詢功能

MyBatis 提供了多種批量查詢方式來解決 N+1 問題:

a) 使用 multiple column 參數傳遞多個值進行批量查詢

<!-- 配置批量查詢的映射 -->
<collection property="orders" ofType="Order"
           column="{userId=id, userName=username}" select="getOrdersByUserParams"/>

<!-- 批量查詢方法接收多個參數 -->
<select id="getOrdersByUserParams" resultType="Order">
    SELECT * FROM orders
    WHERE user_id = #{userId}
    AND create_by = #{userName}
</select>

b) 手動批量查詢優化

// 手動批量查詢優化示例
List<User> users = userMapper.getAllUsers();
List<Integer> userIds = users.stream().map(User::getId).collect(Collectors.toList());
List<Order> allOrders = orderMapper.getOrdersByUserIds(userIds); // 1次批量查詢

// 建立用户-訂單映射關係
Map<Integer, List<Order>> orderMap = allOrders.stream()
    .collect(Collectors.groupingBy(Order::getUserId));

// 處理用户和訂單
for (User user : users) {
    List<Order> userOrders = orderMap.getOrDefault(user.getId(), Collections.emptyList());
    System.out.println("用户" + user.getUsername() + "的訂單數量: " + userOrders.size());
}
注意:雖然 MyBatis 提供了batchSize配置,但它主要用於優化批量插入/更新操作,對延遲加載的 N+1 問題沒有直接幫助。延遲加載的子查詢仍然是單條執行的,需要通過上述手動批量查詢方式優化。

3. N+1 問題的監控與預防

可以通過以下方式監控和預防 N+1 問題:

// 配置SQL監控
@Aspect
@Component
public class LazyLoadingMonitor {
    private static final Logger log = LoggerFactory.getLogger(LazyLoadingMonitor.class);

    // 可通過配置調整閾值
    @Value("${mybatis.lazy.threshold:10}")
    private long threshold;

    @Around("execution(* com.example.entity.*.get*(..))")
    public Object monitorLazyLoading(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        Object target = pjp.getTarget();

        // 判斷是否可能觸發延遲加載的getter方法
        if (methodName.startsWith("get") && !methodName.equals("getClass")) {
            // 記錄方法調用前的時間
            long start = System.currentTimeMillis();
            Object result = pjp.proceed();
            long end = System.currentTimeMillis();

            // 如果執行時間過長,可能觸發了延遲加載
            long duration = end - start;
            if (duration > threshold) {
                log.warn("可能的延遲加載: 類={}, 方法={}, 執行時間={}ms",
                         target.getClass().getSimpleName(),
                         methodName,
                         duration);
            }
            return result;
        }
        return pjp.proceed();
    }
}

也可以使用成熟的監控工具,如 MyBatis Plus 的性能分析插件來監控 SQL 執行。

代理對象序列化問題及解決方案

延遲加載使用的代理對象在序列化時可能會遇到問題,尤其是 CGLIB 代理類。CGLIB 生成的代理類名稱類似$$EnhancerByCGLIB$$xxx,反序列化時需要相同的類路徑和類定義。在分佈式系統中(如微服務架構),這種代理類可能無法在不同節點間正確反序列化,導致ClassNotFoundException異常。

解決方案包括:

1. 確保實體類實現 Serializable 接口

所有實體類都應該實現java.io.Serializable接口,包括關聯實體類。

2. 在序列化前觸發延遲加載

確保在序列化前已經訪問過延遲加載屬性,將代理對象轉換為真實對象:

// 引入Jackson依賴
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class SerializationHelper {
    private static final Logger log = LoggerFactory.getLogger(SerializationHelper.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();

    public static String prepareForSerialization(User user) {
        try {
            // 在序列化前觸發所有延遲加載
            if (user.getOrderList() != null) {
                user.getOrderList().size(); // 觸發延遲加載
            }

            // 現在user中的orderList已經是真實數據,可以安全序列化
            return objectMapper.writeValueAsString(user);
        } catch (JsonProcessingException e) {
            log.error("序列化失敗", e);
            throw new RuntimeException("序列化失敗", e);
        }
    }
}

3. 使用自定義序列化策略

使用 Jackson 或其他序列化工具的自定義序列化功能:

// 使用Jackson註解忽略代理相關屬性
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class User implements Serializable {
    // 實體類定義
}

延遲加載與事務的關係

延遲加載依賴的SqlSession需與事務作用域一致。如果事務提前提交或回滾,會導致後續的延遲加載無法執行:

// 正確示例:在同一事務中完成延遲加載
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    @Transactional
    public int getUserOrderCount(int userId) {
        User user = userMapper.getUserById(userId);
        // 在同一事務中訪問延遲加載屬性
        return user.getOrderList().size();
    }
}

在 Spring 環境中,可以使用OpenSessionInView模式延長會話生命週期,但這可能導致數據庫連接長時間佔用,高併發系統中要謹慎使用。

延遲加載與緩存結合使用

MyBatis 的延遲加載與緩存機制可以協同工作,進一步提升性能:

一級緩存(會話級)

  • 默認開啓,作用域為 SqlSession
  • 延遲加載的結果會存入一級緩存,同一會話內重複訪問不會觸發數據庫查詢
  • 當執行 update、delete、insert 或調用 clearCache()時,一級緩存會被清空

二級緩存(全局)

  • 需手動配置<cache/><cache-ref/>
  • 延遲加載查詢的結果也會被二級緩存緩存
  • 跨會話訪問時可以直接從二級緩存獲取
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 啓用二級緩存 -->
    <cache eviction="LRU"
          flushInterval="60000"  <!-- 刷新間隔,單位毫秒 -->
          size="1024"           <!-- 引用數量 -->
          readOnly="true"/>     <!-- 只讀設置 -->

    <!-- 映射器配置 -->
</mapper>

readOnly=true表示緩存對象不可變,MyBatis 會直接返回緩存對象引用,提升性能;readOnly=false則返回對象副本,保證線程安全。

二級緩存存儲的是完整對象(包括延遲加載後的數據),因此需確保延遲加載觸發後的數據會被正確序列化並緩存。建議在getUserById等主查詢上配置緩存,延遲加載的子查詢(如getOrdersByUserId)可通過flushCache="true"保證數據一致性。

延遲加載的適用場景

適合使用延遲加載的場景

  1. 關聯數據使用頻率低:如用户詳情頁的歷史訂單,只有用户點擊"查看訂單"時才需要加載
  2. 大數據量列表查詢:只加載主數據,關聯數據按需加載,避免一次性加載過多數據
  3. 層級數據結構:如樹形結構,只需要加載當前節點數據,子節點按需加載
  4. 統計報表的明細數據:報表頁面通常只展示彙總數據,詳情數據按需加載

不適合使用延遲加載的場景

  1. 頻繁訪問關聯數據:如訂單詳情頁需同時展示用户和商品信息,此時即時加載更高效
  2. 批量數據處理:需要處理大量關聯數據的場景,延遲加載會導致 N+1 問題
  3. 無狀態服務:如 REST API,每個請求都會創建新的 Session,延遲加載可能導致會話關閉問題
  4. 高併發系統:延遲加載依賴會話,可能導致數據庫連接長時間佔用

複雜關聯關係處理

多對多和嵌套加載處理

在處理複雜關聯關係如多對多(用户-角色)或嵌套關係(用户-訂單-商品)時,配置原理相似,但需要注意關聯條件和層級結構:

<!-- 用户與角色的多對多關係 -->
<resultMap id="userWithRolesMap" type="com.example.entity.User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- 通過中間表查詢關聯角色 -->
    <collection property="roles" ofType="com.example.entity.Role"
                column="id" select="getRolesByUserId" fetchType="lazy"/>
</resultMap>

<!-- 嵌套延遲加載:訂單-商品 -->
<resultMap id="orderMap" type="com.example.entity.Order">
    <id property="id" column="id"/>
    <result property="orderNo" column="order_no"/>
    <!-- 嵌套層級的延遲加載 -->
    <collection property="products" ofType="com.example.entity.Product"
                column="id" select="getProductsByOrderId" fetchType="lazy"/>
</resultMap>

在處理複雜關係時要點:

  • 對於多對多關係:通常需要一個額外查詢處理中間表連接
  • 對於嵌套層級:需確保每層都正確配置延遲加載,並且會話保持活動狀態直到所有層級都訪問完畢

MyBatis 與 Hibernate 延遲加載對比

對於熟悉 Hibernate 的開發者,瞭解兩者差異有助於更好地使用 MyBatis 的延遲加載:

特性 MyBatis Hibernate
代理實現與性能 基於動態代理(JDK/CGLIB),代理對象創建速度快,但功能相對簡單 基於字節碼增強(Javassist/ByteBuddy),初始化較慢但運行性能好
加載方式 通過單獨的 select 查詢(需手動配置) 支持 JOIN 方式和單表查詢兩種延遲加載
會話管理 需手動管理 SqlSession 生命週期 通過 Session/EntityManager 自動處理
配置方式 XML 或註解,需明確設置 fetchType 通過映射關係直接控制(如@OneToMany(fetch=FetchType.LAZY))
N+1 解決 需手動批量查詢或配置關聯查詢 提供批處理機制(batch fetching)自動優化

實際應用建議

  1. 選擇性啓用:不是所有場景都適合使用延遲加載,需要根據業務特點選擇
  2. 合理設置全局配置
  • 開發環境可以設置lazyLoadingEnabled=true方便調試
  • 生產環境根據實際性能測試結果決定
  • 儘量保持aggressiveLazyLoading=false,避免非預期的性能問題
  1. 結合緩存機制:MyBatis 的一級緩存、二級緩存與延遲加載配合使用,可以進一步提升性能
  2. 在 Service 層管理好會話:確保訪問延遲加載屬性時 SqlSession 仍然處於打開狀態,或考慮使用 Spring 的OpenSessionInView模式
  3. 性能測試:在生產環境部署前,對延遲加載的性能影響進行充分測試,包括高併發場景

總結

我們來用表格總結一下 MyBatis 的延遲加載特性:

特性 描述
支持情況 MyBatis 完全支持延遲加載功能
實現原理 基於動態代理機制(JDK 代理或 CGLIB 代理)
延遲容器 使用 ResultLoaderMap 存儲延遲加載任務
全局配置 lazyLoadingEnabledaggressiveLazyLoading控制
局部控制 通過fetchType屬性覆蓋全局設置
觸發條件 調用 getter 方法、集合操作方法(size/isEmpty)、遍歷等
會話依賴 延遲加載依賴活動的 SqlSession 和事務
N+1 優化 批量查詢、multiple columns 傳參
序列化處理 實現 Serializable 接口、預先觸發延遲加載、自定義序列化策略
與緩存結合 延遲加載結果會進入一/二級緩存,提升後續訪問性能
適用場景 關聯數據使用頻率低、大數據量列表查詢、層級數據結構
user avatar qian5201314 頭像 python-learn 頭像 kk_64ec9e6b37cb5 頭像 menglihuaxiangbian 頭像
點贊 4 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.