Cache使用和Redis的使用:
一、准备工作:
新建一个springboot的project,然后配置好mybatis和druid的数据库、数据源。
二、准备初试Cache缓存的作用:
1、新建一个bean.User实体类:
public class User {
private String username;
private String passwd;
private String phone;
private String address;
private String youbian;
public User() {
}
public User(String username, String passwd, String phone, String address, String youbian) {
this.username = username;
this.passwd = passwd;
this.phone = phone;
this.address = address;
this.youbian = youbian;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getYoubian() {
return youbian;
}
public void setYoubian(String youbian) {
this.youbian = youbian;
}
}
2、新建一个UserService服务:
@Service
public class UserService {
@Autowired
UserMapper userMapper;
/**
* 将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存中取出数据
* 1、cacheNames/value 指定缓存的名字;
* 2、key: 缓存数据使用的key;可以用它来指定,默认就是使用方法参数的值: 1-方法的返回值
* 我们也可以使用SpEL: #username; 参数id的值 #a0 #po #root.args[0]
* 3、keyGenerator: key的生成器: 我们也可以使用自己自定key的生成器的组件username
* 4、key/keyGenerator 选一个使用
* 5、cacheMange 缓存管理器:我们可以指定缓存管理器
* 6、condition:指定符合条件的情况下才缓存;
* 7、unless:当unless指定的条件为true,方法的返回值就不会被缓存;unless可以获取到结果进行判断:#result代表方法的返回值
* 8、sync:是否使用异步模式
*
* @param username
* @return
*/
//我们可以用 #root.args[0] 也代表使用username
@Cacheable(cacheNames = "user_emp",key = "#root.args[0]")
public User getUserName(String username){
System.out.println("查询 --"+username+"-- 用户");
return userMapper.getUserUsername(username);
}
public int insertUser(User user){
System.out.println("插入 --"+user.getUsername()+"-- 用户");
return userMapper.insertUser(user);
}
}
@Cacheable注解作用:将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存中取出数据。
1、cacheNames/value 指定缓存的名字;
2、key: 缓存数据使用的key;可以用它来指定,默认就是使用方法参数的值: 1-方法的返回值
我们也可以使用SpEL: #username; 参数id的值 #a0 #po #root.args[0]
3、keyGenerator: key的生成器: 我们也可以使用自己自定key的生成器的组件username
4、key/keyGenerator 选一个使用
5、cacheMange 缓存管理器:我们可以指定缓存管理器
6、condition:指定符合条件的情况下才缓存;
7、unless:当unless指定的条件为true,方法的返回值就不会被缓存;unless可以获取到结果进行判断:#result代表方法的返回值
8、sync:是否使用异步模式。
3、新建一个Controller:
@RestController
public class UserController {
@Autowired
UserService userService;
@GetMapping("/user/{username}")
public User getUser(@PathVariable("username") String username){
return userService.getUserName(username);
}
@GetMapping("/user")
public User insertUser(User user){
int insertUser_id = userService.insertUser(user);
return user;
}
}
4、运行:
我们首次查询的时候:控制台会打印出sql语句。
但是我们第二次 第三次查询这个数据的时候,控制台不会再打印sql语句,这时我们已经把该数据放入到了缓存中,可以直接从缓存中获取数据。
三、Cache缓存的原理:
1、自动配置类:CacheAutoConfiguration
2、缓存的配置类:
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
在CacheAutoConfiguration这个配置类中可以找到这个函数,我们可以用断点来看看导入了哪些配置类。
导入了以下的这些包:
0 = "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration"
1 = "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration"
2 = "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration"
3 = "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration"
4 = "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration"
5 = "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration"
6 = "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration"
7 = "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration"
8 = "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration"
9 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"
3、哪个配置类默认生效呢?
我们可以直接在yml配置文件下添加debug=true打印自动配置报告。我们就可以看到有哪些生效了的配置类。
我们可以找到只生效了:SimpleCacheConfiguration
SimpleCacheConfiguration作用:给容器中注册了一个CacheManager:ConcurrentMapCacheManager
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
//注册了一个CacheManager
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}
}
实现CacheManager后就可以有getCache方法了。
我们来看一下这个函数:
@Override
@Nullable
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
createConcurrentMapCache:
protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256),
isAllowNullValues(), actualSerialization);
}
我们可以看到,这里创建了一个Cache对象,并返回该对象。
4、所以这个SimpleCacheConfiguration所起作用就可以获取和创建ConcurrentMapCache类型的缓存组件
我们来看一下new的这个ConcurrentMapCache类:
其中有一个lookup函数:
protected Object lookup(Object key) {
return this.store.get(key);
}
返回一个key的值。我们来看一下store是一个什么结构:
private final ConcurrentMap<Object, Object> store;
store是一个Map键值对,根据传入的key来获取value值。
5、由以上分析,我们可以得到ConcurrentMapCache类型的缓存组件的作用:将数据保存在ConcurrentMap中,然后根据传入的key来返回存入的value值
//查询函数
@Override
@Nullable
protected Object lookup(Object key) {
return this.store.get(key);
}
//保存函数
@Override
public void put(Object key, @Nullable Object value) {
this.store.put(key, toStoreValue(value));
}
6、运行流程:
-
一、查询缓存是否存在我们所请的数据,按照cacheName指定的名字获取,如果第一次获取缓存如果没有Cache组件会自动创建;
-
二、去Cache中查找缓存的内容,使用一个key,默认方法的参数,key是按照某种策略生成的,默认是使用keyGenerator 生成的,默认使用SimpleKeyGenerator生成key。
SimpleKeyGenerator生成key的默认策略:- 如果没有参数,key=new SimpleKey();
- 如果有一个参数,key=参数值;
- 如果有多个参数,key=new SimpleKey(params);
-
三、不存在的情况下,会调取对应的Service函数,然后调用Dao层,并返回数据;
-
四、拿到数据库的数据后,会put到缓存中,如果设置了cacheNames,那么就是以cacheNames会缓存名字,value就是请求的对象数据;
-
五、然后返回数据到前端页面;
-
六、如果再次请求该数据,那么就会在ConcurrentMap中查找到以传入数据的值为key的缓存,然后就从ConcurrentMap中取出数据并返回给前端页面,不会执行Service函数,也不会访问数据库。
总体来说:
- 方法执行之前先来检查缓存中有没有数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法,并将结果放入缓存,以后再来调用就可以直接使用缓存中的数据。
@CachePut注解的使用:
-
CachePut的使用:在对数据库进行更新后,会随着更新缓存中的数据,以保持数据的一致性,对于update的方法,如果添加了缓存机制,那么最好要添加@CachePut注解,在更新的过程中也更新缓存;
-
CachePut运行时机:先调用目标方法,然后把结果返回到缓存中,更新缓存;
-
/** * @CachePut:运行时机:先调用目标方法,然后再把结果返回到缓存中,更新缓存 * * @param user * @return */ @CachePut(value = "user_emp",key = "#result.id") public User updateUser(User user){ System.out.println("更新--"+user.getUsername()+"--用户"); userMapper.updateUser(user); return user; }
这里设置key设置为**#result.id**,把返回回来的**id(id也是主键属性)**设置为缓存的key值,以更新缓存中的数据。
-
我们来看一下@CachePut的使用:
首先我们查询一下,把id为1的用户放入缓存:
然后我们更新一下id为1的用户,然后我们再查询一下id=1的用户,看是否更新缓存:
再查询:
我们看看后台有没有打印访问数据库:
可以看到,并没去数据库找该数据,所以可以得知:缓存更新成功,并且查询直接从缓存中拿出了该数据。
@Caching注解的使用:复杂注解的使用
/**
* 添加@CachePut后会先执行方法,再更新缓存,无论什么情况都会执行方法,然后把缓存中的队友value值更新
*
* @param username
* @return
*/
@Caching(
cacheable = {
@Cacheable(value = "user_emp",key = "#username")
},
put = {
@CachePut(value = "user_emp",key = "#result.id"),
@CachePut(value = "user_emp",key = "#result.phone")
}
)
public User getUserByName(String username){
System.out.println("使用名称查询:"+username+" 用户");
return userMapper.getUserUsername(username);
}
添加@CachePut注解后,会无论什么情况都会先执行方法,然后再更新缓存,只要对应好value值的情况下,就可以更新其他属性的缓存信息。
@CacheConfig:
public @interface CacheConfig {
/**
* Names of the default caches to consider for caching operations defined
* in the annotated class.
* <p>If none is set at the operation level, these are used instead of the default.
* <p>May be used to determine the target cache (or caches), matching the
* qualifier value or the bean names of a specific bean definition.
*/
String[] cacheNames() default {};
/**
* The bean name of the default {@link org.springframework.cache.interceptor.KeyGenerator} to
* use for the class.
* <p>If none is set at the operation level, this one is used instead of the default.
* <p>The key generator is mutually exclusive with the use of a custom key. When such key is
* defined for the operation, the value of this key generator is ignored.
*/
String keyGenerator() default "";
/**
* The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
* create a default {@link org.springframework.cache.interceptor.CacheResolver} if none
* is set already.
* <p>If no resolver and no cache manager are set at the operation level, and no cache
* resolver is set via {@link #cacheResolver}, this one is used instead of the default.
* @see org.springframework.cache.interceptor.SimpleCacheResolver
*/
String cacheManager() default "";
/**
* The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} to use.
* <p>If no resolver and no cache manager are set at the operation level, this one is used
* instead of the default.
*/
String cacheResolver() default "";
}
在这个配置中可以设置一些公共的属性,比如cacheNames等属性,不需要每次在方法前添加cacheNames。
Redis的使用和配置:
一、Redis的简历:
Redis是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
Redis与其他key-value缓存产品的特点有:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list、set、zset、hash等数据结构存储
- Redis支持数据的备份,即master-slave模式的数据备份。
二、Redis的优势:
- 性能极高:Redis能读的速度是110000次/s,写的速度是81000次/s。
- 丰富的数据类型:Redis支持二进制案例的Strings,Lists,Hashes,Sets及Ordered Sets数据类型操作。
- 原子性:Redis的所有操作都是原子性的,意思就是要么全部成功,么失败则全部不执行。单个操作是原子性的。多个操作也是支持事物的,即原子性:通过MULTI和EXEC只能包括起来。
- 丰富的特征:Redis还支持publish/subscribe,通知,key过期等特性。
三、Redis与其他key-value存储有什么不同呢?
- Redis有着更为复杂的数据结构并且提供了对他们的原子性操作,这是一个不同于其他数据库的进化路径,Redis的数据类型都是基于基本数据结构的同时对程序猿透明,不再需要其他额外的抽象。
- Redis运行在内存中,但是可以持久化到硬盘上,所以在对不同数据集进行高速读写的时候需要权衡内存,因为数据量是不能大于硬件内存的。
- 相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情,同时在磁盘格式方面他们是以紧凑的、以追加的方式产生的,不需要进行随机访问,这样能提高访问的速度。
四、在Windows下安装Redis
https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NhGADiQW-1584521712289)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200316164049567.png)]
然后打开cmd,进入解压后的文件中:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ycVHlFY-1584521712290)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200316164125980.png)]
redis-server.exe redis.windows.conf
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JMyLbxzA-1584521712291)(C:\Users\ouguangji\AppData\Roaming\Typora\typora-user-images\image-20200316164256088.png)]
启动成功,然后再开启一个cmd:
//windows进入方法
redis-cli.exe -h 127.0.0.1 -p 6379
//linux进入方法
redis-cli
然后设置键值对:
set myKey abc
取出键值对:
get myKey
安装完毕!
Redis数据类型
Redis支持五种数据类型:
- String:字符串
- hash:哈希
- list:列表
- set:集合
- zeset(sorted set):有序集合
一、String字符串:
String字符串是redis最基本的类型,可以理解成语Memcached一模一样的类型,一个key对应一个value值。String类型是二进制安全的:redis的string可以包含任何数据,包含汉字、图片、或者序列化对象。String类型是Redis最基本的数据类型,最大能存储512MB。
set ruuoob "小狗"
我在docker中实验redis,因为在windows下,对于使用汉字的时候,会出现乱码的情况:
在linux下只需要设置 --raw 就可以显示中文汉字了
二、Hash哈希:
Redis hash是一个键值对(key=>value)集合。
Redis hash是一个String类型的field和value的映射表,hash特别适合用于存储对象。
删除之前的key
DEL runoob
设置hash
HMSET runoob field1 "hello1" field2 "hello2"
得到hash值
HGET runoob field1
每个hash中可以存储2的32次方-1个键值对。
三、List列表:
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
lpush runoob redis
lpush runoob mongodb
lpush runoob rabitmq
获取值:从第x到第y个值
lrange runoob x y
列表最多存储2的32次方-1个元素
三、Set集合:
Redis的Set集合是string类型的无序集合。
集合是通过哈希表实现的,所以添加,删除,查找都是O(1)。
sadd命令:添加一个string元素到key对应的set集合中,成功返回1,如果元素已经存在在集合中返回0
sadd key member
取出元素:
smembers key
集合最多存储2的32次方-1个元素
四、zset(Sorted set:有序集合)
Redis zset 和set 一样也是String类型元素的集合,且不允许重复的成员。
不同的是:每个元素都会关联一个double类型的分数,redis通过这个分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的。但是分数score是可以重复的。
zadd命令:添加元素到集合中,元素在集合中存在则更新对应的score
zadd key score member
Redis和springboot的整合:
一、Redis的测试环境:
1)、搭建Redis环境;
2)、在springboot中加入redis依赖:
<!--redis加载-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
3)、配置文件配置ip和port:
spring:
redis:
host: 47.97.192.241
port: 6379
4)、在test中测试Redis:
@SpringBootTest
class DemoMybatisApplicationTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
RedisTemplate redisTemplate;
@Test
void contextLoads() {
// String msg = stringRedisTemplate.opsForValue().get("msg");
// System.out.println("111");
// System.out.println(msg);
stringRedisTemplate.opsForList().leftPush("myList","1");
stringRedisTemplate.opsForList().leftPush("myList","2");
stringRedisTemplate.opsForList().leftPush("myList","3");
stringRedisTemplate.opsForList().rightPush("myList","4");
}
}
成功的情况下输出为:
二、redis的实际使用:
1)、配置mybatis环境;
2)、配置redis环境;
3)、添加Service:
@Cacheable(cacheNames = "user_emp",key = "#root.args[0]")
public User getUserById(Integer id){
System.out.println("查询 --id= "+id+"-- 用户");
return userMappers.getUserById(id);
}
注意:User实体类,一定要添加序列化接口。
但是如果这样进行缓存,缓存下来的只能是序列化后的数据,如果我们想缓存json数据到Redis中,那么就必须自定义配置redisconfig。
4)、配置自定义MyRedisConfig:
@Configuration
public class MyRedisConfig {
/**
* 自定义序列化器
* @param redisConnectionFactory
* @return
* @throws
*/
@Bean
public RedisTemplate<Object, Object> User_redisTemplate(RedisConnectionFactory redisConnectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Object> jsonRedisSerializer
= new Jackson2JsonRedisSerializer<Object>(Object.class);
template.setDefaultSerializer(jsonRedisSerializer);
return template;
}
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory){
//初始化一个RedisCacheWriter输出流
RedisCacheWriter redisCacheWriter=RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
//自定义序列化机制
Jackson2JsonRedisSerializer<Object> serializer=new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper mapper=new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
RedisSerializationContext.SerializationPair pair=
RedisSerializationContext
.SerializationPair
.fromSerializer(serializer);
//创建CacheConfig
RedisCacheConfiguration redisCacheConfiguration=RedisCacheConfiguration
.defaultCacheConfig()
.serializeValuesWith(pair);
return new RedisCacheManager(redisCacheWriter,redisCacheConfiguration);
}
}
第一个函数自定义的是RedisTemplate的配置。
RedisTemplate<Object,Object> User_redisTemplate;
RedisTemplate的用法:在Controller中:
@Autowired
RedisTemplate<Object,Object> User_redisTemplate;
@ResponseBody
@GetMapping("/add2")
public Object add2(){
User user=new User();
user.setUsername("ogj");
user.setPasswd("123456");
user.setAddress("重庆市");
User_redisTemplate.opsForValue().set("user",user);
return User_redisTemplate.opsForValue().get("user");
}
第二个函数自定义的是Cacheable注解所自定义的CacheManager:
@Cacheable(cacheNames = "user_emp",key = "#root.args[0]")
public User getUserById(Integer id){
System.out.println("查询 --id= "+id+"-- 用户");
return userMappers.getUserById(id);
}
使用方法和之前Cache的使用方法相同,因为之前使用的是SimpleCacheConfiguration,在加入RedisCache后,自动配置将自动转为redis的配置,所以使用方法完全一致:
- @Cacheable进行缓存
- @CachePut进行缓存更新
- @CacheEvict进行删除
- @Caching进行复杂化操作
5)、运行项目:
json格式存储成功!