1. 程式人生 > >spring boot 開發—第五篇SpringDataJPA整合 Redis 實現快取

spring boot 開發—第五篇SpringDataJPA整合 Redis 實現快取

對druid專案進行改造,增加redis快取支援。

1、redis簡介

Redis 是完全開源免費的,遵守BSD協議,是一個高效能的key-value資料庫。

Redis 與其他 key - value 快取產品有以下三個特點:

  • Redis支援資料的持久化,可以將記憶體中的資料儲存在磁碟中,重啟的時候可以再次載入進行使用。
  • Redis不僅僅支援簡單的key-value型別的資料,同時還提供list,set,zset,hash等資料結構的儲存。
  • Redis支援資料的備份,即master-slave模式的資料備份。

2、Redis 優勢

  • 效能極高 – Redis能讀的速度是110000次/s,寫的速度是81000次/s 。
  • 豐富的資料型別 – Redis支援二進位制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 資料型別操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要麼成功執行要麼失敗完全不執行。單個操作是原子性的。多個操作也支援事務,即原子性,通過MULTI和EXEC指令包起來。
  • 豐富的特性 – Redis還支援 publish/subscribe, 通知, key 過期等等特性。

3、構建專案

這裡寫圖片描述

修改pom檔案,增加對redis的依賴。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.vesus</groupId>
    <artifactId>springboot-redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>springboot-redis</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.vesus</groupId>
        <artifactId>spring-boot-demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--spring jpa-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--引入druid最新maven依賴-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.29</version>
        </dependency>
        <!--引入redis的支援-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

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

2、配置application.properties

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.show-sql=true
spring.jpa.hibernate.naming.strategy=org.hibernate.cfg.ImprovedNamingStrategy
spring.jpa.database=mysql
##########################  druid配置   ##########################
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#最大活躍數
spring.datasource.maxActive=20
#初始化數量
spring.datasource.initialSize=5
#最小活躍數
spring.datasource.minIdle=5
#配置超市等待時間
spring.datasource.maxWait=60000
#配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
#配置一個連線在池中最小生存的時間,單位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM t_user
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.exceptionSorter=true
spring.datasource.testOnReturn=false
#開啟PSCache,並且指定每個連線上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
#配置監控統計攔截的filters,去掉後監控介面sql無法統計,'wall'用於防火牆
spring.datasource.filters=stat,wall,log4j
#通過connectProperties屬性來開啟mergeSql功能;慢SQL記錄
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#合併多個DruidDataSource的監控資料
spring.datasource.useGlobalDataSourceStat=true
###########################redis#########################
#Redis資料庫索引(預設為0)
spring.redis.database=0
#Redis伺服器地址
spring.redis.host=10.211.55.5
#Redis伺服器連線埠
spring.redis.port=6379
#Redis伺服器連線密碼(預設為空)
spring.redis.password=
#連線池最大連線數(使用負值表示沒有限制)
spring.redis.pool.max-active=10
#連線池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.pool.max-wait=-1
#連線池中的最大空閒連線
spring.redis.pool.max-idle=8
#連線池中的最小空閒連線
spring.redis.pool.min-idle=0
#連線超時時間(毫秒)
spring.redis.timeout=0

3、編寫RedisConfig

3.1 模板檔案

Spring Data Redis提供了兩個模板:

  RedisTemplate

  StringRedisTemplate

首先我們先建立一個RedisTemplate模板類,型別的key是String型別,value是Object型別(如果key和value都是String型別,建議使用StringRedisTemplate)

3.2、序列化

當儲存一條資料的時候,key和value都要被序列化成json資料,取出來的時候被序列化成物件,key和value都會使用序列化器進行序列化,spring data redis提供多個序列化器

  • GenericToStringSerializer:使用Spring轉換服務進行序列化;
  • JacksonJsonRedisSerializer:使用Jackson 1,將物件序列化為JSON;
  • Jackson2JsonRedisSerializer:使用Jackson 2,將物件序列化為JSON;
  • JdkSerializationRedisSerializer:使用Java序列化;
  • OxmSerializer:使用Spring O/X對映的編排器和解排器(marshaler和unmarshaler)實
  • 現序列化,用於XML序列化;
  • StringRedisSerializer:序列化String型別的key和value。

RedisTemplate會預設使用JdkSerializationRedisSerializer,這意味著key和value都會通過Java進行序列化。StringRedisTemplate預設會使用StringRedisSerializer

3.3、 例項程式碼如下:

package com.vesus.springbootredis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import java.lang.reflect.Method;


//Redis 快取配置類
public class RedisConfig extends CachingConfigurerSupport {

    //快取物件集合中,快取是以key-value的形式儲存的,當不指定快取key是,會使用KeyGenerator生成key
    //定義快取資料 key 生成策略的bean包名+類名+方法名+所有引數
    @Bean
    public KeyGenerator keyGenerator(){
        return new KeyGenerator(){
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuffer buffer = new StringBuffer();
                buffer.append(target.getClass().getName());
                buffer.append(method.getName());
                for (Object obj : params) {
                    buffer.append(obj.toString());
                }
                return buffer.toString();
            }
        };
    };
    //要啟用spring快取支援,需建立一個 CacheManager的 bean,CacheManager 介面有很多實現,
    // 這裡Redis 的整合,用 RedisCacheManager這個實現類
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate){
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        return cacheManager ;
    }
    //redis模板,Spring Data Redis提供了兩個模板:RedisTemplate,StringRedisTemplate
    public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory factory){
        StringRedisTemplate template = new StringRedisTemplate(factory);
        //序列化
        //spring data redis提供多個序列化器
        //GenericToStringSerializer:使用Spring轉換服務進行序列化;
        //JacksonJsonRedisSerializer:使用Jackson 1,將物件序列化為JSON;
        //Jackson2JsonRedisSerializer:使用Jackson 2,將物件序列化為JSON;
        //JdkSerializationRedisSerializer:使用Java序列化;
        //OxmSerializer:使用Spring O/X對映的編排器和解排器(marshaler和unmarshaler)實
        //現序列化,用於XML序列化;
        //StringRedisSerializer:序列化String型別的key和value。
        Jackson2JsonRedisSerializer serializer = new              Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(om);
        template.setValueSerializer(serializer);
        template.afterPropertiesSet();
        return template ;

    }
}

4、編寫Impl

4.1、Cache註解詳解

  • @CacheConfig:主要用於配置該類中會用到的一些共用的快取配置。在這裡@CacheConfig(cacheNames = “users”):配置了該資料訪問物件中返回的內容將儲存於名為users的快取物件中,我們也可以不使用該註解,直接通過@Cacheable自己配置快取集的名字來定義。
  • @Cacheable:配置了findByName函式的返回值將被加入快取。同時在查詢時,會先從快取中獲取,若不存在才再發起對資料庫的訪問。該註解主要有下面幾個引數:
    • value、cacheNames:兩個等同的引數(cacheNames為Spring 4新增,作為value的別名),用於指定快取儲存的集合名。由於Spring 4中新增了@CacheConfig,因此在Spring 3中原本必須有的value屬性,也成為非必需項了
    • key:快取物件儲存在Map集合中的key值,非必需,預設按照函式的所有引數組合作為key值,若自己配置需使用SpEL表示式,比如:@Cacheable(key = “#p0”):使用函式第一個引數作為快取的key值.
    • condition:快取物件的條件,非必需,也需使用SpEL表示式,只有滿足表示式條件的內容才會被快取,比如:@Cacheable(key = “#p0”, condition = “#p0.length() < 3”),表示只有當第一個引數的長度小於3的時候才會被快取,若做此配置上面的AAA使用者就不會被快取,讀者可自行實驗嘗試。
    • unless:另外一個快取條件引數,非必需,需使用SpEL表示式。它不同於condition引數的地方在於它的判斷時機,該條件是在函式被呼叫之後才做判斷的,所以它可以通過對result進行判斷。
    • keyGenerator:用於指定key生成器,非必需。若需要指定一個自定義的key生成器,我們需要去實現org.springframework.cache.interceptor.KeyGenerator介面,並使用該引數來指定。需要注意的是:該引數與key是互斥的
    • cacheManager:用於指定使用哪個快取管理器,非必需。只有當有多個時才需要使用
    • cacheResolver:用於指定使用那個快取解析器,非必需。需通過org.springframework.cache.interceptor.CacheResolver介面來實現自己的快取解析器,並用該引數指定。

除了這裡用到的兩個註解之外,還有下面幾個核心註解:

  • @CachePut:配置於函式上,能夠根據引數定義條件來進行快取,它與@Cacheable不同的是,它每次都會真是呼叫函式,所以主要用於資料新增和修改操作上。它的引數與@Cacheable類似,具體功能可參考上面對@Cacheable引數的解析
  • @CacheEvict
    :配置於函式上,通常用在刪除方法上,用來從快取中移除相應資料。除了同
    @Cacheable
    一樣的引數之外,它還有下面兩個引數:
    • allEntries:非必需,預設為false。當為true時,會移除所有資料
    • beforeInvocation:非必需,預設為false,會在呼叫方法之後移除資料。當為true時,會在呼叫方法之前移除資料。

4.2、例項程式碼

package com.vesus.springbootredis.service.impl;

import com.vesus.springbootredis.model.User;
import com.vesus.springbootredis.repository.UserRepository;
import com.vesus.springbootredis.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserRepository userRepository ;

    //@Cacheable,如果沒有指定key則方法引數作為key儲存到快取中。
    @Cacheable(value = "users")
    public List<User> findAll() {
        return userRepository.findAll();
    }

    //@CachePut快取新增的或更新的資料到快取,其中快取名字是 user 。資料的key是user的id
    @CachePut(value = "user",key = "#user.id")
    public void saveUser(User user) {
        userRepository.save(user);
    }

    @Cacheable(value = "user",key = "#id")
    public User findOne(long id) {
        User user =  userRepository.findOne(id);
        return user ;
    }

    //@CacheEvict 從快取user中刪除key為id 的資料
    @CacheEvict(value = "user",key = "#id")
    public void delete(long id) {

    }

    @Override
    public List<User> findByName(String name) {
        return null;
    }
}

5、編寫controller

package com.vesus.springbootredis.controller;

import com.vesus.springbootredis.model.User;
import com.vesus.springbootredis.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    @Autowired
    UserService userService ;

    @RequestMapping(value = "/userlist")
    public List<User> getUserList(){

        return userService.findAll() ;
    }

    @RequestMapping(value = "/save")
    public void saveUser(){
        User user = new User() ;
        user.setId(2);
        user.setName("user2");
        user.setAge(22);
        userService.saveUser(user);
    }

    @RequestMapping(value = "/findbyid")
    public User findById(){

        return userService.findOne(new Long(1));
    }

    @RequestMapping(value = "/delid")
    public void delid(){

        userService.delete(new Long(1));
    }
}

第一次查詢資料庫

Hibernate: select user0_.t_id as t_id1_0_0_, user0_.t_address as t_addres2_0_0_, user0_.t_age as t_age3_0_0_, user0_.t_name as t_name4_0_0_, user0_.t_pwd as t_pwd5_0_0_ from t_user user0_ where user0_.t_id=?

再次重新整理,不訪問資料庫