5

I'm currently using Redis (3.2.100) with Spring data redis (1.8.9) and with Jedis connector. When i use save() function on an existing entity, Redis delete my entity and re create the entity.

In my case i need to keep this existing entity and only update attributes of the entity. (I have another thread which read the same entity at the same time)

In Spring documentation (https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis.repositories.partial-updates), i found the partial update feature. Unfortunately, the example in the documentation use the update() method of RedisTemplate. But this method do not exist.

So did you ever use Spring-data-redis partial update?

There is another method to update entity redis without delete before?

Thanks

3 Answers3

4

To get RedisKeyValueTemplate, you can do:

@Autowired
private RedisKeyValueTemplate redisKVTemplate;

redisKVTemplate.update(entity)

1

You should use RedisKeyValueTemplate for make partial update.

1

Well, consider following docs link and also spring data tests (link) actually made 0 contribution to resulting solution.

Consider following entity

@RedisHash(value = "myservice/lastactivity")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class LastActivityCacheEntity implements Serializable {
    @Id
    @Indexed
    @Size(max = 50)
    private String user;
    private long lastLogin;
    private long lastProfileChange;
    private long lastOperation;
}

Let's assume that:

  1. we don't want to do complex read-write exercise on every update.

    entity = lastActivityCacheRepository.findByUser(userId); lastActivityCacheRepository.save(LastActivityCacheEntity.builder() .user(entity.getUser()) .lastLogin(entity.getLastLogin()) .lastProfileChange(entity.getLastProfileChange()) .lastOperation(entity.getLastOperation()).build()); what if there would pop up some 100 rows? then on each update entity got to fetched and saved, quite inefficient, but still would work out.

  2. we don't actually want complex exercises with opsForHash + ObjectMapper + configuring beans approach - it's quite hard to implement and maintain (for example link)

So we're about to use something like:

@Autowired
private final RedisKeyValueTemplate redisTemplate;

void partialUpdate(LastActivityCacheEntity update) {
  var partialUpdate = PartialUpdate
      .newPartialUpdate(update.getUser(), LastActivityCacheEntity.class);
  if (update.getLastLogin() > 0)
      partialUpdate.set("lastlastLogin", update.getLastLogin());
  if (update.getLastProfileChange() > 0)
      partialUpdate.set("lastProfileChange", update.getLastProfileChange());
  if (update.getLastOperation() > 0)
    partialUpdate.set("lastOperation", update.getLastOperation());
  redisTemplate.update(partialUpdate);
}

and the thing is - it doesn't really work for this case.

That is, values getting updated but you can not query new property later on via repository entity lookup: certain lastActivityCacheRepository.findAll() will return unchanged properties.

Here's the solution:

LastActivityCacheRepository.java:

@Repository
public interface LastActivityCacheRepository extends CrudRepository<LastActivityCacheEntity, String>, LastActivityCacheRepositoryCustom {
    Optional<LastActivityCacheEntity> findByUser(String user);
}

LastActivityCacheRepositoryCustom.java:

public interface LastActivityCacheRepositoryCustom {
    void updateEntry(String userId, String key, long date);
}

LastActivityCacheRepositoryCustomImpl.java

@Repository
public class LastActivityCacheRepositoryCustomImpl implements LastActivityCacheRepositoryCustom {
    @Autowired
    private final RedisKeyValueTemplate redisKeyValueTemplate;

    @Override
    public void updateEntry(String userId, String key, long date) {
        redisKeyValueTemplate.update(new PartialUpdate<>(userId, LastActivityCacheEntity.class)
            .set(key, date));
    }
}

And finally working sample:

void partialUpdate(LastActivityCacheEntity update) {
  if ((lastActivityCacheRepository.findByUser(update.getUser()).isEmpty())) {
      lastActivityCacheRepository.save(LastActivityCacheEntity.builder().user(update.getUser()).build());
  }
  if (update.getLastLogin() > 0) {
      lastActivityCacheRepository.updateEntry(update.getUser(),
          "lastlastLogin",
          update.getLastLogin());
  }
  if (update.getLastProfileChange() > 0) {
      lastActivityCacheRepository.updateEntry(update.getUser(),
          "lastProfileChange",
          update.getLastProfileChange());
  }
  if (update.getLastOperation() > 0) {
      lastActivityCacheRepository.updateEntry(update.getUser(),
          "lastOperation",
          update.getLastOperation());
}

all credits to Chris Richardson and his src

If you don't want to type your field names as strings in the updateEntry method, you can use use the lombok annotation on your entity class @FieldNameConstants. This creates field name constants for you and then you can access your field names like this:

...
      if (update.getLastOperation() > 0) {
          lastActivityCacheRepository.updateEntry(update.getUser(),
              LastActivityCache.Fields.lastOperation, // <- instead of "lastOperation"
              update.getLastOperation());
...

This makes refactoring the field names more bug-proof.

im_infamous
  • 972
  • 1
  • 17
  • 29