Spring Boot实战之缓存redis

spring boot对常用的数据库支持外,对nosql 数据库也进行了封装自动化。

redis介绍

Redis是目前业界使用最广泛的内存数据存储。相比memcached,Redis支持更丰富的数据结构,例如hashes, lists, sets等,同时支持数据持久化。除此之外,Redis还提供一些类数据库的特性,比如事务,HA,主从库。可以说Redis兼具了缓存系统和数据库的一些特性,因此有着丰富的应用场景。本文介绍Redis在Spring Boot中两个典型的应用场景。

如何使用

1、引入 spring-boot-starter-redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

2、添加配置文件

# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=192.168.0.58
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0

3、添加cache的配置类

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{

    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    @SuppressWarnings("rawtypes")
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
        //设置缓存过期时间
//        rcm.setDefaultExpiration(10);//秒
        return rcm;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

好了,接下来就可以直接使用了

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(Application.class)
public class TestRedis {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void test() throws Exception {
        stringRedisTemplate.opsForValue().set("aaa", "111");
        Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa"));
    }

    @Test
    public void testObj() throws Exception {
        User user=new User("aa@126.com", "aa", "aa123456", "aa","123");
        ValueOperations<String, User> operations=redisTemplate.opsForValue();
        operations.set("com.neox", user);
        operations.set("com.neo.f", user,1,TimeUnit.SECONDS);
        Thread.sleep(1000);
        //redisTemplate.delete("com.neo.f");
        boolean exists=redisTemplate.hasKey("com.neo.f");
        if(exists){
            System.out.println("exists is true");
        }else{
            System.out.println("exists is false");
        }
       // Assert.assertEquals("aa", operations.get("com.neo.f").getUserName());
    }
}

以上都是手动使用的方式,如何在查找数据库的时候自动使用缓存呢,看下面;

4、创建个JavaBean对象。


@Entity  
public class Post implements Serializable{  
    @Id  
    @GeneratedValue(strategy = GenerationType.AUTO)  
    private Integer id;  
  
    private String content;  
  
    private Integer weight;  
  
    public Integer getId() {  
        return id;  
    }  
  
    public void setId(Integer id) {  
        this.id = id;  
    }  
  
    public String getContent() {  
        return content;  
    }  
  
    public void setContent(String content) {  
        this.content = content;  
    }  
  
    public Integer getWeight() {  
        return weight;  
    }  
  
    public void setWeight(Integer weight) {  
        this.weight = weight;  
    }  
}  
5、创建个repository
@CacheConfig(cacheNames = "post")  
public interface PostRepository extends PagingAndSortingRepository<Post, Integer> {  
    @Cacheable(key = "#p0")  
    Post findById(int id);  
  
    /** 
     * 新增或修改时 
     */  
    @CachePut(key = "#p0.id")  
    @Override  
    Post save(Post post);  
  
    @Transactional  
    @Modifying  
    @CacheEvict(key = "#p0")
    int deleteById(int id);  
} 

这个里面有个CacheConfig,配置了cacheNames。

  • 我在findById方法时加了个@Cacheable(key= "#p0"),#p0代表第一个参数,也就是id。这句话加上之后,当你在调用findById时,就会先从redis的post缓存对象里去查询key等于传过来的id的值。如果没有,就去查表。
  • 在save方法上加了个CachePut,代表往缓存里添加值,key为参数post的id属性,这样当我们save一个Post对象时,redis就会新增一个以id为key的Post对象;如果是update操作,那同样,redis会覆盖id相同的Post对象的值,也完成一次更新。
  • 在delete方法上加了个@CacheEvict(key = "#p0"),当调用deleteById时,就会同时清空指定key的缓存。
  • 更多标签,请看cache标签用法

这样,在对post的增、删、改、查时都会自动缓存到redis里。下面来验证一下。

加个service

@Service  
public class PostService {  
    @Autowired  
    private PostRepository postRepository;  
  
    public Post findById(int id) {  
        return postRepository.findById(id);  
    }  
  
    public Post save(Post post) {  
        return postRepository.save(post);  
    }  
  
    public int delete(int id) {  
        return postRepository.deleteById(id);  
    }  
}  

来个controller

@RestController  
public class PostController {  
    @Autowired  
    private PostService postService;  
  
    @RequestMapping("/query/{id}")  
    public Object query(@PathVariable int id) {  
        return postService.findById(id);  
    }  
  
    @RequestMapping("/save")  
    public Object save(@ModelAttribute Post post) {  
        return postService.save(post);  
    }  
  
    @RequestMapping("/delete/{id}")  
    public Object delete(@PathVariable int id) {  
        return postService.delete(id);  
    }  
  
    @RequestMapping("/queryPage")  
    public Object query(String name, int pageNum, int count) {  
        //根据weight倒序分页查询  
//        Pageable pageable = new PageRequest(pageNum, count, Sort.Direction.DESC, "weight");  
//        return userRepository.findByName(name, pageable);  
        return null;  
    }  
}  

然后启动Application,在启动之前需要加上@EnableCaching注解,缓存才能正常工作。

@SpringBootApplication  
@EnableCaching  
public class Demo0Application {  
  
    public static void main(String[] args) {  
        SpringApplication.run(Demo0Application.class, args);  
    }  
}  

启动后访问 http://localhost:8080/save?content=1&weight=1


如果想看是否能够正常缓存,有三种方法:

  • 在加缓存控制的地方打系统日志,比如
     /** 
     * 新增或修改时 
     */  
    @CachePut(key = "#p0.id")  
    @Override  
    Post save(Post post){
       system.out.println("命中缓存时将不会打印此日志");
    }

} 
  • 在applcation.properties中设置sql控制台日志打开
show-sql: true
  • 登陆redis服务器查看响应的key值是否有增、删、改的变化。
# 获取所有keys
keys *  
# 查看目标key是否有效
get <key>
# 查询操作时,使用的是get方法;新增和更新时,用的set方法;删除时,用的del方法,可以分别查看响应操作时,对应的key是否发生了正确的变化

然后访问查询,http://localhost:8080/query/1
会发现查询到了id为1的这条记录,并且控制台没有走select查询语句,也就是根本没访问数据库,直接从redis缓存拿的值。

下面做一个更新操作,看看是否会同步到redis里。http://localhost:8080/save?content=1&weight=2&id=1
把weight改为2,访问地址看看结果。
控制台打印了两条语句

hibernate: select post0_.id as id1_0_0_, post0_.content as content2_0_0_, post0_.weight as weight3_0_0_ from post post0_ where post0_.id=?Hibernate: update post set content=?, weight=? where id=?

说明数据已经被更新了。然后再查询http://localhost:8080/query/1
发现查到的数据已经改变,并且控制台没有走select语句,说明在update时,redis已经更新了。

下面做删除操作,可以直接在数据库里删这条记录,或者通过浏览器访问来删除。http://localhost:8080/delete/1
控制台走了删除delete语句,redis也一并删除了,这时执行查询将会看不到响应的数据。

这样我们就完成了一个最简单的整合redis的demo。包括了对单个对象的增删改查的缓存。

那么下面来讲几个疑问:

  • 为什么不用配置redis的地址,port,密码什么的?
    上面的那些默认的对redis的操作,来源于Springboot里整合的RedisTemplate,template里会默认使用一个JedisConnectionFactory来做默认的连接属性配置。



    这里面已经对jedis的连接地址和jedisPool做了初始化操作了,都是默认值。系统就会使用这些默认值来操作redis。后面我们会对Connection进行自定义,设置value的序列化方式,还有修改连接地址,那时就会使用自定义的配置了。

  • 能否用上面的方法来存储集合?譬如所有的Post集合,当新增时集合也随之改变?
    不行的,假如给List<Post> findAll做了个缓存,那下次查询时确实不用查表了,但是当你新增、修改、删除任何一个对象时,这个缓存的集合都是不变的。
    除非你在所有的能修改对象的地方,都加上CacheEvict,key为集合的key,这样任何修改,都是删除整个集合对象的缓存,下次再查时才能缓存起来。而大部分时候,集合对象都是在不停变化的,除了一些不变的如城市列表之类的,其他的都不适合用这种缓存方式。会导致频繁创建大对象,而且大部分时候也不需要查整个集合,而是分页。

  • 怎么用redis来做集合查询,分页查询,甚至于条件分页查询

参考文档

http://blog.csdn.net/tianyaleixiaowu/article/details/70314277
https://yq.aliyun.com/articles/91595?t=t1
http://blog.csdn.net/tianyaleixiaowu/article/details/70595073

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容