一级缓存

import com.study.mybatis.config.SpringConfig;
import com.study.mybatis.entity.Country;
import com.study.mybatis.mapper.CountryMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author Zhang B H
 * @create 2023-10-04 15:40
 */
@ActiveProfiles("qa")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class CacheTest {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    /**
     * 测试 MyBatis 的一级缓存作用机制
     */
    @Test
    public void testL1Cache() {
        Country country1 = null;
        // 获取 sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            CountryMapper countryMapper = sqlSession.getMapper(CountryMapper.class);
            // 获取id为1的国家
            country1 = countryMapper.selectByPrimaryKey(1);
            // 修改获取到的对象的name实例字段
            country1.setName("NEW NAME");

            // 再次获取id相同的国家
            // 因为 一级缓存 的原因,因此此行代码并不会执行sql查询
            Country country2 = countryMapper.selectByPrimaryKey(1);
            // 此时,命中一级缓存
            Assert.assertEquals(country1, country2);
        } finally {
            sqlSession.close();
        }


        sqlSession = sqlSessionFactory.openSession();
        try {
            CountryMapper countryMapper = sqlSession.getMapper(CountryMapper.class);
            Country country2 = countryMapper.selectByPrimaryKey(1);

            Assert.assertNotEquals("NEW NAME", country2.getName());
            Assert.assertNotEquals(country1, country2);

            // 执行删除操作
            // 此时,会清除一级缓存
            countryMapper.deleteByPrimaryKey(100);

            Country country3 = countryMapper.selectByPrimaryKey(1);
            Assert.assertNotEquals(country3, country2);
        } finally {
            sqlSession.close();
        }
    }
}

在第一次执行 selectByPrimaryKey 方法获取 Country 时,真正执行了数据库查询,得到了 country1 的结果。而在第二次执行获取 country2 的时候,并没有执行数据库操作。

从测试代码来看,获取 country1 后重新设置了 name 的值,之后没有进行任何更新数据库的操作。在获取 country2 后,可以发现,原来 country1 和 country2 竟然是同一个对象,之所以这样就是因为 MyBatis 的一级缓存

MyBatis 的一级缓存存在于 SqlSession 的生命周期中,在同一个 SqlSession 中查询时,MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个 Map 对象中。如果同一个 SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当 Map 缓存对象中已经存在该键值时,则会返回缓存中的对象。

在关闭第一个 SqlSession 后,又重新获取了一个 SqlSession,然后重新获取了 country2。country2 是一个新的实例,和 country1 没有任何关系。这是因为一级缓存是和 SqlSession 绑定的,只存在于 SqlSession 的生命周期中

接下来执行了一个 deleteByPrimaryKey 操作,然后使用相同的方法和参数获取了 country3。country3 和 country2 是完全不同的两个对象。这是因为任何的 INSERT、UPDATE、DELETE 操作都会清空一级缓存,所以查询 country3 的时候由于缓存不存在,就会再次执行数据库查询获取数据。

注入 mapper 对象,然后调用 mapper 上的方法来操作数据库,这种方式并不会触发一级缓存,这是因为每一次调用方法,都会获取一个新的 sqlSession。

    @Autowired
    private CountryMapper countryMapper;

    @Test
    public void testL1Cache2() {

        // 获取id为1的国家
        Country country1 = countryMapper.selectByPrimaryKey(1);
        // 修改获取到的对象的name实例字段
        country1.setName("NEW NAME");

        // 再次获取id相同的国家
        Country country2 = countryMapper.selectByPrimaryKey(1);
        // 此时,并未命中一级缓存
        Assert.assertNotEquals(country1, country2);
    }

在上面的测试用例中,获取 country2 的方法仍然执行了 SQL 语句查询,因此,country1 和 country2 并不是同一个对象。

Last updated