How to configure Spring Redis Configuration to use Hash instead of string serialization - caching

For a Java POJO, I want to cache it to Redis using Spring's #Cacheable, #CachePut, and #CacheEvict, however I'd prefer to use Redis' Hash capabilities instead of just serializing the POJO into a String. Essentially, I would like to be able to use something like the ObjectHashMapper against the POJO so that POJO properties are automatically saved as Key/Value pairs in a single Redis entry. This behavior can be seen in the RedisRepository functionality which saves POJOs with the ObjectHashMapper, however, I prefer not to define the cache as a Repository, rather I want to use the Cache annotations.
I have successfully created some custom Spring/Redis configuration that helped me get String serialization working propertly. The Spring documentation for customizing the CacheManager and CacheConfiguration are very "light" and I've not found any relevant examples. I'm not sure whether I need a custom serializer, converter, or some other aspect of the CacheConfiguration. It seems like the serializers are more concerned with the individual keys and values, but I don't see where to configure to catch the entire Object and turn it into a Hash first.
Here is my Redis configuration. It is setup for two caches, "v", and "products", plus defaults to a StringRedisSerializer for other caches.
#Slf4j
#RequiredArgsConstructor
#Configuration
#EnableRedisRepositories(enableKeyspaceEvents=RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP)
public class RedisConfig {
private final RedisConnectionFactory connectionFactory;
private static RedisCacheConfiguration createCacheConfiguration(long timeoutInSeconds, RedisSerializationContext.SerializationPair<?> serializationPair) {
logger.info("Creating CacheConfiguration with timeout of {} seconds", timeoutInSeconds);
return RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(serializationPair)
.entryTtl(Duration.ofSeconds(timeoutInSeconds));
}
#Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
logger.info("Creating cache manager");
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
cacheConfigurations.put("v",createCacheConfiguration(1200, RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())));
cacheConfigurations.put("products",createCacheConfiguration(-1,RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer())));
return RedisCacheManager
.builder(connectionFactory)
.cacheDefaults(createCacheConfiguration(-1,RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())))
.withInitialCacheConfigurations(cacheConfigurations)
.build();
}
}
Here is an example of how a POJO is serialized per the configuration above:
#Data
#Builder
#AllArgsConstructor
public class ProductSummary implements Serializable {
#Id
private String id;
private String accountId;
private String name;
private String roles;
private String groups;
}
and the way it is serialized:
\xac\xed\x00\x05sr\x004com.foobar.ProductSummaryh\xb3\x9d
\xd4\x0f\xac\xea\xb3\x02\x00\x05L\x00\taccountIdt\x00
\x12Ljava/lang/String;L\x00\x06groupsq\x00~\x00\x01L\x00
\x02idq\x00~\x00\x01L\x00\x04nameq\x00~\x00\x01L\x00
\x05rolesq\x00~\x00\x01xpt\x00\x19acctFv825MKt\x00
\nimwebuserst\x00\x13prod0lwJAWEYt\x00\x11ProductName/2020t\x00\x00
What I'd like is for it to be (in Redis as a Hash):
>HGETALL KEY
1) "_class"
2) "com.foobar.cache.CheckoutState"
3) "accountId"
4) "ACC000001"
5) "name"
6) "lorem ipsum whatever"
7) "roles"
8) "role1,role2,role3"
9) "groups"
10) "groupA,groupB"
with the hash Key being the id.

Guess this isn't that relevant anymore, but maybe somebody else want to do the same, like me.
It isn't that easy, as deep inside the implementation the DefaultRedisCacheWriter
uses "SET" - what you basically want is connection.hSet. As I see it the only way to achieve this would be to implement an own RedisCacheWriter.
This might be not enough, as the interface defines:
which is called by the redis cache manager:
Long story short, I assume an own "RedisCache" cache class might be the easiest way to go, using just a simple spring data redis "HASH" class or maybe a bit simpler using the RedisTemplate in combination with the spring ObjectHashMapper to created these hashes.

Related

How to access Spring properties from an entity?

I have a spring app, that pushes data in an s3 bucket.
public class Ebook implements Serializable {
#Column(name= "cover_path", unique = true, nullable = true)
private String coverPath;
private String coverDownloadUrl;
#Value("${aws.cloudfront.region}")
private String awsCloudFrontDns;
#PostLoad
public void init(){
// I want to access the property here
System.out.println("PostConstruct");
String coverDownloadUrl = "https://"+awsCloudFrontDns+"/"+coverPath;
}
When a data is pushed, let's say my cover here, I get the key 1/test-folder/mycover.jpg which is the important part of the future http URL of the data.
When I read the data from database, I enter inside #PostLoad method and I want construct the complete URL using the cloudfront value. This value changes frequently so we don't want to save hardly in the database.
How could I do to construct my full path just after reading the data in database?
The only way to do this is to use a service that update the data after using repository to read it? For readbyId it can be a good solution, but for reading list or using other jpa methods, this solutions won't work because I have each time to create a dedicated service for the update.
It doesn't look good for Entity to depend on property.
How about EntityListener.
#Component
public class EbookEntityListener {
#Value("${aws.cloudfront.region}")
private String awsCloudFrontDns;
#PostLoad
void postload(Ebook entity) { entity.updateDns(awsCloudFrontDns); }
}
I recommend trying this way :)

Spring Data Redis. JPA Repository findBy sometimes fails to fetch existing record

I see some weird case. Sometimes my findBy...() method returns null instead of some object inserted and fetched successfully before. After that the needed object fetches fine. In other words sometimes the search is not working.
Spring Boot edition: 1.5.2.RELEASE
spring-boot-starter-data-redis: 1.5.22.RELEASE
"maxmemory-policy" setting is set to "noeviction"
My obj declaration:
#RedisHash("session")
public class Session implements Serializable {
#Id
private String id;
#Indexed
private Long internalChatId;
#Indexed
private boolean active;
#Indexed
private String chatId;
}
JPA Repository:
#Repository
public interface SessionRepository extends CrudRepository<Session, String> {
Session findByInternalChatIdAndActive(Long internalChatId, Boolean isActive);
}
Redis config:
#Bean
public LettuceConnectionFactory redisConnectionFactory(
RedisProperties redisProperties) {
return new LettuceConnectionFactory(
redisProperties.getRedisHost(),
redisProperties.getRedisPort());
}
#Bean
public RedisTemplate<?, ?> redisTemplate(LettuceConnectionFactory connectionFactory) {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
return template;
}
Thanx in advance for any assist.
We have recently seen similar behavior. In our scenario, we can have multiple threads that read and write to the same repository. Our null return occurs when one thread is doing a save to an object while another is doing a findById for that same object. The findById will occasionally fail. It appears that the save implementation does a delete followed by an add; if the findById gets in during the delete, the null result is returned.
We've had good luck so far in our test programs that can reproduce the null return using a Java Semaphore to gate all access (read, write, delete) to the repository. When the repository access methods are all gated by the same semaphore, we have not seen a null return. Our next step is to try adding the synchronized keyword to the methods in the class that access the repository (as an alternative to using the Semaphore).
This should not happen I don't what is reason. But you can use Option class and if it returns null at least you can avoid exception.
Something like:
Optional<Session> findByInternalChatIdAndActive(Long internalChatId, Boolean isActive);

How to explictly state that an Entity is new (transient) in JPA?

I am using a Spring Data JpaRepository, with Hibernate as JPA provider.
Normally when working directly with Hibernate, the decision between EntityManager#persist() and EntityManager#save() is up to the programmer. With Spring Data repositories, there is only save(). I do not want to discuss the pros and cons here. Let us consider the following, simple base class:
#MappedSuperclass
public abstract class PersistableObject {
#Id
private String id;
public PersistableObject(){
this.id = UUID.randomUUID().toString();
}
// hashCode() and equals() are implemented based on equality of 'id'
}
Using this base class, the Spring Data repository cannot tell which Entities are "new" (have not been saved to DB yet), as the regular check for id == null clearly does not work in this case, because the UUIDs are eagerly assigned to ensure the correctness of equals() and hashCode(). So what the repository seems to do is to always invoke EntityManager#merge() - which is clearly inefficient for transient entities.
The question is: how do I tell JPA (or Spring Data) that an Entity is new, such that it uses EntityManager#persist() instead of #merge() if possible?
I was thinking about something along these lines (using JPA lifecycle callbacks):
#MappedSuperclass
public abstract class PersistableObject {
#Transient
private boolean isNew = true; // by default, treat entity as new
#PostLoad
private void loaded(){
// a loaded entity is never new
this.isNew = false;
}
#PostPersist
private void saved(){
// a saved entity is not new anymore
this.isNew = false;
}
// how do I get JPA (or Spring Data) to use this method?
public boolean isNew(){
return this.isNew;
}
// all other properties, constructor, hashCode() and equals same as above
}
I'd like to add one more remark here. Even though it only works for Spring Data and not for general JPA, I think it's worth mentioning that Spring provides the Persistable<T> interface which has two methods:
T getId();
boolean isNew();
By implementing this interface (e.g. as in the opening question post), the Spring Data JpaRepositories will ask the entity itself if it is new or not, which can be pretty handy in certain cases.
Maybe you should add #Version column:
#Version
private Long version
in the case of new entity it will be null

Spring Data Elasticsearch's ElasticsearchTemplate vs ElasticsearchRepository

I am in reference to Spring Data Elasticsearch's
org.springframework.data.elasticsearch.repository.ElasticsearchRepository
org.springframework.data.elasticsearch.core.ElasticsearchTemplate
It seems they are two different APIs that achieve the same goal but I am not sure what the differences are between those two types and more importantly when to use which.
Can someone please provide advice and guidance?
ElasticsearchRepository is intended to be used as a repository for your domain classes, as it's typed. It extends Spring interfaces for repositories so it can used as one of them. You'll feel very comfortable with it if you are used to Spring repositories.
All you need to start indexing your objects to Elasticsearch is to add the #Document annotation to them and create a Repository interface extending ElasticsearchRepository.
The indexable class:
#Document(
indexName = "customers",
type = "customer",
shards = 1,
replicas = 0,
refreshInterval = "-1"
)
public class Customer {
#Id
private Long id;
private String name;
public Customer() {
}
public Customer(String name) {
this.name = name;
}
//Getters and setters omited
}
The repostitory:
public interface CustomerRepository
extends ElasticsearchRepository<Customer, Long>{
}
With this you can, out of the box, make CRUD operations, index, search and other common operations.
ElasticsearchTemplate, by other hand, is an elasticsearch client for working with your indexes, and it's not typed or related to your domain classes. It's more powerful since you can do many tasks not available to the repository implementation, like deleting an index or making aggregated searchs.

Jpa + Spring - automatically setting transient field value after read from DB

what's the best solution to set a value for a field marked #Transient after the entity has been read from the data source?
I'm using EclipseLink and I'm trying the DescriptorEventAdapter with his postBuild event solution because I need also to get the default value using a Spring bean (obviuosly using DI), but I would know if there is any simpler solution that I'm missing.
Thanks in advance
Here's the simple approach if you're using a repository or DAO:
#Repository
class YourRepository {
#Autowired
private Bean bean;
#PersistenceContext
private EntityManager entityManager;
#Transactional(readOnly = true)
public YourEntity find(..) {
YourEntity entity = lookupUsingEntityManager();
entity.transientField = bean.getDefaultValue();
return entity;
}
}
Here's another approach if you are using active record -style entities:
#Entity
class YourEntity {
#Transient
public Object field;
#PostLoad
public void populateField() {
field = new BeanHolder().bean.getDefaultValueForField();
}
#Configurable
private static class BeanHolder {
#Autowired private Bean bean;
}
}
Mind the semi-pseudo-code. Note that the latter approach works only if you use compile- or load-time AspectJ weaving with <context:spring-configured />.
You got entity which has transient field and the value is always taken from service using DI?
What is the purpose of the field? It's used for some calculation within any entity method?
Such calculation should probably use service's method to obtain the value.
As value from any service is used, I'm not sure whether such calculation (method) belong into entity.
Note that entity and service has completely different lifecycle. The value is changing in the time so it does not make the sense to supply the value in entity's factory at the beginning of it's life?

Resources