committed by
Michael Reiche
parent
5c579f02bc
commit
a8df28c000
57
src/main/asciidoc/caching.adoc
Normal file
57
src/main/asciidoc/caching.adoc
Normal file
@@ -0,0 +1,57 @@
|
||||
[[couchbase.caching]]
|
||||
= Caching
|
||||
|
||||
This chapter describes additional support for caching and `@Cacheable`.
|
||||
|
||||
[[caching.usage]]
|
||||
== Configuration & Usage
|
||||
|
||||
Technically, caching is not part of spring-data, but is implemented directly in the spring core. Most database implementations in the spring-data package can't support `@Cacheable`, because it is not possible to store arbitrary data.
|
||||
|
||||
Couchbase supports both binary and JSON data, so you can get both out of the same database.
|
||||
|
||||
To make it work, you need to add the `@EnableCaching` annotation and configure the `cacheManager` bean:
|
||||
|
||||
.`AbstractCouchbaseConfiguration` for Caching
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class Config extends AbstractCouchbaseConfiguration {
|
||||
// general methods
|
||||
|
||||
@Bean
|
||||
public CouchbaseCacheManager cacheManager(CouchbaseTemplate couchbaseTemplate) throws Exception {
|
||||
CouchbaseCacheManager.CouchbaseCacheManagerBuilder builder = CouchbaseCacheManager.CouchbaseCacheManagerBuilder
|
||||
.fromConnectionFactory(couchbaseTemplate.getCouchbaseClientFactory());
|
||||
builder.withCacheConfiguration("mySpringCache", CouchbaseCacheConfiguration.defaultCacheConfig());
|
||||
return builder.build();
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The `persistent` identifier can then be used on the `@Cacheable` annotation to identify the cache manager to use (you can have more than one configured).
|
||||
|
||||
Once it is set up, you can annotate every method with the `@Cacheable` annotation to transparently cache it in your couchbase bucket. You can also customize how the key is generated.
|
||||
|
||||
.Caching example
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Cacheable(value="persistent", key="'longrunsim-'+#time")
|
||||
public String simulateLongRun(long time) {
|
||||
try {
|
||||
Thread.sleep(time);
|
||||
} catch(Exception ex) {
|
||||
System.out.println("This shouldnt happen...");
|
||||
}
|
||||
return "I've slept " + time + " miliseconds.;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
If you run the method multiple times, you'll see a set operation happening first, followed by multiple get operations and no sleep time (which fakes the expensive execution). You can store whatever you want, if it is JSON of course you can access it through views and look at it in the Web UI.
|
||||
|
||||
Note that to use cache.clear() or catch.invalidate(), the bucket must have a primary key.
|
||||
@@ -26,6 +26,7 @@ include::template.adoc[]
|
||||
include::transactions.adoc[]
|
||||
include::collections.adoc[]
|
||||
include::ansijoins.adoc[]
|
||||
include::caching.adoc[]
|
||||
:leveloffset: -1
|
||||
|
||||
[[appendix]]
|
||||
|
||||
@@ -36,7 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@IgnoreWhen(clusterTypes = ClusterType.MOCKED, missesCapabilities = { Capabilities.COLLECTIONS })
|
||||
@IgnoreWhen(clusterTypes = ClusterType.MOCKED, missesCapabilities = { Capabilities.COLLECTIONS })
|
||||
class CouchbaseCacheCollectionIntegrationTests extends CollectionAwareIntegrationTests {
|
||||
|
||||
volatile CouchbaseCache cache;
|
||||
@@ -58,7 +58,6 @@ class CouchbaseCacheCollectionIntegrationTests extends CollectionAwareIntegratio
|
||||
QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void cachePutGet() {
|
||||
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
|
||||
|
||||
@@ -21,10 +21,18 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.data.couchbase.domain.Config;
|
||||
import org.springframework.data.couchbase.domain.User;
|
||||
import org.springframework.data.couchbase.domain.UserRepository;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
import org.springframework.data.couchbase.util.IgnoreWhen;
|
||||
import org.springframework.data.couchbase.util.JavaIntegrationTests;
|
||||
@@ -40,6 +48,8 @@ import com.couchbase.client.java.query.QueryOptions;
|
||||
class CouchbaseCacheIntegrationTests extends JavaIntegrationTests {
|
||||
|
||||
volatile CouchbaseCache cache;
|
||||
@Autowired CouchbaseCacheManager cacheManager; // autowired not working
|
||||
@Autowired UserRepository userRepository; // autowired not working
|
||||
|
||||
@BeforeEach
|
||||
@Override
|
||||
@@ -48,6 +58,16 @@ class CouchbaseCacheIntegrationTests extends JavaIntegrationTests {
|
||||
cache = CouchbaseCacheManager.create(couchbaseTemplate.getCouchbaseClientFactory()).createCouchbaseCache("myCache",
|
||||
CouchbaseCacheConfiguration.defaultCacheConfig());
|
||||
clear(cache);
|
||||
ApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
|
||||
cacheManager = ac.getBean(CouchbaseCacheManager.class);
|
||||
userRepository = ac.getBean(UserRepository.class);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@Override
|
||||
public void afterEach() {
|
||||
clear(cache);
|
||||
super.afterEach();
|
||||
}
|
||||
|
||||
private void clear(CouchbaseCache c) {
|
||||
@@ -69,6 +89,19 @@ class CouchbaseCacheIntegrationTests extends JavaIntegrationTests {
|
||||
assertEquals(user2, cache.get(user2.getId()).get()); // get user2
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheable() {
|
||||
User user = new User("cache_92", "Dave", "Wilson");
|
||||
cacheManager.getCache("mySpringCache").clear();
|
||||
userRepository.save(user);
|
||||
long t0 = System.currentTimeMillis();
|
||||
List<User> users = userRepository.getByFirstname(user.getFirstname());
|
||||
assert (System.currentTimeMillis() - t0 > 1000 * 5);
|
||||
t0 = System.currentTimeMillis();
|
||||
users = userRepository.getByFirstname(user.getFirstname());
|
||||
assert (System.currentTimeMillis() - t0 < 100);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheEvict() {
|
||||
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
|
||||
@@ -98,4 +131,22 @@ class CouchbaseCacheIntegrationTests extends JavaIntegrationTests {
|
||||
assertEquals(user1, cache.get(user1.getId()).get()); // user1.getId() is still user1
|
||||
}
|
||||
|
||||
@Test // this test FAILS (local empty (i.e. fast) Couchbase installation)
|
||||
public void clearFail() {
|
||||
cache.put("KEY", "VALUE"); // no delay between put and clear, entry will not be
|
||||
cache.clear(); // will not be indexed when clear() executes
|
||||
assertNotNull(cache.get("KEY")); // will still find entry, clear failed to delete
|
||||
}
|
||||
|
||||
@Test // this WORKS
|
||||
public void clearWithDelayOk() throws InterruptedException {
|
||||
cache.put("KEY", "VALUE");
|
||||
Thread.sleep(50); // give main index time to update
|
||||
cache.clear();
|
||||
assertNull(cache.get("KEY"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noOpt() {}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,12 +17,17 @@
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.couchbase.CouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
|
||||
import org.springframework.data.couchbase.cache.CouchbaseCacheConfiguration;
|
||||
import org.springframework.data.couchbase.cache.CouchbaseCacheManager;
|
||||
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||
@@ -53,7 +58,7 @@ import com.couchbase.client.java.json.JacksonTransformers;
|
||||
@EnableReactiveCouchbaseRepositories
|
||||
@EnableCouchbaseAuditing(dateTimeProviderRef = "dateTimeProviderRef")
|
||||
@EnableReactiveCouchbaseAuditing(dateTimeProviderRef = "dateTimeProviderRef")
|
||||
|
||||
@EnableCaching
|
||||
public class Config extends AbstractCouchbaseConfiguration {
|
||||
String bucketname = "travel-sample";
|
||||
String username = "Administrator";
|
||||
@@ -214,6 +219,14 @@ public class Config extends AbstractCouchbaseConfiguration {
|
||||
return jacksonTranslationService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CouchbaseCacheManager cacheManager(CouchbaseTemplate couchbaseTemplate) throws Exception {
|
||||
CouchbaseCacheManager.CouchbaseCacheManagerBuilder builder = CouchbaseCacheManager.CouchbaseCacheManagerBuilder
|
||||
.fromConnectionFactory(couchbaseTemplate.getCouchbaseClientFactory());
|
||||
//CouchbaseCacheConfiguration cfg = CouchbaseCacheConfiguration.defaultCacheConfig().withCacheConfiguration("mySpringCache", CouchbaseCacheConfiguration.defaultCacheConfig());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeKey() {
|
||||
return "t"; // this will override '_class', is passed in to new CustomMappingCouchbaseConverter
|
||||
|
||||
@@ -19,6 +19,8 @@ package org.springframework.data.couchbase.domain;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* User entity for tests
|
||||
*
|
||||
@@ -27,7 +29,7 @@ import org.springframework.data.couchbase.core.mapping.Document;
|
||||
*/
|
||||
|
||||
@Document
|
||||
public class User extends AbstractUser {
|
||||
public class User extends AbstractUser implements Serializable {
|
||||
|
||||
@PersistenceConstructor
|
||||
public User(final String id, final String firstname, final String lastname) {
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.springframework.data.couchbase.domain;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
import org.springframework.data.couchbase.repository.ScanConsistency;
|
||||
@@ -27,6 +27,7 @@ import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.couchbase.client.java.json.JsonArray;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* User Repository for tests
|
||||
@@ -35,7 +36,7 @@ import com.couchbase.client.java.json.JsonArray;
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Repository
|
||||
@ScanConsistency(query=QueryScanConsistency.REQUEST_PLUS)
|
||||
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
|
||||
public interface UserRepository extends CouchbaseRepository<User, String> {
|
||||
|
||||
List<User> findByFirstname(String firstname);
|
||||
@@ -57,4 +58,13 @@ public interface UserRepository extends CouchbaseRepository<User, String> {
|
||||
List<User> findByIdIsNotNullAndFirstnameEquals(String firstname);
|
||||
|
||||
List<User> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
|
||||
|
||||
// simulate a slow operation
|
||||
@Cacheable("mySpringCache")
|
||||
default List<User> getByFirstname(String firstname) {
|
||||
try {
|
||||
Thread.sleep(1000 * 5);
|
||||
} catch (InterruptedException ie) {}
|
||||
return findByFirstname(firstname);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user