继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Mybatis一级缓存学习入门

HUX布斯
关注TA
已关注
手记 301
粉丝 84
获赞 378
概述

本文介绍了Mybatis缓存机制的基础知识,重点讲解了一级缓存的工作原理、作用范围、使用场景及其默认行为。文章详细阐述了一级缓存如何提高应用性能,并介绍了清除缓存和避免脏读问题的方法。

Mybatis缓存简介

Mybatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射,为Java程序员提供了更简单、更强大的数据库操作方式。在Mybatis中,缓存机制是提高应用性能的重要手段之一,它能够减少对数据库的访问次数,从而减轻数据库的压力,提高系统整体的响应速度。Mybatis的缓存机制分为一级缓存和二级缓存两个层次。

Mybatis缓存体系结构概述

Mybatis的缓存体系结构主要由一级缓存和二级缓存构成。

  • 一级缓存,也被称为本地缓存,每个SqlSession都维护了一个独立的缓存区域,用于缓存SqlSession中执行过的SQL语句的查询结果。一级缓存是SqlSession级别的缓存,当一个SqlSession完成操作后,一级缓存将会被清除。
  • 二级缓存,也被称为共享缓存,采用全局的缓存区域,应用于所有SqlSession,缓存的结果集可以被不同的SqlSession使用。二级缓存是基于Mapper的缓存,可以被应用中的所有SqlSession共享使用。
Mybatis一级缓存与二级缓存的区别
  • 作用范围:一级缓存是SqlSession级别的缓存,二级缓存是基于Mapper的缓存。
  • 缓存控制:一级缓存无需手动开启,二级缓存需要手动开启。
  • 使用场景:一级缓存适用于SqlSession内部的查询结果复用,二级缓存适用于SqlSession之间的查询结果复用。
  • 数据共享:一级缓存的数据只在当前的SqlSession中可见,二级缓存的数据则可以被所有的SqlSession访问。
Mybatis一级缓存的工作原理

Mybatis一级缓存的工作原理主要是通过SqlSession来实现的。当一个SqlSession执行SQL查询语句时,首先会检查该SqlSession的本地缓存中是否已经存在查询结果。如果存在,则直接从缓存中读取结果,不再执行SQL查询;如果不存在,则执行SQL查询并将结果存储到缓存中。

一级缓存的作用域

一级缓存是SqlSession级别的缓存,这意味着每个SqlSession都有自己独立的缓存区域。当一个SqlSession完成操作后,一级缓存将会被清除。这种机制确保了每个SqlSession中的缓存数据都是最新的,避免了缓存数据的不一致问题。

一级缓存的默认行为

一级缓存是默认开启的,无需任何额外的配置。当一个SqlSession执行SQL查询时,会首先检查缓存中是否有匹配的查询结果。如果有,则直接返回缓存结果,否则执行SQL查询并将结果存储到缓存中。

一级缓存的使用场景

一级缓存适用于SqlSession内部的查询结果复用。通过一级缓存,可以避免不必要的数据库访问,从而提高系统的性能。

如何开启和关闭一级缓存

一级缓存是默认开启的,如果需要关闭一级缓存,可以在SqlSession中通过clearCache()方法来手动清除缓存。

SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    // 执行查询操作
    List<User> users = sqlSession.selectList("com.example.demo.mapper.UserMapper.selectUsers");
    // 清除缓存
    sqlSession.clearCache();
} finally {
    sqlSession.close();
}
一级缓存的自动刷新机制

当SqlSession执行以下几种操作时,会自动清除缓存:

  • 当SqlSession执行了insertupdatedelete等更新操作时,会自动清除缓存中的数据。
  • 当一个SqlSession中,执行了clearCache()方法时,会清除该SqlSession的所有缓存数据。
  • 当一个SqlSession执行了commit()rollback()操作时,会清除该SqlSession的所有缓存数据。
一级缓存失效的场景

当数据库中的数据发生变化,而缓存中的数据没有被及时更新时,可能会导致缓存数据与数据库数据不一致的情况,即脏读问题。此外,还有一些其他场景会导致缓存失效。

手动清除缓存

手动清除缓存可以使用clearCache()方法。例如:

SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    // 执行查询操作
    List<User> users = sqlSession.selectList("com.example.demo.mapper.UserMapper.selectUsers");
    // 执行更新操作
    sqlSession.update("com.example.demo.mapper.UserMapper.updateUser", user);
    // 手动清除缓存
    sqlSession.clearCache();
} finally {
    sqlSession.close();
}
SQL语句不走缓存的情况

当执行SQL查询时,如果查询语句中包含子查询、聚合函数、distinct关键字、join语句等复杂操作,或者查询语句使用了flushCache=true属性,将导致查询结果不会被缓存。

示例代码:

@Select("SELECT * FROM users WHERE id = #{id} ORDER BY name")
List<User> selectUsers(@Param("id") int id);

如果在查询语句中添加了flushCache=true属性,查询结果将不会被缓存。

@Select("SELECT * FROM users WHERE id = #{id} ORDER BY name", flushCache=true)
List<User> selectUsers(@Param("id") int id);
一级缓存的常见问题及解决方案
如何解决缓存中的脏读问题

脏读问题通常发生在更新操作之后,如果更新操作没有影响到缓存中的数据,缓存中的数据将与数据库中的数据不一致。为了避免脏读问题,可以在执行更新操作后,手动清除缓存。

SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    // 执行更新操作
    sqlSession.update("com.example.demo.mapper.UserMapper.updateUser", user);
    // 手动清除缓存
    sqlSession.clearCache();
} finally {
    sqlSession.close();
}

此外,还可以通过设置SqlSession的autoCommit属性为true,使每次执行完SQL操作后自动提交,从而刷新缓存。

缓存与事务管理的配合

为了确保缓存数据的一致性,通常需要将缓存的刷新操作与事务管理相结合。当事务提交成功后,会自动刷新缓存;如果事务提交失败,则不会刷新缓存,以保持数据的一致性。

示例代码:

SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    // 开启事务
    sqlSession.getConnection().setAutoCommit(false);
    // 执行更新操作
    sqlSession.update("com.example.demo.mapper.UserMapper.updateUser", user);
    // 提交事务
    sqlSession.getConnection().commit();
    // 自动刷新缓存
    sqlSession.clearCache();
} catch (SQLException e) {
    // 回滚事务
    sqlSession.getConnection().rollback();
} finally {
    sqlSession.close();
}
实践案例
编写一个简单的Mybatis一级缓存示例

为了验证Mybatis一级缓存的效果,我们编写一个简单的案例。

案例环境

  • 数据库:MySQL
  • Mybatis版本:3.5.6
  • 测试框架:JUnit 5

数据库表结构

假设我们有一个users表,结构如下:

CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(255),
    age INT
);

Mybatis配置文件

在Mybatis的配置文件mybatis-config.xml中,我们不需要额外配置一级缓存,因为一级缓存是默认开启的。

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/mydb" />
                <property name="username" value="root" />
                <property name="password" value="password" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/example/demo/mapper/UserMapper.xml" />
    </mappers>
</configuration>

Mapper接口和XML映射文件

假设我们有一个UserMapper接口和对应的XML映射文件UserMapper.xml

UserMapper接口

public interface UserMapper {
    List<User> selectUsers();
    User selectUserById(int id);
    int updateUser(User user);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.demo.mapper.UserMapper">
    <select id="selectUsers" resultType="com.example.demo.model.User">
        SELECT * FROM users
    </select>
    <select id="selectUserById" resultType="com.example.demo.model.User">
        SELECT * FROM users WHERE id = #{id}
    </select>
    <update id="updateUser">
        UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}
    </update>
</mapper>

用户实体类

定义一个简单的User实体类。

public class User {
    private int id;
    private String name;
    private int age;

    public User() {}

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试代码

编写一个简单的测试类,验证一级缓存的效果。

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.Test;

import java.io.InputStream;
import java.util.List;

public class MybatisCacheTest {
    private SqlSessionFactory sqlSessionFactory;

    public MybatisCacheTest() throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void testFirstLevelCache() throws Exception {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        try {
            // SqlSession1执行查询操作
            List<User> users1 = sqlSession1.selectList("com.example.demo.mapper.UserMapper.selectUsers");
            System.out.println("SqlSession1查询结果:" + users1);

            // SqlSession2执行查询操作
            List<User> users2 = sqlSession2.selectList("com.example.demo.mapper.UserMapper.selectUsers");
            System.out.println("SqlSession2查询结果:" + users2);

            // SqlSession1执行更新操作
            User user = new User(1, "NewName", 30);
            sqlSession1.update("com.example.demo.mapper.UserMapper.updateUser", user);
            sqlSession1.commit();

            // SqlSession1执行查询操作
            List<User> users1AfterUpdate = sqlSession1.selectList("com.example.demo.mapper.UserMapper.selectUsers");
            System.out.println("SqlSession1更新后查询结果:" + users1AfterUpdate);

            // SqlSession2执行查询操作
            List<User> users2AfterUpdate = sqlSession2.selectList("com.example.demo.mapper.UserMapper.selectUsers");
            System.out.println("SqlSession2更新后查询结果:" + users2AfterUpdate);
        } finally {
            sqlSession1.close();
            sqlSession2.close();
        }
    }
}

运行测试

运行上述测试代码,观察输出结果,验证一级缓存的工作原理。

输出结果示例

SqlSession1查询结果:[User{id=1, name='OldName', age=25}]
SqlSession2查询结果:[User{id=1, name='OldName', age=25}]
SqlSession1更新后查询结果:[User{id=1, name='NewName', age=30}]
SqlSession2更新后查询结果:[User{id=1, name='OldName', age=25}]

从输出结果可以看出,SqlSession1执行更新操作后,SqlSession1查询结果被更新,但SqlSession2查询结果没有被更新,因此验证了一级缓存的本地性。

通过上述示例,我们验证了一级缓存的默认行为,并展示了如何手动清除缓存和处理事务提交后自动刷新缓存的场景。

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP