文章目錄
- 一、MyBatis連接池
- 1.1 連接池的作用
- 1.2 MyBatis連接池分類
- 二、動態SQL
- 2.1 if標簽
- 2.2 where標簽
- 2.3 foreach標簽
- 2.4 SQL片段復用
- 三、多表查詢
- 3.1 多對一查詢(一對一)
- 3.2 一對多查詢
- 四、延遲加載
- 4.1 立即加載 vs 延遲加載
- 4.2 配置延遲加載
- 五、MyBatis緩存
- 5.1 一級緩存
- 5.2 二級緩存
- 5.3 一級緩存 vs 二級緩存:核心差異
- 5.4 緩存使用陷阱與最佳實踐**
- 總結
一、MyBatis連接池
1.1 連接池的作用
- 什么是連接池:存儲數據庫連接的容器,避免頻繁創建和銷毀連接。
- 解決的問題:每次執行SQL都創建連接會浪費資源,連接池復用連接提升性能。
1.2 MyBatis連接池分類
類型 | 描述 |
---|---|
POOLED | 使用連接池(默認) |
UNPOOLED | 不使用連接池(適合簡單場景) |
JNDI | 通過JNDI獲取外部連接池 |
配置示例:
<dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/test"/><property name="username" value="root"/>
</dataSource>
二、動態SQL
2.1 if標簽
動態拼接查詢條件,避免手動拼接SQL字符串。
<select id="findByWhere" resultType="User">SELECT * FROM user<where><if test="username != null">AND username LIKE #{username}</if><if test="sex != null">AND sex = #{sex}</if></where>
</select>
2.2 where標簽
自動處理WHERE
后的AND/OR
冗余,替代WHERE 1=1
。
<where><if test="username != null">username LIKE #{username}</if>
</where>
2.3 foreach標簽
遍歷集合生成條件,支持IN
或OR
拼接。
<!-- IN (1,2,3) -->
<foreach collection="ids" item="i" open="id IN (" separator="," close=")">#{i}
</foreach><!-- OR id=1 -->
<foreach collection="ids" item="i" open="id=" separator=" OR id=">#{i}
</foreach>
2.4 SQL片段復用
提取公共SQL片段,減少重復代碼。
<sql id="baseSelect">SELECT * FROM user</sql>
<select id="findAll"><include refid="baseSelect"/>
</select>
三、多表查詢
3.1 多對一查詢(一對一)
需求:查詢賬戶信息并關聯用戶信息。
JavaBean:
public class Account {private Integer id;private Double money;private User user; // 關聯用戶
}
XML配置:
<resultMap id="accountMap" type="Account"><id property="id" column="id"/><association property="user" javaType="User"><result property="username" column="username"/></association>
</resultMap><select id="findAll" resultMap="accountMap">SELECT a.*, u.username FROM account a JOIN user u ON a.uid = u.id
</select>
3.2 一對多查詢
需求:查詢用戶及其所有賬戶。
JavaBean:
public class User {private List<Account> accounts; // 用戶擁有多個賬戶
}
XML配置:
<resultMap id="userMap" type="User"><collection property="accounts" ofType="Account"><result property="money" column="money"/></collection>
</resultMap><select id="findOneToMany" resultMap="userMap">SELECT u.*, a.money FROM user u LEFT JOIN account a ON u.id = a.uid
</select>
四、延遲加載
延遲加載,也叫懶加載,簡單來說就是在真正需要數據的時候才去加載數據,而不是在一開始就把所有關聯數據都加載出來。這可以有效減少資源的浪費,尤其是在處理復雜對象關系時,避免一次性加載大量無用數據。
4.1 立即加載 vs 延遲加載
- 立即加載:查詢主對象時,直接加載關聯對象(默認)。
- 延遲加載:按需加載關聯對象,提升性能。
4.2 配置延遲加載
開啟全局延遲加載:
<settings><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/>
</settings>
一對一延遲加載示例:
假設有 User
和 Address
兩個實體,一個用戶對應一個地址。在傳統的查詢方式中,可能會一次性將用戶及其地址信息都查詢出來。但在延遲加載模式下,查詢用戶時并不會立即查詢地址信息。
然后在 User
的映射文件中配置:
<resultMap id="userMap" type="User"><id column="user_id" property="id"/><result column="username" property="username"/><association property="address" select="cn.tx.mapper.AddressMapper.findAddressByUserId" column="user_id" fetchType="lazy"/>
</resultMap>
這樣,當查詢用戶時,只有在訪問 user.getAddress()
時才會去查詢地址信息。
多對一延遲加載示例:
比如多個訂單對應一個用戶,在查詢訂單時,用戶信息可以延遲加載。在 Order
的映射文件中配置:
<resultMap id="orderMap" type="Order"><id column="order_id" property="id"/><result column="order_no" property="orderNo"/><association property="user" select="cn.tx.mapper.UserMapper.findUserByOrderId" column="user_id" fetchType="lazy"/>
</resultMap>
當訪問 order.getUser()
時才會加載用戶信息。
一對多延遲加載示例:
在 User
的映射文件中配置如下:
<resultMap id="userMap" type="User"><id column="user_id" property="id"/><result column="username" property="username"/><collection property="orders" select="cn.tx.mapper.OrderMapper.findOrdersByUserId" column="user_id" fetchType="lazy"/>
</resultMap>
只有在調用 user.getOrders()
時,才會執行查詢訂單的SQL語句。
多對多延遲加載示例:
假設 User
和 Role
是多對多關系,需要通過中間表 user_role
來關聯。
首先在 User
的映射文件中配置:
<resultMap id="userMap" type="User"><id column="user_id" property="id"/><result column="username" property="username"/><collection property="roles" select="cn.tx.mapper.RoleMapper.findRolesByUserId" column="user_id" fetchType="lazy"/>
</resultMap>
在 Role
的映射文件中也做類似配置:
<resultMap id="roleMap" type="Role"><id column="role_id" property="id"/><result column="role_name" property="roleName"/><collection property="users" select="cn.tx.mapper.UserMapper.findUsersByRoleId" column="role_id" fetchType="lazy"/>
</resultMap>
這樣在查詢用戶或角色時,相關聯的多對多關系數據只有在實際訪問時才會加載。
五、MyBatis緩存
5.1 一級緩存
1. 定義與特性
- 作用范圍:
SqlSession
級別,默認開啟。 - 生命周期:與
SqlSession
綁定,Session 關閉或執行commit()
/rollback()
時清空。 - 工作機制:同一個 Session 內多次執行 相同查詢,優先從緩存讀取。
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user1 = mapper.findById(1); // 第一次查詢,從數據庫獲取數據并放入一級緩存
User user2 = mapper.findById(1); // 第二次查詢,從一級緩存中獲取數據
2. 緩存命中與失效
- 命中條件:相同的
SQL
+ 相同的參數 + 相同的環境。 - 失效場景:
- 執行
INSERT/UPDATE/DELETE
操作。 - 手動調用
sqlSession.clearCache()
。 - 跨
SqlSession
的查詢不會共享緩存。
- 執行
3. 實戰注意點
- 坑點:跨方法調用時,若使用不同
SqlSession
,一級緩存不共享。 - 適用場景:短生命周期操作(如單次請求內的重復查詢)。
5.2 二級緩存
1. 定義與特性
- 作用范圍:
Mapper
級別(跨SqlSession
),需手動開啟。 - 生命周期:與應用進程綁定,重啟或調用
clear()
時清空。 - 存儲結構:序列化后的數據(需實體類實現
Serializable
)。
2. 緩存策略與配置
- 開啟步驟:
<!-- MyBatis 全局配置 --> <settings><setting name="cacheEnabled" value="true"/> </settings><!-- Mapper XML 中聲明 --> <mapper namespace="..."><cache eviction="LRU" flushInterval="60000" size="512"/> </mapper>
- 淘汰策略(
eviction
):LRU
:最近最少使用(默認)。FIFO
:先進先出。SOFT
:軟引用,基于垃圾回收器狀態回收。
3. 工作流程
- 查詢順序:二級緩存 → 一級緩存 → 數據庫。
- 數據提交:
SqlSession
關閉時,一級緩存數據同步到二級緩存。
5.3 一級緩存 vs 二級緩存:核心差異
維度 | 一級緩存 | 二級緩存 |
---|---|---|
作用范圍 | SqlSession 內 | 跨 SqlSession (同一 Mapper) |
默認狀態 | 開啟 | 需手動配置 |
存儲位置 | 內存(JVM 堆) | 可配置為磁盤或第三方緩存(如 Redis) |
數據共享 | 不共享 | 多個 Session 共享 |
生命周期 | 隨 Session 銷毀 | 長期存在,需主動管理 |
5.4 緩存使用陷阱與最佳實踐**
1. 常見問題
- 臟讀風險:跨服務節點時,二級緩存可能導致數據不一致。
- 序列化開銷:二級緩存序列化影響性能,需權衡緩存粒度。
- 緩存穿透:頻繁查詢不存在的數據,需設置空值緩存。
2. 優化建議
- 合理配置:根據數據更新頻率選擇緩存策略(如
flushInterval
)。 - 第三方緩存:集成 Redis 或 Ehcache 實現分布式緩存。
- 注解控制:使用
@CacheNamespace
和@CacheNamespaceRef
精細化管理。
總結
掌握這些核心技能,可以高效利用MyBatis構建健壯的數據庫應用。實際開發中,需根據業務場景選擇合適策略,平衡性能與功能需求。