当前位置:首页 > 技术 > 正文内容

MyBatis 缓存策略与多表查询实现详解

访客 技术 2026年6月8日 1

一、MyBatis 两级缓存架构

缓存的核心价值在于降低数据库访问频次,MyBatis 以 SQL 语句的 namespace + id 作为缓存键值。当多次执行同一标识的查询时,可直接命中缓存。

1.1 一级缓存(会话级)

一级缓存与 SqlSession 绑定,默认启用。其生命周期随会话创建而开始,随会话关闭而终结。同一会话内重复查询将直接读取内存数据。

public void demoFirstLevelCache() {
    // 初始化查询条件
    User criteria = new User();
    criteria.setUsername("李清照");
    
    // 首次查询,访问数据库
    User result1 = userDao.selectByName(criteria);
    System.out.println(result1);
    
    // 同一 SqlSession 内再次查询,命中一级缓存
    User criteria2 = new User();
    criteria2.setUsername("李清照");
    User result2 = userDao.selectByName(criteria2);
    System.out.println(result2);
    
    // 手动清空缓存:session.clearCache();
}

1.2 二级缓存(应用级)

二级缓存作用于 SqlSessionFactory 层面,支持跨会话共享。需显式开启:

全局配置(mybatis-config.xml):

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

映射文件配置:

<!-- 在 Mapper.xml 头部添加 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
public void demoSecondLevelCache() {
    SqlSession session1 = factory.openSession();
    UserMapper mapper1 = session1.getMapper(UserMapper.class);
    
    User param = new User();
    param.setUsername("辛弃疾");
    User data = mapper1.selectByName(param);
    session1.close(); // 数据刷入二级缓存
    
    SqlSession session2 = factory.openSession();
    UserMapper mapper2 = session2.getMapper(UserMapper.class);
    // 新会话直接命中二级缓存,无需访问数据库
    User cached = mapper2.selectByName(param);
}

二、字段映射不一致的解决方案

2.1 别名映射(简易方案)

SELECT emp_id AS employeeId, dept_code AS departmentCode FROM t_employee

2.2 自定义 ResultMap(推荐方案)

<resultMap id="employeeMapping" type="com.example.domain.Employee">
    <id column="emp_id" property="employeeId"/>
    <result column="emp_name" property="employeeName"/>
    <result column="dept_code" property="deptCode"/>
    <result column="salary_amount" property="salary"/>
</resultMap>

<select id="selectById" resultMap="employeeMapping">
    SELECT emp_id, emp_name, dept_code, salary_amount 
    FROM t_employee 
    WHERE emp_id = #{id}
</select>

三、多表关联查询实现模式

以员工(Employee)与部门(Department)为例,展示三种关联查询策略。

3.1 业务层装配模式

Mapper 层保持单表查询,Service 层负责数据组装。

// EmployeeMapper.xml
<select id="selectAll" resultType="Employee">
    SELECT * FROM t_employee
</select>

// DepartmentMapper.xml  
<select id="selectByCode" resultType="Department">
    SELECT * FROM t_department WHERE dept_code = #{code}
</select>

// Service 层装配
public List<Employee> getEmployeeWithDept() {
    List<Employee> employees = employeeMapper.selectAll();
    for (Employee emp : employees) {
        Department dept = deptMapper.selectByCode(emp.getDeptCode());
        emp.setDepartment(dept);
    }
    return employees;
}

3.2 N+1 查询模式(association)

利用 MyBatis 延迟加载机制,自动触发关联查询。

<resultMap id="employeeWithDept" type="Employee">
    <id property="employeeId" column="emp_id"/>
    <result property="employeeName" column="emp_name"/>
    <result property="deptCode" column="dept_code"/>
    
    <association property="department" 
                 javaType="Department"
                 column="dept_code" 
                 select="com.example.mapper.DepartmentMapper.selectByCode"/>
</resultMap>

<select id="selectById" resultMap="employeeWithDept">
    SELECT emp_id, emp_name, dept_code FROM t_employee WHERE emp_id = #{id}
</select>

3.3 嵌套结果映射模式(多对一)

单次 SQL 联表查询,通过 resultMap 完成结果映射。

<resultMap id="employeeNestedResult" type="Employee">
    <id property="employeeId" column="e_id"/>
    <result property="employeeName" column="e_name"/>
    
    <association property="department" javaType="Department">
        <id property="deptCode" column="d_code"/>
        <result property="deptName" column="d_name"/>
        <result property="location" column="d_loc"/>
    </association>
</resultMap>

<select id="selectWithDept" resultMap="employeeNestedResult">
    SELECT 
        e.emp_id as e_id,
        e.emp_name as e_name,
        d.dept_code as d_code,
        d.dept_name as d_name,
        d.location as d_loc
    FROM t_employee e
    LEFT JOIN t_department d ON e.dept_code = d.dept_code
    WHERE e.emp_id = #{id}
</select>

3.4 集合映射模式(一对多)

N+1 方式:

<resultMap id="deptWithEmployees" type="Department">
    <id property="deptCode" column="dept_code"/>
    <collection property="employees" 
                ofType="Employee"
                column="dept_code"
                select="com.example.mapper.EmployeeMapper.selectByDeptCode"/>
</resultMap>

嵌套结果方式:

<resultMap id="deptNestedCollection" type="Department">
    <id property="deptCode" column="d_code"/>
    <result property="deptName" column="d_name"/>
    
    <collection property="employees" ofType="Employee">
        <id property="employeeId" column="e_id"/>
        <result property="employeeName" column="e_name"/>
        <result property="hireDate" column="e_hire"/>
    </collection>
</resultMap>

<select id="selectWithEmployees" resultMap="deptNestedCollection">
    SELECT 
        d.dept_code as d_code,
        d.dept_name as d_name,
        e.emp_id as e_id,
        e.emp_name as e_name,
        e.hire_date as e_hire
    FROM t_department d
    LEFT JOIN t_employee e ON d.dept_code = e.dept_code
    WHERE d.dept_code = #{code}
</select>

3.5 自动映射别名技巧

利用 MyBatis 自动映射特性,通过别名直接映射嵌套属性。

<select id="selectAutoMapping" resultType="Employee">
    SELECT 
        e.emp_id,
        e.emp_name,
        e.hire_date,
        d.dept_code AS "department.deptCode",
        d.dept_name AS "department.deptName",
        d.location AS "department.location"
    FROM t_employee e
    JOIN t_department d ON e.dept_code = d.dept_code
    WHERE e.emp_id = #{id}
</select>

四、注解式开发

MyBatis 3.0+ 支持注解替代 XML 配置,简化单表操作。

public interface EmployeeMapper {
    
    @Select("SELECT * FROM t_employee WHERE emp_id = #{id}")
    @Results({
        @Result(property = "employeeId", column = "emp_id", id = true),
        @Result(property = "employeeName", column = "emp_name"),
        @Result(property = "department", 
                column = "dept_code",
                one = @One(select = "com.example.mapper.DepartmentMapper.selectByCode"))
    })
    Employee selectByIdWithDept(Integer id);
    
    @Insert("INSERT INTO t_employee(emp_name, dept_code) VALUES(#{employeeName}, #{deptCode})")
    @Options(useGeneratedKeys = true, keyProperty = "employeeId")
    int insert(Employee record);
    
    @Update("UPDATE t_employee SET emp_name=#{employeeName}, dept_code=#{deptCode} WHERE emp_id=#{employeeId}")
    int update(Employee record);
    
    @Delete("DELETE FROM t_employee WHERE emp_id = #{id}")
    int deleteById(Integer id);
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。