1. 程式人生 > >1 Springboot中使用redis,自動快取、更新、刪除

1 Springboot中使用redis,自動快取、更新、刪除

第一篇記錄一下在springboot中,redis的基礎用法,自動快取新增的資料,自動修改及刪除。

在本機安裝好mysql和redis。新建一個springboot的web專案,在新建專案時勾選redis,mysql。

pom檔案如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.tianyalei</groupId>
	<artifactId>demo0</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>demo0</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.18</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

資料庫連線池用的druid,裡面使用了spring-boot-starter-data-redis,有這一個依賴就夠了,系統就能識別並應用redis了。dao工具用的jpa,預設集成了hibernate。

下面配置一下application.yml。如下:

spring:
  jpa:
    database: mysql
    show-sql: true
    hibernate:
      ddl-auto: update
  datasource:
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/tx2
      username: root
      password:

配置一下show-sql為true,目的是看看查表時的快取效果。至於redis的ip,埠什麼的都不用配,系統有個預設值,等會看看就知道了。 

建立個java bean。

package com.tianyalei.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;

/**
 * Created by wuwf on 17/4/21.
 */
@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;
    }
}

建立個repository
package com.tianyalei.repository;

import com.tianyalei.domain.Post;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.transaction.annotation.Transactional;

/**
 * Created by wuwf on 17/4/20.
 */
@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
    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物件的值,也完成一次更新。更多標籤,請看http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html#cache-spel-context

這樣,在對post的新增和修改時都會自動快取到redis裡。

下面來驗證一下。

加個service

package com.tianyalei.service;

import com.tianyalei.domain.Post;
import com.tianyalei.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


/**
 * Created by wuwf on 17/4/20.
 */
@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
package com.tianyalei.controller;

import com.tianyalei.domain.Post;
import com.tianyalei.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 * Created by wuwf on 17/4/20.
 */
@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註解,快取才能正常工作。
package com.tianyalei;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.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

這樣就添加了一條記錄,控制檯有一個insert語句。

然後訪問查詢,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語句。再訪問查詢地址。發現依舊能查到這條記錄,也就是db的刪除成功了,但redis並沒有刪除。

那麼怎麼在db刪除時,也刪除redis的相關記錄呢?

用CacheEvict

@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);
}
加上這個標籤後,再走deleteById方法時,就會刪除掉key為id的redis記錄了。可以重啟試試,訪問http://localhost:8080/delete/1

然後再查詢就會發現id為1的值已經查不到了。

這樣我們就完成了一個最簡單的整合redis的demo。包括了對單個物件的增刪改查的快取。

那麼下面來講幾個疑問:

1.為什麼不用配置redis的地址,port,密碼什麼的?

上面的那些預設的對redis的操作,來源於Springboot裡整合的RedisTemplate,template裡會預設使用一個JedisConnectionFactory來做預設的連線屬性配置。


這裡面已經對jedis的連線地址和jedisPool做了初始化操作了,都是預設值。系統就會使用這些預設值來操作redis。

後面我們會對Connection進行自定義,設定value的序列化方式,還有修改連線地址,那時就會使用自定義的配置了。

2.能否用上面的方法來儲存集合?譬如所有的Post集合,當新增時集合也隨之改變?

不行的,假如給List<Post> findAll做了個快取,那下次查詢時確實不用查表了,但是當你新增、修改、刪除任何一個物件時,這個快取的集合都是不變的。

除非你在所有的能修改物件的地方,都加上CacheEvict,key為集合的key,這樣任何修改,都是刪除整個集合物件的快取,下次再查時才能快取起來。而大部分時候,集合物件都是在不停變化的,除了一些不變的如城市列表之類的,其他的都不適合用這種快取方式。會導致頻繁建立大物件,而且大部分時候也不需要查整個集合,而是分頁。

3.怎麼用redis來做集合查詢,分頁查詢,甚至於條件分頁查詢?

這個也是問題2的延續,後面一篇會講。