美文网首页
Spring实战(十二)-使用NoSQL数据库

Spring实战(十二)-使用NoSQL数据库

作者: 阳光的技术小栈 | 来源:发表于2018-07-18 10:17 被阅读50次

    本文基于《Spring实战(第4版)》所写。

    使用MongoDB持久化文档数据

    将数据收集到一个非规范化(也就是文档)的结构中,保持了独立的实体,能够按照这种方式优化并处理文档的数据库,我们称之为文档数据库。

    有些数据具有明显的关联关系,文档型数据库并没有针对存储这样的数据进行优化。

    Spittr应用的域对象并不适合文档数据库。在本章中,我们将会在一个购物订单系统中学习MongoDB。

    MongoDB是最为流行的开源文档数据库之一。Spring Data MongoDB提供了三种方式在Spring应用中使用MongoDB:

    • 通过注解实现对象-文档映射;
    • 使用MongoTemplate实现基于模板的数据库访问;
    • 自动化的运行时Repository生成功能。

    启用MongoDB

    在使用MongoDB之前,我们首先要配置Spring Data MongoBD ,在Spring配置中添加几个必要的bean。

    • 配置MongoClient,以便于访问MongoDB数据库;
    • 配置MongoTemplate bean,实现基于模板的数据库访问;
    • 启用Spring Data MongoDB的自动化Repository生成功能(不是必须,但强烈推荐)。

    如下的程序清单展现了如何编写简单的Spring Data MongoDB配置类,它包含了上述的几个bean:

    package orders.config;
    
    
    import com.mongodb.Mongo;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.mongodb.core.MongoClientFactoryBean;
    import org.springframework.data.mongodb.core.MongoOperations;
    import org.springframework.data.mongodb.core.MongoTemplate;
    import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
    
    @Configuration
    @EnableMongoRepositories(basePackages = "orders.db")  // 启动MongoDB的Repository功能
    public class MongoConfig {
        @Bean
        public MongoClientFactoryBean mongo(){    // MongoClient Bean (MongoFactoryBean已废弃)
            MongoClientFactoryBean mongo = new MongoClientFactoryBean();
            mongo.setHost("localhost");
            return mongo;
        }
        @Bean
        public MongoOperations mongoTemplate(Mongo mongo){  // MongoTemplate bean
            return new MongoTemplate(mongo, "OrdersDB");
        }
    }
    

    @EnableMongoRepositories启动了MongoDB的自动化Repository生成功能。

    以上程序中还包含了两个带有@Bean注解的方法。第一个@Bean方法使用MongoClientFactoryBean声明了一个Mongo实例。这个bean将Spring Data MongoDB与数据库本身连接了起来。尽管我们可以使用MongoClient直接创建Mongo实例,但如果这样做的话,就必须要处理MongoClient构造器所抛出的UnknownHostException异常。在这里,使用Spring Data MongoDB的MongoClientFactoryBean更加简单。因为它是一个工厂bean,因此MongoClientFactoryBean会负责构建Mongo实例,我们不必再担心UnknownHostException异常。

    另外一个@Bean方法声明了MongoTemplate bean,在它构造时,使用了其他@Bean方法所创建的Mongo实例的引用以及数据库的名称。Repository的自动化生成功能在底层使用了它。

    除了直接声明这些bean,我们还可以让配置类扩展AbstractMongoConfiguration并重载getDatabaseName()和mongo()方法。如下的程序展现了如何使用这种配置方式。

    package orders.config;
    
    import com.mongodb.Mongo;
    import com.mongodb.MongoClient;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
    import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
    
    @Configuration
    @EnableMongoRepositories("orders.db")
    public class MongoConfig2  extends AbstractMongoConfiguration{
        protected String getDatabaseName() {  // 指定数据库名称
            return "OrdersDB";
        }
        public Mongo mongo() throws Exception {
            return new MongoClient();             // 创建Mongo客户端
        }
    }
    

    这个新的配置类与上一个的配置类功能是相同的。最为显著的区别在于这个配置中没有直接声明MongoTemplate bean,当然它还是会被隐式低创建。我们在这里重载了getDatabaseName() 方法来提供数据库的名称。mongo()方法依然会创建一个MongoClient的实例,因为它会抛出Exception,所以我们可以直接使用MongoClient,而不必再使用MongoClientFactoryBean了。

    如果MongoDB服务器运行在其他的机器上,那么可以在创建MongoClient的时候进行指定:

    public Mongo mongo() throws Exception {
        return new MongoClient("mongodbserver");
    }
    

    也可指定端口(有时并不是默认的27017)

    public Mongo mongo() throws Exception {
        return new MongoClient("mongodbserver", 37017);
    }
    

    还可启用认证功能:为了访问数据库,需要提供应用的凭证

    package orders.config;
    
    import com.mongodb.Mongo;
    import com.mongodb.MongoClient;
    import com.mongodb.MongoCredential;
    import com.mongodb.ServerAddress;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.env.Environment;
    import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
    import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
    
    import java.util.Arrays;
    
    @Configuration
    @EnableMongoRepositories(basePackages = "orders.db")
    public class MongoConfig3 extends AbstractMongoConfiguration{
        @Autowired
        private Environment env;
    
        protected String getDatabaseName() {
            return "OrdersDB";
        }
    
        public Mongo mongo() throws Exception {
    
            MongoCredential credential = MongoCredential.createMongoCRCredential(  // 创建 MongoDB 凭证
                    env.getProperty("mongo.username"),
                    "OrdersDB",
                    env.getProperty("mongo.password").toCharArray());
    
            return new MongoClient(  //  创建 MongoClient
                    new ServerAddress("localhost" , 37017),
                    Arrays.asList(credential));
        }
    }
    

    为了访问需要认证的MongoDB服务器,MongoClient在实例化的时候必须要有一个MongoCredential的列表。

    除了Java配置的方案,还可以使用XML进行配置Spring Data MongoDB。

    <?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:mongo="http://www.springframework.org/schema/data/mongo"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <mongo:repositories base-package="orders.db" />
        <mongo:mongo />
        <bean id="mongoTemplate"
              class="org.springframework.data.mongodb.core.MongoTemplate">
            <constructor-arg ref="mongo" />
            <constructor-arg value="OrdersDB" />
        </bean>
    </beans>
    

    为模型添加注解,实现MongoDB持久化

    MongoDB没有提供对象-文档映射的注解。Spring Data MongoDB填补了这一空白,提供了一些将Java类型映射为MongoDB文档的注解。下表描述了这些注解

    注解 描述
    @Document 标示映射到MongoDB文档上的领域对象
    @Id 标示某个域为ID域
    @DbRef 标示某个域要引用其他的文档,这个文档有可能位于另一个数据库中
    @Field 为文档域指定自定义的元数据
    @Version 标示某个属性用作版本域

    @Document和@Id注解类似于JPA的@Entity和@Id注解。对于要以文档形式保存到MongoDB数据库的每个Java类型都会使用这两个注解。例如,如下的程序展现了如何为Order类添加注解,它会被持久化到MongoDB中。

    package orders;
    
    import org.springframework.data.annotation.Id;
    import org.springframework.data.mongodb.core.mapping.Document;
    import org.springframework.data.mongodb.core.mapping.Field;
    
    import java.util.Collection;
    import java.util.LinkedHashSet;
    
    @Document
    public class Order {
    
        @Id
        private String id;   // 指定ID
    
        @Field("client")
        private String customer;      // 覆盖默认的域名
    
        private String type;
    
        private Collection<Item> items = new LinkedHashSet<Item>();
    
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getCustomer() {
            return customer;
        }
    
        public void setCustomer(String customer) {
            this.customer = customer;
        }
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        public Collection<Item> getItems() {
            return items;
        }
    
        public void setItems(Collection<Item> items) {
            this.items = items;
        }
    }
    

    Order类添加了@Document注解,这样它就能够借助MongoTemplate或自动生成的Repository进行持久化。其id属性上使用了@Id注解,用来指定它作为文档的ID。除此之外, customer属性上使用了@Field注解,这样的话,当文档持久化的时候customer属性将会映射为名为client的域。

    注意,其他的属性并没有添加注解。除非将属性设置为瞬时态(transient)的,否则Java对象中所有的域都会持久化为文档中的域。并且如果我们不使用@Field注解进行设置的话,那么文档域中的名字将会与对应的Java属性相同。

    同时,需要注意的是items属性,它指的是订单中具体条目的集合。在传统的关系型数据库中,这些条目将会保存在另外的一个数据库表中,通过外键进行应用,items域上很可能还会使用JPA的@OneToMany注解。但在这里,情形完全不同。

    文档展现了关联但非规范化的数据。相关的概念(如订单中的条目)被嵌入到顶层的文档数据中。

    文档可以与其他的文档产生关联,但这并不是文档数据库擅长的功能。在本例购买订单与行条目之间的关联关系中,行条目只是同一个订单文档里面内嵌的一部分。因此,没有必要为这种关联关系添加任何注解。实际上,Item类本身并没有任何注解:

    package orders;
    
    public class Item {
    
        private Long id;
        private Order order;
        private String product;
        private double price;
        private int quantity;
    
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public Order getOrder() {
            return order;
        }
    
        public void setOrder(Order order) {
            this.order = order;
        }
    
        public String getProduct() {
            return product;
        }
    
        public void setProduct(String product) {
            this.product = product;
        }
    
        public double getPrice() {
            return price;
        }
    
        public void setPrice(double price) {
            this.price = price;
        }
    
        public int getQuantity() {
            return quantity;
        }
    
        public void setQuantity(int quantity) {
            this.quantity = quantity;
        }
    }
    

    没有必要为Item添加@Document注解,也没有必要为它的域指定@Id。这是因为我们不会单独将Item持久化为文档。它始终会是Order文档中Item列表的一个成员,并且会作为文档中的嵌入元素。

    使用MongoTemplate访问MongoDB

    配置了MongoTemplate bean后,就可以将其注入到使用它的地方:

    @Autowired
    MongoOperations mongo;
    

    注意,MongoOperations是MongoTemplate所实现的接口。MongoOperations暴露了多个使用MongoDB文档数据库的方法,比如计算文档集合中有多少条文档。使用注入MongoOperations,我们可以得到Order集合并调用count()来得到数量:

    long orderCount = mongo.getCollection("order").count();
    

    现在,假设要保存一个新的Order。为了完成这个任务,我们可以调用save()方法:

    Order order = new Order();
    ... // set properties and add line items
    mongo.save(order, "order");
    

    save()方法的第一个参数是新创建的Order,第二个参数是要保存文档存储的名称。

    另外,我们还可以调用findById()方法来根据ID查找订单:

    String orderId = ...;
    Order order = mongo.findById(orderId, Order.class);
    

    对于更高级的查询,我们需要构造Query对象并将其传递给find()方法。例如,要查找所有client域等于“Chuck Wagon”的订单,可以使用如下的代码:

    List<Order> chucksOrders = mongo.find(Query.query(
            Criteria.where("client").is("Chuck Wagon")), Order.class);
    

    再比如,我们想查询Chuck所有通过Web创建的订单:

    List<Order> chucksWebOrders = mongo.find(Query.query(
            Criteria.where("customer").is("Chuck Wagon")
            .and("type").is("WEB")), Order.class);
    

    如果想移除某一个文档的话,那么就应该使用remove() 方法:

    mongo.remove(order);
    

    编写MongoDB Repository

    如果不愿意编写Repository的(通常,我们将MongoOperations注入到自己设计的Repository类中,并使用它),那么Spring Data MongoDB能够自动在运行时生成Repository实现。

    我们已经通过@EnableMongoRepositories注解启用了Spring Data MongoDB的Repository功能,接下来需要做的就是创建一个接口,Repository实现要基于这个接口来生成,这个接口要扩展MongoRepository。如下的程序中OrderRepository扩展了MongoRepository,为Order文档提供了基本的CRUD操作。

    package orders.db;
    
    import orders.Order;
    import org.springframework.data.mongodb.repository.MongoRepository;
    
    public interface OrderRepository extends MongoRepository<Order, String> {}
    

    因为OrderRepository扩展了MongoRepository,因此它就会传递性的扩展Repository标记接口。任何扩展Repository的接口将会在运行时自动生成实现。

    MongoRepository接口有两个参数,第一个是带有@Document注解的对象类型,也就是该Repository要处理的类型。第二个参数是带有@Id注解的属性类型。

    尽管OrderRepository本身并没有定义任何方法,但是它会继承多个方法,包括对Order文档进行CRUD操作的方法。下表描述了OrderRepository继承的所有方法。

    方法 描述
    long count(); 返回指定Repository类型的文档数量
    void delete(Iterable<? extends T>); 删除与指定对象关联的所有文档
    void delete(T); 删除与指定对象关联的文档
    void delete(ID); 根据ID删除某一个文档
    void deleteAll(); 删除指定Repository类型的所有文档
    boolean exists(Object); 如果存在与指定对象相关联的文档,则返回true
    boolean exists(ID); 如果存在指定ID的文档,则返回true
    List<T> findAll(); 返回指定Repository类型的所有文档
    List<T> findAll(Iterable<ID>); 返回指定文档ID对应的所有文档
    Page<?> findAll(Pageable pageable); 为指定的Repository类型,返回分页且排序的文档列表
    List<T> findAll(Sort); 为指定的Repository类型,返回排序后的文档列表
    T findOne(ID); 为指定的ID返回单个文档
    save(Iterable<s>); 保存指定Iterable中所有文档
    save(<s>); 为给定的对象保存一条文档

    OrderRepository扩展了MongoRepository<Order, String>,那么T就映射为Order,ID映射为String,而s映射为所有扩展Order的类型。

    添加自定义的查询方法

    与Spring Data JPA支持方法命名约定类似,都能够帮助Spring Data为遵循约定的方法自动生成实现。这意味这我们可以为OrderRepository添加自定义的方法:

    public interface OrderRepository extends MongoRepository<Order, String> {
        List<Order> findByCustomer(String c);
        List<Order> findByCustomerLike(String c);
        List<Order> findByCustomerAndType(String c, String t);
        List<Order> findByCustomerLikeAndType(String c, String t);
    }
    

    其中,find这个查询动词并不是固定的,如果喜欢的话,我们还可以使用get或read作为查询动词;

    除此之外,还有一个特殊的动词用来为匹配的对象计数:

    int countByCustomer(String c);
    

    与Spring Data JPA类似,在查询动词与By之前,我们有很大的灵活性。比如,我们可以标示Order或者一些其他的词语,都不会影响获取的内容。

    如果只想要一个Order对象的话,我们可以只需要简单的返回Order:

    Order findASingleOrderByCustomer(String c);
    

    这里,所返回的就是原本List中的第一个Order对象。如果没有匹配元素的话,方法将会返回null。

    指定查询

    @Query能够想在JPA中那样在MongoDB上为Repository方法指定自定义的查询。唯一的区别在于针对MongoDB时,@Query会接受一个JSON查询,而不是JPA查询。

    例如,假设我们想要查询给定类型的订单,并要求customer的名称为“Chuck Wagon”。OrderRepository中如下的方法声明能够完成所需的任务:

    @Query("{'customer' : 'Chuck Wagon', 'type' : ?0 }")
    List<Order> findChucksOrders(String t);
    

    @Query中给定的JSON将会与所有的Order文档进行匹配,并返回匹配的文档。需要注意的是,type属性映射成了“?0”,这表明type属性应该与查询方法的第零个参数相等。如果有多个参数的话,他们可以通过“?1”、“?2”等方式进行引用。

    混合自定义的功能

    对于JPA来说,混合自定义的功能涉及到创建一个中间接口来声明自定义的方法,为这些自定义方法创建实现类并修改自动化的Repository接口,使其扩展中间接口。对于Spring Data MongoDB来说,这些步骤都是相同的。

    假设我们想要查询文档中type属性匹配给定值的Order对象。而且,如果给定的类型是“NET”,那我们就查找type值为“WEB”的Order对象。

    首先,定义中间接口:

    package orders.db;
    import orders.Order;
    import java.util.List;
    
    public interface OrderOperations {
        List<Order> findOrdersByType(String t);
    }
    

    接下来,我们要编写混合实现

    package orders.db;
    
    import orders.Order;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.mongodb.core.MongoOperations;
    import org.springframework.data.mongodb.core.query.Criteria;
    import org.springframework.data.mongodb.core.query.Query;
    
    import java.util.List;
    
    public class OrderRepositoryImpl implements OrderOperations {
        
        @Autowired
        private MongoOperations mongo; // 注入MongoOperations
        
        public List<Order> findOrdersByType(String t) {
            String type = t.equals("NET") ? "WEB" : t;
            Criteria where = Criteria.where("type").is(type);  // 创建查询
            Query query = Query.query(where);
            return mongo.find(query,Order.class);  // 执行查询
        }
    }
    

    剩下的工作就是修改OrderRepository,让其扩展中间接口OrderOperations:

    public interface OrderRepository extends MongoRepository<Order, String>,OrderOperations {
         ...
    }
    

    将这些关联起来的关键点在于实现类的名称为OrderRepositoryImpl。这个名字前半部分与OrderRepository相同,只是添加了“Impl”后缀。当Spring Data MongoDB生成Repository实现时,它会查找这个类并将其混合到自动生成的实现中。

    如果不喜欢“Impl”后缀的话,可以配置。

    @Configuration
    @EnableMongoRepositories(value = "orders.db", repositoryImplementationPostfix = "Stuff")
    public class MongoConfig extends AbstractMongoConfiguration{
        ...
    }
    

    如果使用XML配置的话,我们可以设置<mongo:repositories>的repository-impl-postfix属性:

    <mongo:repositories base-package="orders.db"
                      repository-impl-postfix="Stuff" />
    

    不管采用哪种方式,都让Spring Data MongoDB 查找名为OrderRepositoryStuff的类,而不再查找OrderRepositoryImpl。

    使用Redis操作key-value数据

    Redis是一种key-value存储的数据库,也可以说是持久化的哈希Map。

    Spring Data的关键特性,也就是面向模板的数据访问,能够在使用Redis的时候,为我们提供帮助。

    Spring Data Redis包含了多个模板实现,用来完成Redis数据库的数据存取功能。为了创建Spring Data Redis的模板,我们首先需要有一个Redis连接工厂。Spring Data Redis提供了四个连接工厂供我们选择。

    连接到Redis

    Redis连接工厂会生成到Redis数据库服务器的连接。Spring Data Redis为四种Redis客户端实现提供了连接工厂:

    • JedisConnectionFactory
    • JredisConnectionFactory
    • LettuceConnectionFactory
    • SrpConnectionFactory

    从Spring Data Redis的角度来看,这些连接工厂在适用性上都是相同的。

    例如,如下展示了如何配置JedisConnectionFactory bean:

    @Bean
    public RedisConnectionFactory redisCF() {
        return new JedisConnectionFactory();
    }
    

    通过默认构造器创建的连接工厂会向localhost上的6379端口创建连接,并且没有密码。如果你的Redis服务器运行在其他的主机或端口上,在创建连接工厂的时候,可以设置这些属性:

    @Bean
    public RedisConnectionFactory redisCF(){
        JedisConnectionFactory cf = new JedisConnectionFactory();
        cf.setHostName("redis-server");
        cf.setPort(7379);
        return cf;
    }
    

    类似地,如果你的Redis服务器配置为需要客户端认证的话,那么可以通过调用setPassword()方法来设置密码:

    @Bean
    public RedisConnectionFactory redisCF(){
        JedisConnectionFactory cf = new JedisConnectionFactory();
        cf.setHostName("redis-server");
        cf.setPort(7379);
        cf.setPassword("foobared");
        return cf;
    }
    

    如果使用Spring Data Redis 2.0以上的话,setHostName等方法会被废弃,可以使用以下方式配置

    @Bean
    public JedisConnectionFactory jedisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("localhost", 6379);
        redisStandaloneConfiguration.setPassword(RedisPassword.of("123456"));
        redisStandaloneConfiguration.setDatabase(6);
        return new JedisConnectionFactory(redisStandaloneConfiguration);
    }
    

    假设使用LettuceConnectionFactory的话,可以按照如下的方式进行配置:

    @Bean
    public RedisConnectionFactory redisCF(){
        LettuceConnectionFactory cf = new LettuceConnectionFactory();
        cf.setHostName("redis-server");
        cf.setPort(7379);
        cf.setPassword("foobared");
        return cf;
    }
    

    所有的Redis连接工厂都具有setHostName()、setPort()和setPassword()方法。这样,它们在配置方面实际上是相同的。

    接下来,就可以使用Spring Data Redis模板了。

    顾名思义,Redis连接工厂会生成到Redis key-value存储的连接(以RedisConnection的形式)。借助RedisConnection,可以存储和读取数据。例如,我们可以获取连接并使用它来保存一个问候信息,如下所示:

    RedisConnectionFactory cf = ...;
    RedisConnection conn = cf.getConnection();
    conn.set("greeting".getBytes, "Hello World".getBytes);
    

    与之类似,我们还可以使用RedisConnection来获取之前存储的问候信息:

    byte[] greetingBytes = conn.get("greeting".getBytes());
    String greeting = new String(greetingBytes);
    

    除了这种方式,Spring Data Redis以模板的形式提供了较高等级的数据访问方案。实际上,Spring Data Redis提供了两个模板:

    • RedisTemplate
    • StringRedisTemplate

    RedisTemplate可以极大地简化Redis数据访问,能够让我们持久化各种类型的key和value,并不局限于字节数组。在认识到key和value通常是String类型之后,StringRedisTemplate扩展了RedisTemplate,只关注String类型。

    假设我们已经有了RedisConnectionFactory,那么可以按照如下的方式构建RedisTemplate:

    RedisConnectionFactory cf = ...;
    RedisTemplate<String, Product> redis = 
            new RedisTemplate<String, Product>();
    redis.setConnectionFactory(cf);
    

    注意,RedisTemplate使用了两个类型进行参数化。第一个是key的类型,第二个是value的类型。在这里所构建的RedisTemplate中,将会保存Product对象作为value,并将其赋予一个String类型的key。

    但如果所使用的value和key都是String类型,那么可以考虑使用StringRedisTemplate来代替RedisTemplate:

    RedisConnectionFactory cf = ...;
    StringRedisTemplate redis = new StringRedisTemplate(cf);
    

    注意,与RedisTemplate不同,StringRedisTemplate有一个接受RedisConnectionFactory的构造器,因此没有必要在构建后调用setConnectionFactory();

    如果你经常使用RedisTemplate或StringRedisTemplate的话,可以考虑将其配置为bean,然后注入到需要的地方。如下就是一个声明RedisTemplate的简单@Bean方法:

    @Bean
    public RedisTemplate<String, Product>
                      redisTemplate(RedisConnectionFactory cf) {
        RedisTemplate<String, Product> redis = 
                new RedisTemplate();
        redis.setConnectionFactory(cf);
        return redis;
    }
    

    如下是声明StringRedisTemplate bean的@Bean方法:

    @Bean
    public StringRedisTemplate
                  stringRedisTemplate(RedisConnectionFactory cf) {
        return new StringRedisTemplate(cf);
    }
    

    RedisTemplate的大多数操作都是下表中的子API提供的。

    方法 子API接口 描述
    opsForValue() ValueOperations<K, V> 操作具有简单值的条目
    opsForList() ListOperations<K, V> 操作具有list值的条目
    opsForSet() SetOperations<K, V> 操作具有set值的条目
    opsForZSet() ZSetOperations<K, V> 操作具有ZSet值(排序的set)的条目
    opsForHash() HashOperations<K, HK, HV> 操作具有hash值的条目
    boundValueOps(K) BoundValueOperations<K, V> 以绑定指定key的方式,操作具有简单值的条目
    boundListOps(K) BoundListOperations<K, V> 以绑定指定key的方式,操作具有list值的条目
    boundSetOps(K) BoundSetOperations<K, V> 以绑定指定key的方式,操作具有set值的条目
    boundZSetOps(K) BoundZSetOperations<K, V> 以绑定指定key的方式,操作具有ZSet(排序的set)值的条目
    boundHashOps(K) BoundHashOperations<K, V> 以绑定指定key的方式,操作具有hash值的条目

    使用简单的值

    假设我们通过RedisTemplate<String, Product>保存Product,其中key是sku属性的值。如下的代码片段展示了如何借助opsForValue()方法完成该功能:

    redis.opsForValue().set(product.getSku(), product);
    

    类似地,如果希望获取sku属性为123456的产品,那么可以使用如下的代码片段:

    Product product = redis.opsForValue().get("123456");
    

    如果按照给定的key,无法获得条目的话,将会返回null。

    使用List类型的值

    使用List类型,只需使用opsForList()方法即可。例如,我们可以在一个List类型的条目尾部添加一个值:

    redis.opsForList().rightPush("cart", product);
    

    通过这种方式,我们向列表的尾部添加了一个Prodcut,所使用的这个列表在存储时key为cart。如果这个key尚未存在列表的话,将会创建一个。

    rightPush()会在列表的尾部添加一个元素,而leftPush() 则会在列表的头部添加一个值:

    redis.opsForList().leftPush("cart", product);
    

    我们有很多方式从列表中获取元素,可以通过leftPop()或者rightPop()方法从列表中弹出一个元素:

    Product first = redis.opsForList().leftPop("cart");
    Product last = redis.opsForList().rightPop("cart");
    

    除了从列表中获取值以外,这两个方法还有一个副作用就是从列表中移除所弹出的元素。如果你只是想获取值的话(甚至可能要在列表中间获取),那么可以使用range()方法:

    List<Product> products = redis.opsForList().range("cart", 2, 12);
    

    range()方法不会从列表中移除任何元素。但是它会根据指定的key和索引防伪,获取范围内的一个或多个值。上面的样例中,会获取11个元素,从索引为2的元素到索引为12的元素(不包含)。如果范围超出了列表的边界,那么只会返回索引在范围内的元素。如果该索引范围内没有元素的话,将会返回一个空的列表。

    在Set上执行操作

    除了操作列表以外,我们还可以使用opsForSet()操作Set。最为常见的是

    redis.opsForSet().add("cart", product);
    

    在我们有多个Set并填充值之后,就可以对这些Set进行一些有意思的操作,如获取其差异,求交集和求并集:

    List<Product> diff = redis.opsForSet().difference("cart1", "cart2");
    List<Product> union = redis.opsForSet().union("cart1", "cart2");
    List<Product> isect = redis.opsForSet().isect("cart1","cart2");
    

    当然,我们还可以移除它的元素:

    redis.opsForSet().remove(product);
    

    我们设置可一颗随机获取Set中的一个元素:

    Product random = redis.opsForSet().randomMember("cart");
    

    因为Set没有索引和内部的排序,因此我们无法精准定位某个点,然后从Set中获取元素。

    绑定到某个key上

    为了记录阐述这些子API的用法,我们假设将Product对象保存到一个list中,并且ket为cart。这种场景下,假设我们想从list的右侧弹出一个元素,然后在list的尾部新增三个元素。我们此时可以使用boundListOps() 方法所返回的BoundListOperations:

    BoundListOperations <String, Product> cart = 
                redis.boundListOps("cart");
    Product popped = cart.rightPop();
    cart.rightPush(product1);
    cart.rightPush(product2);
    cart.rightPush(product3);
    

    注意,我们只在一个地方使用了条目的key,也就是调用boundListOps() 的时候。对返回的BoundListOperations执行的所有操作都会应用到这个key上。

    使用key和value的序列化器

    当某个条目保存到Redis key-value存储的时候,key和value都会使用Redis的序列化器(serializer)进行序列化。Spring Data Redis提供了多个这样的序列化器:

    • GenericToStringSerializer:使用String转化服务进行序列化;
    • JacksonJsonRedisSerializer:使用Jackson 1,将对象序列化为JSON;
    • Jackson2JsonRedisSerializer:使用Jackson 2,将对象序列化为JSON;
    • JdkSerializationRedisSerializer:使用Java序列化;
    • OxmSerializer:使用Spring O/X映射的编排器和解排器(marshaler和unmarshaler)实现序列化,用户XML序列化;
    • StringRedisSerializer:序列化String类型的key和value。

    这些序列化器都实现了RedisSerializer接口,如果其中没有符合需求的序列化器,那么还可以自行创建。

    RedisTemplate会使用JdkSerializationRedisSerializer,这意味着key和value都会通过Java进行序列化。StringRedisTemplate默认会使用StringRedisSerializer,它实际上就是实现String与byte数组之间的相互转换。这些默认的设置适用于很多的场景,但有时候可能会发现使用一个不同的序列化器也是很有用处的。

    例如,假设当使用RedisTemplate的时候,我们希望将Product类型的value序列化为JSON,而key是String类型。RedisTemplate的setKeySerializer()和setValueSerializer()方法就需要如下所示:

    @Bean
    public 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;
    }
    

    在这里,我们设置RedisTemplate在序列化key的时候,使用StringRedisSerializer,并且也设置了在序列化Product的Jackson2JsonRedisSerializer。

    相关文章

      网友评论

          本文标题:Spring实战(十二)-使用NoSQL数据库

          本文链接:https://www.haomeiwen.com/subject/mmvvcftx.html