深入掌握MyBatis:连接池、动态SQL、多表查询与缓存

文章目录

  • 一、MyBatis连接池
    • [1.1 连接池的作用](#1.1 连接池的作用)
      • [1.2 MyBatis连接池分类](#1.2 MyBatis连接池分类)
  • 二、动态SQL
    • [2.1 if标签](#2.1 if标签)
    • [2.2 where标签](#2.2 where标签)
    • [2.3 foreach标签](#2.3 foreach标签)
    • [2.4 SQL片段复用](#2.4 SQL片段复用)
  • 三、多表查询
      • [3.1 多对一查询(一对一)](#3.1 多对一查询(一对一))
      • [3.2 一对多查询](#3.2 一对多查询)
  • 四、延迟加载
      • [4.1 立即加载 vs 延迟加载](#4.1 立即加载 vs 延迟加载)
      • [4.2 配置延迟加载](#4.2 配置延迟加载)
  • 五、MyBatis缓存
    • [5.1 一级缓存](#5.1 一级缓存)
    • [5.2 二级缓存](#5.2 二级缓存)
    • [5.3 一级缓存 vs 二级缓存:核心差异](#5.3 一级缓存 vs 二级缓存:核心差异)
    • [5.4 缓存使用陷阱与最佳实践**](#5.4 缓存使用陷阱与最佳实践**)
  • 总结

一、MyBatis连接池

1.1 连接池的作用

  • 什么是连接池:存储数据库连接的容器,避免频繁创建和销毁连接。
  • 解决的问题:每次执行SQL都创建连接会浪费资源,连接池复用连接提升性能。

1.2 MyBatis连接池分类

类型 描述
POOLED 使用连接池(默认)
UNPOOLED 不使用连接池(适合简单场景)
JNDI 通过JNDI获取外部连接池

配置示例

xml 复制代码
<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字符串。

xml 复制代码
<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

xml 复制代码
<where>
    <if test="username != null">username LIKE #{username}</if>
</where>

2.3 foreach标签

遍历集合生成条件,支持INOR拼接。

xml 复制代码
<!-- 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片段,减少重复代码。

xml 复制代码
<sql id="baseSelect">SELECT * FROM user</sql>
<select id="findAll">
    <include refid="baseSelect"/>
</select>

三、多表查询

3.1 多对一查询(一对一)

需求:查询账户信息并关联用户信息。

JavaBean

java 复制代码
public class Account {
    private Integer id;
    private Double money;
    private User user; // 关联用户
}

XML配置

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

java 复制代码
public class User {
    private List<Account> accounts; // 用户拥有多个账户
}

XML配置

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 配置延迟加载

开启全局延迟加载

xml 复制代码
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

一对一延迟加载示例

假设有 UserAddress 两个实体,一个用户对应一个地址。在传统的查询方式中,可能会一次性将用户及其地址信息都查询出来。但在延迟加载模式下,查询用户时并不会立即查询地址信息。

然后在 User 的映射文件中配置:

xml 复制代码
<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 的映射文件中配置:

xml 复制代码
<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 的映射文件中配置如下:

xml 复制代码
<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语句。

多对多延迟加载示例

假设 UserRole 是多对多关系,需要通过中间表 user_role 来关联。

首先在 User 的映射文件中配置:

xml 复制代码
<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 的映射文件中也做类似配置:

xml 复制代码
<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 内多次执行 相同查询,优先从缓存读取。
java 复制代码
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. 缓存策略与配置

  • 开启步骤

    xml 复制代码
    <!-- 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. 工作流程

  1. 查询顺序:二级缓存 → 一级缓存 → 数据库。
  2. 数据提交SqlSession 关闭时,一级缓存数据同步到二级缓存。

5.3 一级缓存 vs 二级缓存:核心差异

维度 一级缓存 二级缓存
作用范围 SqlSession SqlSession(同一 Mapper)
默认状态 开启 需手动配置
存储位置 内存(JVM 堆) 可配置为磁盘或第三方缓存(如 Redis)
数据共享 不共享 多个 Session 共享
生命周期 随 Session 销毁 长期存在,需主动管理

5.4 缓存使用陷阱与最佳实践**

1. 常见问题

  • 脏读风险:跨服务节点时,二级缓存可能导致数据不一致。
  • 序列化开销:二级缓存序列化影响性能,需权衡缓存粒度。
  • 缓存穿透:频繁查询不存在的数据,需设置空值缓存。

2. 优化建议

  • 合理配置 :根据数据更新频率选择缓存策略(如 flushInterval)。
  • 第三方缓存:集成 Redis 或 Ehcache 实现分布式缓存。
  • 注解控制 :使用 @CacheNamespace@CacheNamespaceRef 精细化管理。

总结

掌握这些核心技能,可以高效利用MyBatis构建健壮的数据库应用。实际开发中,需根据业务场景选择合适策略,平衡性能与功能需求。

相关推荐
gwf21611 分钟前
SSD读写速度深度解析:顺序读写vs随机读写、IOPS、延迟,你的硬盘性能到底怎么看?
git·嵌入式硬件·缓存·github·智能硬件
不剪发的Tony老师2 小时前
VeloxDB:一款免费开源的轻量级数据库管理工具
数据库·sql
dotnet902 小时前
批量生成所有表的修改脚本
数据库·sql·oracle
番茄炒鸡蛋加糖3 小时前
缓存三大问题
缓存
灯澜忆梦4 小时前
【MySQL7】多表查询
数据库·sql
Qlittleboy4 小时前
tp5网站链接不上slqserver,计算机积极拒绝了链接
数据库·sql
江晓鱼未暖7 小时前
十七、Redis 核心原理与架构详解
大数据·数据库·数据仓库·redis·缓存·架构
小罗水8 小时前
第13章 Redis 缓存、幂等锁与任务状态
数据库·redis·缓存
热心市民lcj8 小时前
Spring Boot 整合 Caffeine 本地缓存实战
spring boot·后端·缓存
zx1154509 小时前
【热点 Key 探测与加长缓存 TTL 实战】
缓存