1. 程式人生 > >學習筆記——spring boot(11)

學習筆記——spring boot(11)

Redis快取

  每一次使用者訪問資料都要通過資料庫的話,使用者訪問的速度會大大下降,尤其是在多使用者同時訪問資料庫時,此時就會出現載入不出的問題。這時候就要引入redis快取機制,由於redis是直接通過記憶體資料儲存,在速度上比直接訪問資料庫要快很多。redis仍是進行資料儲存,它的模式時key-value模式,也就是管理者通過key來索引所儲存的相關值。另外,redis支援更豐富的資料結構,例如hashes, lists, sets等,同時支援資料持久化。除此之外,redis還提供一些類資料庫的特性,比如事務,HA,主從庫。可以說redis兼具了快取系統和資料庫的一些特性,因此有著豐富的應用場景。

  新接觸redis的同學不要認為使用了redis就不需要資料庫(例如mysql),由於Redis是將資料儲存在記憶體中(為了訪問速度更快),是不可以長久儲存的(暫存),而資料庫是儲存在硬碟中,儲存的資料才可以長久儲存。而redis的機制也是在第一次訪問資料庫的時候同時將資料也儲存在記憶體中,而後訪問該資料的時候僅需要訪問記憶體即可。當網頁重啟的話,第一次訪問相關資料又是通過訪問資料庫來讀取。

  由於spring boot有集合redis,使用RedisTemplate實現兩者的整合,所以實現也十分簡單。

  首先要引入redis的依賴:

<!--redis的匯入-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

  然後在配置檔案中進行配置:

# REDIS (RedisProperties)

# Redis資料庫索引(預設為0)
spring.redis.database=0  
# Redis伺服器地址
spring.redis.host=127.0.0.1
# 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  

  新增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(60);//秒
        return rcm;
    }
    
    @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;
    }

}

  其實對cache的配置有什麼作用我暫時不是很清楚,而且我在編寫程式的時候註釋掉對程式的影響也不大,但是注意,當註釋掉該配置類時,記得在啟動類上添加註釋@EnableCaching。

  注意,當沒有進行caching配置類的時候,由於redis儲存資料不是使用json格式的,所以我們要測試的話就會很麻煩,這時候我們要配置json格式(不是必須的,沒有配置類時才加):

/* *
* Redis的序列化配置
* 進行json資料的存取,防止亂碼
* */
@Configuration
public class MyRedisConfig {

    /*
    *注意觀察下面的User類,這是對user這個類進行redis快取
    *將user改為你需要快取的類名即可使用
    */

    //user的配置類
    @Bean
    public RedisTemplate<Object,Prize> userRedisTemplate
            (RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object,User> template=new RedisTemplate<Object,User>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<User> serializer=new Jackson2JsonRedisSerializer<User>(User.class);
        template.setDefaultSerializer(serializer);
        return template;
    }

    //@Primary是設定為預設的管理器,一定要設定一個預設管理器
    @Bean
    @Primary
    public RedisCacheManager userCacheManager(RedisTemplate<Object,User> prizeRedisTemplate){
        RedisCacheManager cacheManager=new RedisCacheManager(userRedisTemplate);
        //使用字首,使用cachName作為字首
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }

}

  而進行了一系列的新增配置之後,終於來到了redis的使用,redis的使用分為“手動的使用”和“查詢資料庫時自動使用redis”,但是在介紹redis的使用之前,我們需要先了解一下redis的語法:

  Redis是基於key-value模式,也就是說,使用者儲存資料,是通過key來進行索引,value進行儲存。key的話相當於一個關鍵詞,該關鍵詞可以是儲存資料的某個變數的值,也可以是類中方法完成返回的值等等。我們使用redis管理器可以發現,一個數據表下面是有多個關鍵詞key,而我們通過key,可以找到我們儲存的值。如果我們要修改刪除等操作的話,也是要先通過key來找到value才可以進行操作的。

  手動使用redis我們需要參照Redis官網中文件,通過查詢語法文件我們可以redis的各類語法,知道語法之後我們就可以手動使用redis:

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

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
	@Autowired
	private RedisTemplate redisTemplate;

    @Test
    public void test() throws Exception {
        //以key為aaa,value為111儲存
        stringRedisTemplate.opsForValue().set("aaa", "111");
        //比較以key為aaa檢索出的值是否為111
        Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa"));
    }
    
    @Test
    public void testObj() throws Exception {
        //redis可以儲存物件,儲存的是user,user中含有各類變數
        User user=new User("[email protected]", "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());
    }
}

  而在瞭解結合各類資料庫使用前,我們需要先看一張圖:

這幾條註解是分別使用在不同方法上,在進行資料查詢時使用@Cableable,進行資料新增或修改時使用@CablePut,進行資料刪除時使用@CacheEvict,當然這裡還少了一條註解:@Caching,這是一條組合註解,它是可以結合上面多條註解同時存在的。我們來看一下註解內key如何編寫及其他語法:

 看完這些註解,我們需要應用到方法上,而應用到哪裡?其實redis簡化了許多,也支援不同資料庫,而我們使用的時候,僅需要通過service層的方法進行註釋即可(不同資料庫都會有service層),我們看看mysql+jpa的例子:

//@CacheConfig設定cache
@Service
@CacheConfig(cacheNames = "user",cacheManager = "userCacheManager")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    @Transactional
    public List<User> findAll() {
        return userRepository.findAll();
    }

    //通過使用者id查詢使用者,使用@Cacheable註解
    @Cacheable(key = "#id")
    @Override
    @Transactional
    public User getUserById(Integer id) {
        return userRepository.findOne(id);
    }
   
    /* *
    * 儲存user,使用@CacbePut進行註解
    * */
    @CachePut(key="#result.id")
    @Override
    @Transactional
    public User saveUser(User user) {
        try{
            userRepository.save(user);
        }catch (Exception e){
            throw new RuntimeException("Add User Error:"+e.getMessage());
        }
        return user;
    }

    /* *
     * 修改prize,由於修改,則稽核通過
     * */
    @CachePut(key="#result.id")
    @Override
    @Transactional
    public User updateUser(User user) {
        try{
            userRepository.save(user);
        }catch (Exception e){
            throw new RuntimeException("Update User Error:"+e.getMessage());
        }
        return user;
    }

    //刪除user,通過@CacheEvict註解
    @CacheEvict(key="#id")
    @Override
    @Transactional
    public void removeUser(Integer id) {
        userRepository.delete(id);
    }

   

    //通過使用者名稱查詢使用者
    //使用@Caching複合註解
    @Caching(
            cacheable = {
                    @Cacheable(key="#userName")
            },
            put ={
                     @CachePut(key="#result.id")
            }
    )
    @Override
    @Transactional
    public Prize findByUserName(String userName) {
        return userRepository.findByUserName(userName);
    }
}

  其中,在service類上要加上@CacheConfig()註解,其中cacheNames指明這次要儲存的資料的實體類是什麼,cacheManager指明該service的redis管理器是什麼(也就是在上面redis序列化配置防亂碼時指明的管理器)。然後使用@Cableable,進@CablePut,@CacheEvict,@Caching進行多維操作。