Java Spring Boot Redis – How to know when is the Entity updated so i can fetch from database instead of Redis?

huangapple go评论75阅读模式
英文:

Java Spring Boot Redis – How to know when is the Entity updated so i can fetch from database instead of Redis?

问题

I understand that you want a translation of the provided code and text without any additional information or responses. Here is the translated code:

我对Redis还不熟悉这可能是一个基本问题

我使用了`@Cacheable()``@CacheEvict()`注解当用户被更新时如果我通过ID获取用户它会获取到缓存的过时的数据当然如果我使用`@CacheEvict()`,这种情况就不会发生

然而我对`@CacheEvict()`感到困惑因为它的结果与不使用它的结果相同那么使用它有什么意义呢如果有一个需要3秒才能完成的过程那么使用`CacheEvict()`也需要3秒

这是我的`UserServiceImpl.java`

```java
package com.example.demo.serviceImpl;

import lombok.AllArgsConstructor;
import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
import com.example.demo.service.UserService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

@Service
@EnableCaching
@AllArgsConstructor
public class UserServiceImpl implements UserService {

    private UserRepository userRepository;

    @Override
    public User createUser(User user) {
        return userRepository.save(user);
    }

    @Override
    @CacheEvict(value = "users")
    public User findUser(String userId) {
        doLongRunningTask();
        return userRepository.findById(userId).orElseThrow();
    }

    @Override
    @Cacheable(value = "users")
    public List<User> findAll() {
        return (List<User>) userRepository.findAll();
    }

    @Override
    @CacheEvict(value = "users", key = "#user.id")
    public User updateUser(String userId, User user) {
        doLongRunningTask();
        user.setUpdatedAt(LocalDateTime.now());
        return userRepository.save(user);
    }

    @Override
    @CacheEvict(value = "users", key = "#userId")
    public void deleteUser(String userId) {
        userRepository.deleteById(userId);
    }

    private void doLongRunningTask() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

我的RedisConfig.java类:

package com.example.demo.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;

import java.time.Duration;

import static org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair.fromSerializer;

@Configuration
public class RedisConfig {

    @Value("${redis.host}")
    private String redisHost;

    @Value("${redis.port}")
    private int redisPort;

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(redisHost);
        configuration.setPort(redisPort);
        return new LettuceConnectionFactory(configuration);
    }

    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheConfiguration cacheConfig = myDefaultCacheConfig(Duration.ofMinutes(10)).disableCachingNullValues();

        return RedisCacheManager
                .builder(redisConnectionFactory())
                .cacheDefaults(cacheConfig)
                .withCacheConfiguration("users", myDefaultCacheConfig(Duration.ofMinutes(5)))
                .build();
    }

    private RedisCacheConfiguration myDefaultCacheConfig(Duration duration) {
        return RedisCacheConfiguration
                .defaultCacheConfig()
                .entryTtl(duration)
                .serializeValuesWith(fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}

首次获取数据需要3秒。下一次获取相同数据只需要5毫秒(这次从Redis而不是PostgreSQL中获取)。然而,更新此用户并再次获取它会返回过时的数据而不是新更新的用户,导致数据不一致。

英文:

I'm new to redis, this might be a basic question.

I use @Cacheable() and @CacheEvict() annotation. When the user gets updated, and if i fetch the user by id, it fetches the cached (outdated) data. Of course, if i were to use @CacheEvict() this wouldn't happen.

However, i'm confused about @CacheEvict(), because the results are the same as if i don't use it -- so whats the point of using it? If there is a process that takes 3 seconds to finish, then using CacheEvict() would also take 3 seconds.

Here is my UserServiceImpl.java class:

package com.example.demo.serviceImpl;

import lombok.AllArgsConstructor;
import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
import com.example.demo.service.UserService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

@Service
@EnableCaching
@AllArgsConstructor
public class UserServiceImpl implements UserService {

    private UserRepository userRepository;

    @Override
    public User createUser(User user) {
        return userRepository.save(user);
    }

    @Override
    @CacheEvict(value = &quot;users&quot;)
    public User findUser(String userId) {
        doLongRunningTask();
        return userRepository.findById(userId).orElseThrow();
    }

    @Override
    @Cacheable(value = &quot;users&quot;)
    public List&lt;User&gt; findAll() {
        return (List&lt;User&gt;) userRepository.findAll();
    }

    @Override
    @CacheEvict(value = &quot;users&quot;, key = &quot;#user.id&quot;)
    public User updateUser(String userId, User user) {
        doLongRunningTask();
        user.setUpdatedAt(LocalDateTime.now());
        return userRepository.save(user);
    }

    @Override
    @CacheEvict(value = &quot;users&quot;, key = &quot;#userId&quot;)
    public void deleteUser(String userId) {
        userRepository.deleteById(userId);
    }

    private void doLongRunningTask() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

My RedisConfig.java class:

package com.example.demo.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;

import java.time.Duration;

import static org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair.fromSerializer;

@Configuration
public class RedisConfig {

    @Value(&quot;${redis.host}&quot;)
    private String redisHost;

    @Value(&quot;${redis.port}&quot;)
    private int redisPort;

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(redisHost);
        configuration.setPort(redisPort);
        return new LettuceConnectionFactory(configuration);
    }

    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheConfiguration cacheConfig = myDefaultCacheConfig(Duration.ofMinutes(10)).disableCachingNullValues();

        return RedisCacheManager
                .builder(redisConnectionFactory())
                .cacheDefaults(cacheConfig)
                .withCacheConfiguration(&quot;users&quot;, myDefaultCacheConfig(Duration.ofMinutes(5)))
                .build();
    }

    private RedisCacheConfiguration myDefaultCacheConfig(Duration duration) {
        return RedisCacheConfiguration
                .defaultCacheConfig()
                .entryTtl(duration)
                .serializeValuesWith(fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }

}

Fetching data for the first time takes 3 seconds. Fetching the same data next time takes 5 ms (this time gets pulled from Redis instead of postgres). However updating this user and fetching it again, gives outdated data instead of the newly updated user, causing data inconsistencies.

UPDATE: this is my model/User.java model class

package com.example.demo.model;

import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@Data
@Builder
@RedisHash(&quot;user&quot;)
public class User {

    @Id
    private String id;
    private String name;
    private Integer age;

}

I also have dto/UserDTO.java for converting the model into a REST response/request via API:

package com.example.demo.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO implements Serializable {

    @JsonProperty(value = &quot;id&quot;)
    private String id;

    @JsonProperty(value = &quot;name&quot;)
    private String name;

    @JsonProperty(value = &quot;age&quot;)
    private Integer age;

}

Thanks to @Max Kozlov this DTO class is now a Serializable so that Redis Cache can work properly.

The new RedisCacheConfig.java thanks to @Max Kozlov's answer looks like this:

package com.example.demo.config;

import com.example.demo.handler.DefaultCacheErrorHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

import java.time.Duration;

@Configuration
@EnableCaching
public class RedisCacheConfig implements CachingConfigurer {

    @Value(&quot;${redis.host}&quot;)
    private String redisHost;

    @Value(&quot;${redis.port}&quot;)
    private int redisPort;

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(redisHost);
        configuration.setPort(redisPort);
        return new LettuceConnectionFactory(configuration);
    }

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        return RedisCacheConfiguration
                .defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(15));
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        return new DefaultCacheErrorHandler();
    }

    @Bean(&quot;longLifeCacheManager&quot;)
    public CacheManager longLifeCacheManager() {
        RedisCacheConfiguration defaultConfiguration = RedisCacheConfiguration
                .defaultCacheConfig()
                .entryTtl(Duration.ofDays(90));

        return RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory())
                .cacheDefaults(defaultConfiguration)
                .build();
    }

    @Primary
    @Bean(&quot;shortLifeCacheManager&quot;)
    public CacheManager shortLifeCacheManager() {
        RedisCacheConfiguration defaultConfiguration = RedisCacheConfiguration
                .defaultCacheConfig()
                .entryTtl(Duration.ofDays(1));

        return RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory())
                .cacheDefaults(defaultConfiguration)
                .build();
    }

}

答案1

得分: 1

你对使用注解 @Cachable 的逻辑有误,因为你正在缓存整个用户列表而没有特定的键。

换句话说,你需要缓存特定的用户,例如,按 id

现在你已经用键 users 缓存了完整的用户列表。但是带有 users:id 键的条目被删除了。因此你的缓存没有被清除。

为了使缓存正常工作,你需要以以下方式重写你的服务类。

@Service
@EnableCaching
@AllArgsConstructor
public class UserServiceImpl implements UserService {

    private UserRepository userRepository;

    @Override
    public User createUser(User user) {
        return userRepository.save(user);
    }

    @Override
    @Cacheable(value = "users", key = "#userId")
    public User findUser(String userId) {
        doLongRunningTask();
        return userRepository.findById(userId).orElseThrow();
    }

    @Override
    public List<User> findAll() {
        return (List<User>) userRepository.findAll();
    }

    @Override
    @CacheEvict(value = "users", key = "#user.id")
    public User updateUser(String userId, User user) {
        doLongRunningTask();
        user.setUpdatedAt(LocalDateTime.now());
        return userRepository.save(user);
    }

    @Override
    @CacheEvict(value = "users", key = "#userId")
    public void deleteUser(String userId) {
        userRepository.deleteById(userId);
    }

}

在这里,我将注解 @Cacheable(value = "users", key = "#userId") 从方法 findAll() 移动到方法 findUser(String userId)。我还纠正了注解 @Cacheable 并在那里添加了键 key = "#userId"

不管怎样,如果你想要在 Redis 中缓存数据,你需要避免列表缓存,只对特定的实体应用这个方法。你还需要注意,如果你想要将实体存储在缓存中,那么你需要在实体本身创建一个序列化版本。

希望我的回答对你有帮助 =)

更新

对于 spring-boot 2.7.* 版本,这是 CacheConfiguration 类。

@EnableCaching
@Configuration
public class CacheConfiguration extends CachingConfigurerSupport {

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                                      .entryTtl(Duration.ofMinutes(15));
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        return new DefaultCacheErrorHandler();
    }

    @Bean("longLifeCacheManager")
    public CacheManager longLifeCacheManager(
        RedisConnectionFactory redisConnectionFactory
    ) {
        RedisCacheConfiguration defaultConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(90));

        return RedisCacheManager.RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory)
                .cacheDefaults(defaultConfiguration)
                .build();
    }

    @Bean("shortLifeCacheManager")
    @Primary
    public CacheManager shortLifeCacheManager(
        RedisConnectionFactory redisConnectionFactory
    ) {
        RedisCacheConfiguration defaultConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(1));

        return RedisCacheManager.RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory)
                .cacheDefaults(defaultConfiguration)
                .build();
    }

}

以及用于异常处理的 DefaultCacheErrorHandler 类。

public class DefaultCacheErrorHandler extends SimpleCacheErrorHandler {

    private static final Logger LOG = LoggerFactory.getLogger(DefaultCacheErrorHandler.class);

    @Override
    public void handleCacheGetError(
        RuntimeException exception,
        Cache cache,
        Object key
    ) {
        LOG.info(
            "handleCacheGetError ~ {}: {} - {}",
            exception.getMessage(),
            cache.getName(),
            key
        );
    }

    @Override
    public void handleCachePutError(
        RuntimeException exception,
        Cache cache,
        Object key,
        Object value
    ) {
        LOG.info(
            "handleCachePutError ~ {}: {} - {}",
            exception.getMessage(),
            cache.getName(),
            key
        );
        super.handleCachePutError(exception, cache, key, value);
    }

    @Override
    public void handleCacheEvictError(
        RuntimeException exception,
        Cache cache,
        Object key
    ) {
        LOG.info(
            "handleCacheEvictError ~ {}: {} - {}",
            exception.getMessage(),
            cache.getName(),
            key
        );
        super.handleCacheEvictError(exception, cache, key);
    }

    @Override
    public void handleCacheClearError(
        RuntimeException exception,
        Cache cache
    ) {
        LOG.info(
            "handleCacheClearError ~ {}: {}",
            exception.getMessage(),
            cache.getName()
        );
        super.handleCacheClearError(exception, cache);
    }
}

在这种情况下,使用了简单的 Java 序列化程序。需要缓存的对象类需要实现 Serializable 接口。

public class User implements Serializable {

    private Long id;
    private Long name;

    // getters/setters

}

配置类是启用通过 Redis 进行缓存的标准配置,除了一个细节,即 DefaultCacheErrorHandler。如果更改实体类并相应地更改该类的 serialVersionUID,则需要 DefaultCacheErrorHandler 以重置缓存。通常情况下,由于某种原因,Spring 在发生序列化错误时不会自动删除缓存,而是会引发错误,导致需要手动从 Redis 中删除必要的键。

附加回答

将实体序列化为 JSON 的问题与缓存本身不再相关。这是一个旧问题,更容易避免,并没有真正的解决方案。链接。总的来说,这种方法属于不良架构。因此,如果需要将从数据库中获取的数据序列化,那么最正确的选项将是创建一个 DTO 对象,并从实体中填充数据。因此,由于从数据库缓存数据是一个常见的任务,最好妥协而不是以不良架构告终。

还需要记住序列化和反序列化的速度。通常,在不需要大量时间来处理业务逻辑的任务中,大部分时间都用于 JSON 数据的序列化和反序列化。尽管 Jackson 库无论多么出色,都需要定期记住这

英文:

You have the wrong logic for using the annotation @Cachable because you are caching the entire list of users without a specific key.

In other words, you need to cache a specific user, for example, by id.

Now you have a full list of users cached with key users. But the entry with the users:id key is deleted. Therefore your cache is not evict.

For the cache to work, you need to rewrite your service class in this way.

@Service
@EnableCaching
@AllArgsConstructor
public class UserServiceImpl implements UserService {

    private UserRepository userRepository;

    @Override
    public User createUser(User user) {
        return userRepository.save(user);
    }

    @Override
    @Cacheable(value = &quot;users&quot;, key = &quot;#userId&quot;)
    public User findUser(String userId) {
        doLongRunningTask();
        return userRepository.findById(userId).orElseThrow();
    }

    @Override
    public List&lt;User&gt; findAll() {
        return (List&lt;User&gt;) userRepository.findAll();
    }

    @Override
    @CacheEvict(value = &quot;users&quot;, key = &quot;#user.id&quot;)
    public User updateUser(String userId, User user) {
        doLongRunningTask();
        user.setUpdatedAt(LocalDateTime.now());
        return userRepository.save(user);
    }

    @Override
    @CacheEvict(value = &quot;users&quot;, key = &quot;#userId&quot;)
    public void deleteUser(String userId) {
        userRepository.deleteById(userId);
    }

}

Here I moved the annotation @Cacheable(value = &quot;users&quot;, key = &quot;#userId&quot;) from method findAll() to method findUser(String userId). I also corrected the annotation @Cacheable and added the key key = &quot;#userId&quot; there.

Anyway, if you want to cache data in Redis. You need to avoid list caching, and only apply this approach to specific entities. You also need to pay attention to the fact that if you want to store entities in the cache, then you need to create a serial version in the entity itself.

Anyway, if you want to cache data in Redis. You need to avoid list caching, and only apply this approach to specific entities. You also need to pay attention to the fact that you are serializing entities in json. It is highly discouraged to do this, because relationships such as @ManyToOne and @OneToMany can cause you to get a recursive call to these relationships at the time of serialization.

Hope my answer helps you =)

UPDATE

CacheConfiguration class for spring-boot 2.7.* version.

@EnableCaching
@Configuration
public class CacheConfiguration extends CachingConfigurerSupport {

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                                      .entryTtl(Duration.ofMinutes(15));
    }

    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        return new DefaultCacheErrorHandler();
    }

    @Bean(&quot;longLifeCacheManager&quot;)
    public CacheManager longLifeCacheManager(
        RedisConnectionFactory redisConnectionFactory
    ) {
        RedisCacheConfiguration defaultConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(90));

        return RedisCacheManager.RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory)
                .cacheDefaults(defaultConfiguration)
                .build();
    }

    @Bean(&quot;shortLifeCacheManager&quot;)
    @Primary
    public CacheManager shortLifeCacheManager(
        RedisConnectionFactory redisConnectionFactory
    ) {
        RedisCacheConfiguration defaultConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(1));

        return RedisCacheManager.RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory)
                .cacheDefaults(defaultConfiguration)
                .build();
    }

}

and DefaultCacheErrorHandler class for exception handlings

public class DefaultCacheErrorHandler extends SimpleCacheErrorHandler {

    private static final Logger LOG = LoggerFactory.getLogger(DefaultCacheErrorHandler.class);

    @Override
    public void handleCacheGetError(
        @NotNull RuntimeException exception,
        @NotNull Cache cache,
        @NotNull Object key
    ) {
        LOG.info(
            &quot;handleCacheGetError ~ {}: {} - {}&quot;,
            exception.getMessage(),
            cache.getName(),
            key
        );
    }

    @Override
    public void handleCachePutError(
        @NotNull RuntimeException exception,
        @NotNull Cache cache,
        @NotNull Object key,
        Object value
    ) {
        LOG.info(
            &quot;handleCachePutError ~ {}: {} - {}&quot;,
            exception.getMessage(),
            cache.getName(),
            key
        );
        super.handleCachePutError(exception, cache, key, value);
    }

    @Override
    public void handleCacheEvictError(
        @NotNull RuntimeException exception,
        @NotNull Cache cache,
        @NotNull Object key
    ) {
        LOG.info(
            &quot;handleCacheEvictError ~ {}: {} - {}&quot;,
            exception.getMessage(),
            cache.getName(),
            key
        );
        super.handleCacheEvictError(exception, cache, key);
    }

    @Override
    public void handleCacheClearError(
        @NotNull RuntimeException exception,
        @NotNull Cache cache
    ) {
        LOG.info(
            &quot;handleCacheClearError ~ {}: {}&quot;,
            exception.getMessage(),
            cache.getName()
        );
        super.handleCacheClearError(exception, cache);
    }
}

in this case, a simple java serealizer is used. The object classes that need to be cached need to be implemented from the Serializable interface.


public class User implements Serializable {

    private Long id;
    provate Long name;

    // getters/setters

}

The config class is standard for enabling caching via redis, except for one detail, namely the DefaultCacheErrorHandler. The DefaultCacheErrorHandler is needed in order to reset the cache if you change the entity class and change the serialVersionUID of this class accordingly. This is usually required if you are adding or removing fields from a class. By default, for some reason, spring does not delete the cache when a serialization error occurs, but throws an error that leads to the need to manually delete the necessary keys from the redis.

Additional Answers

The problem of serializing entities to json is no longer related to the cache as such. This is an old problem that is easier to avoid and has no real solution. link In general, this approach lies in the plane of bad architecture. For this reason, if it is necessary to serialize data received from the database, then the most correct option would be to make an DTO object and fill it with data from the entity. For this reason, since caching data from the database is a common task, it is better to use standard java serialization.

It is also necessary to remember about the speed of serialization and deserialization. Often, in tasks where a lot of time is not required to process business logic, most of the time is taken just by serialization and deserialization of data from json. Unfortunately, no matter how good the Jackson library is, it needs to be remembered periodically. This question is beyond the scope of this discussion, but there are many interesting answers on this topic in the stackoverflow.

Plus, you can speculate on the amount of memory that is needed to store the cache. Json, as shown by recent changes in the backend development, is well replaced by the serialization approach in the same grpc. This allows in some cases to significantly save in terms of information transmitted over the network, which can also save a lot of memory in terms of caching. True, I do not know how much the standard java serialization is better in this respect than json.

Summarizing, we can say that although the data in the radis is better perceived by a person in the form of a json object. But as my experience suggests, for this task it is better to compromise than to end up with a bad architecture.

huangapple
  • 本文由 发表于 2023年7月6日 17:01:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/76627167.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定