Spring实战——缓存数据

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fancheng614/article/details/85938173

缓存(caching)可以存储经常会用到的信息,如果不使用缓存,每次查询数据时都会请求数据库,为了减小数据库的压力,可以将数据添加到缓存中,这样每次需要的时候,这些信息都是立即可用的。Spring自身并没有实现缓存的解决方案,但是它对缓存功能提供了声明式的支持,能够与多种流行的缓存实现集成。

本示例中需要的Jar包依赖(注意redis相关依赖的版本):

<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mfc.spring11</groupId>
  <artifactId>SpringInAction11</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>SpringInAction11 Maven Webapp</name>
  <url>http://maven.apache.org</url>
  
  <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <spring.version>4.3.10.RELEASE</spring.version>
  </properties>
  <dependencies>
  
  	<!-- Java 缓存管理器依赖Start -->
  	<!-- https://mvnrepository.com/artifact/javax.cache/cache-api -->
	<dependency>
	    <groupId>javax.cache</groupId>
	    <artifactId>cache-api</artifactId>
	    <version>1.1.0</version>
	</dependency>
  	
  	<!-- Java 缓存管理器依赖End -->
  	
  	<!-- 使用EhCache缓存需要的依赖Start -->
 	 <!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
	<dependency>
	    <groupId>net.sf.ehcache</groupId>
	    <artifactId>ehcache</artifactId>
	    <version>2.10.4</version>
	</dependency>
	<!-- 使用EhCache缓存需要的依赖End -->
	
	
	<!-- 使用Redis缓存需要的依赖Start -->
	<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
	<dependency>
	    <groupId>org.springframework.data</groupId>
	    <artifactId>spring-data-redis</artifactId>
	    <version>1.8.6.RELEASE</version>
	</dependency>
	<dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>2.7.0</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.7.0</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.7.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
	<dependency>
	    <groupId>redis.clients</groupId>
	    <artifactId>jedis</artifactId>
	    <version>2.9.0</version>
	</dependency>	
	<!-- 使用Redis缓存需要的依赖End -->

	 <!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
	<dependency>
	    <groupId>commons-dbcp</groupId>
	    <artifactId>commons-dbcp</artifactId>
	    <version>1.4</version>
	</dependency>

    <!-- https://mvnrepository.com/artifact/com.oracle/ojdbc14 -->
	<dependency>
	    <groupId>com.oracle</groupId>
	    <artifactId>ojdbc14</artifactId>
	    <version>10.2.0.1.0</version>
	</dependency>  
	
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
          <!-- springframework 4 dependencies begin -->
         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
       <!--  https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-aspects</artifactId>
		    <version>${spring.version}</version>
		</dependency>
		
		<!-- spring data jpa 数据库持久层 -->
         <dependency>
             <groupId>org.springframework.data</groupId>
             <artifactId>spring-data-jpa</artifactId>
             <version>1.10.4.RELEASE</version>
         </dependency>
        
        <!-- springframework 4 dependencies end -->
        
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
		<dependency>
		    <groupId>org.hibernate</groupId>
		    <artifactId>hibernate-validator</artifactId>
		    <version>4.3.1.Final</version>
		</dependency>
		
		<dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.3.11.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.3.11.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.hibernate.javax.persistence/hibernate-jpa-2.0-api -->
		<dependency>
		    <groupId>org.hibernate.javax.persistence</groupId>
		    <artifactId>hibernate-jpa-2.0-api</artifactId>
		    <version>1.0.1.Final</version>
		</dependency>
        

		<dependency>
            <groupId>javax.servlet.jsp.jstl</groupId>
            <artifactId>jstl-api</artifactId>
            <version>1.2</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.servlet.jsp</groupId>
                    <artifactId>jsp-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>	        
        <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
		<dependency>
		    <groupId>commons-fileupload</groupId>
		    <artifactId>commons-fileupload</artifactId>
		    <version>1.3.1</version>
		</dependency>
		        
  </dependencies>
  <build>
    <finalName>SpringInAction7</finalName>
  </build>
</project>

一、启用对缓存的支持

Spring对缓存的支持有两种方式:

  • 注解驱动的缓存
  • XML声明的缓存

1、缓存管理器:

Spring3.1内置了五个缓存管理器的实现:

  • SimpleCacheManager
  • NoOpCacheManager
  • ConcurrentMapCacheManager
  • CompositeCacheManager
  • EhcacheManager

Spring Data又提供了两个缓存管理器

  • RedisCacheManager(来自Spring Data Redis项目)
  • GemfireCacheManager(来自Spring Data Gemfire项目)

2、使用Ehcache缓存

①在Spring中注入EhCacheCacheManager:

XML方式注入:

	<!-- 配置Ehcache缓存 -->
	<!-- 启用注解缓存 -->
	<cache:annotation-driven/>
	<!-- 配置缓存管理器 -->
	<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
		<property name="cacheManager" ref="cacheManagerFactoryBean"></property>
	</bean>
	<bean id="cacheManagerFactoryBean" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
		<property name="configLocation" value="classpath:ehcache.xml"></property>
	</bean>

Java配置方法注入:@EnableCaching启用注解驱动的缓存。

package com.mfc.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import net.sf.ehcache.CacheManager;

@Configuration
@EnableCaching
public class CachingConfig {

	@Bean
	public EhCacheCacheManager cacheManager(CacheManager cm){
		return new EhCacheCacheManager(cm);
	}
	
	@Bean
	public EhCacheManagerFactoryBean ehcache(){
		EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();
		bean.setConfigLocation(new ClassPathResource("classpath:ehcache.xml"));
		return bean;
	}
}

②ehcache.xml文件:

扫描二维码关注公众号,回复: 4898862 查看本文章

主要看name=“tUserCache”的缓存配置,maxBytesLocalHeap=“50m”表示最大的堆储存是50M,timeToLiveSeconds="100000"表示缓存事件是100000秒。

关于Ehcache缓存的更多属性:http://www.ehcache.org/documentation/configuration

<ehcache>
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            />
    <cache name="tUserCache"
    		maxBytesLocalHeap="50m"
    		timeToLiveSeconds="100000">
    </cache>
</ehcache>

3、使用Redis缓存

注意开启Redis服务,Redis教程:https://blog.csdn.net/fancheng614/article/details/85929956

这里直接使用Java配置。

package com.mfc.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

@Configuration
@EnableCaching
public class CachingConfig {

	@Bean
	public CacheManager cacheManager(RedisTemplate redisTemplate){
		return new RedisCacheManager(redisTemplate);
	}
	
	@Bean
	public JedisConnectionFactory jedisConnectionFactory(){
		JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
		connectionFactory.afterPropertiesSet();
		return connectionFactory;
	}
	
	@Bean
	public RedisTemplate<String, String> redisTemplate(JedisConnectionFactory connectionFactory){
		RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
		redisTemplate.setConnectionFactory(connectionFactory);
		redisTemplate.afterPropertiesSet();
		return redisTemplate;
	}
}

4、使用多个缓存管理器

	@Bean
	public CacheManager cacheManager1(net.sf.ehcache.CacheManager cm, javax.cache.CacheManager jcm){
		CompositeCacheManager cacheManager = new CompositeCacheManager();
		List<CacheManager> cacheManagers = new ArrayList<CacheManager>();
		cacheManagers.add(new JCacheCacheManager(jcm));
		cacheManagers.add(new EhCacheCacheManager(cm));
		cacheManager.setCacheManagers(cacheManagers);
		return cacheManager;
	}

二、为方法添加注解以支持缓存

Spring的缓存抽象在很大程度上是围绕切面构建的。在Spring中启用缓存时,会创建一个切面,它触发一个或更多的Spring的缓存注解。并且所有的注解都能运用在方法或类上。当注解运用在方法上时,只对该方法起作用;注解运用在类上面时,缓存的行为会运用到这个类的所有方法上。

Spring提供4个注解来声明缓存规则
注解 描述
@Cacheable 表明Spring在调用方法之前,首先应该在缓存中查找方法的返回值。如果这个值能够找到,就会返回缓存的值。否则这个方法就会被调用,返回值会放到缓存之中。
@CachePut 表明Spring应该将方法的返回值放到缓存中。在方法调用前并不会检查缓存,方法始终都会被调用。
@CacheEvict 表明Spring应该在缓存中清除一个或多个条目
@Caching 这是一个分组的注解,能够同时应用多个其他的缓存注解

1、填充缓存

@Cacheable和@CachePut注解都可以填充缓存。它们有一些共有的属性:

@Cacheable和@CachePut共有的属性
属性 类型 描述
value String[] 要使用的缓存名称
condition String SpEL表达式,如果得到的值时false,不会将缓存应用到方法的调用上
key String SpEL表达式,用来计算自定义的缓存key
unless String SpEL表达式,如果得到的值时true,返回值不会放到缓存中

使用@Cacheable示例:

	@Cacheable("tUserCache")
	public Tuser findByPrimary(String userId) {
		return (Tuser) getSession().get(Tuser.class, userId);
	}

当然注解也可以放在接口上,这样的话,所有这个接口的实现方法都会使用这个缓存:

	@Cacheable("tUserCache")
	public Tuser findByPrimary(String userId);

①将值放到缓存之中

@Cacheable注解每次调用这个方法时都会先查询缓存,但是如果我现在要调用add()方法存储一个对象,存进去之后就需要立即将这个对象放进缓存中(否则在缓存有效时间内在查询所有的user,是查不到新添加的这个user的)。所以现在调用add()方法的需求就是:改方法调用之前不比检查缓存,该方法始终都会被调用,调用之后将改方法的返回值放进缓存中。这时候就要用@CachePut注解了:

	@CachePut("tUserCache")
	public Tuser add(Tuser tuser);

②自定义缓存key

在① 中有一个问题:默认的缓存key要基于方法的参数来确定,而add(Tuser tuser)的唯一参数是tuser对象,所以这个对象会用作缓存的key。这样就是将tuser对象放进缓存中,而它的缓存key就是同一个tuser对象。这样就感觉怪怪的了。

解决:@Cacheable和@CachePut都有一个名为key的属性,这个属性能替换默认的key,它是通过SpEL表达式计算得到的。

Spring提供了多个用来定义缓存规则的DpEL
表达式 描述
#root.args 传递给缓存方法的参数,形式为数组
#root.caches 该方法执行时所对应的缓存,形式为数组
#root.target 目标对象
#root.targetClass 目标对象的类,是#root.target.class的简写
#root.method 缓存方法
#root.methodName 缓存方法的名字,是#root.method.name的简写
#result 方法调用的返回值(不能用在@Cacheable注解上)
#Argument 任意的方法参数名(如#argName)或参数索引(如#a0或#p0)

示例:

	@CachePut(value = "tUserCache", key = "#result.userId")
	public Tuser add(Tuser tuser);

③条件化缓存

在有些场景下希望缓存关闭这时需要使用@Cacheable和@CachePut注解的unless和condition属性。

unless属性的值为true时只能阻止将对象放进缓存,但是在这个方法调用的时候,依然会去缓存中查找,如果找到了匹配的值,返回找到的值。

condition的表达式计算结果为false时,在这个方法调用的过程中,缓存是被禁用的。

示例一:对于message属性包含“NoCache”的Tuser对象,不对其进行缓存:

	@Cacheable(value = "tUserCache", unless = "#result.message.contains('NoCache')")
	public Tuser findByPrimary(String userId);

示例二:对userId等于10的Tuser关闭缓存

	@Cacheable(value = "tUserCache", unless = "#result.message.contains('NoCache')", condition = "#userId == '10'")
	public Tuser findByPrimary(String userId);

④移除缓存条目

如果带有@CacheEvict注解的方法被执行。就会有一个或更多的条目会在缓存中移除。

	@CacheEvict("tUserCache")
	public void remove();

注意:@Cacheable和@CachePut必须在非void返回值的方法上才能用,@CacheEvict可以在返回值为void的方法上使用。

@CacheEvict注解的属性,指定了哪些缓存条目应该被移除
属性 类型 描述
value String[] 要使用的缓存名称
key String SpEL表达式,用来计算自定义的缓存key
condition String SpEL表达式,如果得到的值时false,缓存不会应用到方法调用上
allEntries boolean 如果为true,特定缓存的所有条目都会被移除掉
beforeInvocation boolean 如果为true,在方法调用之前移除条目。为false(默认值),在方法成功调用之后移除条目

三、使用XML声明缓存

使用XML声明缓存的原因:

  • 觉得在自己的源码中添加Spring的注解不太舒服
  • 需要在没有源码的bean上应用缓存功能

Spring的cache命名空间提供了使用XML声明缓存规则的方法,要结合aop命名空间使用。

 
元素 描述  
<cache:annotation-driven/> 启用注解驱动缓存,等同于Java配置中的@EnableCaching  
<cache:advice> 定义缓存通知(advice)。结合<aop:advisor>,将通知应用到切点上  
<cache:caching> 在缓存通知中,定义一组特定的缓存规则  
<cache:cacheable/> 指明某个方法进行缓存,等同于@Cacheable注解  
<cache:cache-put/> 指明某个方法要填充缓存,但不会考虑缓存中是否已有匹配的值,等同于@CachePut注解  
<cache:cache-evict/> 指明某个方法要从缓存中移除一个或多个条目,等同于@CacheEvict注解  

1、使用XML声明缓存示例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:cache="http://www.springframework.org/schema/cache"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	
	<!-- 将缓存通知绑定到一个切点上 -->
	<aop:config>
		<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.mfc.dao.TuserDao.*(..))"/>
	</aop:config>
	
	<cache:advice id="cacheAdvice" cache-manager="cacheManager">
		<cache:caching>
			<!-- 配置为支持缓存 -->
			<cache:cacheable cache="tUserCache" method="find"/>
			<cache:cacheable cache="tUserCache" method="findByPrimary"/>
			<cache:cache-put cache="tUserCache" method="add" key="#result.userId"/>
			<cache:cache-evict cache="tUserCache" method="remove"/>
		</cache:caching>
	</cache:advice>
	
	<bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager"></bean>
</beans>

2、注意上面的<cache:cacheable/>、<cache:cache-put/>、<cache:cache-evict/>元素都引用了同一个名为tUserCache的缓存,为了消除这种重复,可以在<cache:caching>元素上指明缓存的名字。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:cache="http://www.springframework.org/schema/cache"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	
	<!-- 将缓存通知绑定到一个切点上 -->
	<aop:config>
		<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.mfc.dao.TuserDao.*(..))"/>
	</aop:config>
	
	<cache:advice id="cacheAdvice" cache-manager="cacheManager">
		<cache:caching cache="tUserCache">
			<!-- 配置为支持缓存 -->
			<cache:cacheable method="find"/>
			<cache:cacheable method="findByPrimary"/>
			<cache:cache-put method="add" key="#result.userId"/>
			<cache:cache-evict method="remove"/>
		</cache:caching>
	</cache:advice>
	
	<bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager"></bean>
</beans>

<cache:caching>元素还有几个可以供<cache:cacheable/>、<cache:cache-put/>、<cache:cache-evict/>共享的属性:

①cache:指明要存储和获取值的缓存

②condition:spEL表达式,如果计算得到的值为false,将会为这个方法禁用缓存

③key:spEL表达式,用来得到缓存的key(默认为方法的参数)

④method:要缓存的方法名

除此之外,<cache:cacheable/>、<cache:cache-put/>还有一个unless属性,这个属性是一个spEL表达式,如果计算的值时true,就会阻止将返回值放到缓存中。

<cache:cache-evict/>特性:

①all-entries:如果是true,缓存中所有的条目都会被移除掉。如果是false,只有匹配key的条目才会被移除掉。

②before-invocation:如果为true,缓存条目将会在方法调用之前被移除掉,如果为false,缓存条目在方法调用之后移除掉。

③all-entries和before-invocation的默认值都是false。

猜你喜欢

转载自blog.csdn.net/fancheng614/article/details/85938173