>Java >java지도 시간 >SpringBoot 프로젝트에서 캐시를 사용하는 방법

SpringBoot 프로젝트에서 캐시를 사용하는 방법

PHPz
PHPz앞으로
2023-05-16 14:34:13989검색

머리말

캐싱은 자주 액세스하는 데이터를 메모리에 저장하고 데이터베이스와 같은 기본 데이터 소스에 대한 부담을 줄여 시스템의 성능과 안정성을 효과적으로 향상시킬 수 있습니다. 다들 프로젝트에 어느 정도 사용해 보셨을 거라 생각하는데, 저희 프로젝트도 예외는 아닙니다. 그런데 최근 회사의 코드를 검토해보니 글이 매우 멍청하고 수준이 낮았습니다.

public User getById(String id) {
	User user = cache.getUser();
    if(user != null) {
        return user;
    }
    // 从数据库获取
    user = loadFromDB(id);
    cahce.put(id, user);
	return user;
}

In 사실, Spring Boot는 캐싱 추상화를 통해 애플리케이션에 캐싱을 쉽게 추가할 수 있는 강력한 기능을 제공합니다. 이 기사에서는 캐싱에 대한 모범 사례를 구현하기 위해 Spring에서 제공하는 다양한 캐시 주석을 사용하는 방법에 대해 설명합니다.

캐싱 활성화 @EnableCaching

이제 대부분의 프로젝트는 SpringBoot 프로젝트입니다. 캐싱 기능을 활성화하려면 시작 클래스에 @EnableCaching 주석을 추가할 수 있습니다. @EnableCaching来开启缓存功能。

@SpringBootApplication
@EnableCaching
public class SpringCacheApp {

    public static void main(String[] args) {
        SpringApplication.run(Cache.class, args);
    }
}

既然要能使用缓存,就需要有一个缓存管理器Bean,默认情况下,@EnableCaching 将注册一个ConcurrentMapCacheManager的Bean,不需要单独的 bean 声明。ConcurrentMapCacheManager将值存储在ConcurrentHashMap的实例中,这是缓存机制的最简单的线程安全实现。

自定义缓存管理器

默认的缓存管理器并不能满足需求,因为她是存储在jvm内存中的,那么如何存储到redis中呢?这时候需要添加自定义的缓存管理器。

1.添加依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.配置Redis缓存管理器

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
            .disableCachingNullValues()
            .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory())
            .cacheDefaults(redisCacheConfiguration)
            .build();

        return redisCacheManager;
    }
}

现在有了缓存管理器以后,我们如何在业务层面操作缓存呢?

我们可以使用@Cacheable@CachePut 或@CacheEvict 注解来操作缓存了。

@Cacheable

该注解可以将方法运行的结果进行缓存,在缓存时效内再次调用该方法时不会调用方法本身,而是直接从缓存获取结果并返回给调用方。

SpringBoot 프로젝트에서 캐시를 사용하는 방법

例子1:缓存数据库查询的结果。

@Service
public class MyService {

    @Autowired
    private MyRepository repository;

    @Cacheable(value = "myCache", key = "#id")
    public MyEntity getEntityById(Long id) {
        return repository.findById(id).orElse(null);
    }
}

在此示例中,@Cacheable 注解用于缓存 getEntityById()方法的结果,该方法根据其 ID 从数据库中检索 MyEntity 对象。

但是如果我们更新数据呢?旧数据仍然在缓存中?

@CachePut

然后@CachePut 出来了, 与 @Cacheable 注解不同的是使用 @CachePut 注解标注的方法,在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式写入指定的缓存中。@CachePut 注解一般用于更新缓存数据,相当于缓存使用的是写模式中的双写模式。

@Service
public class MyService {

    @Autowired
    private MyRepository repository;

    @CachePut(value = "myCache", key = "#entity.id")
    public void saveEntity(MyEntity entity) {
        repository.save(entity);
    }
}

@CacheEvict

标注了 @CacheEvict 注解的方法在被调用时,会从缓存中移除已存储的数据。@CacheEvict 注解一般用于删除缓存数据,相当于缓存使用的是写模式中的失效模式。

SpringBoot 프로젝트에서 캐시를 사용하는 방법

@Service
public class MyService {

    @Autowired
    private MyRepository repository;

     @CacheEvict(value = "myCache", key = "#id")
    public void deleteEntityById(Long id) {
        repository.deleteById(id);
    }
}

@Caching

@Caching 注解用于在一个方法或者类上,同时指定多个 Spring Cache 相关的注解。

SpringBoot 프로젝트에서 캐시를 사용하는 방법

例子1:@Caching注解中的evict属性指定在调用方法 saveEntity 时失效两个缓存。

@Service
public class MyService {

    @Autowired
    private MyRepository repository;

    @Cacheable(value = "myCache", key = "#id")
    public MyEntity getEntityById(Long id) {
        return repository.findById(id).orElse(null);
    }

    @Caching(evict = {
        @CacheEvict(value = "myCache", key = "#entity.id"),
        @CacheEvict(value = "otherCache", key = "#entity.id")
    })
    public void saveEntity(MyEntity entity) {
        repository.save(entity);
    }

}

例子2:调用getEntityById方法时,Spring会先检查结果是否已经缓存在myCache缓存中。如果是,Spring 将返回缓存的结果而不是执行该方法。如果结果尚未缓存,Spring 将执行该方法并将结果缓存在 myCache 缓存中。方法执行后,Spring会根据@CacheEvict注解从otherCache缓存中移除缓存结果。

@Service
public class MyService {

    @Caching(
        cacheable = {
            @Cacheable(value = "myCache", key = "#id")
        },
        evict = {
            @CacheEvict(value = "otherCache", key = "#id")
        }
    )
    public MyEntity getEntityById(Long id) {
        return repository.findById(id).orElse(null);
    }

}

例子3:当调用saveData方法时,Spring会根据@CacheEvict注解先从otherCache缓存中移除数据。然后,Spring 将执行该方法并将结果保存到数据库或外部 API。

方法执行后,Spring 会根据@CachePut注解将结果添加到 myCachemyOtherCache 和 myThirdCache 缓存中。Spring 还将根据@Cacheable注解检查结果是否已缓存在 myFourthCache 和 myFifthCache

@Service
public class MyService {

    @Caching(
        put = {
            @CachePut(value = "myCache", key = "#result.id"),
            @CachePut(value = "myOtherCache", key = "#result.id"),
            @CachePut(value = "myThirdCache", key = "#result.name")
        },
        evict = {
            @CacheEvict(value = "otherCache", key = "#id")
        },
        cacheable = {
            @Cacheable(value = "myFourthCache", key = "#id"),
            @Cacheable(value = "myFifthCache", key = "#result.id")
        }
    )
    public MyEntity saveData(Long id, String name) {
        // Code to save data to a database or external API
        MyEntity entity = new MyEntity(id, name);
        return entity;
    }

}

캐싱을 사용하려면 캐시 관리자 Bean이 필요합니다. 기본적으로 @EnableCachingConcurrentMapCacheManager Bean을 등록하며 별도의 Bean은 없습니다. 필수. ConcurrentMapCacheManager은 캐싱 메커니즘의 가장 간단한 스레드 안전 구현인 ConcurrentHashMap의 인스턴스에 값을 저장합니다. 🎜🎜사용자 정의 캐시 관리자🎜🎜기본 캐시 관리자는 jvm 메모리에 저장되기 때문에 요구 사항을 충족할 수 없는데 어떻게 redis에 저장하나요? 이때 사용자 정의 캐시 관리자를 추가해야 합니다. 🎜🎜1. 종속성 추가🎜
@CacheConfig(cacheNames={"myCache"})
@Service
public class MyService {

    @Autowired
    private MyRepository repository;

    @Cacheable(key = "#id")
    public MyEntity getEntityById(Long id) {
        return repository.findById(id).orElse(null);
    }

    @CachePut(key = "#entity.id")
    public void saveEntity(MyEntity entity) {
        repository.save(entity);
    }

    @CacheEvict(key = "#id")
    public void deleteEntityById(Long id) {
        repository.deleteById(id);
    }
}
🎜2. Redis 캐시 관리자 구성🎜
//when id >10, the @CachePut works. 
@CachePut(key = "#entity.id", condition="#entity.id > 10")
public void saveEntity(MyEntity entity) {
	repository.save(entity);
}


//when result != null, the @CachePut works.
@CachePut(key = "#id", condition="#result == null")
public void saveEntity1(MyEntity entity) {
	repository.save(entity);
}
🎜이제 캐시 관리자가 생겼으니 비즈니스 수준에서 캐시를 어떻게 운영할까요? 🎜🎜@Cacheable, @CachePut 또는 @CacheEvict 주석을 사용하여 캐시를 작동할 수 있습니다. 🎜🎜@Cacheable🎜🎜이 주석은 메소드 실행 결과를 캐시할 수 있습니다. 캐시 시간 제한 내에 메소드가 다시 호출되면 메소드 자체는 호출되지 않지만 결과는 캐시에서 직접 얻어서 반환됩니다. 방문객. 🎜🎜SpringBoot 프로젝트에서 캐시 캐시를 사용하는 방법🎜🎜예제 1: 캐시 데이터베이스 쿼리의 결과입니다. 🎜
//方法调用完成之后,清理所有缓存
@CacheEvict(value="myCache",allEntries=true)
public void delectAll() {
    repository.deleteAll();
}

//方法调用之前,清除所有缓存
@CacheEvict(value="myCache",beforeInvocation=true)
public void delectAll() {
    repository.deleteAll();
}
🎜이 예에서 @Cacheable 주석은 ID를 기반으로 데이터베이스에서 getEntityById() 메서드의 결과를 캐시하는 데 사용됩니다. > MyEntity 개체를 검색합니다. 🎜🎜하지만 데이터를 업데이트하면 어떻게 될까요? 오래된 데이터가 아직 캐시에 있나요? 🎜🎜@CachePut🎜🎜 그러다가 @CachePut이 나왔습니다. @Cacheable 주석과 다른 점은 @CachePut 주석 방식을 사용한다는 것입니다. 실행 이전에 실행한 결과가 캐시에 있는지 확인하는 대신 매번 메소드를 실행하고 실행 결과를 키-값 쌍의 형태로 지정된 캐시에 기록합니다. @CachePut 주석은 일반적으로 캐시 데이터를 업데이트하는 데 사용되며 이는 쓰기 모드에서 이중 쓰기 모드를 사용하는 캐시와 동일합니다. 🎜rrreee🎜@CacheEvict🎜🎜 @CacheEvict 주석이 달린 메서드는 호출 시 저장된 데이터를 캐시에서 제거합니다. @CacheEvict 주석은 일반적으로 캐시된 데이터를 삭제하는 데 사용됩니다. 이는 쓰기 모드에서 실패 모드를 사용하는 캐시와 동일합니다. 🎜🎜SpringBoot 프로젝트에서 캐시 캐시를 사용하는 방법🎜rrreee🎜@Caching 🎜 🎜@Caching 주석은 메서드나 클래스에 여러 Spring Cache 관련 주석을 동시에 지정하는 데 사용됩니다. 🎜🎜SpringBoot 프로젝트에서 캐시 캐시를 사용하는 방법🎜🎜예제 1: @Caching 주석의 evict 속성은 saveEntity 메소드가 호출될 때 두 캐시가 모두 무효화됨을 지정합니다. 🎜rrreee🎜예 2: getEntityById 메서드를 호출할 때 Spring은 먼저 결과가 myCache 캐시에 캐시되었는지 여부를 확인합니다. 그렇다면 Spring은 메서드를 실행하는 대신 캐시된 결과를 반환합니다. 결과가 아직 캐시되지 않은 경우 Spring은 메서드를 실행하고 myCache 캐시에 결과를 캐시합니다. 메서드가 실행된 후 Spring은 @CacheEvict 주석에 따라 otherCache 캐시에서 캐시된 결과를 제거합니다. 🎜rrreee🎜예 3: saveData 메서드를 호출할 때 Spring은 먼저 @CacheEvict 주석에 따라 otherCache 캐시에서 데이터를 제거합니다. 그런 다음 Spring은 메서드를 실행하고 결과를 데이터베이스나 외부 API에 저장합니다. 🎜🎜메서드가 실행된 후 Spring은 @CachePut에 따라 <code>myCache, myOtherCachemyThirdCache 캐시에 결과를 추가합니다. 주석 중간. Spring은 또한 @Cacheable 주석을 기반으로 결과가 myFourthCachemyFifthCache 캐시에 캐시되었는지 여부를 확인합니다. 결과가 아직 캐시되지 않은 경우 Spring은 적절한 캐시에 결과를 캐시합니다. 결과가 이미 캐시된 경우 Spring은 메서드를 다시 실행하는 대신 캐시된 결과를 반환합니다. 🎜
@Service
public class MyService {

    @Caching(
        put = {
            @CachePut(value = "myCache", key = "#result.id"),
            @CachePut(value = "myOtherCache", key = "#result.id"),
            @CachePut(value = "myThirdCache", key = "#result.name")
        },
        evict = {
            @CacheEvict(value = "otherCache", key = "#id")
        },
        cacheable = {
            @Cacheable(value = "myFourthCache", key = "#id"),
            @Cacheable(value = "myFifthCache", key = "#result.id")
        }
    )
    public MyEntity saveData(Long id, String name) {
        // Code to save data to a database or external API
        MyEntity entity = new MyEntity(id, name);
        return entity;
    }

}

@CacheConfig

通过@CacheConfig 注解,我们可以将一些缓存配置简化到类级别的一个地方,这样我们就不必多次声明相关值:

@CacheConfig(cacheNames={"myCache"})
@Service
public class MyService {

    @Autowired
    private MyRepository repository;

    @Cacheable(key = "#id")
    public MyEntity getEntityById(Long id) {
        return repository.findById(id).orElse(null);
    }

    @CachePut(key = "#entity.id")
    public void saveEntity(MyEntity entity) {
        repository.save(entity);
    }

    @CacheEvict(key = "#id")
    public void deleteEntityById(Long id) {
        repository.deleteById(id);
    }
}

Condition & Unless

  • condition作用:指定缓存的条件(满足什么条件才缓存),可用 SpEL 表达式(如 #id>0,表示当入参 id 大于 0 时才缓存)

  • unless作用 : 否定缓存,即满足 unless 指定的条件时,方法的结果不进行缓存,使用 unless 时可以在调用的方法获取到结果之后再进行判断(如 #result == null,表示如果结果为 null 时不缓存)

//when id >10, the @CachePut works. 
@CachePut(key = "#entity.id", condition="#entity.id > 10")
public void saveEntity(MyEntity entity) {
	repository.save(entity);
}


//when result != null, the @CachePut works.
@CachePut(key = "#id", condition="#result == null")
public void saveEntity1(MyEntity entity) {
	repository.save(entity);
}

清理全部缓存

通过allEntriesbeforeInvocation属性可以来清除全部缓存数据,不过allEntries是方法调用后清理,beforeInvocation是方法调用前清理。

//方法调用完成之后,清理所有缓存
@CacheEvict(value="myCache",allEntries=true)
public void delectAll() {
    repository.deleteAll();
}

//方法调用之前,清除所有缓存
@CacheEvict(value="myCache",beforeInvocation=true)
public void delectAll() {
    repository.deleteAll();
}

SpEL表达式

Spring Cache注解中频繁用到SpEL表达式,那么具体如何使用呢?

SpEL 表达式的语法

SpringBoot 프로젝트에서 캐시를 사용하는 방법

Spring Cache可用的变量

SpringBoot 프로젝트에서 캐시를 사용하는 방법

最佳实践

通过Spring缓存注解可以快速优雅地在我们项目中实现缓存的操作,但是在双写模式或者失效模式下,可能会出现缓存数据一致性问题(读取到脏数据),Spring Cache 暂时没办法解决。最后我们再总结下Spring Cache使用的一些最佳实践。

  • 只缓存经常读取的数据:缓存可以显着提高性能,但只缓存经常访问的数据很重要。很少或从不访问的缓存数据会占用宝贵的内存资源,从而导致性能问题。

  • 根据应用程序的特定需求选择合适的缓存提供程序和策略。SpringBoot 支持多种缓存提供程序,包括 EhcacheHazelcast 和 Redis

  • 使用缓存时请注意潜在的线程安全问题。对缓存的并发访问可能会导致数据不一致或不正确,因此选择线程安全的缓存提供程序并在必要时使用适当的同步机制非常重要。

  • 避免过度缓存。缓存对于提高性能很有用,但过多的缓存实际上会消耗宝贵的内存资源,从而损害性能。在缓存频繁使用的数据和允许垃圾收集不常用的数据之间取得平衡很重要。

  • 使用适当的缓存逐出策略。使用缓存时,重要的是定义适当的缓存逐出策略以确保在必要时从缓存中删除旧的或陈旧的数据。

  • 使用适当的缓存键设计。缓存键对于每个数据项都应该是唯一的,并且应该考虑可能影响缓存数据的任何相关参数,例如用户 ID、时间或位置。

  • 常规数据(读多写少、即时性与一致性要求不高的数据)完全可以使用 Spring Cache,至于写模式下缓存数据一致性问题的解决,只要缓存数据有设置过期时间就足够了。

  • 特殊数据(读多写多、即时性与一致性要求非常高的数据),不能使用 Spring Cache,建议考虑特殊的设计(例如使用 Cancal 中间件等)。

위 내용은 SpringBoot 프로젝트에서 캐시를 사용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제