本文基于《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。