本文基于《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。
网友评论