8. Redis存储库
使用Redis存储库允许在Redis哈希中无缝地转换和存储域对象,应用自定义映射策略并利用二级索引。
Redis存储库至少需要Redis Server 2.8.0版。 |
8.1。用法
要访问存储在Redis中的域实体,您可以利用存储库支持,从而轻松实现这些实现。
@RedisHash("persons")
public class Person {@Id String id;String firstname;String lastname;Address address;
}
我们在这里有一个非常简单的域对象。请注意,它有一个名为id
annotated 的属性org.springframework.data.annotation.Id
和一个@RedisHash
注释类型。这两个负责创建用于保存散列的实际密钥。
注释的属性@Id 以及命名id 的属性被视为标识符属性。那些带有注释的人比其他人更受青睐。 |
现在实际上有一个负责存储和检索的组件,我们需要定义一个存储库接口。
public interface PersonRepository extends CrudRepository<Person, String> {}
随着我们的资源库的扩展,CrudRepository
它提供了基本的CRUD和查找操作。我们需要将两者粘合在一起的是Spring配置。
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {@Beanpublic RedisConnectionFactory connectionFactory() {return new JedisConnectionFactory();}@Beanpublic RedisTemplate<?, ?> redisTemplate() {RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();return template;}
}
鉴于上面的设置,我们可以继续并注入PersonRepository
我们的组件。
@Autowired PersonRepository repo;public void basicCrudOperations() {Person rand = new Person("rand", "al'thor");rand.setAddress(new Address("emond's field", "andor"));repo.save(rand); repo.findOne(rand.getId()); repo.count(); repo.delete(rand);
}
如果当前值是null 或重新使用已经设置的id值,则生成一个新的id Person ,并keyspace:id 在此情况下将具有模式的键存储在Redis Hash中的类型属性,例如。persons:5d67b7e1-8640-4475-beeb-c666fab4c0e5 。 | |
使用提供的ID来检索存储在的对象keyspace:id 。 | |
统计密钥空间内提供实体总数人的定义@RedisHash 上Person 。 | |
从Redis中删除给定对象的键。 |
8.2。对象到哈希映射
Redis Repository支持持久化Hashes中的对象。这需要一个由a完成的对象哈希转换RedisConverter
。默认实现Converter
用于将属性值映射到Redis本地和从Redis本地映射byte[]
。
鉴于Person
前面几节中的类型,默认映射如下所示:
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
address.city = emond's field
address.country = andor
该_class 属性包含在根级别以及任何嵌套的接口或抽象类型中。 | |
简单的属性值由路径映射。 | |
复杂类型的属性由它们的点路径映射。 |
类型 | 样品 | 映射值 |
---|---|---|
简单类型 | String firstname =“rand”; | firstname =“rand” |
复杂类型 | address address = new Address(“emond's field”); | address.city =“emond的字段” |
| 列表<String> nicknames = asList(“龙重生”,“lews therin”); | 昵称。[0] =“龙重生”, |
| Map <String,String> atts = asMap({“eye-color”,“gray”},{“... | atts。[eye-color] =“gray”, |
| List <Address> addresses = asList(new Address(“em ... | 地址。[0] .city =“emond's field”, |
| Map <String,Address> addresses = asMap({“home”,new Address(“em ... | 地址。[home] .city =“emond's field”, |
由于平面表示结构Map键需要是简单的类型,如String s或Number s。 |
映射行为可以通过根据注册定制Converter
在RedisCustomConversions
。这些转换器可以处理从单个转换到另一个byte[]
,Map<String,byte[]>
而第一个转换器适用于例如。将一种复杂类型转换为例如。二进制JSON表示仍然使用默认映射哈希结构。第二个选项提供了对最终散列的完全控制。将对象写入Redis哈希将从哈希中删除内容并重新创建整个哈希,因此未映射的数据将丢失。
@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());}@Overridepublic 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());}@Overridepublic Address convert(byte[] value) {return serializer.deserialize(value);}
}
使用上述字节[] Converter
产生例如。
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
address = { city : "emond's field", country : "andor" }
@WritingConverter
public class AddressToMapConverter implements Converter<Address, Map<String,byte[]>> {@Overridepublic Map<String,byte[]> convert(Address source) {return singletonMap("ciudad", source.getCity().getBytes());}
}@ReadingConverter
public class MapToAddressConverter implements Converter<Address, Map<String, byte[]>> {@Overridepublic Address convert(Map<String,byte[]> source) {return new Address(new String(source.get("ciudad")));}
}
使用上面的Map Converter
生成例如。
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
ciudad = "emond's field"
自定义转换对索引分辨率没有影响。即使对于自定义转换类型,二级索引仍将被创建。 |
8.3。Keyspaces
密钥空间定义用于为Redis哈希创建实际密钥的前缀。默认情况下,前缀设置为getClass().getName()
。此默认值可以通过@RedisHash
聚合根级别或通过设置编程配置来更改。但是,带注释的密钥空间将取代任何其他配置。
@Configuration
@EnableRedisRepositories(keyspaceConfiguration = MyKeyspaceConfiguration.class)
public class ApplicationConfig {//... RedisConnectionFactory and RedisTemplate Bean definitions omittedpublic static class MyKeyspaceConfiguration extends KeyspaceConfiguration {@Overrideprotected Iterable<KeyspaceSettings> initialConfiguration() {return Collections.singleton(new KeyspaceSettings(Person.class, "persons"));}}
}
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {//... RedisConnectionFactory and RedisTemplate Bean definitions omitted@Beanpublic RedisMappingContext keyValueMappingContext() {return new RedisMappingContext(new MappingConfiguration(new MyKeyspaceConfiguration(), new IndexConfiguration()));}public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {@Overrideprotected Iterable<KeyspaceSettings> initialConfiguration() {return Collections.singleton(new KeyspaceSettings(Person.class, "persons"));}}
}
8.4。二级索引
二级索引用于启用基于本机Redis结构的查找操作。值在每次保存时写入相应的索引,并在删除或过期时删除。
8.4.1。简单的财产指数
给定示例Person
实体,我们可以通过注释属性为firstname创建一个索引@Indexed
。
@RedisHash("persons")
public class Person {@Id String id;@Indexed String firstname;String lastname;Address address;
}
索引是为实际属性值构建的。挽救两个人,例如。“rand”和“aviendha”导致设置如下所示的索引。
SADD persons:firstname:rand e2c7dcee-b8cd-4424-883e-736ce564363e
SADD persons:firstname:aviendha a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56
在嵌套元素上也可以有索引。假设Address
有一个城市属性,注明@Indexed
。在这种情况下,一旦person.address.city
不是null
,我们就为每个城市设置了套数。
SADD persons:address.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e
此外,编程设置允许在地图键和列表属性上定义索引。
@RedisHash("persons")
public class Person {// ... other properties omittedMap<String,String> attributes; Map<String Person> relatives; List<Address> addresses;
}
SADD persons:attributes.map-key:map-value e2c7dcee-b8cd-4424-883e-736ce564363e | |
SADD persons:relatives.map-key.firstname:tam e2c7dcee-b8cd-4424-883e-736ce564363e | |
SADD persons:addresses.city:tear e2c7dcee-b8cd-4424-883e-736ce564363e |
索引将不会在引用上解析。 |
与密钥空间相同,可以配置索引而不需要注释实际的域类型。
@Configuration
@EnableRedisRepositories(indexConfiguration = MyIndexConfiguration.class)
public class ApplicationConfig {//... RedisConnectionFactory and RedisTemplate Bean definitions omittedpublic static class MyIndexConfiguration extends IndexConfiguration {@Overrideprotected Iterable<IndexDefinition> initialConfiguration() {return Collections.singleton(new SimpleIndexDefinition("persons", "firstname"));}}
}
@Configuration
@EnableRedisRepositories
public class ApplicationConfig {//... RedisConnectionFactory and RedisTemplate Bean definitions omitted@Beanpublic RedisMappingContext keyValueMappingContext() {return new RedisMappingContext(new MappingConfiguration(new KeyspaceConfiguration(), new MyIndexConfiguration()));}public static class MyIndexConfiguration extends IndexConfiguration {@Overrideprotected Iterable<IndexDefinition> initialConfiguration() {return Collections.singleton(new SimpleIndexDefinition("persons", "firstname"));}}
}
8.4.2。地理空间索引
假设该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); List<Person> findByAddressLocationWithin(Circle circle);
}Person rand = new Person("rand", "al'thor");
rand.setAddress(new Address(new Point(13.361389D, 38.115556D)));repository.save(rand); repository.findByAddressLocationNear(new Point(15D, 37D), new Distance(200));
使用点和距离查询嵌套属性的方法声明。 | |
使用Circle在内搜索嵌套属性的查询方法声明。 | |
GEOADD persons:address:location 13.361389 38.115556 e2c7dcee-b8cd-4424-883e-736ce564363e | |
GEORADIUS persons:address:location 15.0 37.0 200.0 km |
在上面的例子中,GEOADD
使用对象id
作为成员的名字来存储lon / lat值。查找器方法允许查询这些值的用法Circle
或Point, Distance
组合。
这是不是可以组合near / within 与其他标准。 |
8.5。生存时间
存储在Redis中的对象只能在一段时间内有效。这对于在Redis中保留短暂的对象尤其有用,而不必在到达其寿命时手动删除它们。以秒为单位的到期时间可以通过@RedisHash(timeToLive=…)
以及通过设置KeyspaceSettings
(请参阅密钥空间)。
可以通过@TimeToLive
在数字属性或方法上使用注释来设置更灵活的到期时间。但是,不适用于@TimeToLive
同一个类中的方法和属性。
public class TimeToLiveOnProperty {@Idprivate String id;@TimeToLiveprivate Long expiration;
}public class TimeToLiveOnMethod {@Idprivate String id;@TimeToLivepublic long getTimeToLive() {return new Random().nextLong();}
}
显式注释属性将从Redis @TimeToLive 回读实际值TTL 或PTTL 值。-1表示该对象没有过期关联。 |
存储库实现确保通过通过订阅Redis密钥空间通知RedisMessageListenerContainer
。
当到期被设置为正值时,EXPIRE
执行相应的命令。除了保留原始文件外,幻影副本在Redis中保留并设置为在原始文件后5分钟到期。这样做是为了使存储库支持能够在密钥过期时RedisKeyExpiredEvent
通过泉发布持有过期值,ApplicationEventPublisher
即使原始值已经消失。所有连接的应用程序将使用Spring Data Redis存储库接收到期事件。
默认情况下,初始化应用程序时,密钥到期监听器被禁用。启动模式可以在应用程序中调整@EnableRedisRepositories
或RedisKeyValueAdapter
启动监听程序,也可以在首次插入具有TTL的实体时启动。查看EnableKeyspaceEvents
可能的值。
该RedisKeyExpiredEvent
会保存实际过期的域对象的副本以及密钥。
延迟或禁用到期事件侦听器启动会影响RedisKeyExpiredEvent 发布。禁用的事件侦听器不会发布到期事件。由于延迟侦听器初始化,延迟启动可能导致事件丢失。 |
密钥空间通知消息侦听器将改变notify-keyspace-events Redis中的设置(如果这些设置尚未设置)。现有的设置不会被覆盖,所以留给用户的时候不要将它们留空。请注意,CONFIG 在AWS ElastiCache中禁用了此功能,并且启用了侦听器导致的错误。 |
Redis Pub / Sub消息不是持久的。如果在应用程序关闭期间某个键过期,则不会处理到期事件,这可能会导致二级索引包含对已过期对象的静态引用。 |
8.6。坚持参考
使用标记属性@Reference
允许存储简单的键引用,而不是将值复制到散列本身。在从Redis加载时,引用会自动解析并映射回对象。
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
mother = persons:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56
引用存储keyspace:id 引用对象的整个键()。 |
引用对象在保存引用对象时不会保留更改。请确保分开保存对引用对象的更改,因为只有引用将被存储。在引用类型的属性上设置的索引不会被解析。 |
8.7。坚持部分更新
在某些情况下,不需要加载和重写整个实体,只需在其中设置一个新值即可。上次活动时间的会话时间戳可能是您只想更改一个属性的场景。 PartialUpdate
允许在现有对象上定义set
和delete
操作,同时考虑更新实体本身的潜在到期时间以及索引结构。
PartialUpdate<Person> update = new PartialUpdate<Person>("e2c7dcee", Person.class).set("firstname", "mat") .set("address.city", "emond's field") .del("age"); template.update(update);update = new PartialUpdate<Person>("e2c7dcee", Person.class).set("address", new Address("caemlyn", "andor")) .set("attributes", singletonMap("eye-color", "grey")); template.update(update);update = new PartialUpdate<Person>("e2c7dcee", Person.class).refreshTtl(true); .set("expiration", 1000);template.update(update);
将简单属性firstname设置为mat。 | |
将简单属性address.city设置为emond的字段,而不必传入整个对象。这在注册自定义转换时不起作用。 | |
删除楼龄。 | |
设置复杂的地址。 | |
设置一个地图/值集合会删除先前存在的地图/集合,并用给定值替换这些值。 | |
更改生存时间时自动更新服务器到期时间。 |
更新复杂对象以及映射/集合结构需要与Redis进一步交互以确定现有值,这意味着可能会发现重写整个实体可能会更快。 |
8.8。查询和查询方法
查询方法允许从方法名称自动派生简单的查找器查询。
public interface PersonRepository extends CrudRepository<Person, String> {List<Person> findByFirstname(String firstname);
}
请确保在查找器方法中使用的属性设置为索引。 |
Redis存储库的查询方法仅支持查询具有分页的实体和实体集合。 |
使用派生查询方法可能并不总是足以对要执行的查询建模。RedisCallback
可以更好地控制索引结构的实际匹配,甚至可以自定义添加索引结构。它只需提供一个RedisCallback
返回一个或一Iterable
组id值的方法。
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片段 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
8.9。运行在群集上的Redis存储库
在群集的Redis环境中使用Redis存储库支持很好。有关配置详细信息,请参阅Redis群集部分ConnectionFactory
。仍然需要考虑一些因素,因为默认的密钥分配会将实体和二级索引分散到整个集群及其插槽中。
键 | 类型 | 插槽 | 节点 |
---|---|---|---|
人数:e2c7dcee-b8cd-4424-883e-736ce564363e | ID为散列 | 15171 | 127.0.0.1:7381 |
人数:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 | ID为散列 | 7373 | 127.0.0.1:7380 |
人数:姓:兰特 | 指数 | 1700 | 127.0.0.1:7379 |
当所有涉及的密钥映射到相同的插槽时,一些命令只能在服务器端处理SINTER
并且SUNION
只能处理。否则,计算必须在客户端完成。因此将密钥空间固定到单个插槽可以立即使用Redis服务器计算。
键 | 类型 | 插槽 | 节点 |
---|---|---|---|
{}人:e2c7dcee-b8cd-4424-883e-736ce564363e | ID为散列 | 2399 | 127.0.0.1:7379 |
{}人:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56 | ID为散列 | 2399 | 127.0.0.1:7379 |
{}人:姓:兰特 | 指数 | 2399 | 127.0.0.1:7379 |
在使用Redis群集时,通过`@RedisHash(“{yourkeyspace}”)定义和固定密钥空间到特定的插槽。 |
8.10。CDI整合
存储库接口的实例通常由一个容器创建,当使用Spring Data时,Spring是最自然的选择。有复杂的支持来轻松设置Spring来创建bean实例。Spring Data Redis附带一个自定义CDI扩展,允许在CDI环境中使用存储库抽象。该扩展是JAR的一部分,因此您只需要将Spring Data Redis JAR放入类路径即可。
现在,您可以设置基础结构通过实施为一个CDI生产者RedisConnectionFactory
和RedisOperations
:
class RedisOperationsProducer {@ProducesRedisConnectionFactory redisConnectionFactory() {JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(new RedisStandaloneConfiguration());jedisConnectionFactory.afterPropertiesSet();return jedisConnectionFactory;}void disposeRedisConnectionFactory(@Disposes RedisConnectionFactory redisConnectionFactory) throws Exception {if (redisConnectionFactory instanceof DisposableBean) {((DisposableBean) redisConnectionFactory).destroy();}}@Produces@ApplicationScopedRedisOperations<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 bean的Repositories,并在容器请求存储库类型的bean时创建Spring Data repository的代理。因此,获取Spring Data存储库的一个实例就是声明一个@Injected
属性的问题:
class RepositoryClient {@InjectPersonRepository repository;public void businessMethod() {List<Person> people = repository.findAll();}
}
Redis存储库需要RedisKeyValueAdapter
和RedisKeyValueTemplate
实例。如果未找到提供的bean,则这些bean由Spring Data CDI扩展创建和管理。但是,您可以提供自己的豆子配置的特定属性RedisKeyValueAdapter
和RedisKeyValueTemplate
。
源码下载:https://github.com/daqiang123/basic