深入掌握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构建健壮的数据库应用。实际开发中,需根据业务场景选择合适策略,平衡性能与功能需求。

相关推荐
冬瓜的编程笔记1 小时前
【MySQL成神之路】MySQL查询用法总结
数据库·sql
鸭鸭鸭进京赶烤2 小时前
第九届电子信息技术与计算机工程国际学术会议(EITCE 2025)
人工智能·计算机视觉·ai·云计算·aigc·mybatis·制造
Musennn2 小时前
SQL 数值计算全解析:ABS、CEIL、FLOOR与ROUND函数深度精讲
数据库·sql
林的快手4 小时前
基于 Redis 实现短信验证码登录功能的完整方案
java·开发语言·数据库·redis·缓存·bootstrap
Bug退退退1238 小时前
Redis 的 key 的过期策略是怎么实现的
数据库·redis·缓存
Access开发易登软件9 小时前
Access链接Azure SQL
数据库·后端·sql·flask·vba·azure·access
we1998989813 小时前
Spring Boot中的分布式缓存方案
spring boot·分布式·缓存
Мартин.13 小时前
[Meachines] [Hard] Dab Enumerate+memcached+ldconfig-Lib-Hijack特權升級+Tyrant
数据库·缓存·memcached
艺杯羹14 小时前
深入解析应用程序分层及 BaseDao 的封装策略
数据库·sql·mysql·jdbc·应用分层