MyBatis 進階:連接池、動態 SQL 與多表關聯查詢

MyBatis 作為一款靈活的持久層框架,除了基礎的 CRUD 操作,還提供了連接池管理、動態 SQL 以及多表關聯查詢等高級特性。本文將從連接池原理出發,深入講解動態 SQL 的常用標簽,并通過實例演示一對多、多對多等復雜關聯查詢的實現,幫助你掌握 MyBatis 的進階用法。

一、MyBatis 連接池:提升數據庫交互性能

連接池是存儲數據庫連接的容器,它的核心作用是避免頻繁創建和關閉連接,從而減少資源消耗、提高程序響應速度。在 MyBatis 中,連接池的配置通過dataSource標簽的type屬性實現,支持三種類型的連接池:

1. 連接池類型詳解

  • POOLED:使用 MyBatis 內置的連接池
    MyBatis 會維護一個連接池,當需要連接時從池中獲取,使用完畢后歸還給池,避免頻繁創建連接。適用于高并發場景,是開發中最常用的類型。

    配置示例:

    <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:不使用連接池
    每次執行 SQL 時都會創建新的連接,使用后直接關閉。適用于低并發場景,性能較差,一般僅用于簡單測試。

    配置示例:

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

    配置示例:

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

2. 連接池的優勢

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

二、動態 SQL:靈活拼接 SQL 語句

在實際開發中,查詢條件往往是動態變化的(如多條件篩選、批量操作等)。MyBatis 的動態 SQL 標簽可以優雅地解決 SQL 語句拼接問題,避免手動拼接導致的語法錯誤和 SQL 注入風險。

1.?<if>標簽:條件判斷

<if>標簽用于根據參數值動態生成 SQL 片段,常用來處理多條件查詢。

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

  • UserMapper 接口

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

    <select id="findByWhere" parameterType="user" resultType="user">select * from user<where><!-- 當username非空且非空字符串時,添加條件 --><if test="username != null and username != ''">and username like #{username}</if><!-- 當sex非空且非空字符串時,添加條件 --><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);// 遍歷結果...
    }
    

說明test屬性中的表達式用于判斷參數是否有效,where標簽會自動處理多余的andor,避免 SQL 語法錯誤。

?

2.?<foreach>標簽:遍歷集合

<foreach>標簽用于遍歷集合或數組,常用來處理in查詢或批量操作。

場景 1:查詢 ID 在指定集合中的用戶(in查詢)
  • User 實體類:添加存儲 ID 集合的屬性

    public class User {private List<Integer> ids; // 存儲多個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片段結尾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,只需調整<foreach>openseparator

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

3.?<sql><include>標簽:SQL 片段復用

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

示例:復用查詢用戶的 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為片段唯一標識,refid指定要引用的片段 ID,適用于多表查詢中重復的字段列表。

三、一對多查詢:用戶與賬戶的關聯

在實際業務中,表之間往往存在關聯關系(如用戶與賬戶:一個用戶可以有多個賬戶)。MyBatis 通過<collection>標簽處理一對多關聯查詢。

1. 表結構與實體類設計

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

實體類設計

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

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

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

2. 多對一查詢(賬戶關聯用戶)

查詢所有賬戶,并關聯查詢所屬用戶的信息。

  • AccountMapper 接口

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

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

說明<association>標簽用于映射關聯的單個對象,javaType指定對象類型。

?

3. 一對多查詢(用戶關聯賬戶)

查詢所有用戶,并關聯查詢其名下的所有賬戶。

  • UserMapper 接口

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

    <select id="findOneToMany" resultMap="userAccountMap">select u.*, a.id as aid, a.money from user uleft 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"/><!-- 關聯賬戶列表(一對多) --><collection property="accounts" ofType="account"><id property="id" column="aid"/> <!-- 注意別名避免與用戶ID沖突 --><result property="money" column="money"/></collection>
    </resultMap>
    

說明<collection>標簽用于映射關聯的集合對象,ofType指定集合中元素的類型。

?

四、多對多查詢:用戶與角色的關聯

多對多關系需要通過中間表實現(如用戶與角色:一個用戶可擁有多個角色,一個角色可分配給多個用戶,通過user_role表關聯)。

1. 表結構與實體類設計

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

實體類設計

  • Role 類(多對多:一個角色包含多個用戶):
    public class Role implements Serializable {private Integer id;private String role_name;private String role_desc;// 關聯的用戶列表private List<User> users;// 省略getter、setter
    }
    

2. 多對多查詢實現

查詢所有角色,并關聯查詢擁有該角色的用戶信息。

  • 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 rJOIN user_role ur ON r.id = ur.RIDJOIN 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"/><!-- 關聯用戶列表(多對多) --><collection property="users" ofType="user"><id property="id" column="user_id"/> <!-- 別名避免與角色ID沖突 --><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("關聯用戶:" + role.getUsers());}
    }
    

說明:多對多查詢本質是雙向的一對多查詢,通過中間表建立關聯,同樣使用<collection>標簽映射集合對象。

?

?

五、MyBatis 延遲加載策略

1. 延遲加載的概念

延遲加載(Lazy Loading)是一種數據庫查詢優化策略,其核心思想是:僅在需要使用關聯數據時才進行實際查詢。與立即加載(Eager Loading)相比,延遲加載避免了不必要的數據庫訪問,提高了系統性能。

對比示例(一對多關系)

  • 立即加載:查詢用戶時,同時加載該用戶的所有賬戶信息(即使后續可能不使用賬戶數據)。
  • 延遲加載:查詢用戶時,僅加載用戶基本信息;當程序調用user.getAccounts()時,才會觸發賬戶數據的查詢。

2. 應用場景選擇

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

3. 多對一延遲加載實現

(1)配置文件示例
<!-- AccountMapper.xml -->
<resultMap type="Account" id="accountMap"><id column="id" property="id"/><result column="uid" property="uid"/><result column="money" property="money"/><!-- 配置延遲加載:通過select屬性指定關聯查詢方法 --><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)核心配置參數
<!-- SqlMapConfig.xml -->
<settings><!-- 開啟延遲加載功能 --><setting name="lazyLoadingEnabled" value="true"/><!-- 禁用積極加載(默認false,按需加載) --><setting name="aggressiveLazyLoading" value="false"/>
</settings>

?測試方法

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

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

?

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

?

(3)工作原理

當執行account.getUser()時,MyBatis 會:

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

4. 一對多延遲加載實現

(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)延遲加載觸發時機
List<User> users = userMapper.findAll();
for (User user : users) {// 調用getAccounts()時觸發延遲查詢System.out.println(user.getAccounts()); 
}

測試代碼:

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

?

5. 延遲加載注意事項

  1. N+1 查詢問題:延遲加載可能導致 N+1 查詢(主查詢 1 次,關聯查詢 N 次),需結合二級緩存優化。
  2. Session 生命周期:延遲加載需確保關聯查詢時SqlSession未關閉(可通過openSession(true)保持會話)。
  3. 序列化問題:延遲加載的對象在序列化時可能丟失代理狀態,需通過<setting name="serializationFactory" value="..."/>配置。

七、MyBatis 緩存機制

1. 緩存的基本概念

緩存是一種內存臨時存儲技術,用于減少數據庫訪問次數,提高系統響應速度。適合緩存的數據特點:

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

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

(1)緩存原理
  • 作用域:每個SqlSession獨享一個緩存實例;
  • 存儲結構:底層使用PerpetualCache(基于HashMap實現);
  • 生命周期:與SqlSession一致,session.close()后緩存銷毀。
(2)緩存驗證示例
@Test
public void testFirstLevelCache() {try (SqlSession session = sqlSessionFactory.openSession()) {UserMapper mapper = session.getMapper(UserMapper.class);// 第一次查詢:觸發SQLUser user1 = mapper.findById(1);// 第二次查詢:命中緩存User user2 = mapper.findById(1);System.out.println(user1 == user2); // 輸出true(同一對象)}
}

?

(3)緩存失效場景

以下操作會導致一級緩存清空:

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

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. 創建緩存KeyCacheKey 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 {// 緩存未命中,查詢數據庫return queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}
}

4. 一級緩存的應用建議

  • 優勢:無需額外配置,自動生效,適用于單次會話內的重復查詢;
  • 局限:無法跨SqlSession共享,對長事務可能導致數據不一致;
  • 最佳實踐
    • 避免在同一SqlSession內進行重復查詢;
    • 及時提交事務或關閉SqlSession以釋放緩存資源。

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

當延遲加載與一級緩存結合時,需注意:

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

mysql緩存存的是語句,稍有修改就會更新緩存。一級緩存,輸出的對象是同一個,二級緩存輸出不同是因為通過序列化組裝了兩

一、一級緩存(SqlSession 級緩存)—— 同一個對象實例

1. 緩存本質與作用范圍

一級緩存是?SqlSession 私有?的本地緩存,MyBatis 默認開啟。在同一個 SqlSession 內,只要查詢條件、SQL 語句相同,MyBatis 會直接從緩存取結果,不會重復訪問數據庫。

2. “輸出對象是同一個” 的原因
  • 當執行查詢時,MyBatis 會先檢查一級緩存:若命中緩存,直接返回?緩存中存儲的對象引用?。
  • 也就是說,多次查詢拿到的是同一個 Java 對象實例(JVM 中同一個內存地址的對象 )。例如:
try (SqlSession session = sqlSessionFactory.openSession()) {UserMapper mapper = session.getMapper(UserMapper.class);// 第一次查詢,從數據庫加載,存入一級緩存User user1 = mapper.getUserById(1);  // 第二次查詢,命中一級緩存,直接返回 user1 的引用User user2 = mapper.getUserById(1);  System.out.println(user1 == user2); // 輸出 true,是同一個對象實例
}

?

3. 緩存失效場景

當執行?updateinsertdeletecommitclose?等操作時,一級緩存會被清空 。后續查詢會重新從數據庫加載數據,存入新的對象實例到緩存。

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

1. 緩存本質與作用范圍

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

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

?

3. 二級緩存的核心特點
  • 跨 SqlSession 共享:多個 SqlSession 可共用 Mapper 級的緩存數據;
  • 序列化存儲:緩存數據需實現?Serializable?接口,存儲和讀取時會經歷序列化 / 反序列化,因此每次命中緩存會生成新對象;
  • 緩存策略靈活:可配置?eviction(回收策略,如 LRU、FIFO )、flushInterval(刷新間隔 )、readOnly(是否只讀 )等。

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

對比項一級緩存二級緩存
作用范圍SqlSession 私有Mapper 作用域(跨 SqlSession 共享)
對象實例同一對象引用反序列化生成新對象
開啟方式默認開啟需手動配置(<cache>?標簽或注解)
存儲機制直接存對象引用存序列化后的字節流
數據一致性依賴 SqlSession 內操作,易維護需注意多表關聯、更新同步問題

四、實際開發中的注意事項

  1. 一級緩存的 “隱式風險”
    若在同一個 SqlSession 內,先查詢再更新數據,由于一級緩存未及時清理(需手動 commit/close 觸發 ),可能拿到舊數據。建議在增刪改后,及時 commit 或 close SqlSession,保證緩存與數據庫一致。

  2. 二級緩存的 “使用前提”
    啟用二級緩存時,實體類必須實現?Serializable?接口(否則序列化報錯 );同時,若涉及多表關聯查詢,需注意緩存的更新策略(比如某張表數據變化后,關聯的 Mapper 緩存需及時刷新 )。

  3. 緩存的合理選擇

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

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

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/88950.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88950.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/88950.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

反射型跨站點腳本(XSS)漏洞中網絡安全防火墻(WAF)被繞過進行內容植入與遠程劫持機制分析

在一次安全測試中&#xff0c;我發現目標站點在錯誤處理頁面對用戶輸入的查詢參數名未做任何轉義&#xff0c;當參數名中包含 <script> 標簽時&#xff0c;頁面會原樣渲染并執行其中的 JavaScript。本文將從實戰角度&#xff0c;詳細講解如何定位該反射型 XSS 漏洞、通過…

RAG實戰指南 Day 15:多語言與領域特定嵌入技術

【RAG實戰指南 Day 15】多語言與領域特定嵌入技術 引言 歡迎來到"RAG實戰指南"系列的第15天&#xff01;今天我們將深入探討多語言與領域特定嵌入技術——這是構建全球化、專業化RAG系統的關鍵技術。在現實業務場景中&#xff0c;我們經常需要處理多種語言的文檔&a…

無鉛PCB和無鹵pcb有什么區別?

在電子制造領域&#xff0c;環保法規的升級催生了多種特殊工藝的PCB產品。其中&#xff0c;無鉛PCB與無鹵PCB作為兩大主流方向&#xff0c;雖同屬綠色制造范疇&#xff0c;卻在技術路徑與應用場景上存在本質差異。環保指向的根本區別無鉛PCB的核心在于焊接材料的革新。傳統PCB采…

基于51單片機的貪吃蛇游戲Protues仿真設計

目錄 1 系統設計目的 2 系統實現功能 3 系統硬件設計 3.1系統設計框圖 3.2 液晶顯示模塊LCD12864 3.3 按鍵輸入模塊 3.4 時鐘電路和復位電路 4 系統軟件設計 4.1系統軟件流程 4.2 游戲引擎模塊程序設計 4.3 顯示模塊程序設計 4.4 輸入處理模塊程序設計 5 系統仿真…

HTML+CSS

一、HTML相關內容- <img> 標簽&#xff1a;- 用于在網頁中嵌入圖像&#xff0c; src 屬性指定圖像的路徑&#xff0c;可以是絕對路徑&#xff08;如 D:\Git\java115_java116\課堂代碼\前端代碼\pic\cat.jpg &#xff09;、相對路徑&#xff08;如 ./pic/cat.jpg &#x…

基于 Gitlab、Jenkins與Jenkins分布式、SonarQube 、Nexus 的 CiCd 全流程打造

前言 在當今數字化飛速發展的時代&#xff0c;軟件開發與交付的效率和質量成為了企業競爭的關鍵要素。為了滿足市場對軟件快速迭代和高質量交付的需求&#xff0c;越來越多的企業開始探索和實踐持續集成與持續交付&#xff08;CI/CD&#xff09;的開發模式。而 GitLab、Jenkin…

[密碼學實戰]密評相關題庫解析

[密碼學實戰]密評相關題庫解析 一、背景 依據《密碼法》第二十二條&#xff0c;關鍵信息基礎設施&#xff08;關基&#xff09;運營者必須開展商用密碼應用安全性評估&#xff0c;且需定期進行&#xff08;不少于每年一次&#xff09;。 二、核心解析 2.1 測評標準框架&#x…

谷歌開源庫gflags詳細說明

目錄 一.gflags 介紹 二.gflags安裝 三.gflags使用 1.包含頭文件 2.定義參數 3.訪問參數 4.不同文件訪問參數 5.初始化所有參數 6.運行參數設置 7.配置文件的使用 8.特殊參數標識 四.總結 一.gflags 介紹 gflags 是 Google 開發的一個開源庫&#xff0c;用于 C 應用…

Python爬蟲實戰:研究XlsxWriter 庫相關技術

1. 研究背景與意義 1.1 網絡爬蟲技術價值 網絡爬蟲作為數據采集的核心工具,在金融、醫療、教育等領域發揮關鍵作用。據 Statista 數據顯示,2025 年全球大數據市場規模預計達 3250 億美元,高效的數據獲取能力成為企業核心競爭力。Python 以其 80% 的市場占有率成為爬蟲開發首…

ThreadLocal內部結構深度解析(Ⅰ)

目錄 使用ThreadLocal 例子 內部結構分析 源碼解析 圖示詳解 ThreadLocal是Java中一個非常重要且常用的線程局部變量工具類&#xff0c;它使得每個線程可以獨立地持有自己的變量副本&#xff0c;而不是共享變量&#xff0c;解決了多線程環境下變量共享的線程安全問題。下面我…

Python 數據挖掘之數據探索

在數據挖掘的流程中&#xff0c;數據探索是非常關鍵的第一步&#xff0c;它能幫助我們深入了解數據的特點&#xff0c;為后續的預處理和模型構建打下堅實的基礎。我們主要圍繞四個方面展開&#xff1a;數據對象與特征、數據統計描述、數據可視化以及相關性和相似性度量。一、數…

高并發點贊場景Synchronized、AtomicLong、LongAdder 和 LongAccumulator性能分析

在高并發點贊場景中&#xff0c;我們需要一個高效、線程安全的計數器來記錄點贊數。synchronized、AtomicLong、LongAdder 和 LongAccumulator 都是 Java 中用于實現原子操作的類&#xff0c;但它們的性能在高并發下差異顯著。性能主要取決于線程競爭程度&#xff1a;競爭越高&…

postgreSQL的sql語句

目錄 一&#xff1a;前提準備1.postgreSQL的安裝可以參考我下面一片文章&#xff1a; 二&#xff1a;SQL語句 1.相同點&#xff1a;支持標準sql類型 2.參考詳細學習地址&#xff1a; 3.postgresql與mysql的不同點 一&#xff1a;前提準備 1.postgreSQL的安裝可以參考我下面…

vue3 JavaScript 數據累加 reduce

在Vue 3中&#xff0c;你可以使用JavaScript的reduce方法來處理數據累加。reduce方法通常用在數組上&#xff0c;它將數組中的每個元素通過一個累加器函數&#xff08;accumulator&#xff09;從左到右累積&#xff0c;最終生成一個單一的值。這在計算總和、累加值等場景中非常…

史上最清楚!讀者,寫者問題(操作系統os)

讀者-寫者問題是另一個里程碑式的同步互斥問題。它比生產者-消費者更復雜&#xff0c;因為它引入了不對稱的訪問權限&#xff1a;讀者和讀者之間是共享的&#xff0c;但寫者和任何人&#xff08;包括讀者和其他寫者&#xff09;之間都是互斥的。我們用一個生動的比喻來解析這個…

使用Starrocks替換Clickhouse的理由

背景 Starrocks和clickhouse都是非常優秀的OLAP數據庫&#xff0c;那么什么情況下使用clickhouse&#xff0c;什么場景下使用starrocks呢&#xff0c;本文就簡單列舉一下他們的優缺點 理由 首先兩者都是列存儲&#xff0c;并且都實現了列壓縮&#xff0c;所以從存儲中兩者的壓縮…

Mybatis 兩級緩存可能導致的問題

Mybatis 兩級緩存可能導致的問題兩級緩存簡介一級緩存 localCache效果開關二級緩存兩級緩存可能導致的問題分布式環境下查詢到過期數據事務隔離級別失效讀已提交失效讀未提交失效總結兩級緩存簡介 一級緩存 localCache 效果 一級緩存是 session 或者說事務級別的&#xff0c…

vue3+uniapp 使用vue-plugin-hiprint中實現打印效果

前言&#xff1a; vue3uniapp 使用vue-plugin-hiprint中實現打印效果 官網地址&#xff1a;gitee https://gitee.com/ccsimple/vue-plugin-hiprinthttps://gitee.com/ccsimple/vue-plugin-hiprint 實現效果&#xff1a; 預覽打印內容&#xff1a; 實現步驟&#xff1a; 1、安…

【elementUI踩坑記錄】解決 el-table 固定列 el-table__fixed 導致部分滾動條無法拖動的問題

目錄一、問題背景二、 問題現象三、核心原因四、解決辦法增強方案&#x1f680;寫在最后一、問題背景 在使用 Element UI 的 el-table 組件時&#xff0c;固定列功能雖然實用&#xff0c;但會引發滾動條交互問題&#xff1a; 固定列區域懸浮顯示滾動條但無法正常拖動滾動條 …

【機器人編程基礎】python文件的打開和關閉

文件的打開和關閉 在Python中,文件操作是一項基本而重要的任務,涉及到打開、讀取、寫入、關閉文件等操作。正確地管理文件對于數據持久化、輸入輸出處理等至關重要。下面將詳細解釋如何在Python中打開和關閉文件,并提供相應的代碼示例。 文件打開 在Python中,可以使用內…