【SSM - MyBatis篇11】MyBatis缓存,spring整合MyBatis开启二级缓存,MyBatis开启二级缓存

1. MyBatis缓存机制

1.1 一级缓存、二级缓存

   一级缓存:它指的是Mybatis中sqlSession对象的缓存(基于PerpetualCacheHashMap本地缓存,作用域是Session),当我们执行查询以后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map集合,当我们再次查询同样的数据,mybatis会先去sqlsession中查询是否有,有的话直接拿出来用,当SqlSession对象消失时,mybatis的一级缓存也就消失了,同时一级缓存是SqlSession范围的缓存当调用SqlSession对象的修改、添加、删除、commit()、flush、close等方法时,就会清空一级缓存。

   二级缓存:是Mybatis中SqlSessionFactory对象的缓存(默认也是采用 PerpetualCacheHashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache),由同一个SqlSessionFactory对象创建的SqlSession共享其缓存,但是其中缓存的是数据而不是对象,所以从二级缓存再次查询出的结果的对象与第一次存入的对象是不一样的。(就是二级缓存存下来的是对象的数据(堆中具体存放的数据,而不是对象的引用,所以如果修改对象的值,但是二级缓存里面该对象的数据是不变的))

   MyBatis的缓存数据更新机制中,当某一个作用域(不管是一级缓存sqlSession/二级缓存SqlSessionFactory)的进行了C/U/D 操作后,默认该作用域下所有select查询中的缓存都将被clear(因为数据被改变更新,所以缓存就无效了,继续使用就可能是没有更新的数值)
   update、delete、insert修改数据库的方法,无论是否commit提交,会同时清空一级和二级缓存。


1.2 关于一级缓存(本地缓存)

缓存:就是把数据放到内存数据中,下次使用直接去缓存(内存)中查找
MyBatis的一级缓存默认是开启状态,且不能关闭,开发人员不需要管理它。
一级缓存对于不同的session对应各自的缓存,session之间不能相互访问对方的缓存(session间不共享缓存)
当一个 SqlSession 关闭和提交时,该 SqlSession 中的一级查询缓存也会清空。
可以通过session.clearCache();来清空一级缓存

数据查询时:

  • 第一次查询后,将数据放入一级缓存中,也就是默认缓存。
  • 第二次查询,会先从一级缓存中查询数据,如果命中(缓存中找到):使用一级缓存数据 ;如果未命中:发sql语句去数据库查询然后返回结果。

1.2 关于二级缓存

  • MyBatis的二级缓存是mapper范围级别的(namespace)
  • 二级缓存是默认开启的。(想开启就不必做任何配置)
  • SqlSession关闭后才会将数据写到二级缓存区域

每个sql语句都可以有一个针对二级缓存的设置(sql语句中的useCache属性)
  除了遵循大的< cache >设置外,针对每一条sql语句可以单独设置是否使用二级缓存。通过useCache="true"开启二级缓存,false关闭二级缓存。如果缓存设置flushCache="false" ,那么缓存将不清空。

<select … flushCache=“false” useCache=“true”/>
<inser … flushCache=“true”/>
(insert update delete更新数据库的时候,默认情况下会同时清空一级、二级缓存,只有session被关闭的时候,才会将自己一级缓存中的数据放入二级缓存。)

  < cache-ref >用于 在多个命名空间中共享相同的缓存配置和实例,通过cache-ref来引用另一个缓存,每个二级缓存是针对命名空间设置的,可以通过cache-ref 将多个缓存合并,缓存合并只是内存空间的合并,存储数据的时候还是按照命名空间存储。
  如果AMapper和当前BMapper共用一个缓存空间,如果一个1M大小,一个2M大小,那么这个缓存空间就是3M数据。

	<!-- 在Mapper.xml映射文件中开启二级缓存 -->
	<cache flushInterval="10000" eviction="LRU" blocking="false" readOnly="false" size="1024"/>
	<!-- 当前缓存和UserMapper共享一个缓存空间 -->
    <cache-ref namespace="com.xgf.cache.dao.UserMapper"/>


2. Spring整合MyBatis开启二级缓存【*****】

启用二级缓存步骤:

  1. mybatis核心配置文件中开启二级缓存全局变量:cacheEnabled = true (在mybatis核心配置文件中,设置属性<setting name="cacheEnabled" value="true"/>
  2. 在spring核心配置文件,sqlSessionFactory的bean中,将mybatis的配置粘入
  3. 需要在对应的映射文件XxxMapper.xml的命名空间namespace下加入< cache/ >来开启二级缓存。
  4. 需要在bean对象实现Serializable 接口序列化,仅用于标识(如果cache的readonly设置为true可以不用这一步)

2.1 创建MyBatis核心配置文件,在settings中开启二级缓存

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

<configuration>

    <!-- 使用mybatis方式全局变量设置在核心配置文件sqlMapConfig.xml的settings中
         spring和mybatis整合的情况下,mybatis下的全局参数<settings>在spring的配置文件中设置,
            在spring的sqlSessionFactory对象中,将配置粘入bean的property中

        settings用于配置全局变量,有顺序要求,要放在environment前面,配置全局参数
        lazyLoadingEnabled 配置懒加载,全局允许或静止懒加载,所有的任务都懒加载
        具体实现懒加载:通过在resultMap中设置fetchType实现懒加载

        <setting name="cacheEnabled" value="true"/> cacheEnabled允许二级缓存
    -->
    <settings>
        <!-- 打开延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将积极加载改为消极加载即按需要加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 开启二级缓存(默认也是开启的)-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

</configuration>

2.2 在spring的核心配置文件中,sqlSessionFactory的bean中,将mybatis的配置粘入

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--将缓存添加到spring中的sessionFactory中-->
        <property name="configLocation" value="com/xgf/mysql_cache/config/sqlMapConfig.xml"></property>
    </bean>

2.3 在mapper.xml映射文件中添加< cache >< /cache >开启二级缓存

cache属性 描述
size 二级缓存中可以存储多少对象,默认1024个,超过就会按照指定的算法清除
eviction 二级缓存size存满的时候,按照什么规则来清除对象,默认LRU(最近最少使用),其他方式FIFO(先进先出)、SOFT(软引用)、WEAK(弱引用)
flushInterval 刷新间隔、清除时间,每隔多长时间进行缓存的清除
blocking 默认为false,为true时为阻塞式缓存,当有一个线程读取的时候,其他线程只能等待,只有当前线程操作完其他线程才能操作
type 第三方缓存,将第三方缓存实现类写在这里(比如EhcacheCache)
readOnly 默认为false
true:只读方式,缓存当中对象的引用(地址)交给程序,速度快,但是不安全 。
false: 兑换成中对象进行克隆clone操作,速度慢,但是安全,类需要实现Serializable
<?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.xgf.mysql_cache.dao.CustomerMapper">

    <!--在映射文件中开启二级缓存,flushInterval:每隔多长时间进行缓存的清除
        eviction:二级缓存size存满的时候,按照什么规则来清除对象
        size:二级缓存中可以存储多少对象
        readOnly:设置为false,会兑换成中对象进行克隆clone操作,速度慢,但是安全,类需要实现Serializable
        blocking:设置为false,为非阻塞式缓存
     -->
    <cache flushInterval="10000" eviction="LRU" blocking="false" readOnly="false" size="1024"/>

    <!-- 可以在sql语句中单独设置是否启用二级缓存 通过useCache="true"设置启用二级缓存 -->
    <select id="getCustomerByNameAndJobForWhere"
            parameterType="com.xgf.mysql_cache.bean.Customer"
            resultType="com.xgf.mysql_cache.bean.Customer" 
            useCache="true">
        select id,username,job
        from customer
        <where>
            <if test="username!=null and username!=''">
                and username like concat('%',#{username},'%')
            </if>
            <if test="job!=null and job!=''">
                and job=#{job}
            </if>
        </where>
    </select>
</mapper>

2.4 在对应的javabean类实现序列化接口Serializable(仅仅是一个标识作用,不实现会报错)(如果缓存cache的readOnly属性为true,就可以不设置)

public class beanClass implements Serializable {
    
    }

3. MyBatis中开启二级缓存

  MyBatis中开启二级缓存机制和Spring整合MyBatis开启二级缓存步骤差不多,只是少了一个步骤(2.2 在spring的核心配置文件中,sqlSessionFactory的bean中,将mybatis的配置粘入),其他都一样。



4. Spring整合MyBatis实现二级缓存案例

4.1 创建customer表

-- ----------------------------
-- customer表,主键自动递增
-- ----------------------------
DROP TABLE IF EXISTS `customer`;
CREATE TABLE `customer` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `job` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;

-- ----------------------------
-- 初始数据
-- ----------------------------
INSERT INTO `customer` VALUES ('1', '张三', '程序员');
INSERT INTO `customer` VALUES ('2', '李四', '项目经理');
INSERT INTO `customer` VALUES ('3', '王五', '测试员');
INSERT INTO `customer` VALUES ('4', '赵六', '开发人员');
INSERT INTO `customer` VALUES ('5', '赵钱孙李', '开发人员');
INSERT INTO `customer` VALUES ('6', '赵云', '保卫工作');
INSERT INTO `customer` VALUES ('7', '完璧归赵', '历史人物');

4.2 创建javabean对象

public class Customer {
    
    

    private Integer id;         //id主键自动增长
    private String username;    //用户名
    private String job;         //工作
    //省略getter/setter方法
}

4.3 创建dao层接口

public interface CustomerMapper {
    
    
//    where标签查询数据 name和job满足条件
    List<Customer> getCustomerByNameAndJobForWhere(Customer customer);
}

4.4 创建连接数据库的属性文件db.properties(键值对形式)

jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&useSSL=false
jdbc.username = root
jdbc.password = 861221293

4.5 MyBatis核心配置文件开启二级缓存

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

<configuration>
    <settings>
        <!-- 打开延迟加载/懒加载 具体实现懒加载:通过在resultMap中设置fetchType实现懒加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将积极加载改为消极加载即按需要加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

</configuration>

4.6 spring整合MyBatis,在sqlSessionFactory的bean中,将mybatis配置粘入bean的property中

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mybatis="http://mybatis.org/schema/mybatis-spring" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--1. 引入jdbc的属性文件,在配置中通过占位使用 -->
    <context:property-placeholder location="classpath*:db.properties" />

    <!--2. <context:component-scan>扫描包中注解所标注的类(@Component、@Service、@Controller、@Repository) -->
    <context:component-scan base-package="com.xgf.mysql_cache"/>

    <!--3. 由spring管理    配置数据源数据库连接(从jdbc属性文件中读取参数) -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
    </bean>

    <!--  通过spring来管理Mybatis的sqlSessionFactory对象创建,将MyBatis的二级缓存配置configLocation粘入  -->
    <!--4. 通过完全限定名匹配查找  创建SqlSessionFactoryBean  -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 将缓存添加到spring中的sessionFactory中 【***】-->
        <property name="configLocation" value="com/xgf/mysql_cache/config/sqlMapConfig.xml"></property>
    </bean>

    <!-- 5. mybatis提供的一个注解扫描标签(搜索映射器 Mapper 接口),通过自动扫描注解的机制,创建每个dao接口定义的bean  -->
    <mybatis:scan base-package="com.xgf.mysql_cache.dao"/>

</beans>

4.7 映射文件mapper.xml中加入< cache/ >实现二级缓存

<?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.xgf.mysql_cache.dao.CustomerMapper">

    <!--在映射文件中开启二级缓存,flushInterval:每隔多长时间进行缓存的清除
        eviction:二级缓存size存满的时候,按照什么规则来清除对象
        size:二级缓存中可以存储多少对象
        readOnly:设置为false,会兑换成中对象进行克隆clone操作,速度慢,但是安全,类需要实现Serializable
        blocking:设置为false,为非阻塞式缓存
     -->
    <cache flushInterval="10000" eviction="LRU" blocking="false" readOnly="false" size="1024"/>

    <!--where标签,如果后面没有条件(条件都不满足)不加where,如果后面有条件,会自动增加一个where
    会将多余的and、or去掉,会自动填充第一个缺失的and、or-->
    <!-- 可以在sql语句中单独设置是否启用二级缓存 通过useCache="true"设置启用二级缓存 -->
    <select id="getCustomerByNameAndJobForWhere"
            parameterType="com.xgf.mysql_cache.bean.Customer"
            resultType="com.xgf.mysql_cache.bean.Customer"
            useCache="true">
        select id,username,job
        from customer
        <where>
            <if test="username!=null and username!=''">
                and username like concat('%',#{username},'%')
            </if>
            <if test="job!=null and job!=''">
                and job=#{job}
            </if>
        </where>
    </select>
</mapper>

4.8 创建测试类

4.8.1 数据准备

public class TestCache {
    
    

    private static ApplicationContext applicationContext = null;
    private static CustomerMapper customerMapper = null;
    //查询对象
    //通过getCustomerByNameAndJobForWhere查询相当于
    private static Customer customer = new Customer("赵","开发人员");//初始化customer数据
    //查询结果
    private List<Customer> customerList = null;

    //只加载一次  @BeforeClass@BeforeClass只在类中执行一次, 必须声明为public static
    @BeforeClass
    public static void init(){
    
    
        //加载配置文件
        applicationContext = new ClassPathXmlApplicationContext("com/xgf/mysql_cache/config/applicationContext.xml");
        //获取bean的两种方式
        // 1.类名首字母小写
//        customerMapper = (CustomerMapper) applicationContext.getBean("customerMapper");
        // 2.类.class
        customerMapper = (CustomerMapper) applicationContext.getBean(CustomerMapper.class);
    }
}

4.8.2 测试一级缓存

//测试一级缓存
    @Test
    public void test01(){
    
    

        System.out.println("******第一次查询******");
//        查询数据customerList和customer是前面的声明
        customerList = customerMapper.getCustomerByNameAndJobForWhere(customer);
        System.out.println(customerList);

        //第二次查询(非第一次),去一级缓存中查询数据,命中:使用一级缓存数据  未命中:发sql去数据库查询
        System.out.println("\n******第二次查询,查询和第一次相同内容(命中:使用一级缓存数据 )******");
        customerList = customerMapper.getCustomerByNameAndJobForWhere(customer);
        System.out.println(customerList);

        System.out.println("\n*****第三次查询,修改查询内容,未命中,发送sql语句去数据库查询********");
        customer.setUsername("修改username");
        customer.setJob("修改job");
        customerList = customerMapper.getCustomerByNameAndJobForWhere(customer);
        System.out.println(customerList);
    }

运行结果
>

猜你喜欢

转载自blog.csdn.net/qq_40542534/article/details/108889033