MyBatis的缓存问题

MyBatis的理论问题主要涉及到事务和缓存。

一、事务

事务是多个操作要么同时成功,要么同时失败。有四大特性:原子性、一致性、隔离性、持久性。目前关于事务只是知道下面两点:

Mybatis是在主配置文件sqlMapConfig.xml中配置事务的。

<configuration>
	<environments default="mysql">
        <environment id="mysql">
            <!-- 配置事务的类型,使用本地事务策略-->
            <transactionManager type="JDBC"></transactionManager>
        </environment>
    </environments>
</configuration>

还有就是在测试类中设置事务的自动提交。

SqlSession session;
//创建SqlSession对象,参数为true是设置事务的自动提交
session = factory.openSession(true);
二、缓存
1. MyBatis的缓存策略

MyBatis主要是对jdbc进行简单的封装,然后访问数据库的。但数据库文件在磁盘中,当要处理大量数据时,磁盘延迟会严重影响应用性能。所以内存为了减少和磁盘的交互,将 频繁查询且很少修改或不修改 的数据库文件放到了内存上的缓存中。内存缓存作用是提高数据库访问性能。

要想看清楚MyBatis的缓存策略,得先看清楚MyBatis框架操作数据库的步骤。

摘自优质博客:

https://blog.csdn.net/luanlouis/article/details/41408341

https://blog.csdn.net/ex_tang/article/details/83155031

从图上可以看出,MyBatis有两级缓存。其中MyBatis查询数据的顺序是:二级缓存 ——> 一级缓存 ——> 数据库

2. 一级缓存和二级缓存

每个 SqlSession 对象都有自己的一级缓存(本地缓存),一级缓存是默认开启的。二级缓存是全局缓存,在 SqlSessionFactory中存储,所以一个 SqlSessionFactory 下的多个 SqlSession 对象有着相同的二级缓存,但是二级缓存没有默认开启。

当二级缓存没有开启时,每个 SqlSession 对象创建的接口代理对象的第一次查询会连接数据库,之后的查询会先由Executor对象访问 local cache,如果其中有缓存结果,就不需要连接数据库了,直接返回结果。如果没有再连接数据库。

当二级缓存开启时,不同的 SqlSession 对象共用相同的二级缓存。但是需要注意的是,SqlSession 对象查询后返回的数据 默认存储在它们自己的一级本地缓存中,只有手动提交数据或者关闭当前会话(调用session.commit()或者session.close()方法),也就是说 清除掉一级缓存,数据才会写入二级缓存中。之后 SqlSession 对象的每次请求 会先由 CatchingExecutor 对象判断在二级缓存中是否有缓存结果,如果有,直接返回;如果缓存中没有,再交给真正的Executor对象来完成查询操作(访问一级缓存或者连接数据库查询),之后CatchingExecutor会将真正Executor返回的查询结果放置到二级缓存中,然后再返回给用户。

CatchingExecutorExecutor的装饰者,以增强Executor的功能,使其具有查询二级缓存的功能,这里用到了设计模式的装饰者功能。

可以理解为什么MyBatis查询数据的顺序是:二级缓存 ——> 一级缓存 ——> 数据库:

因为一级缓存中的数据,二级缓存可能有也可能没有:二级缓存默认是不开启的,MyBatis可以指定某一条查询语句是否使用二级缓存。而且必须清除一级缓存,数据才会写入二级缓存。因此可以理解为二级缓存中的数据并不全。

3. 一级缓存

需要注意的是,缓存的数据是很少被修改或不被修改的数据,一旦进行增删改操作并提交数据之后,缓存中的数据很明显就没有存储的意义了(因为是增删改前的数据),就会被清除掉。所以一级缓存是会失效的。

一级缓存失效的四种情况:

  • 上一次查询的SqlSession对象和本次的不同(一级缓存就不同)
  • SqlSession对象相同但查询条件不同
  • SqlSession对象相同但是两次查询中间执行了增删改操作(进行了提交数据:调用session.commit()方法)
  • SqlSession对象相同但是手动清除了一级缓存(调用session.clearCache()方法)

下面的示例测试一级缓存的作用:

public class TestUser{
    SqlSession session;  //SqlSession对象
    InputStream in;
    UserDao userDao;  //接口代理对象
    
    @Before
    public void init() throws IOException {
        //加载主配置文件,目的是为了构建SqlSessionFactory对象
        in = Resources.getResourceAsStream("mybatis.xml");
        //创建SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //创建SqlSession对象
        session = factory.openSession(true);
        //通过SqlSession对象创建UserDao接口的代理对象
        userDao = session.getMapper(UserDao.class);
    }

    @After
    public void destory() throws IOException {
        //关闭资源
        session.close();
        in.close();
    }
    
    /*
    测试一级缓存
     */
    @Test
    public void run(){
        User user = userDao.findById(3);
        //session.clearCache();  //手动清除一级缓存会使缓存失效
        System.out.println("----------");
        //userDao.delete(66);   //SQL Session相同的两次相同查询操作之间执行删操作,会导致会话提交,缓存清除失效
        User user1 = userDao.findById(3);
        System.out.println(user == user1);
    }
}
Cache Hit Ratio [com.qcby.dao.UserDao]: 0.0
Opening JDBC Connection
Created connection 313540687.
==>  Preparing: select * from user where id = ? 
==> Parameters: 3(Integer)
<==    Columns: id, username, birthday, sex, address
<==        Row: 3, 熊二, 2018-03-04 11:34:34, 女, 深圳
<==      Total: 1
User{id=3, username='熊二', birthday=Sun Mar 04 19:34:34 CST 2018, sex='女', address='深圳'}
----------
Cache Hit Ratio [com.qcby.dao.UserDao]: 0.0
User{id=3, username='熊二', birthday=Sun Mar 04 19:34:34 CST 2018, sex='女', address='深圳'}
true
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@12b0404f]
Returned connection 313540687 to pool.

可以从上面的日志中看到由于有一级缓存,所以只查询了一次。当把代码中的注释依次放开后,可以从下面的日志中看到查询了两次,证明一级缓存失效。

Cache Hit Ratio [com.qcby.dao.UserDao]: 0.0
Opening JDBC Connection
Created connection 313540687.
==>  Preparing: select * from user where id = ? 
==> Parameters: 3(Integer)
<==    Columns: id, username, birthday, sex, address
<==        Row: 3, 熊二, 2018-03-04 11:34:34, 女, 深圳
<==      Total: 1
User{id=3, username='熊二', birthday=Sun Mar 04 19:34:34 CST 2018, sex='女', address='深圳'}
----------
==>  Preparing: delete from user where id= ? 
==> Parameters: 66(Integer)
<==    Updates: 0
Cache Hit Ratio [com.qcby.dao.UserDao]: 0.0
==>  Preparing: select * from user where id = ? 
==> Parameters: 3(Integer)
<==    Columns: id, username, birthday, sex, address
<==        Row: 3, 熊二, 2018-03-04 11:34:34, 女, 深圳
<==      Total: 1
User{id=3, username='熊二', birthday=Sun Mar 04 19:34:34 CST 2018, sex='女', address='深圳'}
false
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@12b0404f]
Returned connection 313540687 to pool.
4.二级缓存

通过创建两个SqlSession对象测试二级缓存:

<!-- 在主配置文件中开启二级缓存 -->
<!-- 注意settings顺序 -->
<settings>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
public class TestUser{
    SqlSession session;
    SqlSession session2;  //两个SqlSession对象
    InputStream in;
    UserDao userDao;
    UserDao userDao2;  
    @Before
    public void init() throws IOException {
        //加载主配置文件,目的是为了构建SqlSessionFactory对象
        in = Resources.getResourceAsStream("mybatis.xml");
        //创建SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        
        //通过不同的SqlSession对象创建接口代理对象
        //创建SqlSession对象
        session = factory.openSession(true);
        //通过SqlSession对象创建UserDao接口的代理对象
        userDao = session.getMapper(UserDao.class);

        session2 = factory.openSession(true);
        userDao2 = session2.getMapper(UserDao.class);
    }

    @After
    public void destory() throws IOException {
        //关闭资源
        session.close();
        session2.close();
        in.close();
    }
    
    /*
    测试二级缓存
     */
    @Test
    public void run6(){
        List<User> users = userDao.findByUserName("熊");
        //session.commit();  //手动提交清除一级缓存
        List<User> users2 = userDao2.findByUserName("熊");
        System.out.println(users == users2);
    }
}

当不手动提交清除一级缓存时,会看到查询了两次

Cache Hit Ratio [com.qcby.dao.UserDao]: 0.0
Opening JDBC Connection
Created connection 345902941.
==>  Preparing: select * from user where username like '%熊%' 
==> Parameters: 
<==    Columns: id, username, birthday, sex, address
<==        Row: 2, 熊大, 2018-03-02 15:09:37,, 上海
<==        Row: 3, 熊二, 2018-03-04 11:34:34,, 深圳
<==      Total: 2
Cache Hit Ratio [com.qcby.dao.UserDao]: 0.0
Opening JDBC Connection
Created connection 204715855.
==>  Preparing: select * from user where username like '%熊%' 
==> Parameters: 
<==    Columns: id, username, birthday, sex, address
<==        Row: 2, 熊大, 2018-03-02 15:09:37,, 上海
<==        Row: 3, 熊二, 2018-03-04 11:34:34,, 深圳
<==      Total: 2
false
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@149e0f5d]
Returned connection 345902941 to pool.
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@c33b74f]
Returned connection 204715855 to pool.

当手动提交,清除一级缓存后,会看到只查询了一次。证明数据写回了二级缓存。

Cache Hit Ratio [com.qcby.dao.UserDao]: 0.0
Opening JDBC Connection
Created connection 345902941.
==>  Preparing: select * from user where username like '%熊%' 
==> Parameters: 
<==    Columns: id, username, birthday, sex, address
<==        Row: 2, 熊大, 2018-03-02 15:09:37,, 上海
<==        Row: 3, 熊二, 2018-03-04 11:34:34,, 深圳
<==      Total: 2
Cache Hit Ratio [com.qcby.dao.UserDao]: 0.5
true
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@149e0f5d]
Returned connection 345902941 to pool.
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值