SpringCache的使用

在目前的项目中, 缓存的使用越来越多,适用的场景也越来越广.对于缓存,Spring提供的Spring Cache框架,能够非常方便地使用缓存.

1 SpringCache的简介

缓存,就是将数据从数据库等数据来源获取数据,将数据缓存在内存或其他设备如Redis中,为了二次查询能够快速高效的响应结果.

Spring Cache是3.1开始提供, 通过注解的形式,对于整合业务代码友好.

Spring Cache特点:

  • 提供Cache通用入口 ,方便多种实现切换缓存源,如Redis,Guava Cache等
  • 支持事务, 即食物回滚时,缓存同时自动回滚

Cache源码说明:

/**
 * Interface that defines common cache operations.
 *
 * <b>Note:</b> Due to the generic use of caching, it is recommended that
 * implementations allow storage of <tt>null</tt> values (for example to
 * cache methods that return {@code null}).
 *
 * @author Costin Leau
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 * @since 3.1
 */
// 定义公共缓存操作的接口
public interface Cache {
    
    

	/**
	 * Return the cache name. 
	 */
    // 缓存的名称
	String getName();

	/**
	 * Return the underlying native cache provider.
	 */
    // 得到底层的缓存 如Ehcache
	Object getNativeCache();

	// 根据可以得到ValueWrapper对象,再给句get方法获取值
	@Nullable
	ValueWrapper get(Object key);

	// 根据key,和value的类型获取value
	@Nullable
	<T> T get(Object key, @Nullable Class<T> type);

	// 添加缓存
	void put(Object key, @Nullable Object value);


    // 存在key则获取,不存在则添加
	@Nullable
	default ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
    
    
		ValueWrapper existingValue = get(key);
		if (existingValue == null) {
    
    
			put(key, value);
		}
		return existingValue;
	}

	// 根据key清除对应缓存
	void evict(Object key);

	// 如key存在,则清除对应缓存
	default boolean evictIfPresent(Object key) {
    
    
		evict(key);
		return false;
	}

	// 清空缓存
	void clear();

    // 缓存值的ValueWrapper对象
	@FunctionalInterface
	interface ValueWrapper {
    
    
        
		@Nullable
		Object get();
	}

}

Cache接口提供的默认实现类有等:

  • AbstractValueAdaptingCache 抽象类
  • ConcurrentMapCache 基于java.util.concurrent.ConcurrentHashMap的缓存
  • JCacheCache 对javax.cache.Cache的实例
  • CaffeineCache 对com.github.benmanes.caffeine.cache.Cache的实例
  • RedisCache 基于Redis的缓存实现类

Spring中提供的缓存管理的类

CacheManager接口:

/**
 * Spring's central cache manager SPI.
 *
 * <p>Allows for retrieving named {@link Cache} regions.
 *
 * @author Costin Leau
 * @author Sam Brannen
 * @since 3.1
 */
public interface CacheManager {
    
    

	/**
	 * Get the cache associated with the given name.
	 * <p>Note that the cache may be lazily created at runtime if the
	 * native provider supports it.
	 * @param name the cache identifier (must not be {@code null})
	 * @return the associated cache, or {@code null} if such a cache
	 * does not exist or could be not created
	 */
	@Nullable
    // 根据缓存名字获取缓存
	Cache getCache(String name);

	/**
	 * Get a collection of the cache names known by this manager.
	 * @return the names of all caches known by the cache manager
	 */
    // 得到所有缓存的名字
	Collection<String> getCacheNames();

}

CacheManager接口提供的默认实现类有等:

  • AbstractCacheManager 抽象类
  • ConcurrentMapCacheManager 对应 ConcurrentMapCacheFactoryBean
  • EhCacheCacheManager 对应 EhCacheManagerFactoryBean
  • JCacheCacheManager 对应 EhCacheManagerFactoryBean
  • RedisCacheManager 基于Redis的缓存管理
  • CompositeCacheManager 组合管理CacheManager的实现类

KeyGenerator接口
接口提供了一套生成缓存默认key的策略.

/**
 * Cache key generator. Used for creating a key based on the given method
 * (used as context) and its parameters.
 *
 * @author Costin Leau
 * @author Chris Beams
 * @author Phillip Webb
 * @since 3.1
 */
@FunctionalInterface
public interface KeyGenerator {
    
    

	/**
	 * Generate a key for the given method and its parameters.
	 * @param target the target instance
	 * @param method the method being called
	 * @param params the method parameters (with any var-args expanded)
	 * @return a generated key
	 */
	Object generate(Object target, Method method, Object... params);

}

KeyGenerator接口提供的默认实现类有等:

  • DefaultKeyGenerator Spring3.1版本默认key生成实现类, 已过期,从Spring4.0开始, 使用SimpleKeyGenerator来代替

  • SimpleKeyGenerator Spring4.0开始,默认的实现类

  • KeyGeneratorAdapter key生成相关参数适配

2 SpringCache的使用

1 注解说明

@Cacheable

使用场景: 查询方法接口

该注解会把方法的返回值缓存下来, 下一次调用方法时, 先查询缓存中是否存在,存在则直接返回,不存在,则查询数据返回,并将数据缓存.

@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
    
    

	/**
	 * Alias for {@link #cacheNames}.
	 */
	@AliasFor("cacheNames")
    // 缓存的名称,数据可以写入多个缓存
	String[] value() default {
    
    };

	/**
	 * Names of the caches in which method invocation results are stored.
	 * <p>Names may be used to determine the target cache (or caches), matching
	 * the qualifier value or bean name of a specific bean definition.
	 * @since 4.2
	 * @see #value
	 * @see CacheConfig#cacheNames
	 */
	@AliasFor("value")
    // 缓存的名称,数据可以写入多个缓存
	String[] cacheNames() default {
    
    };

	/**
	 * Spring Expression Language (SpEL) expression for computing the key dynamically.
	 */
    // 缓存key, 默认使用KeyGenerator生成
	String key() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator}
	 * to use.
	 * <p>Mutually exclusive with the {@link #key} attribute.
	 * @see CacheConfig#keyGenerator
	 */
    // key生成器
	String keyGenerator() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
	 */
    // 缓存管理
	String cacheManager() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver}
	 * to use.
	 * @see CacheConfig#cacheResolver
	 */
    // 缓存解析
	String cacheResolver() default "";

	/**
	 * Spring Expression Language (SpEL) expression used for making the method
	 * caching conditional.
	 * </ul>
	 */
    // 满足condition条件,才缓存数据 (在方法执行前后都判断)
	String condition() default "";

	/**
	 * Spring Expression Language (SpEL) expression used to veto method caching.
	 * <p>Unlike {@link #condition}, this expression is evaluated after the method
	 * has been called and can therefore refer to the {@code result}.
	 * @since 3.2
	 */
    // 否决缓存更新 (方法执行后判断)
	String unless() default "";

	/**
	 * Synchronize the invocation of the underlying method if several threads are
	 * attempting to load a value for the same key. The synchronization leads to
	 */
    // 如果多个线程试图为同一个键加载一个值,则同步底层方法的调用。
	boolean sync() default false;

}

@CachePut

使用场景: 新增或修改方法

该注解会把方法的返回值Put到缓存中, 供其他查询使用.

@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CachePut {
    
    

	/**
	 * Alias for {@link #cacheNames}.
	 */
	@AliasFor("cacheNames")
	String[] value() default {
    
    };

	/**
	 * Names of the caches to use for the cache put operation.
	 * <p>Names may be used to determine the target cache (or caches), matching
	 * the qualifier value or bean name of a specific bean definition.
	 * @since 4.2
	 * @see #value
	 * @see CacheConfig#cacheNames
	 */
	@AliasFor("value")
	String[] cacheNames() default {
    
    };

	/**
	 * Spring Expression Language (SpEL) expression for computing the key dynamically.
	 * <p>Default is {@code ""}, meaning all method parameters are considered as a key,
	 */
	String key() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator}
	 * to use.
	 * <p>Mutually exclusive with the {@link #key} attribute.
	 * @see CacheConfig#keyGenerator
	 */
	String keyGenerator() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
	 * create a default {@link org.springframework.cache.interceptor.CacheResolver} if none
	 * is set already.
	 * <p>Mutually exclusive with the {@link #cacheResolver} attribute.
	 * @see org.springframework.cache.interceptor.SimpleCacheResolver
	 * @see CacheConfig#cacheManager
	 */
	String cacheManager() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver}
	 * to use.
	 * @see CacheConfig#cacheResolver
	 */
	String cacheResolver() default "";

	/**
	 * Spring Expression Language (SpEL) expression used for making the cache
	 * put operation conditional.
	 */
	String condition() default "";

	/**
	 * Spring Expression Language (SpEL) expression used to veto the cache put operation.
	 */
	String unless() default "";

}

@CacheEvict

使用场景: 删除或修改方法

该注解会清空指定缓存.

@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {
    
    

	/**
	 * Alias for {@link #cacheNames}.
	 */
	@AliasFor("cacheNames")
	String[] value() default {
    
    };

	/**
	 * Names of the caches to use for the cache eviction operation.
	 * <p>Names may be used to determine the target cache (or caches), matching
	 * the qualifier value or bean name of a specific bean definition.
	 * @since 4.2
	 * @see #value
	 * @see CacheConfig#cacheNames
	 */
	@AliasFor("value")
	String[] cacheNames() default {
    
    };

	/**
	 * Spring Expression Language (SpEL) expression for computing the key dynamically.
	 */
	String key() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator}
	 * to use.
	 * <p>Mutually exclusive with the {@link #key} attribute.
	 * @see CacheConfig#keyGenerator
	 */
	String keyGenerator() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
	 */
	String cacheManager() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver}
	 * to use.
	 * @see CacheConfig#cacheResolver
	 */
	String cacheResolver() default "";

	/**
	 * Spring Expression Language (SpEL) expression used for making the cache
	 * eviction operation conditional.
	 */
	String condition() default "";

	/**
	 * Whether all the entries inside the cache(s) are removed.
	 * <p>By default, only the value under the associated key is removed.
	 * <p>Note that setting this parameter to {@code true} and specifying a
	 * {@link #key} is not allowed.
	 */
    // 是否移除所有数据 
	boolean allEntries() default false;

	/**
	 * Whether the eviction should occur before the method is invoked.
	 * <p>Setting this attribute to {@code true}, causes the eviction to
	 * occur irrespective of the method outcome (i.e., whether it threw an
	 * exception or not).
	 */
	boolean beforeInvocation() default false;

}

@Caching

使用场景: 同时操作多个缓存

该注解是一个组合缓存,可以同时添加几个不同的注解,供不同的应用场景使用.如一个接口,可能同时操作多个缓存,且缓存的处理状态都不一样.

@Target({
    
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
    
    

	Cacheable[] cacheable() default {
    
    };

	CachePut[] put() default {
    
    };

	CacheEvict[] evict() default {
    
    };

}

@CacheConfig

该注解用于配置该类中用的一些共用的缓存配置

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
    
    

	/**
	 * Names of the default caches to consider for caching operations defined
	 * in the annotated class.
	 */
	String[] cacheNames() default {
    
    };

	/**
	 * The bean name of the default {@link org.springframework.cache.interceptor.KeyGenerator} to
	 */
	String keyGenerator() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
	 */
	String cacheManager() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} to use.
	 */
	String cacheResolver() default "";

}

关于自定义key值的定义策略: 可以使用Spring的EL表达式来指定key

1 直接使用 “#参数名”或者“#p参数index”

   @Cacheable(value = "user", key = "#id")
   public Object select(String id) {
    
    
      return null;
   }

   @Cacheable(value="user", key="#p0")
   public Object select(String id) {
    
    
      return null;
   }

   @Cacheable(value="user", key="#user.id")
   public Object select(User user) {
    
    
      return null;
   }

   @Cacheable(value="user", key="#p0.id")
   public Object select(User user) {
    
    
      return null;
   }

2 使用root对象来生成key

属性名称 描述 示例
methodName 当前方法名 #root.methodName
method 当前方法 #root.method.name
target 当前被调用的对象 #root.target
targetClass 当前被调用的对象的class #root.targetClass
args 当前方法参数组成的数组 #root.args[0]
caches 当前被调用的方法使用的Cache #root.caches[0].name

2 案例

1 准备一个SpringBoot环境

2 添加maven依赖

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

3 添加一个Service类

@Slf4j
@Service
public class UserService {
    
    

    @Autowired
    private UserMapper userMapper;

    // 先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中
    @Cacheable(value = "user", key = "#id")
    public User select(String id) {
    
    
        log.info("=====select查询数据库啦=====");
        List<User> users = userMapper.selectAll();
        for (User user : users) {
    
    
            if (user.getId().equals(id)) {
    
    
                return user;
            }
        }
        return null;
    }

    // 移除对应的缓存
    @CacheEvict(value = "user", key = "#user.id")
    public User update(User user) {
    
    
        log.info("=====update查询数据库啦=====");
        userMapper.updateByPrimaryKey(user);
        return user;
    }

    // 查询数据库,将查询结果放到缓存
    @CachePut(value = "user", key = "#user.id")
    public User save(User user) {
    
    
        log.info("=====save查询数据库啦=====");
        userMapper.insert(user);
        return user;
    }

}

4 添加一个测试类

@RunWith(SpringRunner.class)
// 指定启动类
@SpringBootTest(classes = Application.class)
@Slf4j
public class DemoApplicationTests {
    
    
    @Autowired
    private UserService userService;


    @Test
    public void testCache() {
    
    
        String id = "8";
        User user = userService.save(new User(id, "李白"));
        log.info("[ save方法  - {} ]",user);
        User user1 = userService.select(id);
        log.info("[ select 方法  - {} ]",user1);
        userService.update(user);
        User user2 = userService.select(id);
        log.info("[ select 方法  - {} ]",user2);
        User user3 = userService.select(id);
        log.info("[ select 方法  - {} ]",user3);
    }
}    
/*  运行结果:

    =====save查询数据库啦=====
    [ save方法  - User(id=8, username=李白, queryTime=Fri Nov 12 15:41:33 CST 2021) ]
    [ select 方法  - User(id=8, username=李白, queryTime=Fri Nov 12 15:41:33 CST 2021) ]
    =====update查询数据库啦=====
    =====select查询数据库啦=====
    [ select 方法  - User(id=8, username=李白, queryTime=Fri Nov 12 15:41:34 CST 2021) ]
    [ select 方法  - User(id=8, username=李白, queryTime=Fri Nov 12 15:41:34 CST 2021) ]
*/

从上面运行结果可知, 当调用save方法时, 将id为8的用户数据给缓存到了user缓存中, 第一次查询select方法时, 因缓存中存在,所以直接从缓存中获取, 当执行了update方法, 缓存中数据被删除了, 第二次查询select方法时, 因为缓存中没有,所以select方法中查询数据库,并将数据缓存起来了. 第三次调用select方法时,因为缓存中又存在数据了,所以直接返回.

3 SpringCache整合Redis

1 添加Redis的maven依赖

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

2 添加Redis的配置信息

#缓存类型
spring.cache.type=redis
#毫秒为单位  1h = 1*60*60*1000ms
spring.cache.redis.time-to-live=3600000
#如果指定了前缀就用我们指定的前缀CACHE_,没有默认就用缓存的名字作为前缀
spring.cache.redis.key-prefix=CACHE_
#允许使用前缀
spring.cache.redis.use-key-prefix=true
#是否缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true

3 添加配置类

@EnableConfigurationProperties(CacheProperties.class)
@Configuration
//@EnableCaching // 开启缓存功能 启动类上添加, 此处可省略
public class MyCacheConfig {
    
    
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
    
    
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        //将配置文件中的所有配置都生效
        if (redisProperties.getTimeToLive() != null) {
    
    
            config = config.entryTtl((redisProperties.getTimeToLive()));
        }
        if (redisProperties.getKeyPrefix() != null) {
    
    
            config = config.prefixCacheNameWith((redisProperties.getKeyPrefix()));
        }
        if (!redisProperties.isCacheNullValues()) {
    
    
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
    
    
            config = config.disableKeyPrefix();
        }

        return config;
    }

}

4 使用上面测试类

@RunWith(SpringRunner.class)
// 指定启动类
@SpringBootTest(classes = Application.class)
@Slf4j
public class DemoApplicationTests {
    
    
    @Autowired
    private UserService userService;

    @Test
    public void testCache() {
    
    
        String id = "9";
        User user = userService.save(new User(id, "李白"));
        log.info("[ save方法  - {} ]",user);
        User user1 = userService.select(id);
        log.info("[ select 方法  - {} ]",user1);
        userService.update(user);
        User user2 = userService.select(id);
        log.info("[ select 方法  - {} ]",user2);
        User user3 = userService.select(id);
        log.info("[ select 方法  - {} ]",user3);
    }
}  

使用Redis连接工具查看Redis数据库:

/*
  CACHE_user::9
{
  "@class": "com.cf.demo.pojo.User",
  "id": "9",
  "username": "李白",
  "queryTime": [
    "java.util.Date",
    1636704213634
  ]
}
*/

参考资料:

https://www.jianshu.com/p/33c019de9115

https://www.cnblogs.com/fashflying/p/6908028.html

https://blog.csdn.net/yiyihuazi/article/details/109065327

猜你喜欢

转载自blog.csdn.net/ABestRookie/article/details/121297482