1. 程式人生 > >Spring Data Redis ----Redis倉庫----筆記6

Spring Data Redis ----Redis倉庫----筆記6

繼續。。。。。

官方文件:http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/

7.Redis Repositories(Redis 倉庫)

Redis 倉庫允許你無縫轉換和儲存域物件到Redis Hashes,應用自定義mapping策略和使用二級索引。

警告:Redis Repositories 要求Redis 伺服器版本為2.8.0+

7.1.使用

為了獲取儲存在Redis的域物件,你可以利用庫存支援很容易實現。

例子 Person實體類

@RedisHash("persons")
public class Person {

  @Id String id;
  String firstname;
  String lastname;
  Address address;
}
在這裡我們已經有一個實體類,注意屬性Id上的註解org.springframework.data.annotation.Id 和 @RedisHash代表對應的型別。它們目的就是建立一個真實的key儲存到hash中。

提示:使用註解@Id,考慮到id作為唯一識別符號。

為了現在有一個元件可以儲存和取用資料,我們需要定義倉庫介面。

例子6.簡單倉庫介面去持久化Person實體類

public interface PersonRepository extends CrudRepository<Person, String> {

}

我們倉庫類繼承CrudRepository類,它提供增刪改查功能。這些事情需要通過配置來整合在一起。

例子7,javaConfig 配置

@Configuration
@EnableRedisRepositories
public class ApplicationConfig {

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

  @Bean
  public RedisTemplate<?, ?> redisTemplate() {

    RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
    return template;
  }
}

給出一些設定讓我們將PersonRepository注入到元件中。

例子8.獲取Person實體類

@Autowired PersonRepository repo;

public void basicCrudOperations() {

  Person rand = new Person("rand", "al'thor");
  rand.setAddress(new Address("emond's field", "andor"));

  repo.save(rand);                       1                  

  repo.findOne(rand.getId());            2                  

  repo.count();                          3                  

  repo.delete(rand);                     4                    
}
註釋:
1.如果當前值不為空的話會生成一個新的Id,或者重複使用已經存在id 值和儲存Person型別的屬性。Person在hash中將會如下展示

keyspace:id persons:5d67b7e1-8640-4475-beeb-c666fab4c0e5

2.通過id取出Person物件

3.計算實體類的物件的數量,被@RedisHash註解的Person

4.移除物件。

7.2.Object to Hash Mapping (物件對映成HashMapping)

Redis倉庫支援Hashs持久化物件。它需要將會物件轉換成Hash的轉換工作。它可以被RedisConverter完成。 預設實現使用Converter的屬性值來自於原生Redis byte[]。

如上例子中給出一個Person型別,它預設mapping對映如下所示:

_class = org.example.Person                   1
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand                              2
lastname = al’thor
address.city = emond's field                  3
address.country = andor

註釋:

1._class屬性包含在根節點水平,它作為任何內部介面或者抽象型別

2.簡單的屬性值通過路徑對映

3.複雜的屬性型別可以通過路徑.進行對映 例如:address.city

預設對映規則
型別 例子 對映值
簡單型別(例如字串) String firstname="rand" firstname="rand"
複雜型別(例如地址) Address address= new Address("emond's field") address.city="emond's field"
List簡單例子 List<String> nicknames=asList("dragon reborn", "lewstherin") nicknames.[0] ="dragon reborn",
nicknames.[1] ="lews therin"
Map簡單例子 Map<String, String> atts = asMap({"eye-color", "grey"}, {"…​ atts.[eye-color] = "grey",
atts.[hair-color] = "…​
List複雜例子 List<Address> addresses = asList(new Address("em… addresses.[0].city = "emond’s field",
addresses.[1].city = "…​
Map複雜例子 Map<String, Address> addresses = asMap({"home", new Address("em…​ addresses.[home].city = "emond’s field",
addresses.[work].city = "…​

對映行為可以自定義,只需在CustomConversions註冊對應的Converter轉換器。這些轉換器關心的是把單一byte[]變成Map<String, byte[]>,因此適合將一個複雜型別轉換成二進位制JSON,但這個JSON還是使用預設的hash結構。第二個選項提供了全控制hash結果。寫一個物件到Redis hash中,將會刪除hash中原來的內容。然後重寫整個hash值。所以沒有對映的資料將會丟失。

例子9. 簡單 byte[]轉換

@WritingConverter
public class AddressToBytesConverter implements Converter<Address, byte[]> {

  private final Jackson2JsonRedisSerializer<Address> serializer;

  public AddressToBytesConverter() {

    serializer = new Jackson2JsonRedisSerializer<Address>(Address.class);
    serializer.setObjectMapper(new ObjectMapper());
  }

  @Override
  public byte[] convert(Address value) {
    return serializer.serialize(value);
  }
}

@ReadingConverter
public class BytesToAddressConverter implements Converter<byte[], Address> {

  private final Jackson2JsonRedisSerializer<Address> serializer;

  public BytesToAddressConverter() {

    serializer = new Jackson2JsonRedisSerializer<Address>(Address.class);
    serializer.setObjectMapper(new ObjectMapper());
  }

  @Override
  public Address convert(byte[] value) {
    return serializer.deserialize(value);
  }
}

使用如上byte[]轉換將會產生如下結果
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
address = { city : "emond's field", country : "andor" }

例子10.簡單 Map<String, byte[]> 轉換器
@WritingConverter
public class AddressToMapConverter implements Converter<Address, Map<String,byte[]>> {

  @Override
  public Map<String,byte[]> convert(Address source) {
    return singletonMap("ciudad", source.getCity().getBytes());
  }
}

@ReadingConverter
public class MapToAddressConverter implements Converter<Address, Map<String, byte[]>> {

  @Override
  public Address convert(Map<String,byte[]> source) {
    return new Address(new String(source.get("ciudad")));
  }
}
使用如上Map 轉換器將會產生如下結果
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
ciudad = "emond's field"

提示:

自定義轉換器對索引沒有影響,將會為自定義轉換型別建立二級索引。

7.3.Keyspaces (鍵名稱空間)

鍵名稱空間為新建立的實際key新增字首,預設情況下字首是getClass().getName().這個預設的字首將會通過@RedisHash 設定或者設定程式的配置。然而。註解的鍵名稱空間會取締任何其他配置的鍵名稱空間。

例子11.通過@EnableRedisRepositories設定鍵名稱空間

@Configuration
@EnableRedisRepositories(keyspaceConfiguration = MyKeyspaceConfiguration.class)
public class ApplicationConfig {

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

  public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {

    @Override
    protected Iterable<KeyspaceSettings> initialConfiguration() {
      return Collections.singleton(new KeyspaceSettings(Person.class, "persons"));
    }
  }
}

例子12.通過程式配置設定鍵名稱空間
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

  @Bean
  public RedisMappingContext keyValueMappingContext() {
    return new RedisMappingContext(
      new MappingConfiguration(
        new MyKeyspaceConfiguration(), new IndexConfiguration()));
  }

  public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {

    @Override
    protected Iterable<KeyspaceSettings> initialConfiguration() {
      return Collections.singleton(new KeyspaceSettings(Person.class, "persons"));
    }
  }
}

7.4.(Secondary Indexes)二級索引

二級索引被用來基於原生Redis結構進行檢索。在每一次儲存或者通過物件刪除和過期而移除操作都會儲存與之對應的索引。

7.4.1. 簡單的屬性索引

給出一個簡單物件Person實體類,我們可以為firstname建立一個索引,只要在屬性上新增@Indexed註解

例子13. 註解驅動索引

@RedisHash("persons")
public class Person {

  @Id String id;
  @Indexed String firstname;
  String lastname;
  Address address;
}

索引將會為實際的屬性值設定。儲存兩個Person物件。例如“rand”和“aviendha” 結果,索引儲存的結果如下
SADD persons:firstname:rand e2c7dcee-b8cd-4424-883e-736ce564363e
SADD persons:firstname:aviendha a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56

也有可能有索引在內部元素中。假設Address有一個city的屬性,然後它被註解@Indexed。在這種情況下。一旦 person.address.city不為null,我們將會為city如下設定
SADD persons:address.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e

更多的通過程式進行設定。允許定義一個索引作為map的key。如下所示
@RedisHash("persons")
public class Person {

  // ... other properties omitted

  Map<String,String> attributes;      1
  Map<String Person> relatives;       2
  List<Address> addresses;            3
}

註釋:

1.SADD persons:attributes.map-key:map-value e2c7dcee-b8cd-4424-883e-736ce564363e

2.SADD persons:relatives.map-key.firstname:tam e2c7dcee-b8cd-4424-883e-736ce564363e

3.SADD persons:addresses.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e

警告:索引將不會在引用解決。

與keyspaces類似。它可能配置索引而不需要註解實際域型別。

例子14 通過 @EnableRedisRepositories設定索引

@Configuration
@EnableRedisRepositories(indexConfiguration = MyIndexConfiguration.class)
public class ApplicationConfig {

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

  public static class MyIndexConfiguration extends IndexConfiguration {

    @Override
    protected Iterable<IndexDefinition> initialConfiguration() {
      return Collections.singleton(new SimpleIndexDefinition("persons", "firstname"));
    }
  }
}

例子15 。程式配置索引
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {

  //... RedisConnectionFactory and RedisTemplate Bean definitions omitted

  @Bean
  public RedisMappingContext keyValueMappingContext() {
    return new RedisMappingContext(
      new MappingConfiguration(
        new KeyspaceConfiguration(), new MyIndexConfiguration()));
  }

  public static class MyIndexConfiguration extends IndexConfiguration {

    @Override
    protected Iterable<IndexDefinition> initialConfiguration() {
      return Collections.singleton(new SimpleIndexDefinition("persons", "firstname"));
    }
  }
}

7.4.2. Geospatial Index(地理空間索引)

假設Address型別包含一個屬性location是Point型別,它包含特定座標的地理位置。通過註解屬性@GeoIndexed ,這些值將會通過Redis GEO命令新增。
@RedisHash("persons")
public class Person {

  Address address;

  // ... other properties omitted
}

public class Address {

  @GeoIndexed Point location;

  // ... other properties omitted
}

public interface PersonRepository extends CrudRepository<Person, String> {

  List<Person> findByAddressLocationNear(Point point, Distance distance);       1 
  List<Person> findByAddressLocationWithin(Circle circle);                      2
}

Person rand = new Person("rand", "al'thor");
rand.setAddress(new Address(new Point(13.361389D, 38.115556D)));

repository.save(rand);                                                          3

repository.findByAddressLocationNear(new Point(15D, 37D), new Distance(200));   4

註釋:

1.查詢方法使用Point 和 Distance宣告內部屬性

2.查詢方法使用Circle宣告內部屬性。

3.新增地址位置: persons:address:location 13.361389 38.115556 e2c7dcee-b8cd-4424-883e-736ce564363e

4.檢索附近半徑  persons:address:location 15.0 37.0 200.0 km

以上例子中經度/緯度使用GEOADD進行儲存。把物件id作為成員的名稱。查詢方法使用Circle或Point,Distance查詢它的值。

注意:這個不可能使用其他規則合併 near/within 。

7.5. Time To Live(多久失效)

物件儲存在Redis中可能只是驗證一段時間。這對於持久化有效時間短的物件特別有用,當時間到期不需要手動移除該物件。設定失效方式有兩種,既可以通過@RedisHash(timeToLive=...),也可以通過KeyspaceSettings(參考Keyspaces)

更多靈活過期的時間可以通過@TimeToLive註解實現。這個註解放置到數字型別的屬性上例如Long,然而不要應用@TimeToLive同時設定到同一個類的屬性和方法上

例子16 過期

public class TimeToLiveOnProperty {

  @Id
  private String id;

  @TimeToLive
  private Long expiration;
}

public class TimeToLiveOnMethod {

  @Id
  private String id;

  @TimeToLive
  public long getTimeToLive() {
  	return new Random().nextLong();
  }
}

提示:

註解屬性@TimeToLive將會讀取Redis實際TTL或PTTL值,-1表明這個物件永不過期。

這個倉庫的實現確保通過RedisMessageListenerContainer訂閱 Redis keyspace notifications

通過EXPIRE命令將會執行一個正值的過期時間。除了一個複製原始的物件被持久化設定的過期時間為比原始物件多5分鐘。這樣做確保倉庫支援釋出RedisKeyExpiredEvent 來獲取失效的值,它是通過Spring 的ApplicationEventPublisher獲取。這樣就不管一個key失效,還是原始的值已經移除。使用Spring Data Redis 倉庫,失效時間將會收到所有連線的應用。

在預設情況,當初始化應用的時候,key失效監聽是禁用的。啟動模式將會被調整。它通過@EnableRedisRepositories 或者 RedisKeyValueAdapter 在應用中啟動監聽器或者在第一次插入TTL修飾的實體類,參考EnableKeyspaceEvents所有可能的值。

RedisKeyExpiredEvent可以複製失效物件作為key。

提示: 延遲或者在啟動時候失效事件監聽器不可用都會影響到RedisKeyExpiredEvent釋出。一個不可用的事件監聽器將不會發布失效事件。延遲啟動會導致事件的丟失。

提示;如果它沒有設定notify-keyspace-events的值,keyspace資訊監聽將會改變notify-keyspace-events的設定。存在的設定將不會被重寫。所有使用者設定正確的值而不要為空。請注意CONFIG在AWS ElasticCache不可用,如果開啟將會報錯。

提示:Redis 訂閱/釋出資訊將會被儲存。如果一個鍵失效了,這個應用將會移除過期事件,這個將導致二級索引仍然會引用過期的物件

7.6. Persisting References(持久引用)

在屬性添加註解@Reference允許儲存一個簡單的key去引用一個物件而不是複製物件。在從Redis載入該引用屬性,引用會自動引用到具體那個物件上。

例子17. 簡單屬性引用

_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
mother = persons:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56       1
註釋

1.引用儲存整個引用物件的key(keyspace:id)

警告:當儲存這個引用的物件的時候不屬於持久化的改變。只要引用被儲存,請確保單獨在引用物件進行持久化改變。引用型別的索引將不會改變。

7.7 Persisting Partial Updates(持久化更新部分內容)

在有些情況下,我們並不需要載入或重寫整個實體物件。而只是設定一個新的值。你想儲存最近會話活躍的時間戳屬性。PartialUpdate 允許你在已經存在物件定義設定和刪除動作,它只關心更新實體物件失效的時間 作為索引的結構。

例子18.部分更新

PartialUpdate<Person> update = new PartialUpdate<Person>("e2c7dcee", Person.class)
  .set("firstname", "mat")                       1                                     
  .set("address.city", "emond's field")          2                                    
  .del("age");                                   3
template.update(update);

update = new PartialUpdate<Person>("e2c7dcee", Person.class)
  .set("address", new Address("caemlyn", "andor"))             4                      
  .set("attributes", singletonMap("eye-color", "grey"));       5                      

template.update(update);

update = new PartialUpdate<Person>("e2c7dcee", Person.class)
  .refreshTtl(true);                                           6                    
  .set("expiration", 1000);

template.update(update);
註釋:

1.設定一個簡單屬性firstname為mat

2.設定簡單屬性address.city為emond's field而改變整個物件。當自定義轉換器被註冊的整個方法將會不可用

3.移除屬性age

4.設定複雜型別address
5. 設定一個map集合的值,將會移除先前已經的存在的值。

6.當改變Time To Live 將會自動更新伺服器的失效時間。

提示:

更新複雜物件也就是map集合結構要求更深Redis的互動,整個實體重寫可能會比區域性更新物件更快一些。

7.8.Queries and Query Methods ( 查詢和查詢方法)

查詢方法允許從方法名自動衍生出一個簡單查詢器

例子19. 簡單倉庫查詢方法

public interface PersonRepository extends CrudRepository<Person, String> {

  List<Person> findByFirstname(String firstname);
}

提示:為了確保屬性作為查詢方法的引數,請為屬性設定索引

提示:對於Redis 倉庫查詢方法只支援查詢實體和包含實體的集合分頁。

使用衍生查詢方法可能並不一直有效。RedisCallback提供更多實際匹配索引的結構或者甚至是使用者自定義新增的。所有這些都提供了一個RedisCallback,它將會返回單個或Iterable (id的集合)

例子20 使用RedisCallback進行查詢

String user = //...

List<RedisSession> sessionsByUser = template.find(new RedisCallback<Set<byte[]>>() {

  public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
    return connection
      .sMembers("sessions:securityContext.authentication.principal.username:" + user);
  }}, RedisSession.class);

這裡將會展示Redis支援的關鍵字。方法包含關鍵字進行轉換。
在方法支援的關鍵字
關鍵字 例子 Redis 片段
And findByLastnameAndFirstname SINTER …:firstname:rand …:lastname:al’thor
Or findByLastnameOrFirstname SUNION …:firstname:rand …:lastname:al’thor
Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals SINTER …:firstname:rand
Top,First findFirst10ByFirstname,findTop5ByFirstname

7.9. Redis Repositories running on Cluster (Redis倉庫執行在叢集上)

在叢集Redis環境使用Redis 倉庫也是很好的。詳情參考Redis Cluster章節去獲取ConnectionFactory的配置詳情。仍然有些需要考慮,例如預設分散式的key將會分配到實體和二級索引,它是通過整個叢集和它slots完成的。

型別 slot 節點
persons:e2c7dcee-b8cd-4424-883e-736ce564363e id for hash 15171 127.0.0.1:7381
persons:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 id for hash 7373 127.0.0.1:7380
persons:firstname:rand index 1700 127.0.0.1:7379

當所有key對映到同一個slot的時候,有些命令例如SINTER和SUNION將只能在服務端進行處理。所以不得不在客戶端計算。因此可以在同一slot中使用pin keyspace(就是設定字首),這樣的話允許在服務端進行計算。

型別 slot 節點
{persons}:e2c7dcee-b8cd-4424-883e-736ce564363e id for hash 15171 127.0.0.1:7381
{persons}:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 id for hash 7373 127.0.0.1:7380
{persons}:firstname:rand index 1700 127.0.0.1:7379

提示:當使用Redis叢集的時候,定義設定pin keyspaces 可以通過“@RedisHash({yourkeyspace})” 定義具體的slots

7.10 CDI integration (CDI 整合)

例項化一個庫存介面通過由容器進行建立。使用SpringData,那麼Spring自然而然最好的選擇。它很容器支援Spring去例項化bean。Spring Data Redis 裝載自定義CDI擴充套件。它允許在CDI環境使用倉庫抽象。這個擴充套件是jar一部分。所以你需要啟用它,把Spring Data Redis jar 新增到你的classpath路徑下。

CDI(Contexts And Dependency Injection)是JavaEE 6標準中一個規範,將依賴注入IOC/DI上升到容器級別, 它提供了Java EE平臺上服務注入的元件管理核心,簡化應該是CDI的目標,讓一切都可以被註解被注入。

你現在可以通過實現一個CDI Producer 為RedisConnectionFactory和RedisOperations進行基礎設定。

class RedisOperationsProducer {


  @Produces
  RedisConnectionFactory redisConnectionFactory() {

    JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
    jedisConnectionFactory.setHostName("localhost");
    jedisConnectionFactory.setPort(6379);
    jedisConnectionFactory.afterPropertiesSet();

    return jedisConnectionFactory;
  }

  void disposeRedisConnectionFactory(@Disposes RedisConnectionFactory redisConnectionFactory) throws Exception {

    if (redisConnectionFactory instanceof DisposableBean) {
      ((DisposableBean) redisConnectionFactory).destroy();
    }
  }

  @Produces
  @ApplicationScoped
  RedisOperations<byte[], byte[]> redisOperationsProducer(RedisConnectionFactory redisConnectionFactory) {

    RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
    template.setConnectionFactory(redisConnectionFactory);
    template.afterPropertiesSet();

    return template;
  }

}

這些必要設定大多數依賴於執行的JavaEE環境

Spring Data Redis CDI 擴充套件將會獲取所有倉庫作為CDI beans 和 為Spring Data 倉庫建立代理。無論什麼時候需要都可以呼叫。 所以獲取一個Spring Data 倉庫關鍵就是在屬性上添加註解@Injected

class RepositoryClient {

  @Inject
  PersonRepository repository;

  public void businessMethod() {
    List<Person> people = repository.findAll();
  }
}

Redis 倉庫要求 RedisKeyValueAdapter 和 RedisKeyValueTemplate例項。如果沒有提供的話。Spring Data CDI擴充套件將會建立管理這兩個bean。當然你可以提供你自己的Bean去配置RedsiKeyValueAdapter RedisKeyValueTemplate.

總結:除了附錄,整個文件初略翻譯完了,會有很多錯誤,大家可以討論提出一下,相互學習。