赞
踩
Spring从3.1开始定义了org.springframework.cache.Cache和
org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-
107)注解简化我们开发;
Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCache ,ConcurrentMapCache
等;
每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如
果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用
直接从缓存中获取。
使用Spring缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
适合使用缓存的情况:
1.数据访问频繁
2.数据变化非常小
1.Springboot启用缓存
1.1 pom中导入缓存启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
1.2在主程序入口加上@EnableCaching注解启用缓存
@SpringBootApplication
@EnableTransactionManagement
@EnableCaching
public class SpringbootMabatisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMabatisDemoApplication.class, args);
}
}
这样之后就可以使用缓存了。
2.基于注解的缓存使用
@Cacheable: triggers cache population,缓存入口
@CacheEvict :triggers cache eviction,缓存清除
@CachePut :updates the cache without interfering with the method execution 在不影响方法执
行的情况下更新缓存
@Caching :regroups multiple cache operations to be applied on a method 将应用于方法的多
个缓存操作重新分组
@CacheConfig: shares some common cache-related settings at class-level在类级别共享一些常
见的缓存相关设置
2.1 @Cacheable(使用缓存示例)
//以传入的参数们作为 key(使用默认生成key的策略) 存入名为users的缓存中去,
// 下次同样的参数调用 方法就不执行了,直接返回缓存中的数据
@Cacheable(cacheNames = {"users"})
public Tusers queryByUserName(String userName){
System.out.println("登录 按用户名查询用户了");
QueryWrapper<Tusers> qw = new QueryWrapper<>();
qw.eq("usersn",userName);
List<Tusers> users =userDao.selectList(qw);
if (users.size()>0){
return users.get(0);
}else
return null;
}
2.1.1默认生成key的策略:
1.如果没有给出参数,则返回SimpleKey.EMPTY。
2.如果只给出一个参数,则返回该实例,也就是该参数传入的值作为key。
3.如果指定了多个参数,则返回一个包含所有参数的SimpleKey,也就是生成一个综合了所有参数值的
key。
要提供一个不同的默认密钥生成器,需要实现org.springframework.cache.interceptor.KeyGenerator
接口
2.1.2自定义key的策略声明
由于缓存是通用的,目标方法很可能具有各种不能简单映射到缓存结构顶部的签名。 当目标方法有多个
参数,其中只有一些适合缓存时(而其他参数仅由方法逻辑使用),这一点就变得很明显。 例如:
@Cacheable(cacheNames = {"users"},key ="#userName")
public Tusers queryByUserName(String userName){
System.out.println("登录 按用户名查询用户了");
QueryWrapper<Tusers> qw = new QueryWrapper<>();
qw.eq("usersn",userName);
List<Tusers> users =userDao.selectList(qw);
if (users.size()>0){
return users.get(0);
}else
return null;
}
用户名作为缓存数据的key可能就够了(当然更可能不够,可能还需要密码)。
开发人员可以使用SpEL(Spring Expression Language)来挑选感兴趣的参数(或它们嵌套的属性)、执行操
作甚至调用任意方法,而无需编写任何代码或实现任何接口。 例如: #user.id
2.1.3 关于SpEL
Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root对象 | 当前被调用的方法名 | #root.methodname |
method | root对象 | 当前被调用的方法 | #root.method.name |
target | root对象 | 当前被调用的目标对象实例 | #root.target |
targetClass | root对象 | 当前被调用的目标对象的类 | #root.targetClass |
args | root对象 | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root对象 | 当前方法调用使用的缓存列表 | #root.caches[0].name |
ArgumentName | 执行上下文 | 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 | #artsian.id |
result | 执行上下文 | 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) | #result |
注意:
1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root
对象的属性。 如 :
@Cacheable(key = “targetClass + methodName +#p0”)
2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:
@Cacheable(value=“users”, key=“#id”)
@Cacheable(value=“users”, key=“#p0”)
p是paramter参数的意思 0是第0个的意思
SpEL提供了多种运算符
类型 | 运算符 |
---|---|
关系 | <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne |
算术 | +,- ,* ,/,%,^ |
逻辑 | &&,!,and,or,not,between,instanceof |
条件 | ?: (ternary),?: (elvis) |
正则表达式 | matches |
其他类型 | ?.,?[…],![…],1,$[…] |
2.2 @CachePut annotation
对于需要在不影响方法执行的情况下更新缓存的情况,可以使用@CachePut注释。 也就是说,该方法将
始终被执行,其结果将被放置到缓存中(根据@CachePut选项)。 它支持与@Cacheable相同的选项,应
该用于缓存填充。
对数据的修改操作上,往往需要@CachePut 注解,因为数据更改了,那么缓存中的相同数据也要同步更
改。
注意,在同一个方法上使用@CachePut和@Cacheable注解通常是不被鼓励的,因为它们有不同的行
为。 后者会通过使用缓存跳过方法执行,而前者会强制执行以执行缓存更新。 这将导致意想不到的行
为,除了特定的特殊情况(例如注释具有相互排除的条件)外,应该避免这样的声明。 还要注意的是,这
种条件不应该依赖于结果对象(即#result变量),因为这些都是预先验证的,以确认排除。
2.3@CacheEvict annotation
缓存抽象不仅允许填充缓存存储,还允许清除缓存存储。 这个过程对于从缓存中删除过时或未使用的数
据非常有用。 与@Cacheable相反,@CacheEvict标注了执行缓存清除的方法,即充当触发器从缓存中
删除数据的方法。 就像它的兄弟一样,@CacheEvict需要指定一个(或多个)受该操作影响的缓存,允许
自定义缓存和键解析或指定条件,但另外,还有一个额外的参数allEntries,用于指示是否需要执行缓存
范围内的清除,而不仅仅是一个条目(基于键):
比如删除数据时,缓存中的也应该删除掉
2.4@Caching annotation
在某些情况下,需要指定多个相同类型的注释,例如@CacheEvict或@CachePut,因为不同缓存之间的
条件或键表达式不同。 @Caching允许在同一个方法上使用多个嵌套的@Cacheable, @CachePut和
@CacheEvict:
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") }) public Book importBooks(String deposit, Date date)
2.5@CacheConfig annotation
到目前为止,我们已经看到缓存操作提供了许多定制选项,可以在操作的基础上设置这些选项。 但是,
如果一些定制选项应用于类的所有操作,那么配置它们可能会很繁琐。 例如,为类的每个缓存操作指定
要使用的缓存名称可以用单个类级别定义代替。 这就是@CacheConfig发挥作用的地方
@CacheConfig("books") public class BookRepositoryImpl implements BookRepository { @Cacheable public Book findBook(ISBN isbn) {...} }
3.1 当我们使用缓存后,再次进行相同操作,则不需要再次访问数据库了,直接从缓存中去获取即可,这便是缓存的方便之处。示例:
这里我用了一个叫"users"的缓存器,当我第一次执行登录时,会执行以下代码,并在控制台打印出"登录 按用户名查询用户了",还有相应的查询用户名的sql语句,但当第二次执行登录时,便不会执行下列方法,也不会打印sql语句,前提是同一用户.
@Cacheable(cacheNames = {"users"},key ="#userName")
public Tusers queryByUserName(String userName){
System.out.println("登录 按用户名查询用户了");
QueryWrapper<Tusers> qw = new QueryWrapper<>();
qw.eq("usersn",userName);
List<Tusers> users =userDao.selectList(qw);
if (users.size()>0){
return users.get(0);
}else
return null;
}
第一次执行:
当我第二次执行登录时:
控制台信息为空。因为此次登录是直接从缓存中去取,不访问数据库。
另外我在分页上也用到了缓存器。分页缓存器名"topics"
@Cacheable(cacheNames ="topics")
public IPage<TvoteTopic> queryByCondition(TopicCondition con){
System.out.println("分页");
Page<TvoteTopic> page = new Page<>();
page.setCurrent(con.getPageNo());
page.setSize(con.getPageSize());
QueryWrapper<TvoteTopic> qw = new QueryWrapper<>();
if (con.getTopicName()!=null&&!con.getTopicName().isEmpty()){
qw.like("topic",con.getTopicName());
}
return dao.selectPage(page,qw);
}
当执行分页后,并会在控制台打印"分页",执行sql语句,在数据库中进行查找,再返回。
当我点击第一页时:
执行了sql语句.
当我再次点击第一页时,控制台没有打印:
当我点击第二页时:
控制台又打印了"分页"和sql语句。
第二次点击第二页:
控制台再次没有打印。
这说明了第二次点击是从缓存中获取,没有去再次访问数据库。
缓存的作用减少了数据的频繁访问,加快了访问速度!!!
… ↩︎
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。