本文篇幅较长,建议合理利用右上角目录进行查看(如果没有目录请刷新)。
本文是对《SPRING实战第4版》的总结,大家也可以去仔细研读该书
十二、Spring中使用NoSQL数据库
12.1、使用MongoDB持久化文档数据
待补充
12.2、使用Noe4j操作图数据
待补充
12.3、使用Redis操作key-value数据
Redis是一种基于key-value存储的数据库,Spring Data没有把Repository生成功能应用到Redis中,而是使用面向模版的数据访问的方式来支持Redis的访问。
Spring Data Redis通过提供一个连接工厂来创建模版
12.3.1、连接到Redis
创建连接工厂:
为了连接和访问Redis,有很多Redis客户端,如Jedis、JRedis等。
Spring Data Redis为4种Redis客户端实现提供了连接工厂:
-
- JedisConnectionFactory
- JredisConnectionFactory
- LettuceConnectionFactory
- SrpConnectionFactory
选择哪个客户端实现取决于你,对于Spring Data Redis,这些连接工厂的适用性是一样的
我们使用一个bean来创建这个工厂
@Bean public RedisConnectionFactory redisCF() { JedisConnectionFactory cf = new JedisConnectionFactory(); cf.setHostName("redis-server"); cf.setPort(7379); cf.setPassword("123456"); return cf; }
对于不同的客户端实现,他的工厂的方法(如setHostName)也是相同的,所以他们在配置方面是相同的操作。
12.3.2、Redis模版RedisTemplate
获取RedisTemplate
其中一种访问Redis的方式是,从连接工厂中获取连接RedisConnection,使用字节码存取数据(不推荐):
RedisConnectionFactory cf = ...; RedisConnection conn = cf.getConnection(); conn.set("greeting".getBytes(),"Hello World".getBytes()); byte[] greetingBytes = conn.get("greeting".getBytes()); String greeting = new String(greetingBytes);
Spring Date Redis提供了基于模版的较高等级的数据访问方案,其中提供了2个模版:
-
- RedisTemplate:直接持久化各种类型的key和value,而不是只局限于字节数组
- StringRedisTemplate:扩展了RedisTemplate,只关注String类型
RedisConnectionFactory cf = ..; RedisTemplate<String, Product> redis = new RedisTemplate<String, Product>(); redis.setConnectionFactory(cf); RedisConnectionFactory cf = ..; StringRedisTemplate redis = new StringRedisTemplate(cf); //设置为bean @Bean public RedisTemplate<String, Product> redisTemplate(RedisConnectionFactory cf) { RedisTemplate<String, Product> redis = new RedisTemplate<String, Product>(); redis.setConnectionFactory(cf); return redis; } @Bean public StringRedisTemplate redisTemplate(RedisConnectionFactory cf) { return new StringRedisTemplate(cf); }
使用RedisTemplate存取数据
//redis是一个RedisTemplate<String,Product>类型的bean //【使用简单的值】 //通过product的sku进行存储和读取product对象 redis.opsForValue().set(product.getSku(),product); Product product = redis.opsForValue().get("123456"); //【使用List类型的值】 //在List类型条目尾部/头部添加一个值,如果没有cart这个key的列表,则会创建一个 redis.opsForList().rightPush("cart",product); redis.opsForList().leftPush("cart",product); //弹出一个元素,会移除该元素 Product product = redis.opsForList().rightPop("cart"); Product product = redis.opsForList().leftPop("cart"); //只是想获取值 //获取索引2到12的元素;如果超出范围,则只返回范围内的;如果范围内没有,则返回空值 List<Product> products = redis.opsForList().range("cart",2,12); //在Set上执行操作 //添加一个元素 redis.opsForSet().add("cart", product); //求差异、交集、并集 Set<Product> diff = redis.opsForSet().difference("cart1", "cart2"); Set<Product> union = redis.opsForSet().union("cart1", "cart2"); Set<Product> isect = redis.opsForSet().intersect("cart1", "cart2"); //移除元素 redis.opsForSet().remove(product); //随机元素 Product random = redis.opsForSet().randomMember("cart"); //绑定到某个key上,相当于创建一个变量,引用这个变量时都是针对这个key来进行的 BoundListOperations<String, Product> cart = redis.boundListOps("cart"); Product popped = cart.rightPop(); cart.rightPush(product); cart.rightPush(product2); cart.rightPush(product3);
12.3.3、使用key和value的序列化器
当某个条目保存到Redis key-value存储的时候,key和value都会使用Redis的序列化器进行序列化。
Spring Data Redis提供了多个这样的序列化器,包括:
- GenericToStringSerializer:使用Spring转换服务进行序列化
- JacksonJsonRedisSerializer:使用Jackson1,讲对象序列化为JSON
- Jackson2JsonRedisSerializer:使用Jackson2,讲对象序列化为JSON
- JdkSerializationRedisSerializer:使用Java序列化
- OxmSerializer:使用Spring O/X映射的编排器和解排器实现序列化,用于XML薛丽华
- StringRedisSerializer:序列化String类型的key和value
这些序列化器都实现了RedisSerializer接口,如果其中没有符合需求的序列化器,可以自行创建。
RedisTemplate默认使用JdkSerializationRedisSerializer,那么key和value都会通过Java进行序列化。
StringRedisTemplate默认使用StringRedisSerializer,实际就是实现String和byte数组之间的转化。
例子:
使用RedisTemplate,key是String类型,我们希望使用StringRedisSerializer进行序列化;value是Product类型,我们希望使用JacksonJsonRedisSerializer序列化为JSON
@Bean public void RedisTemplate<String,Product> redisTemplate(RedisConnectionFactory cf) { RedisTemplate<String, Product> redis = new RedisTemplate<String, Product>(); redis.setConnectionFactory(cf); redis.setKeySerializer(new StringRedisSerializer()); redis.setValueSerializer(new Jackson2JsonRedisSerializer<Product>(Product.class)); return redis; }
十三、使用Spring缓存技术
什么叫缓存:
一些变动不频繁或者不变动的数据,当每次获取的时候,都需要从数据库中提取或者计算,每次都需要消耗资源。
我们可以把这些计算后的结果,在某个地方存放起来,当下次访问的时候直接返回,就可以避免了多次的资源消耗,这就叫缓存技术。
13.1、启用缓存支持
13.1.1、2种方式启用Spring对注解驱动缓存的支持:
- 在一个配置类上使用@EnableCaching注解
- 使用XML进行配置
@EnableCaching注解方式:
@Configuration @EnableCaching public class CachingConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager(); } }
XML方式:
使用Spring cache命名空间中的<cache:annotation-driven>元素启动注解驱动的缓存
<?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" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <!-- 启用缓存--> <cache:annotation-driven /> <!-- 声明缓存管理器--> <bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager" /> <beans>
代码解析:
2种方式本质上是一样的,都创建一个切面并触发Spring缓存注解的切点
根据所使用的注解和缓存状态,切面会从缓存中获取数据,并将数据添加到缓存中或者从缓存删除某个值。
其中不仅启用了注解驱动的缓存,还声明了一个缓存管理器(cache manager)的bean。
缓存管理器是SPring缓存抽象的狠心,可以和不同的缓存实现进行集成。
2个例子都是用ConcurrentHashMap作为缓存存储,是基于内存的,用在开发或者测试可以,但是在大型企业级应用程序就有其他更好的选择了。
13.1.2、 配置缓存管理器
Spring3.1内置五个缓存管理器实现:
- SimpleCacheManager
- NoOpCacheManager
- ConcurrentMapCacheManager
- CompositeCacheManager
- EhCacheCacheManager
Spring3.2引入了另外一个缓存管理器实现
- 这个管理器可以用于基于JCache(JSR-107)的缓存提供商之中
除了核心Spring框架,Spring Data提供了2个缓存管理器实现
- RedisCacheManager(来自于Spring Data Redis项目)
- GemfireCacheManager(来自于Spring Data GemFire项目)
我们选择一个缓存管理器,然后在Spring应用上下文中,以bean的形式进行设置。使用不同的缓存管理器会影响数据如何存储,但是不会影响Spring如何声明缓存。
例子——配置EhCache缓存管理器:
此例子暂时省略。。。
例子——配置Redis缓存管理器:
使用RedisCacheManager,它会与一个Redis服务器协作,并通过RedisTemplate存取条目
RedisCacheManager要求:
- 一个RedisConnectFactory实现类的bean
- 一个RedisTemplate的bean
@Configuration @EnableCaching public class CachingConfig { //Redis缓存管理器bean @Bean public CacheManager cacheManager(RedisTemplate redisTemplate) { return new RedisCacheManager(redisTemplate); } //Redis连接工厂bean @Bean public JedisConnectionFactory redisConnectionFactory() { JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(); jedisConnectionFactory.afterPropertiesSet(); return jedisConnectionFactory; } //RedisTemplate bean @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisCF) { RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>(); redisTemplate.setConnectionFactory(redisCF); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
例子——使用多个缓存管理器:
使用Spring的CompositeCacheManager
以下例子创建一个CompositeCacheManager的bean,里面配置了多个缓存管理器,会按顺序迭代查找这些缓存管理器里面是否存在值
@Bean public CacheManager cacheManager(net.sf.ehcache.CacheManager cm,javax.cache.CacheManager jcm){ CompositeCacheManager cacheManager = new CompositeCacheManager(); List<CacheManager> managers = new ArrayList<CacheManager>(); managers.add(new JCacheCacheManager(jcm)); managers.add(new EhcacheCacheManager(cm)); manager.add(new RedisCacheManager(reisTemplate())); cacheManager.setCacheManagers(managers); return cacheManager; }
13.2、为方法添加注解以支持缓存
设置好了连接工厂和缓存管理器,我们就可以使用注解来设置缓存了。
Spring的缓存抽象很大程度是基于切面构建的,启用了缓存,就会创建一个切面,触发Spring的缓存注解。
这些注解可以用在方法或类上,用在类上,则类的所有方法都会应用该缓存行为。
13.2.1、填充缓存
@Cacheable和@CachePut
最简单的情况下只需要使用value属性即可
@Cacheable("spittleCache") public Spittle findOne(long id){ return new Spittle ();//可以是具体的逻辑 }
缓存流程:
- 调用findOne方法
- 缓存切面拦截
- 缓存切面在缓存中名为spittleCache的缓存数据中,key为id的值
- 如果有值,则直接返回缓存值,不再调用方法。
- 如果无值,则调用方法,并将结果放到缓存中。
缓存注解也可以写在接口上,那么它的所有实现类都会应用这个缓存规则。
将值放到缓存中:
一个使用情景:我们保存一个数据,然后前台刷新或者其他用户获取,我们可以保存时直接更新缓存
@CachePut("spittleCache")
Spittle save(Spittle spittle);
自定义缓存key:
上面例子中,默认把Spittle参数作为缓存的key,这样并不合适,我们希望用Spittle的ID作为key值
但是Spittle未保存情况下,又未有ID,这样我们可以通过SpEL表达式解决这个问题
@CachePut(value="spittleCache",key="#result.id")
Spittle save(Spittle spittle);
条件化缓存:
使用unless和condition属性,接受一个SpEL表达式
unless:如果为false,调用方法时依然会在缓存中查找,只是阻止结果放进缓存;
condition:如果是false,则禁用整个缓存,包括方法调用前的缓存查找和方法调用后把结果放进缓存都被禁用
@Cacheable(value="spittleCache"
unless="#result.message.contains('NoCache')") Spittle findOne(long id);
@Cacheable(value="spittleCache"
unless="#result.message.contains('NoCache')"
condition="#id >= 10") Spittle findOne(long id);
13.2.2、移除缓存条目
@CacheEvict注解
调用方法时,会删除该缓存记录,
@CacheEvict("spittleCache") void remove(long spittleId);
13.3、使用XML声明缓存
有时候无法是注解,或者不想使用注解,可以使用XML声明,这里暂不介绍。