最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MyBatis連接池、動(dòng)態(tài) SQL 與多表關(guān)聯(lián)查詢的注意事項(xiàng)

 更新時(shí)間:2025年07月12日 16:12:11   作者:越來越無動(dòng)于衷  
本文將從連接池原理出發(fā),深入講解動(dòng)態(tài) SQL 的常用標(biāo)簽,并通過實(shí)例演示一對多、多對多等復(fù)雜關(guān)聯(lián)查詢的實(shí)現(xiàn),幫助你掌握 MyBatis 的進(jìn)階用法,感興趣的朋友一起看看吧

MyBatis 作為一款靈活的持久層框架,除了基礎(chǔ)的 CRUD 操作,還提供了連接池管理、動(dòng)態(tài) SQL 以及多表關(guān)聯(lián)查詢等高級特性。本文將從連接池原理出發(fā),深入講解動(dòng)態(tài) SQL 的常用標(biāo)簽,并通過實(shí)例演示一對多、多對多等復(fù)雜關(guān)聯(lián)查詢的實(shí)現(xiàn),幫助你掌握 MyBatis 的進(jìn)階用法。

一、MyBatis 連接池:提升數(shù)據(jù)庫交互性能

連接池是存儲(chǔ)數(shù)據(jù)庫連接的容器,它的核心作用是避免頻繁創(chuàng)建和關(guān)閉連接,從而減少資源消耗、提高程序響應(yīng)速度。在 MyBatis 中,連接池的配置通過dataSource標(biāo)簽的type屬性實(shí)現(xiàn),支持三種類型的連接池:

1. 連接池類型詳解

  • POOLED:使用 MyBatis 內(nèi)置的連接池
    MyBatis 會(huì)維護(hù)一個(gè)連接池,當(dāng)需要連接時(shí)從池中獲取,使用完畢后歸還給池,避免頻繁創(chuàng)建連接。適用于高并發(fā)場景,是開發(fā)中最常用的類型。

    配置示例:

<dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

UNPOOLED:不使用連接池
每次執(zhí)行 SQL 時(shí)都會(huì)創(chuàng)建新的連接,使用后直接關(guān)閉。適用于低并發(fā)場景,性能較差,一般僅用于簡單測試。

配置示例:

<dataSource type="UNPOOLED">
    <!-- 同POOLED的屬性配置 -->
</dataSource>
  • JNDI:依賴容器的連接池
    由 Web 容器(如 Tomcat)提供連接池管理,MyBatis 僅負(fù)責(zé)從容器中獲取連接。適用于Java EE 環(huán)境,需在容器中提前配置連接池。

    配置示例:

<dataSource type="JNDI">
    <property name="data_source" value="java:comp/env/jdbc/mybatis_db"/>
</dataSource>

2. 連接池的優(yōu)勢

  • 資源復(fù)用:連接池中的連接可重復(fù)使用,減少創(chuàng)建連接的開銷;
  • 響應(yīng)速度:提前創(chuàng)建連接,避免 SQL 執(zhí)行時(shí)的連接創(chuàng)建延遲;
  • 并發(fā)控制:通過最大連接數(shù)限制,防止數(shù)據(jù)庫因連接過多而崩潰。

二、動(dòng)態(tài) SQL:靈活拼接 SQL 語句

在實(shí)際開發(fā)中,查詢條件往往是動(dòng)態(tài)變化的(如多條件篩選、批量操作等)。MyBatis 的動(dòng)態(tài) SQL 標(biāo)簽可以優(yōu)雅地解決 SQL 語句拼接問題,避免手動(dòng)拼接導(dǎo)致的語法錯(cuò)誤和 SQL 注入風(fēng)險(xiǎn)。

1. <if>標(biāo)簽:條件判斷

<if>標(biāo)簽用于根據(jù)參數(shù)值動(dòng)態(tài)生成 SQL 片段,常用來處理多條件查詢。

示例場景:根據(jù)用戶名和性別查詢用戶(參數(shù)非空時(shí)才添加條件)。

UserMapper 接口

public interface UserMapper {
    // 條件查詢用戶
    List<User> findByWhere(User user);
}

UserMapper.xml 配置

<select id="findByWhere" parameterType="user" resultType="user">
    select * from user
    <where>
        <!-- 當(dāng)username非空且非空字符串時(shí),添加條件 -->
        <if test="username != null and username != ''">
            and username like #{username}
        </if>
        <!-- 當(dāng)sex非空且非空字符串時(shí),添加條件 -->
        <if test="sex != null and sex != ''">
            and sex = #{sex}
        </if>
    </where>
</select>
  • 測試代碼

@Test
public void testFindByWhere() {
    User user = new User();
    user.setUsername("%zz%"); // 模糊查詢包含"zz"的用戶名
    user.setSex("m");
    List<User> list = userMapper.findByWhere(user);
    // 遍歷結(jié)果...
}

說明test屬性中的表達(dá)式用于判斷參數(shù)是否有效,where標(biāo)簽會(huì)自動(dòng)處理多余的andor,避免 SQL 語法錯(cuò)誤。 

2. <foreach>標(biāo)簽:遍歷集合

<foreach>標(biāo)簽用于遍歷集合或數(shù)組,常用來處理in查詢或批量操作。

場景 1:查詢 ID 在指定集合中的用戶(in查詢)

  • User 實(shí)體類:添加存儲(chǔ) ID 集合的屬性

public class User {
    private List<Integer> ids; // 存儲(chǔ)多個(gè)ID
    // 省略getter、setter
}
  • UserMapper 接口

List<User> findByIds(User user);
  • UserMapper.xml 配置

<select id="findByIds" parameterType="user" resultType="user">
    select * from user
    <where>
        <!-- 
            collection:集合屬性名(此處為ids)
            open:SQL片段開頭
            close:SQL片段結(jié)尾
            separator:元素分隔符
            item:遍歷的元素別名
        -->
        <foreach collection="ids" open="id in (" separator="," close=")" item="id">
            #{id}
        </foreach>
    </where>
</select>
  • 測試代碼

@Test
public void testFindByIds() {
    User user = new User();
    List<Integer> ids = new ArrayList<>();
    ids.add(1);
    ids.add(2);
    ids.add(3);
    user.setIds(ids);
    List<User> list = userMapper.findByIds(user); // 查詢ID為1、2、3的用戶
}

場景 2:批量查詢(or條件)

如需生成id = 1 or id = 2 or id = 3形式的 SQL,只需調(diào)整<foreach>openseparator

<foreach collection="ids" open="id = " separator="or id = " item="id">
    #{id}
</foreach>

3. <sql>與<include>標(biāo)簽:SQL 片段復(fù)用

對于頻繁使用的 SQL 片段(如查詢字段、表名等),可以用<sql>標(biāo)簽定義,再通過<include>標(biāo)簽引用,減少代碼冗余。

示例:復(fù)用查詢用戶的 SQL 片段。

  • UserMapper.xml 配置
<!-- 定義SQL片段 -->
<sql id="userColumns">
    id, username, birthday, sex, address
</sql>
<!-- 引用SQL片段 -->
<select id="findAll" resultType="user">
    select <include refid="userColumns"/> from user
</select>

說明id為片段唯一標(biāo)識,refid指定要引用的片段 ID,適用于多表查詢中重復(fù)的字段列表。

三、一對多查詢:用戶與賬戶的關(guān)聯(lián)

在實(shí)際業(yè)務(wù)中,表之間往往存在關(guān)聯(lián)關(guān)系(如用戶與賬戶:一個(gè)用戶可以有多個(gè)賬戶)。MyBatis 通過<collection>標(biāo)簽處理一對多關(guān)聯(lián)查詢。

1. 表結(jié)構(gòu)與實(shí)體類設(shè)計(jì)

  • 用戶表(user):存儲(chǔ)用戶基本信息(id、username 等);
  • 賬戶表(account):存儲(chǔ)賬戶信息,通過uid關(guān)聯(lián)用戶表(多對一關(guān)系)。

實(shí)體類設(shè)計(jì)

  • Account 類(多對一:一個(gè)賬戶屬于一個(gè)用戶):

public class Account implements Serializable {
    private Integer id;
    private Integer uid; // 關(guān)聯(lián)用戶ID
    private Double money;
    // 關(guān)聯(lián)的用戶對象
    private User user; 
    // 省略getter、setter
}
  • User 類(一對多:一個(gè)用戶有多個(gè)賬戶):

public class User implements Serializable {
    private Integer id;
    private String username;
    // 關(guān)聯(lián)的賬戶列表
    private List<Account> accounts; 
    // 省略getter、setter
}

2. 多對一查詢(賬戶關(guān)聯(lián)用戶)

查詢所有賬戶,并關(guān)聯(lián)查詢所屬用戶的信息。

  • AccountMapper 接口

public interface AccountMapper {
    List<Account> findAll();
}
  • AccountMapper.xml 配置

<select id="findAll" resultMap="accountMap">
    <!-- 關(guān)聯(lián)查詢賬戶和用戶 -->
    select a.*, u.username, u.address 
    from account a
    left join user u on a.uid = u.id
</select>
<!-- 定義結(jié)果映射 -->
<resultMap id="accountMap" type="account">
    <id property="id" column="id"/>
    <result property="uid" column="uid"/>
    <result property="money" column="money"/>
    <!-- 關(guān)聯(lián)用戶對象(多對一) -->
    <association property="user" javaType="user">
        <result property="username" column="username"/>
        <result property="address" column="address"/>
    </association>
</resultMap>

說明<association>標(biāo)簽用于映射關(guān)聯(lián)的單個(gè)對象,javaType指定對象類型。

3. 一對多查詢(用戶關(guān)聯(lián)賬戶)

查詢所有用戶,并關(guān)聯(lián)查詢其名下的所有賬戶。

  • UserMapper 接口

public interface UserMapper {
    // 查詢用戶及關(guān)聯(lián)的賬戶
    List<User> findOneToMany();
}
  • UserMapper.xml 配置

<select id="findOneToMany" resultMap="userAccountMap">
    select u.*, a.id as aid, a.money 
    from user u
    left join account a on u.id = a.uid
</select>
<resultMap id="userAccountMap" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="sex" column="sex"/>
    <result property="address" column="address"/>
    <!-- 關(guān)聯(lián)賬戶列表(一對多) -->
    <collection property="accounts" ofType="account">
        <id property="id" column="aid"/> <!-- 注意別名避免與用戶ID沖突 -->
        <result property="money" column="money"/>
    </collection>
</resultMap>

說明<collection>標(biāo)簽用于映射關(guān)聯(lián)的集合對象,ofType指定集合中元素的類型。

四、多對多查詢:用戶與角色的關(guān)聯(lián)

多對多關(guān)系需要通過中間表實(shí)現(xiàn)(如用戶與角色:一個(gè)用戶可擁有多個(gè)角色,一個(gè)角色可分配給多個(gè)用戶,通過user_role表關(guān)聯(lián))。

1. 表結(jié)構(gòu)與實(shí)體類設(shè)計(jì)

  • 角色表(role):存儲(chǔ)角色信息(id、role_name 等);
  • 中間表(user_role):通過uidrid關(guān)聯(lián)用戶表和角色表。

實(shí)體類設(shè)計(jì)

Account 類(多對一:一個(gè)賬戶屬于一個(gè)用戶):

public class Account implements Serializable {
    private Integer id;
    private Integer uid; // 關(guān)聯(lián)用戶ID
    private Double money;
    // 關(guān)聯(lián)的用戶對象
    private User user; 
    // 省略getter、setter
}
  • Role 類(多對多:一個(gè)角色包含多個(gè)用戶):
public class User implements Serializable {
    private Integer id;
    private String username;
    // 關(guān)聯(lián)的賬戶列表
    private List<Account> accounts; 
    // 省略getter、setter
}

2. 多對多查詢實(shí)現(xiàn)

查詢所有角色,并關(guān)聯(lián)查詢擁有該角色的用戶信息。

  • RoleDao 接口

public interface RoleDao {
    List<Role> findAll();
}
  • RoleDao.xml 配置

<select id="findAll" resultMap="roleMap">
    SELECT r.*, u.id as user_id, u.username 
    FROM role r
    JOIN user_role ur ON r.id = ur.RID
    JOIN user u ON u.id = ur.UID
</select>
<resultMap type="role" id="roleMap">
    <id property="id" column="id"/>
    <result property="role_name" column="role_name"/>
    <result property="role_desc" column="role_desc"/>
    <!-- 關(guān)聯(lián)用戶列表(多對多) -->
    <collection property="users" ofType="user">
        <id property="id" column="user_id"/> <!-- 別名避免與角色I(xiàn)D沖突 -->
        <result property="username" column="username"/>
    </collection>
</resultMap>
  • 測試代碼

@Test
public void testFindAllRoles() {
    List<Role> roles = roleDao.findAll();
    for (Role role : roles) {
        System.out.println("角色:" + role.getRole_name());
        System.out.println("關(guān)聯(lián)用戶:" + role.getUsers());
    }
}

說明:多對多查詢本質(zhì)是雙向的一對多查詢,通過中間表建立關(guān)聯(lián),同樣使用<collection>標(biāo)簽映射集合對象。

五、MyBatis 延遲加載策略

1. 延遲加載的概念

延遲加載(Lazy Loading)是一種數(shù)據(jù)庫查詢優(yōu)化策略,其核心思想是:僅在需要使用關(guān)聯(lián)數(shù)據(jù)時(shí)才進(jìn)行實(shí)際查詢。與立即加載(Eager Loading)相比,延遲加載避免了不必要的數(shù)據(jù)庫訪問,提高了系統(tǒng)性能。

對比示例(一對多關(guān)系)

  • 立即加載:查詢用戶時(shí),同時(shí)加載該用戶的所有賬戶信息(即使后續(xù)可能不使用賬戶數(shù)據(jù))。
  • 延遲加載:查詢用戶時(shí),僅加載用戶基本信息;當(dāng)程序調(diào)用user.getAccounts()時(shí),才會(huì)觸發(fā)賬戶數(shù)據(jù)的查詢。

2. 應(yīng)用場景選擇

場景加載策略示例
多對一關(guān)系立即加載查詢賬戶時(shí),同時(shí)加載所屬用戶
一對多 / 多對多延遲加載查詢用戶時(shí),暫不加載賬戶信息

3. 多對一延遲加載實(shí)現(xiàn)

(1)配置文件示例

<!-- AccountMapper.xml -->
<resultMap type="Account" id="accountMap">
    <id column="id" property="id"/>
    <result column="uid" property="uid"/>
    <result column="money" property="money"/>
    <!-- 配置延遲加載:通過select屬性指定關(guān)聯(lián)查詢方法 -->
    <association property="user" javaType="User" 
                 select="com.qcbyjy.mapper.UserMapper.findById" 
                 column="uid">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
    </association>
</resultMap>

(2)核心配置參數(shù)

<!-- SqlMapConfig.xml -->
<settings>
    <!-- 開啟延遲加載功能 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 禁用積極加載(默認(rèn)false,按需加載) -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

 測試方法

@Test
    public void testFindAll1() throws IOException {
        // 先加載主配置文件,加載到輸入流中
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 創(chuàng)建SqlSessionFactory對象,創(chuàng)建SqlSession對象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        // 創(chuàng)建SqlSession對象
        SqlSession session = factory.openSession();
        // 獲取代理對象
        AccountMapper mapper = session.getMapper(AccountMapper.class);
        // 1. 查詢主對象(賬戶)
        List<Account> accounts = mapper.findAll();
        System.out.println("===== 主查詢已執(zhí)行 =====");
        // 2. 遍歷賬戶,但不訪問關(guān)聯(lián)的用戶
        for (Account account : accounts) {
            System.out.println("賬戶ID:" + account.getId() + ",金額:" + account.getMoney());
        }
        System.out.println("===== 未訪問關(guān)聯(lián)對象 =====");
        // 3. 首次訪問關(guān)聯(lián)的用戶
        for (Account account : accounts) {
            System.out.println("===== 開始訪問用戶 =====");
            System.out.println("用戶名:" + account.getUser().getUsername()); // 觸發(fā)懶加載
            System.out.println("===== 訪問用戶結(jié)束 =====");
        }
        // 關(guān)閉資源
        session.close();
        inputStream.close();
    }

執(zhí)行findAll()時(shí),日志僅輸出賬戶表的查詢 SQL 。遍歷賬戶但不訪問用戶時(shí),無新的 SQL 輸出

首次訪問account.getUser()時(shí),日志輸出用戶表的查詢 SQL(按需加載) 

(3)工作原理

當(dāng)執(zhí)行account.getUser()時(shí),MyBatis 會(huì):

  1. 檢查lazyLoadingEnabled是否為true
  2. 通過select屬性調(diào)用UserMapper.findById(uid)方法;
  3. 將結(jié)果封裝到Account.user屬性中。

4. 一對多延遲加載實(shí)現(xiàn)

(1)配置文件示例

<!-- UserMapper.xml -->
<resultMap type="User" id="userMap">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <!-- 配置延遲加載:集合屬性 -->
    <collection property="accounts" ofType="Account" 
                select="com.qcbyjy.mapper.AccountMapper.findByUid" 
                column="id">
        <id column="id" property="id"/>
        <result column="money" property="money"/>
    </collection>
</resultMap>

(2)延遲加載觸發(fā)時(shí)機(jī)

List<User> users = userMapper.findAll();
for (User user : users) {
    // 調(diào)用getAccounts()時(shí)觸發(fā)延遲查詢
    System.out.println(user.getAccounts()); 
}

測試代碼:

    @Test
    public void testFindAllq() throws Exception {
        // 調(diào)用方法
        List<User> list = mapper.findAll();
        for (User user : list) {
            System.out.println(user.getUsername());
            System.out.println(user.getAccounts());
            System.out.println("==============");
        }
    }

5. 延遲加載注意事項(xiàng)

  1. N+1 查詢問題:延遲加載可能導(dǎo)致 N+1 查詢(主查詢 1 次,關(guān)聯(lián)查詢 N 次),需結(jié)合二級緩存優(yōu)化。
  2. Session 生命周期:延遲加載需確保關(guān)聯(lián)查詢時(shí)SqlSession未關(guān)閉(可通過openSession(true)保持會(huì)話)。
  3. 序列化問題:延遲加載的對象在序列化時(shí)可能丟失代理狀態(tài),需通過<setting name="serializationFactory" value="..."/>配置。

七、MyBatis 緩存機(jī)制

1. 緩存的基本概念

緩存是一種內(nèi)存臨時(shí)存儲(chǔ)技術(shù),用于減少數(shù)據(jù)庫訪問次數(shù),提高系統(tǒng)響應(yīng)速度。適合緩存的數(shù)據(jù)特點(diǎn):

  • 頻繁查詢但很少修改;
  • 數(shù)據(jù)一致性要求不高;
  • 數(shù)據(jù)量適中且訪問頻率高。

2. 一級緩存(SqlSession 級緩存)

(1)緩存原理

  • 作用域:每個(gè)SqlSession獨(dú)享一個(gè)緩存實(shí)例;
  • 存儲(chǔ)結(jié)構(gòu):底層使用PerpetualCache(基于HashMap實(shí)現(xiàn));
  • 生命周期:與SqlSession一致,session.close()后緩存銷毀。

(2)緩存驗(yàn)證示例

@Test
public void testFirstLevelCache() {
    try (SqlSession session = sqlSessionFactory.openSession()) {
        UserMapper mapper = session.getMapper(UserMapper.class);
        // 第一次查詢:觸發(fā)SQL
        User user1 = mapper.findById(1);
        // 第二次查詢:命中緩存
        User user2 = mapper.findById(1);
        System.out.println(user1 == user2); // 輸出true(同一對象)
    }
}

(3)緩存失效場景

以下操作會(huì)導(dǎo)致一級緩存清空:

  • session.clearCache():手動(dòng)清空緩存;
  • session.commit()/session.rollback():事務(wù)提交或回滾;
  • 執(zhí)行insert/update/delete操作(任何數(shù)據(jù)變更)。

3. 一級緩存源碼分析

核心源碼位于BaseExecutor類:

// BaseExecutor.java
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 1. 創(chuàng)建緩存Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 2. 查詢一級緩存
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 從本地緩存中獲取
    List<E> list = localCache.getObject(key);
    if (list != null) {
        // 緩存命中
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        return list;
    } else {
        // 緩存未命中,查詢數(shù)據(jù)庫
        return queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
}

4. 一級緩存的應(yīng)用建議

  • 優(yōu)勢:無需額外配置,自動(dòng)生效,適用于單次會(huì)話內(nèi)的重復(fù)查詢;
  • 局限:無法跨SqlSession共享,對長事務(wù)可能導(dǎo)致數(shù)據(jù)不一致;
  • 最佳實(shí)踐
    • 避免在同一SqlSession內(nèi)進(jìn)行重復(fù)查詢;
    • 及時(shí)提交事務(wù)或關(guān)閉SqlSession以釋放緩存資源。

八、延遲加載與一級緩存的協(xié)同工作

當(dāng)延遲加載與一級緩存結(jié)合時(shí),需注意:

  1. 關(guān)聯(lián)查詢緩存:延遲加載的關(guān)聯(lián)對象(如user.getAccounts())會(huì)被存入一級緩存;
  2. 會(huì)話隔離:不同SqlSession的延遲加載結(jié)果相互獨(dú)立;
  3. 數(shù)據(jù)一致性:若主對象已緩存,關(guān)聯(lián)對象的變更可能無法實(shí)時(shí)反映。

mysql緩存存的是語句,稍有修改就會(huì)更新緩存。一級緩存,輸出的對象是同一個(gè),二級緩存輸出不同是因?yàn)橥ㄟ^序列化組裝了兩

一、一級緩存(SqlSession 級緩存)—— 同一個(gè)對象實(shí)例

1. 緩存本質(zhì)與作用范圍

一級緩存是 SqlSession 私有 的本地緩存,MyBatis 默認(rèn)開啟。在同一個(gè) SqlSession 內(nèi),只要查詢條件、SQL 語句相同,MyBatis 會(huì)直接從緩存取結(jié)果,不會(huì)重復(fù)訪問數(shù)據(jù)庫。

2. “輸出對象是同一個(gè)” 的原因

  • 當(dāng)執(zhí)行查詢時(shí),MyBatis 會(huì)先檢查一級緩存:若命中緩存,直接返回 緩存中存儲(chǔ)的對象引用 。
  • 也就是說,多次查詢拿到的是同一個(gè) Java 對象實(shí)例(JVM 中同一個(gè)內(nèi)存地址的對象 )。例如:
try (SqlSession session = sqlSessionFactory.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    // 第一次查詢,從數(shù)據(jù)庫加載,存入一級緩存
    User user1 = mapper.getUserById(1);  
    // 第二次查詢,命中一級緩存,直接返回 user1 的引用
    User user2 = mapper.getUserById(1);  
    System.out.println(user1 == user2); // 輸出 true,是同一個(gè)對象實(shí)例
}

3. 緩存失效場景

當(dāng)執(zhí)行 update、insert、deletecommit、close 等操作時(shí),一級緩存會(huì)被清空 。后續(xù)查詢會(huì)重新從數(shù)據(jù)庫加載數(shù)據(jù),存入新的對象實(shí)例到緩存。

二、二級緩存(Mapper 級緩存)—— 不同對象實(shí)例(因序列化 / 反序列化)

1. 緩存本質(zhì)與作用范圍

二級緩存是 Mapper 作用域 的緩存,需手動(dòng)開啟(在 Mapper XML 或注解中配置 )。它可以在多個(gè) SqlSession 間共享,底層通常依賴序列化 / 反序列化機(jī)制存儲(chǔ)數(shù)據(jù) 。

2. “輸出不同對象” 的原因

  • 二級緩存存儲(chǔ)的是 對象的序列化數(shù)據(jù) (如 Java 對象先序列化為字節(jié)流,再存入緩存 )。
  • 當(dāng)不同 SqlSession 查詢命中二級緩存時(shí),MyBatis 會(huì) 反序列化 緩存中的字節(jié)流,重新生成一個(gè)新的 Java 對象實(shí)例 。例如:
// 開啟二級緩存后,不同 SqlSession 測試
try (SqlSession session1 = sqlSessionFactory.openSession()) {
    UserMapper mapper1 = session1.getMapper(UserMapper.class);
    User user1 = mapper1.getUserById(1); 
    session1.commit(); // 提交后,數(shù)據(jù)可能同步到二級緩存(取決于配置)
}
try (SqlSession session2 = sqlSessionFactory.openSession()) {
    UserMapper mapper2 = session2.getMapper(UserMapper.class);
    User user2 = mapper2.getUserById(1); // 命中二級緩存,反序列化生成新對象
    System.out.println(user1 == user2); // 輸出 false,是不同對象實(shí)例
}

3. 二級緩存的核心特點(diǎn)

  • 跨 SqlSession 共享:多個(gè) SqlSession 可共用 Mapper 級的緩存數(shù)據(jù);
  • 序列化存儲(chǔ):緩存數(shù)據(jù)需實(shí)現(xiàn) Serializable 接口,存儲(chǔ)和讀取時(shí)會(huì)經(jīng)歷序列化 / 反序列化,因此每次命中緩存會(huì)生成新對象;
  • 緩存策略靈活:可配置 eviction(回收策略,如 LRU、FIFO )、flushInterval(刷新間隔 )、readOnly(是否只讀 )等。

三、一、二級緩存的核心差異對比

對比項(xiàng)一級緩存二級緩存
作用范圍SqlSession 私有Mapper 作用域(跨 SqlSession 共享)
對象實(shí)例同一對象引用反序列化生成新對象
開啟方式默認(rèn)開啟需手動(dòng)配置(<cache> 標(biāo)簽或注解)
存儲(chǔ)機(jī)制直接存對象引用存序列化后的字節(jié)流
數(shù)據(jù)一致性依賴 SqlSession 內(nèi)操作,易維護(hù)需注意多表關(guān)聯(lián)、更新同步問題

四、實(shí)際開發(fā)中的注意事項(xiàng)

  1. 一級緩存的 “隱式風(fēng)險(xiǎn)”
    若在同一個(gè) SqlSession 內(nèi),先查詢再更新數(shù)據(jù),由于一級緩存未及時(shí)清理(需手動(dòng) commit/close 觸發(fā) ),可能拿到舊數(shù)據(jù)。建議在增刪改后,及時(shí) commit 或 close SqlSession,保證緩存與數(shù)據(jù)庫一致。

  2. 二級緩存的 “使用前提”
    啟用二級緩存時(shí),實(shí)體類必須實(shí)現(xiàn) Serializable 接口(否則序列化報(bào)錯(cuò) );同時(shí),若涉及多表關(guān)聯(lián)查詢,需注意緩存的更新策略(比如某張表數(shù)據(jù)變化后,關(guān)聯(lián)的 Mapper 緩存需及時(shí)刷新 )。

  3. 緩存的合理選擇

    • 一級緩存適合短生命周期的 SqlSession(如單次請求內(nèi)的多次查詢 );
    • 二級緩存適合查詢頻率高、數(shù)據(jù)變化少的場景(如系統(tǒng)字典表 ),但需謹(jǐn)慎處理數(shù)據(jù)更新后的緩存同步。

簡單來說,一級緩存是 “同一個(gè)對象復(fù)用”,二級緩存是 “序列化后重新組裝對象”,這種差異由它們的作用范圍和存儲(chǔ)機(jī)制決定。開發(fā)中根據(jù)業(yè)務(wù)場景合理利用緩存,既能提升性能,又能避免數(shù)據(jù)一致性問題~ 若你在實(shí)際配置或調(diào)試緩存時(shí)遇到具體問題(比如二級緩存不生效、序列化報(bào)錯(cuò) ),可以接著展開說場景幫你分析 。

到此這篇關(guān)于MyBatis連接池、動(dòng)態(tài) SQL 與多表關(guān)聯(lián)查詢的文章就介紹到這了,更多相關(guān)mybatis多表關(guān)聯(lián)查詢內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java網(wǎng)絡(luò)編程基礎(chǔ)用法詳解

    Java網(wǎng)絡(luò)編程基礎(chǔ)用法詳解

    網(wǎng)絡(luò)編程是指編寫運(yùn)行在多個(gè)設(shè)備(計(jì)算機(jī))的程序,這些設(shè)備都通過網(wǎng)絡(luò)連接起來,本文將帶大家詳細(xì)了解Java的網(wǎng)絡(luò)編程,文中有相關(guān)的代碼示例,需要的朋友可以參考下
    2023-05-05
  • Springboot整合freemarker和相應(yīng)的語法詳解

    Springboot整合freemarker和相應(yīng)的語法詳解

    FreeMarker是一款Spring官方推薦使用的模板引擎。接下來通過本文給大家介紹Springboot整合freemarker和相應(yīng)的語法,感興趣的朋友一起看看吧
    2021-09-09
  • Java Filter 過濾器詳細(xì)介紹及實(shí)例代碼

    Java Filter 過濾器詳細(xì)介紹及實(shí)例代碼

    Filter也稱之為過濾器,它是Servlet技術(shù)中最實(shí)用的技術(shù),本文章WEB開發(fā)人員通過Filter技術(shù),對web服務(wù)器管理的所有web資源進(jìn)行攔截,從而實(shí)現(xiàn)一些特殊的功能,本文章將向大家介紹Java 中的 Filter 過濾器,需要的朋友可以參考一下
    2016-12-12
  • Java計(jì)算數(shù)學(xué)表達(dá)式代碼詳解

    Java計(jì)算數(shù)學(xué)表達(dá)式代碼詳解

    這篇文章主要介紹了Java計(jì)算數(shù)學(xué)表達(dá)式代碼詳解,具有一定借鑒價(jià)值,需要的朋友可以了解下。
    2017-12-12
  • SpringBoot讀寫操作yml配置文件方法

    SpringBoot讀寫操作yml配置文件方法

    之前一直用的application.properties配置文件,只能是KV結(jié)構(gòu),后來的yml配置文件更像是樹狀結(jié)構(gòu),支持層級,比properties更靈活
    2023-01-01
  • Java中調(diào)用Python的實(shí)現(xiàn)示例

    Java中調(diào)用Python的實(shí)現(xiàn)示例

    本文主要介紹了Java中調(diào)用Python的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • 詳解解決IDEA2020.1版本的lombok插件問題

    詳解解決IDEA2020.1版本的lombok插件問題

    這篇文章主要介紹了詳解解決IDEA2020.1版本的lombok插件問題。文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 利用Spring JPA中的@Version注解實(shí)現(xiàn)樂觀鎖

    利用Spring JPA中的@Version注解實(shí)現(xiàn)樂觀鎖

    樂觀鎖是數(shù)據(jù)庫和應(yīng)用程序中使用的一種并發(fā)控制策略,用于在多個(gè)事務(wù)嘗試更新單個(gè)記錄時(shí)確保數(shù)據(jù)完整性,Java Persistence API (JPA) 提供了一種借助@Version注解在 Java 應(yīng)用程序中實(shí)現(xiàn)樂觀鎖的機(jī)制,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下
    2023-11-11
  • spring mvc中直接注入的HttpServletRequst安全嗎

    spring mvc中直接注入的HttpServletRequst安全嗎

    這篇文章主要給大家介紹了關(guān)于spring mvc中直接注入的HttpServletRequst是不是安全的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。
    2018-04-04
  • Spring排序機(jī)制之接口與注解的使用方法

    Spring排序機(jī)制之接口與注解的使用方法

    本文介紹了Spring中多種排序機(jī)制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了詳細(xì)示例,并指導(dǎo)如何選擇合適的排序機(jī)制來控制Bean的加載順序和優(yōu)先級,感興趣的朋友一起看看吧
    2025-02-02

最新評論

蚌埠市| 普兰店市| 淳安县| 河南省| SHOW| 莆田市| 贡觉县| 蛟河市| 科技| 临泽县| 富源县| 定边县| 汉寿县| 新乡市| 长沙县| 伽师县| 泰州市| 壶关县| 湟中县| 西藏| 荔浦县| 门头沟区| 阳春市| 建宁县| 罗源县| 亚东县| 双桥区| 什邡市| 保靖县| 贡嘎县| 罗平县| 邹城市| 绥江县| 宁陵县| 莒南县| 峨山| 收藏| 漳州市| 客服| 恩平市| 武夷山市|