MyBatis的一二级缓存原理

    科技2022-07-10  114

    MyBatis的一二级缓存原理

    Mybatis的一级缓存:

    MyBatis的一级缓存指的是在一个Session域内,session关闭前执行的查询会根据SQL为key被缓存(跟mysql缓存一样,修改任何参数的值都会导致缓存失效),用来保存用户对数据库的操作信息(sql)和数据库返回的数据,如果下一次用户再执行相同的请求,那么直接从内存中读数数据而不是从数据库读取。

    Mybatis的一级缓存的作用域是在同一个SqlSession中,而且一级缓存在spring中是没有作用的;‘

    单独使用MyBatis而不继承Spring,使用原生的MyBatis的SqlSessionFactory来构造sqlSession查询

    public class Test { public static void main(String[] args) throws IOException { String mybatisconfig= "mybatisconfig.xml"; InputStream is = Resources.getResourceAsStream(config); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession = factory.openSession(); System.out.println(sqlSession .selectOne("selectUserByID", 1)); // 同一个session的相同sql查询,将会使用一级缓存 System.out.println(sqlSession .selectOne("selectUserByID", 1)); // 参数改变,需要重新查询 System.out.println(sqlSession .selectOne("selectUserByID", 2)); // 清空缓存后需要重新查询 sqlSession .clearCache(); System.out.println(sqlSession .selectOne("selectUserByID", 1)); // sqlSession close以后,仍然使用同一个db connection sqlSession .close(); sqlSession = factory.openSession(); System.out.println(sqlSession .selectOne("selectUserByID", 1)); } }

    执行后:当参数不变的时候只进行了一次查询,参数变更以后,则需要重新进行查询,而清空缓存以后,参数相同的查询过的SQL也需要重新查询,而且使用的数据库连接是同一个数据库连接,这里要得益于我们在mybatis config.xml里面的datasource设置

    <environments default="development"> <environment id="development"> <transactionManager type="JDBC"> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatistest?characterEncoding=utf8" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments>

    注意:datasource使用的是POOLED,也就是使用了连接池,所以数据库连接可回收利用,当然这个environment属性在集成spring的时候是不需要的,因为我们需要另外配置datasource的bean.

    跟Spring集成的时候(使用mybatis-spring),直接在dao里查询两次同样参数的sql

    @Repository public class UserDao extends SqlSessionDaoSupport { public User selectUserById(int id) { SqlSession session = getSqlSession(); session.selectOne("dao.userdao.selectUserByID", id); // 由于session的实现是SqlSessionTemplate的动态代理实现 // 它已经在代理类内执行了session.close(),所以无需手动关闭session return session.selectOne("dao.userdao.selectUserByID", id); } }

    这里执行了2次sql查询,看似我们使用了同一个sqlSession,但是实际上因为我们的dao继承了SqlSessionDaoSupport,而SqlSessionDaoSupport内部sqlSession的实现是使用用动态代理实现的,这个动态代理sqlSessionProxy使用一个模板方法封装了select()等操作,每一次select()查询都会自动先执行openSession(),执行完close()以后调用close()方法,相当于生成了一个新的session实例,所以我们无需手动的去关闭这个session(),当然也无法使用mybatis的一级缓存,也就是说mybatis的一级缓存在spring中是没有作用的,所以一旦系统 采用了SSM的框架时一级缓存是无法使用的,只能使用二级缓存。

    其中数据的生命周期有两个影响因素

    1.commit操作

    如果对sqlsession执行commit操作,也就意味着用户执行了update、delete等操作,那么数据库中的数据势必会发生变化,如果用户请求数据仍然使用之前内存中的数据,那么将读到脏数据。所以在执行sqlsession操作后,会清除保存数据的HashMap,用户在发起查询请求时就会重新读取数据并放入一级缓存中了。所以在commit之后mybatis对数据重新进行了查询。

    2.关闭session

    关闭sqlsession时,一般在mybatis集成spring时,会把SqlSessionFactory设置为单例注入到IOC容器中,不把sqlsession也设置为单例的原因是sqlsession是线程不安全的,所以不能为单例。那也就意味着其实是有关闭sqlsession的过程的。其实,对于每一个service中的sqlsession是不同的,这是通过mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer创建sqlsession自动注入到service中的。 在第一次查询完后关闭sqlsession,然后创建新的sqlsession和mapper来重新执行一次查询操作说明关闭了sqlsession后的确把之前的缓存数据清空了,之后再执行同样的查询操作也会再访问一遍数据库。为了解决这个问题,需要使用二级缓存 而一级缓存的设计是每个sqlsession单独使用一个缓存空间,不同的sqlsession是不能互相访问数据的。当然,在sqlsession关闭后,其中数据自然被清空。

    Mybatis的二级缓存:

    二级缓存就是global caching,它超出session范围之外,可以被所有sqlSession共享,它的实现机制和mysql的缓存一样,开启它只需要在mybatis的配置文件开启settings里的. 需要注意的是global caching的作用域是针对Mapper的Namespace而言的,也就是说只在有在这个Namespace内的查询才能共享这个cache.例如上面的 dao.userdao namespace 。但并不是意味着同一个namespace创建的mapper可以互相读取缓存内容,

    这里的原则是,如果开启了二级缓存,那么在关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。

    开启二级缓存第一步:

    打开二级缓存总开关: 打开总开关,只需要在mybatis总配置文件中加入一行设置

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

    开启二级缓存第二步:

    打开需要使用二级缓存的mapper的开关: 在需要开启二级缓存的mapper.xml中加入caceh标签

    在UserMapper.xml中配置

    <!--开启mybatis的二级缓存 --> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true" />

    开启二级缓存第三步:

    让需要使用二级缓存的POJO类实现Serializable接口,如

    public class User implements Serializable {
    Processed: 0.010, SQL: 8