一级缓存:它指的是Mybatis中sqlSession对象的缓存(基于PerpetualCache 的 HashMap本地缓存,作用域是Session),当我们执行查询以后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map集合,当我们再次查询同样的数据,mybatis会先去sqlsession中查询是否有,有的话直接拿出来用,当SqlSession对象消失时,mybatis的一级缓存也就消失了,同时一级缓存是SqlSession范围的缓存,当调用SqlSession对象的修改、添加、删除、commit()、flush、close等方法时,就会清空一级缓存。
二级缓存:是Mybatis中SqlSessionFactory对象的缓存(默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache),由同一个SqlSessionFactory对象创建的SqlSession共享其缓存,但是其中缓存的是数据而不是对象,所以从二级缓存再次查询出的结果的对象与第一次存入的对象是不一样的。(就是二级缓存存下来的是对象的数据(堆中具体存放的数据,而不是对象的引用,所以如果修改对象的值,但是二级缓存里面该对象的数据是不变的))
MyBatis的缓存数据更新机制中,当某一个作用域(不管是一级缓存sqlSession/二级缓存SqlSessionFactory)的进行了C/U/D 操作后,默认该作用域下所有select查询中的缓存都将被clear(因为数据被改变更新,所以缓存就无效了,继续使用就可能是没有更新的数值)
update、delete、insert修改数据库的方法,无论是否commit提交,会同时清空一级和二级缓存。
缓存:就是把数据放到内存数据中,下次使用直接去缓存(内存)中查找
MyBatis的一级缓存默认是开启状态,且不能关闭,开发人员不需要管理它。
一级缓存对于不同的session对应各自的缓存,session之间不能相互访问对方的缓存(session间不共享缓存)
当一个 SqlSession 关闭和提交时,该 SqlSession 中的一级查询缓存也会清空。
可以通过session.clearCache();
来清空一级缓存
数据查询时:
- 第一次查询后,将数据放入一级缓存中,也就是默认缓存。
- 第二次查询,会先从一级缓存中查询数据,如果命中(缓存中找到):使用一级缓存数据 ;如果未命中:发sql语句去数据库查询然后返回结果。
每个sql语句都可以有一个针对二级缓存的设置(sql语句中的useCache属性)
除了遵循大的< cache >设置外,针对每一条sql语句可以单独设置是否使用二级缓存。通过useCache="true"
开启二级缓存,false关闭二级缓存。如果缓存设置flushCache="false"
,那么缓存将不清空。
< cache-ref >用于 在多个命名空间中共享相同的缓存配置和实例,通过cache-ref来引用另一个缓存,每个二级缓存是针对命名空间设置的,可以通过cache-ref 将多个缓存合并,缓存合并只是内存空间的合并,存储数据的时候还是按照命名空间存储。
如果AMapper和当前BMapper共用一个缓存空间,如果一个1M大小,一个2M大小,那么这个缓存空间就是3M数据。
<cache flushInterval="10000" eviction="LRU" blocking="false" readOnly="false" size="1024"/>
<cache-ref namespace="com.xgf.cache.dao.UserMapper"/>
启用二级缓存步骤:
)
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<setting name="cacheEnabled" value="true"/>
settings>
configuration>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="com/xgf/mysql_cache/config/sqlMapConfig.xml">property>
bean>
cache属性 | 描述 |
---|---|
size | 在二级缓存中可以存储多少对象,默认1024个,超过就会按照指定的算法清除 |
eviction | 二级缓存size存满的时候,按照什么规则来清除对象,默认LRU(最近最少使用),其他方式FIFO(先进先出)、SOFT(软引用)、WEAK(弱引用) |
flushInterval | 刷新间隔、清除时间,每隔多长时间进行缓存的清除 |
blocking | 默认为false,为true时为阻塞式缓存,当有一个线程读取的时候,其他线程只能等待,只有当前线程操作完其他线程才能操作 |
type | 第三方缓存,将第三方缓存实现类写在这里(比如EhcacheCache) |
readOnly | 默认为false true:只读方式,缓存当中对象的引用(地址)交给程序,速度快,但是不安全 。 false: 兑换成中对象进行克隆clone操作,速度慢,但是安全,类需要实现Serializable 。 |
<mapper namespace="com.xgf.mysql_cache.dao.CustomerMapper">
<cache flushInterval="10000" eviction="LRU" blocking="false" readOnly="false" size="1024"/>
<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>
public class beanClass implements Serializable {
}
MyBatis中开启二级缓存机制和Spring整合MyBatis开启二级缓存步骤差不多,只是少了一个步骤(2.2 在spring的核心配置文件中,sqlSessionFactory的bean中,将mybatis的配置粘入),其他都一样。
-- ----------------------------
-- 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', '完璧归赵', '历史人物');
public class Customer {
private Integer id; //id主键自动增长
private String username; //用户名
private String job; //工作
//省略getter/setter方法
}
public interface CustomerMapper {
// where标签查询数据 name和job满足条件
List<Customer> getCustomerByNameAndJobForWhere(Customer customer);
}
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
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<setting name="cacheEnabled" value="true"/>
settings>
configuration>
<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">
<context:property-placeholder location="classpath*:db.properties" />
<context:component-scan base-package="com.xgf.mysql_cache"/>
<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>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="com/xgf/mysql_cache/config/sqlMapConfig.xml">property>
bean>
<mybatis:scan base-package="com.xgf.mysql_cache.dao"/>
beans>
<mapper namespace="com.xgf.mysql_cache.dao.CustomerMapper">
<cache flushInterval="10000" eviction="LRU" blocking="false" readOnly="false" size="1024"/>
<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>
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);
}
}
//测试一级缓存
@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);
}