【Spring】一篇文章搞掂Spring

本文篇幅较长,建议合理利用右上角目录进行查看(如果没有目录请刷新)。

本文是对《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声明,这里暂不介绍。

猜你喜欢

转载自www.cnblogs.com/LiveYourLife/p/9249321.html