Skip to content

Commit

Permalink
DATACOUCH-275 Key auto generation using Attributes, UUID
Browse files Browse the repository at this point in the history
Original pull request: #142.
  • Loading branch information
bsubhashni committed Apr 12, 2017
1 parent 766e4ee commit 069ceea
Show file tree
Hide file tree
Showing 14 changed files with 797 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.springframework.data.couchbase;

import org.springframework.context.annotation.Configuration;

@Configuration
public class IntegrationTestCustomKeySettings extends IntegrationTestApplicationConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package org.springframework.data.couchbase.core;

import static org.junit.Assert.*;
import static org.springframework.data.couchbase.core.mapping.id.GenerationStrategy.*;

import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue;
import org.springframework.data.couchbase.core.mapping.id.IdAttribute;
import org.springframework.data.couchbase.core.mapping.id.IdPrefix;
import org.springframework.data.couchbase.core.mapping.id.IdSuffix;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
* @author Subhashni Balakrishnan
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
public class CouchbaseTemplateIdGenerationTests {

@Rule
public TestName testName = new TestName();

@Autowired
private Bucket client;

@Autowired
private CouchbaseTemplate template;


private void removeIfExist(String key) {
try {
client.remove(key);
}
catch (DocumentDoesNotExistException e) {
//ignore
}
}

@Test
public void shouldGenerateIdUsingAtrributes() throws Exception {
SimpleClassWithGeneratedIdValueUsingAttributes simpleClass = new SimpleClassWithGeneratedIdValueUsingAttributes();
String generatedId = template.getGeneratedId(simpleClass);

removeIfExist(generatedId);
assertEquals("Id generation should be correct", generatedId,
"prefix1::prefix2::0::1::2.0::3.0::4::Simple::Nested{value:simple}::suffix1::suffix2");
template.insert(simpleClass);
assertEquals("Exists after insert", true, template.exists(generatedId));
simpleClass.value = "modified";
template.save(simpleClass);
SimpleClassWithGeneratedIdValueUsingAttributes modifiedClass = template.findById(generatedId,
SimpleClassWithGeneratedIdValueUsingAttributes.class);
assertEquals("Get after save id should be correct", generatedId, modifiedClass.id);
template.update(simpleClass);
SimpleClassWithGeneratedIdValueUsingAttributes updatedClass = template.findById(generatedId,
SimpleClassWithGeneratedIdValueUsingAttributes.class);
assertEquals("Get after update id should be correct", generatedId, updatedClass.id);
template.remove(generatedId);
assertEquals("Exists after remove", false, template.exists(generatedId));
}

@Test
public void shouldGenerateIdUsingUUID() throws Exception {
SimpleClassWithGeneratedIdValueUsingUUID simpleClass = new SimpleClassWithGeneratedIdValueUsingUUID();
String generatedId = template.getGeneratedId(simpleClass);
simpleClass.id = generatedId;
template.insert(simpleClass);
assertEquals("Should not regenerate id", generatedId, simpleClass.id);
template.remove(generatedId);
assertEquals("Exists after remove", false, template.exists(generatedId));
}


@Document
static class SimpleClassWithGeneratedIdValueUsingAttributes {

@Id @GeneratedValue(strategy = USE_ATTRIBUTES, delimiter = "::")
public String id;

@IdAttribute(order = 6)
public Nested nested = new Nested("simple");

@IdAttribute(order = 5)
public String type = "Simple";

@IdAttribute(order = 4)
public int intNum = 4;

@IdAttribute(order = 2)
public float floatNum = 2F;

@IdAttribute(order = 3)
public double doubleNum = 3;

@IdAttribute
public long longNum = 0L;

@IdAttribute(order = 1)
public short shortNum = 1;

@IdPrefix(order = 1)
public String prefix2 = "prefix2";

@IdPrefix
public String prefix1 = "prefix1";

@IdSuffix(order = 1)
public String suffix2 = "suffix2";

@IdSuffix
public String suffix1 = "suffix1";

public String value = "new";

}

static class Nested {
private String value;

public Nested(String value) {
this.value = value;
}

@Override
public String toString() {
return "Nested{value:" + value + "}";
}
}

@Document
static class SimpleClassWithGeneratedIdValueUsingUUID {
@Id @GeneratedValue(strategy = UNIQUE)
public String id;

public String value = "new";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.springframework.data.couchbase.core;

import static org.junit.Assert.*;

import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.error.DocumentDoesNotExistException;
import com.couchbase.client.java.repository.annotation.Id;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.couchbase.IntegrationTestCustomKeySettings;
import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.data.couchbase.core.mapping.KeySettings;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
* @author Subhashni Balakrishnan
*/

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = IntegrationTestCustomKeySettings.class)
public class CouchbaseTemplateKeySettingsTests {

@Rule
public TestName testName = new TestName();

@Autowired
private CouchbaseTemplate template;

@Before
public void setup() {
if (template.keySettings() == null) {
template.keySettings(KeySettings.build().prefix("MyAppPrefix").suffix("MyAppSuffix").delimiter("::"));
}
}

@Test
public void shouldAddCustomKeySettings() throws Exception {
SimpleClass simpleClass = new SimpleClass();
String generatedId = template.getGeneratedId(simpleClass);
assertEquals("Id generated should include custom key settings", "MyAppPrefix::myId::MyAppSuffix", generatedId);
}

@Test
public void shouldNotAllowKeySettingsToBeChanged() {
try {
template.keySettings(KeySettings.build().prefix("MyAppPrefix").suffix("MyAppSuffix").delimiter("::"));
fail("excepted unsupportedOperationException");
} catch(Exception ex) {

}
}

@Document
static class SimpleClass {
@Id
public String id = "myId";
}
}
106 changes: 106 additions & 0 deletions src/main/asciidoc/autokeygeneration.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
[[couchbase.autokeygeneration]]
= Auto generating keys

This chapter describes how couchbase document keys can be auto-generated using builtin mechanisms.
There are two types of auto-generation strategies supported.

- <<couchbase.autokeygeneration.usingattributes>>
- <<couchbase.autokeygeneration.unique>>

NOTE: The maximum key length supported by couchbase is 250 bytes.

[[couchbase.autokeygeneration.configuration]]
== Configuration

Keys to be auto-generated should be annotated with `@GeneratedValue`. The default strategy is `USE_ATTRIBUTES`. Prefix
and suffix for the key can be provided as part of the entity itself, these values are not persisted, they are only
used for key generation. The prefixes and suffixes are ordered using the `order` value. The default order is `0`, multiple
prefixes without order will overwrite the previous. If a value for id is already available, auto-generation will be skipped.
The delimiter for concatenation can be provided using `delimiter`, the default delimiter is `.`.

.Annotation for GeneratedValue
====
[source,java]
----
@Document
public class User {
@Id @GeneratedValue(strategy = USE_ATTRIBUTES, delimiter = ".")
private String id;
@IdPrefix(order=0)
private String userPrefix;
@IdSuffix(order=0)
private String userSuffix;
...
}
----
====

Common prefix and suffix for all entities keys can be added to `CouchbaseTemplate` directly. Once added to the `CouchbaseTemplate`,
they become immutable. These settings are always applied irrespective of the `GeneratedValue` annotation.

.Common key settings in CouchbaseTemplate
====
[source,java]
----
@Autowired
CouchbaseTemplate couchbaseTemplate;
...
couchbaseTemplate.keySettings(KeySettings.build().prefix("ApplicationA").suffix("Server1").delimiter("::"));
----
====

Key will be auto-generated only for operations with direct entity input like insert, update, save, delete using entity.
For other operations requiring just the key, it can be generated using `CouchbaseTemplate`.

.Standalone key generation in CouchbaseTemplate
====
[source,java]
----
@Autowired
CouchbaseTemplate couchbaseTemplate;
...
String id = couchbaseTemplate.getGeneratedId(entity);
...
repo.exists(id);
----
====

[[couchbase.autokeygeneration.usingattributes]]
== Key generation using attributes

It is a common practice to generate keys using a combination of the document attributes. Key generation using attributes
concatenates all the attribute values annotated with `IdAttribute`, based on the ordering provided similar to prefixes and suffixes.

.Annotation for IdAttribute
====
[source,java]
----
@Document
public class User {
@Id @GeneratedValue(strategy = USE_ATTRIBUTES)
private String id;
@IdAttribute
private String userid;
...
}
----
====

[[couchbase.autokeygeneration.unique]]
== Key generation using uuid

This auto-generation uses UUID random generator to generate document keys consuming 16 bytes of key space. This mechanism
is only recommended for test scaffolding.

.Annotation for Unique key generation
====
[source,java]
----
@Document
public class User {
@Id @GeneratedValue(strategy = UNIQUE)
private String id;
...
}
----
====
1 change: 1 addition & 0 deletions src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ include::preface.adoc[]
:leveloffset: +1
include::configuration.adoc[]
include::entity.adoc[]
include::autokeygeneration.adoc[]
include::{spring-data-commons-docs}/repositories.adoc[]
include::repository.adoc[]
include::template.adoc[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.KeySettings;
import org.springframework.data.couchbase.core.query.Consistency;


Expand Down Expand Up @@ -384,4 +385,31 @@ public interface CouchbaseOperations {
* @return the consistency to use for generated repository queries.
*/
Consistency getDefaultConsistency();

/**
* Add common key settings
*
* Throws {@link UnsupportedOperationException} if KeySettings is already set. It becomes immutable.
*
* @param settings {@link KeySettings}
*/
void keySettings(final KeySettings settings);

/**
* Get key settings associated with the template
*
* @return {@link KeySettings}
*/
KeySettings keySettings();

/**
* Get generated id - applies both using prefix and suffix through entity as well as template {@link KeySettings}
*
* ** NOTE: UNIQUE strategy will generate different ids each time ***
*
* @param entity the entity object.
* @return id the couchbase document key.
*/
String getGeneratedId(Object entity);

}

0 comments on commit 069ceea

Please sign in to comment.