DATACOUCH-59 - Allow expiry touch on read

Configuration is based on Document annotation. When read of document takes place then touch action is executed on document. It is an asynchronous action so it does not block read. It is executed only on single reads (not view-based or N1QL-based ones).
This commit is contained in:
Andrzej Wisłowski
2016-02-23 14:27:09 +01:00
committed by Simon Baslé
parent 90839ef604
commit 40e4212ad7
6 changed files with 99 additions and 6 deletions

View File

@@ -402,6 +402,22 @@ public class CouchbaseTemplateTests {
assertEquals(versionedClass.getVersion(), foundClass.getVersion());
}
/**
* @see DATACOUCH-59
*/
@Test
public void expiryWhenTouchOnReadDocument() throws InterruptedException {
String id = "simple-doc-with-update-expiry-for-read";
DocumentWithTouchOnRead doc = new DocumentWithTouchOnRead(id);
template.save(doc);
Thread.sleep(1500);
assertNotNull(template.findById(id, DocumentWithTouchOnRead.class));
Thread.sleep(1500);
assertNotNull(template.findById(id, DocumentWithTouchOnRead.class));
Thread.sleep(3000);
assertNull(template.findById(id, DocumentWithTouchOnRead.class));
}
/**
* A sample document with just an id and property.
*/
@@ -433,6 +449,20 @@ public class CouchbaseTemplateTests {
}
}
/**
* A sample document that expires in 2 seconds and touchOnRead set.
*/
@Document(expiry = 2, touchOnRead = true)
static class DocumentWithTouchOnRead {
@Id
private final String id;
public DocumentWithTouchOnRead(String id) {
this.id = id;
}
}
@Document
static class ComplexPerson {

View File

@@ -281,10 +281,15 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP
@Override
public <T> T findById(final String id, Class<T> entityClass) {
final CouchbasePersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
RawJsonDocument result = execute(new BucketCallback<RawJsonDocument>() {
@Override
public RawJsonDocument doInBucket() {
return client.get(id, RawJsonDocument.class);
if (entity.isTouchOnRead()) {
return client.getAndTouch(id, entity.getExpiry(), RawJsonDocument.class);
} else {
return client.get(id, RawJsonDocument.class);
}
}
});

View File

@@ -16,12 +16,7 @@
package org.springframework.data.couchbase.core.mapping;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import com.couchbase.client.java.repository.annotation.Id;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -31,6 +26,10 @@ import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
/**
* The representation of a persistent entity.
*
@@ -123,4 +122,11 @@ public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T,
}
}
@Override
public boolean isTouchOnRead() {
org.springframework.data.couchbase.core.mapping.Document annotation = getType().getAnnotation(
org.springframework.data.couchbase.core.mapping.Document.class);
return annotation == null ? false : getExpiry() > 0 && annotation.touchOnRead();
}
}

View File

@@ -42,4 +42,12 @@ public interface CouchbasePersistentEntity<T> extends
*/
int getExpiry();
/**
* Flag for using getAndTouch operations for reads, resetting the expiration (if one was set) when the
* entity is directly read (eg. findOne, findById).
*
* @return true if a direct read of the document should trigger a touch, resetting its expiration timer.
*/
boolean isTouchOnRead();
}

View File

@@ -46,4 +46,10 @@ public @interface Document {
*/
TimeUnit expiryUnit() default TimeUnit.SECONDS;
/**
* An optional flag associated with {@link #expiry()} indicating whether the expiry timer should
* be reset whenever the document is directly read (eg. findByOne, findById).
*/
boolean touchOnRead() default false;
}

View File

@@ -17,6 +17,8 @@
package org.springframework.data.couchbase.core.mapping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Calendar;
import java.util.Date;
@@ -103,6 +105,42 @@ public class BasicCouchbasePersistentEntityTests {
assertEquals(expected.get(Calendar.SECOND), calendar.get(Calendar.SECOND));
}
@Test
public void doesNotUseGetExpiry() throws Exception {
assertEquals(0, getBasicCouchbasePersistentEntity(SimpleDocument.class).getExpiry());
}
@Test
public void usesGetExpiry() throws Exception {
assertEquals(10, getBasicCouchbasePersistentEntity(SimpleDocumentWithExpiry.class).getExpiry());
}
@Test
public void doesNotUseIsUpdateExpiryForRead() throws Exception {
assertFalse(getBasicCouchbasePersistentEntity(SimpleDocument.class).isTouchOnRead());
assertFalse(getBasicCouchbasePersistentEntity(SimpleDocumentWithExpiry.class).isTouchOnRead());
}
@Test
public void usesTouchOnRead() throws Exception {
assertTrue(getBasicCouchbasePersistentEntity(SimpleDocumentWithTouchOnRead.class).isTouchOnRead());
}
private BasicCouchbasePersistentEntity getBasicCouchbasePersistentEntity(Class<?> clazz) {
return new BasicCouchbasePersistentEntity(ClassTypeInformation.from(clazz));
}
public static class SimpleDocument {
}
@Document(expiry = 10)
public static class SimpleDocumentWithExpiry {
}
@Document(expiry = 10, touchOnRead = true)
public static class SimpleDocumentWithTouchOnRead {
}
/**
* Simple POJO to test default expiry.
*/