-
Notifications
You must be signed in to change notification settings - Fork 2
Add caching example with Spring Data Redis #256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| /gradlew text eol=lf | ||
| *.bat text eol=crlf | ||
| *.jar binary |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| HELP.md | ||
| .gradle | ||
| build/ | ||
| !gradle/wrapper/gradle-wrapper.jar | ||
| !**/src/main/**/build/ | ||
| !**/src/test/**/build/ | ||
|
|
||
| ### STS ### | ||
| .apt_generated | ||
| .classpath | ||
| .factorypath | ||
| .project | ||
| .settings | ||
| .springBeans | ||
| .sts4-cache | ||
| bin/ | ||
| !**/src/main/**/bin/ | ||
| !**/src/test/**/bin/ | ||
|
|
||
| ### IntelliJ IDEA ### | ||
| .idea | ||
| *.iws | ||
| *.iml | ||
| *.ipr | ||
| out/ | ||
| !**/src/main/**/out/ | ||
| !**/src/test/**/out/ | ||
|
|
||
| ### NetBeans ### | ||
| /nbproject/private/ | ||
| /nbbuild/ | ||
| /dist/ | ||
| /nbdist/ | ||
| /.nb-gradle/ | ||
|
|
||
| ### VS Code ### | ||
| .vscode/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| plugins { | ||
| id 'java' | ||
| id 'org.springframework.boot' version '3.5.3' | ||
| id 'io.spring.dependency-management' version '1.1.7' | ||
| } | ||
|
|
||
| group = 'zin.rashidi' | ||
| version = '0.0.1-SNAPSHOT' | ||
|
|
||
| java { | ||
| toolchain { | ||
| languageVersion = JavaLanguageVersion.of(21) | ||
| } | ||
| } | ||
|
|
||
| repositories { | ||
| mavenCentral() | ||
| } | ||
|
|
||
| dependencies { | ||
| implementation 'org.springframework.boot:spring-boot-starter-data-jpa' | ||
| implementation 'org.springframework.boot:spring-boot-starter-data-redis' | ||
| runtimeOnly 'org.postgresql:postgresql' | ||
| testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
| testImplementation 'org.springframework.boot:spring-boot-testcontainers' | ||
| testImplementation 'org.testcontainers:junit-jupiter' | ||
| testImplementation 'org.testcontainers:postgresql' | ||
| testImplementation 'com.redis:testcontainers-redis' | ||
| testRuntimeOnly 'org.junit.platform:junit-platform-launcher' | ||
| } | ||
|
|
||
| tasks.named('test') { | ||
| useJUnitPlatform() | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| rootProject.name = 'data-redis-cache' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package zin.rashidi.dataredis.cache; | ||
|
|
||
| import org.springframework.boot.SpringApplication; | ||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
|
|
||
| @SpringBootApplication | ||
| public class DataRedisCacheApplication { | ||
|
|
||
| public static void main(String[] args) { | ||
| SpringApplication.run(DataRedisCacheApplication.class, args); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package zin.rashidi.dataredis.cache.cache; | ||
|
|
||
| import org.springframework.cache.annotation.EnableCaching; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.data.redis.cache.RedisCacheManager; | ||
| import org.springframework.data.redis.connection.RedisConnectionFactory; | ||
|
|
||
| /** | ||
| * @author Rashidi Zin | ||
| */ | ||
| @Configuration | ||
| @EnableCaching | ||
| class CacheConfiguration { | ||
|
|
||
| @Bean | ||
| public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { | ||
| return RedisCacheManager.create(connectionFactory); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||
| package zin.rashidi.dataredis.cache.customer; | ||||||
|
|
||||||
| import jakarta.persistence.Entity; | ||||||
| import jakarta.persistence.GeneratedValue; | ||||||
| import jakarta.persistence.Id; | ||||||
|
|
||||||
| import java.io.Serializable; | ||||||
|
|
||||||
| /** | ||||||
| * @author Rashidi Zin | ||||||
| */ | ||||||
| @Entity | ||||||
| class Customer implements Serializable { | ||||||
|
|
||||||
| @Id | ||||||
| @GeneratedValue | ||||||
| private Long id; | ||||||
|
|
||||||
| private String name; | ||||||
|
|
||||||
| @Override | ||||||
| public final boolean equals(Object o) { | ||||||
| return o instanceof Customer another && this.id.equals(another.id); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implementation will throw a
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| @Override | ||||||
| public int hashCode() { | ||||||
| return id.hashCode(); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This A common and safer practice is to use a constant hash code that doesn't depend on the generated ID, ensuring the hash code is stable throughout the entity's lifecycle.
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package zin.rashidi.dataredis.cache.customer; | ||
|
|
||
| import org.springframework.cache.annotation.Cacheable; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| /** | ||
| * @author Rashidi Zin | ||
| */ | ||
| interface CustomerRepository extends JpaRepository<Customer, Long> { | ||
|
|
||
| @Override | ||
| @Cacheable(cacheNames = "customers", key = "#root.methodName") | ||
| List<Customer> findAll(); | ||
|
|
||
| @Override | ||
| @Cacheable(cacheNames = "customerById") | ||
| Optional<Customer> findById(Long id); | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| spring.application.name=data-redis-cache |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package zin.rashidi.dataredis.cache; | ||
|
|
||
| import org.springframework.boot.SpringApplication; | ||
|
|
||
| public class TestDataRedisCacheApplication { | ||
|
|
||
| public static void main(String[] args) { | ||
| SpringApplication.from(DataRedisCacheApplication::main).with(TestcontainersConfiguration.class).run(args); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package zin.rashidi.dataredis.cache; | ||
|
|
||
| import com.redis.testcontainers.RedisContainer; | ||
| import org.springframework.boot.test.context.TestConfiguration; | ||
| import org.springframework.boot.testcontainers.service.connection.ServiceConnection; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.testcontainers.containers.PostgreSQLContainer; | ||
| import org.testcontainers.utility.DockerImageName; | ||
|
|
||
| @TestConfiguration(proxyBeanMethods = false) | ||
| public class TestcontainersConfiguration { | ||
|
|
||
| @Bean | ||
| @ServiceConnection | ||
| PostgreSQLContainer<?> postgresContainer() { | ||
| return new PostgreSQLContainer<>(DockerImageName.parse("postgres:latest")); | ||
| } | ||
|
|
||
| @Bean | ||
| @ServiceConnection(name = "redis") | ||
| RedisContainer redisContainer() { | ||
| return new RedisContainer(DockerImageName.parse("redis:latest")); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package zin.rashidi.dataredis.cache.customer; | ||
|
|
||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.boot.autoconfigure.ImportAutoConfiguration; | ||
| import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; | ||
| import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; | ||
| import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; | ||
| import org.springframework.cache.CacheManager; | ||
| import org.springframework.cache.annotation.EnableCaching; | ||
| import org.springframework.context.annotation.ComponentScan.Filter; | ||
| import org.springframework.context.annotation.Import; | ||
| import org.springframework.test.context.jdbc.Sql; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import zin.rashidi.dataredis.cache.TestcontainersConfiguration; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; | ||
|
|
||
| /** | ||
| * @author Rashidi Zin | ||
| */ | ||
| @Import(TestcontainersConfiguration.class) | ||
| @ImportAutoConfiguration({ RedisAutoConfiguration.class, CacheAutoConfiguration.class }) | ||
| @Sql(executionPhase = BEFORE_TEST_CLASS, statements = "INSERT INTO customer (id, name) VALUES (1, 'Rashidi Zin')") | ||
| @DataJpaTest(properties = "spring.jpa.hibernate.ddl-auto=create-drop", includeFilters = @Filter(EnableCaching.class)) | ||
| class CustomerRepositoryTests { | ||
|
|
||
| @Autowired | ||
| private CustomerRepository customers; | ||
|
|
||
| @Autowired | ||
| private CacheManager caches; | ||
|
|
||
| @Test | ||
| @Transactional(readOnly = true) | ||
| @DisplayName("Given the method name is configured as the cache's key Then subsequent retrieval should return the same value as initial retrieval") | ||
| void findAll() { | ||
| var persisted = customers.findAll(); | ||
| var cached = caches.getCache("customers").get("findAll").get(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Directly chaining A more robust approach would be to verify the |
||
|
|
||
| assertThat(cached).isEqualTo(persisted); | ||
| } | ||
|
|
||
| @Test | ||
| @Transactional(readOnly = true) | ||
| @DisplayName("Given the cache is configured Then subsequent retrieval with the same key should return the same value as initial retrieval") | ||
| void findById() { | ||
| var persisted = customers.findById(1L).get(); | ||
| var cached = caches.getCache("customerById").get(1L).get(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Directly chaining A more robust approach would be to verify the |
||
|
|
||
| assertThat(cached).isEqualTo(persisted); | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
RedisCacheManager.create()method has been deprecated since Spring Framework 6.1. It's recommended to use the builder pattern, which provides more flexibility for configuration.