DATACOUCH-169 - Automatic index creation
Main indexes (ie indexes that can be used by repositories to retrieve ALL the documents of a particular entity type) are covered. Repository interfaces can be annotated with an annotation for each type of main index that can be automatically created. The IndexManager is responsible for creating said indexes (potentially skipping creation if an index is already in place, or if the IndexManager has been configured to skip it, see constructor). Since IndexManager is created as a bean (either in JavaConfig or xml), there can be a different instance eg. in Prod vs Dev. Added doc and integration test. Renamed package core.view to core.query since it now contains various non-view related annotations and classes.
This commit is contained in:
@@ -140,6 +140,9 @@ function (doc, meta) {
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, if view creation isn't too costly, you can ask the framework to create it automatically by annotating the
|
||||
repository with `@ViewIndexed(designDoc = "userInfo", viewName = "all")`.
|
||||
|
||||
## N1QL and Query Derivation
|
||||
With the introduction of `N1QL`, Couchbase can now better support query derivation (the mechanism that allows you to
|
||||
add custom methods that will automatically be implemented as a N1QL query derived from the method's name).
|
||||
@@ -175,6 +178,10 @@ List<UserInfo> findPatrickAndJackAmongOthers();
|
||||
List<UserInfo> findUsersWithTheirFirstnameLike(String likePattern);
|
||||
```
|
||||
|
||||
N1QL needs at least a generic purpose `N1QL primary index` to work with, and can make use of a more entity
|
||||
type-specific `N1QL secondary index`. You can create both automatically (provided you are confident this
|
||||
is not to much of a cost) by annotating a repository with `@N1qlPrimaryIndexed` and/or `@N1qlSecondaryIndexed`.
|
||||
|
||||
## Using The Repository
|
||||
|
||||
Extending `CrudRepository` causes CRUD methods being pulled into the interface so that you can easily save and find
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.WriteResultChecking;
|
||||
import org.springframework.data.couchbase.core.view.Consistency;
|
||||
import org.springframework.data.couchbase.core.query.Consistency;
|
||||
|
||||
@Configuration
|
||||
public class IntegrationTestApplicationConfig extends AbstractCouchbaseConfiguration {
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.view.Consistency;
|
||||
import org.springframework.data.couchbase.core.query.Consistency;
|
||||
import org.springframework.data.couchbase.repository.User;
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
||||
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.mapping.PropertyReferenceException;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
@@ -58,11 +59,14 @@ public class CouchbaseRepositoryViewTests {
|
||||
@Autowired
|
||||
private RepositoryOperationsMapping operationsMapping;
|
||||
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
private CustomUserRepository repository;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
repository = new CouchbaseRepositoryFactory(operationsMapping).getRepository(CustomUserRepository.class);
|
||||
repository = new CouchbaseRepositoryFactory(operationsMapping, indexManager).getRepository(CustomUserRepository.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.util.List;
|
||||
|
||||
import com.couchbase.client.java.document.json.JsonObject;
|
||||
|
||||
import org.springframework.data.couchbase.core.view.View;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
|
||||
/**
|
||||
* @author David Harrigan
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.util.List;
|
||||
|
||||
import com.couchbase.client.java.document.json.JsonArray;
|
||||
|
||||
import org.springframework.data.couchbase.core.view.Dimensional;
|
||||
import org.springframework.data.couchbase.core.query.Dimensional;
|
||||
import org.springframework.data.geo.Box;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Distance;
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.geo.Box;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Distance;
|
||||
@@ -37,11 +38,14 @@ public class DimensionalQueryTests {
|
||||
@Autowired
|
||||
private RepositoryOperationsMapping templateMapping;
|
||||
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
private DimensionalPartyRepository repository;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(templateMapping);
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(templateMapping, indexManager);
|
||||
repository = factory.getRepository(DimensionalPartyRepository.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -49,11 +50,14 @@ public class N1qlCouchbaseRepositoryTests {
|
||||
@Autowired
|
||||
private RepositoryOperationsMapping operationsMapping;
|
||||
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
private PartyPagingRepository repository;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
|
||||
repository = factory.getRepository(PartyPagingRepository.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -50,6 +51,9 @@ public class N1qlCrudRepositoryTests {
|
||||
@Autowired
|
||||
private RepositoryOperationsMapping operationsMapping;
|
||||
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
private PartyRepository partyRepository;
|
||||
private ItemRepository itemRepository;
|
||||
|
||||
@@ -58,8 +62,8 @@ public class N1qlCrudRepositoryTests {
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
partyRepository = new CouchbaseRepositoryFactory(operationsMapping).getRepository(PartyRepository.class);
|
||||
itemRepository = new CouchbaseRepositoryFactory(operationsMapping).getRepository(ItemRepository.class);
|
||||
partyRepository = new CouchbaseRepositoryFactory(operationsMapping, indexManager).getRepository(PartyRepository.class);
|
||||
itemRepository = new CouchbaseRepositoryFactory(operationsMapping, indexManager).getRepository(ItemRepository.class);
|
||||
|
||||
itemRepository.save(item);
|
||||
partyRepository.save(party);
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Slice;
|
||||
@@ -35,11 +36,14 @@ public class PageAndSliceTests {
|
||||
@Autowired
|
||||
private RepositoryOperationsMapping operationsMapping;
|
||||
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
private UserRepository repository;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
|
||||
repository = factory.getRepository(UserRepository.class);
|
||||
}
|
||||
@Test
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.springframework.data.couchbase.repository;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.core.view.View;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
|
||||
/**
|
||||
* @author Simon Baslé
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
@@ -36,11 +37,14 @@ public class QueryDerivationConversionTests {
|
||||
@Autowired
|
||||
private RepositoryOperationsMapping operationsMapping;
|
||||
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
private PartyRepository repository;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
|
||||
repository = factory.getRepository(PartyRepository.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
@@ -53,11 +54,14 @@ public class SimpleCouchbaseRepositoryTests {
|
||||
@Autowired
|
||||
private RepositoryOperationsMapping operationsMapping;
|
||||
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
private UserRepository repository;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
|
||||
repository = factory.getRepository(UserRepository.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ import java.util.List;
|
||||
|
||||
import com.couchbase.client.java.view.ViewQuery;
|
||||
|
||||
import org.springframework.data.couchbase.core.view.Query;
|
||||
import org.springframework.data.couchbase.core.view.View;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.data.couchbase.repository.User;
|
||||
import org.springframework.data.couchbase.repository.UserRepository;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -52,6 +53,9 @@ public class FeatureDetectionRepositoryTests {
|
||||
@Autowired
|
||||
private RepositoryOperationsMapping operationsMapping;
|
||||
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
@Autowired
|
||||
private ClusterInfo clusterInfo;
|
||||
|
||||
@@ -62,7 +66,7 @@ public class FeatureDetectionRepositoryTests {
|
||||
|
||||
@Test
|
||||
public void testN1qlIncompatibleClusterFailsFastForN1qlBasedRepository() throws Exception {
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
|
||||
try {
|
||||
factory.getRepository(UserRepository.class);
|
||||
fail("expected UnsupportedCouchbaseFeatureException");
|
||||
@@ -73,7 +77,7 @@ public class FeatureDetectionRepositoryTests {
|
||||
|
||||
@Test
|
||||
public void testN1qlIncompatibleClusterDoesntFailForViewBasedRepository() throws Exception {
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping);
|
||||
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
|
||||
ViewOnlyUserRepository repository = factory.getRepository(ViewOnlyUserRepository.class);
|
||||
assertNotNull(repository);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.couchbase.repository.index;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
|
||||
import org.springframework.data.couchbase.core.query.ViewIndexed;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.User;
|
||||
|
||||
@N1qlSecondaryIndexed(indexName = IndexedRepositoryTests.IGNORED_SECONDARY)
|
||||
public interface AnotherIndexedUserRepository extends CouchbaseRepository<User, String> {
|
||||
|
||||
@ViewIndexed(designDoc = IndexedRepositoryTests.VIEW_DOC, viewName = IndexedRepositoryTests.IGNORED_VIEW_NAME)
|
||||
public List<User> findByAge(int age);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.springframework.data.couchbase.repository.index;
|
||||
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.query.Index;
|
||||
import com.couchbase.client.java.query.N1qlQuery;
|
||||
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
|
||||
|
||||
/**
|
||||
* A test listener that will remove the indexes created in {@link IndexedRepositoryTests} before test case is run.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
public class IndexedRepositoryTestListener extends DependencyInjectionTestExecutionListener {
|
||||
|
||||
@Override
|
||||
public void beforeTestClass(final TestContext testContext) throws Exception {
|
||||
Bucket client = (Bucket) testContext.getApplicationContext().getBean("couchbaseBucket");
|
||||
client.bucketManager().removeDesignDocument(IndexedRepositoryTests.VIEW_DOC);
|
||||
client.query(N1qlQuery.simple(Index.dropPrimaryIndex(client.name())));
|
||||
client.query(N1qlQuery.simple(Index.dropIndex(client.name(), IndexedRepositoryTests.SECONDARY)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.repository.index;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import com.couchbase.client.java.query.N1qlQuery;
|
||||
import com.couchbase.client.java.query.N1qlQueryResult;
|
||||
import com.couchbase.client.java.view.DesignDocument;
|
||||
import com.couchbase.client.java.view.View;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* This tests automatic index creation features in the Couchbase connector.
|
||||
* Automatic index creation is performed before construction of the repository implementation.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
|
||||
@TestExecutionListeners(IndexedRepositoryTestListener.class)
|
||||
public class IndexedRepositoryTests {
|
||||
|
||||
public static final String SECONDARY = "autogeneratedIndexIndexedUserN1qlSecondary";
|
||||
public static final String VIEW_DOC = "autogeneratedIndex";
|
||||
public static final String VIEW_NAME = "IndexedUserView";
|
||||
|
||||
public static final String IGNORED_VIEW_NAME = "AnotherIndexedUserView";
|
||||
public static final String IGNORED_SECONDARY = "AnotherIndexedUserN1qlSecondary";
|
||||
|
||||
@Autowired
|
||||
private RepositoryOperationsMapping operationsMapping;
|
||||
|
||||
@Autowired
|
||||
private IndexManager indexManager;
|
||||
|
||||
private CouchbaseOperations template;
|
||||
private RepositoryFactorySupport factory;
|
||||
|
||||
private RepositoryFactorySupport ignoringIndexFactory;
|
||||
private IndexManager ignoringIndexManager = new IndexManager(true, true, true);
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
factory = new CouchbaseRepositoryFactory(operationsMapping, indexManager);
|
||||
template = operationsMapping.getDefault();
|
||||
ignoringIndexFactory = new CouchbaseRepositoryFactory(operationsMapping, ignoringIndexManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindN1qlPrimaryIndex() {
|
||||
IndexedUserRepository repository = factory.getRepository(IndexedUserRepository.class);
|
||||
|
||||
String bucket = template.getCouchbaseBucket().name();
|
||||
N1qlQuery existQuery = N1qlQuery.simple("SELECT COUNT(name) = 1 AS exist FROM system:indexes WHERE keyspace_id = \"" + bucket
|
||||
+ "\" AND is_primary");
|
||||
N1qlQueryResult exist = template.queryN1QL(existQuery);
|
||||
|
||||
assertTrue(exist.finalSuccess());
|
||||
assertEquals(1, exist.allRows().size());
|
||||
assertEquals(true, exist.allRows().get(0).value().getBoolean("exist"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindN1qlSecondaryIndex() {
|
||||
IndexedUserRepository repository = factory.getRepository(IndexedUserRepository.class);
|
||||
|
||||
String bucket = template.getCouchbaseBucket().name();
|
||||
N1qlQuery existQuery = N1qlQuery.simple("SELECT COUNT(name) = 1 AS exist FROM system:indexes WHERE keyspace_id = \"" + bucket
|
||||
+ "\" AND name = \"" + SECONDARY + "\"");
|
||||
N1qlQueryResult exist = template.queryN1QL(existQuery);
|
||||
|
||||
assertTrue(exist.finalSuccess());
|
||||
assertEquals(1, exist.allRows().size());
|
||||
assertEquals(true, exist.allRows().get(0).value().getBoolean("exist"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindViewIndex() {
|
||||
IndexedUserRepository repository = factory.getRepository(IndexedUserRepository.class);
|
||||
|
||||
DesignDocument designDoc = template.getCouchbaseBucket()
|
||||
.bucketManager()
|
||||
.getDesignDocument(VIEW_DOC);
|
||||
|
||||
assertNotNull(designDoc);
|
||||
for (View view : designDoc.views()) {
|
||||
if (view.name().equals(VIEW_NAME)) return;
|
||||
}
|
||||
fail("View not found");
|
||||
}
|
||||
@Test
|
||||
public void shouldNotFindN1qlSecondaryIndexWithIgnoringIndexManager() {
|
||||
AnotherIndexedUserRepository repository = ignoringIndexFactory.getRepository(AnotherIndexedUserRepository.class);
|
||||
|
||||
String bucket = template.getCouchbaseBucket().name();
|
||||
N1qlQuery existQuery = N1qlQuery.simple("SELECT COUNT(name) = 1 AS exist FROM system:indexes WHERE keyspace_id = \"" + bucket
|
||||
+ "\" AND name = \"" + IGNORED_SECONDARY + "\"");
|
||||
N1qlQueryResult exist = template.queryN1QL(existQuery);
|
||||
|
||||
assertTrue(exist.finalSuccess());
|
||||
assertEquals(1, exist.allRows().size());
|
||||
assertEquals(false, exist.allRows().get(0).value().getBoolean("exist"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotFindViewIndexWithIgnoringIndexManager() {
|
||||
AnotherIndexedUserRepository repository = ignoringIndexFactory.getRepository(AnotherIndexedUserRepository.class);
|
||||
|
||||
DesignDocument designDoc = template.getCouchbaseBucket()
|
||||
.bucketManager()
|
||||
.getDesignDocument(VIEW_DOC);
|
||||
|
||||
if (designDoc != null) {
|
||||
for (View view : designDoc.views()) {
|
||||
if (view.name().equals(IGNORED_VIEW_NAME)) fail("Found unexpected " + IGNORED_VIEW_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.springframework.data.couchbase.repository.index;
|
||||
|
||||
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
|
||||
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
|
||||
import org.springframework.data.couchbase.core.query.ViewIndexed;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.User;
|
||||
|
||||
@N1qlPrimaryIndexed
|
||||
@N1qlSecondaryIndexed(indexName = IndexedRepositoryTests.SECONDARY)
|
||||
@ViewIndexed(designDoc = IndexedRepositoryTests.VIEW_DOC, viewName = IndexedRepositoryTests.VIEW_NAME)
|
||||
public interface IndexedUserRepository extends CouchbaseRepository<User, String> {
|
||||
}
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
<couchbase:template/>
|
||||
|
||||
<couchbase:indexManager/>
|
||||
|
||||
<couchbase:repositories base-package="org.springframework.data.couchbase.repository.xmlconfig"/>
|
||||
|
||||
</beans>
|
||||
@@ -9,7 +9,10 @@ There are three backing mechanisms in Couchbase for repositories, described in t
|
||||
- <<couchbase.repository.views.querying>>
|
||||
- <<couchbase.repository.spatial>>
|
||||
|
||||
Note that you can tune the consistency you want for your queries (see <<couchbase.repository.consistency>>).
|
||||
CRUD operations are still mostly backed by Couchbase views (see <<couchbase.repository.views>>).
|
||||
Such views (and, for N1QL, equivalent indexes) can be automatically built, but note this is an **expensive operation** (see <<couchbase.repository.indexing>>).
|
||||
|
||||
Note that you can tune the consistency you want for your queries (see <<couchbase.repository.consistency>>) and have different repositories backed by different buckets (see <<couchbase.repository.multibucket>>)
|
||||
|
||||
[[couchbase.repository.configuration]]
|
||||
== Configuration
|
||||
@@ -228,6 +231,19 @@ Note that the important part in this map function is to only include the documen
|
||||
|
||||
Also make sure to publish your design documents into production so that they can be picked up by the library! Also, if you are curious why we use `emit(null, null)` in the view: the document id is always sent over to the client implicitly, so we can shave off a view bytes in our view by not duplicating the id. If you use `emit(meta.id, null)` it won't hurt much too.
|
||||
|
||||
[[couchbase.repository.indexing]]
|
||||
== Automatic Index Management
|
||||
We've seen that the repositories default methods can be backed by two broad kind of features: views and N1QL (in the case of paging and sorting).
|
||||
In order for the CRUD operations to work, the adequate view must have been created beforehand, and this is usually left for the user to do because view creation (and index creation) is an expensive operation that can take quite some time if the quantity of documents is high.
|
||||
|
||||
In the case where the index creation cost isn't considered too high, it can be triggered automatically instead. You just need to annotate the repositories you want managed with the relevant annotation(s):
|
||||
|
||||
- `@ViewIndexed` will create a view like the "all" view previously seen, to list all entities in the bucket. It can also be used on methods, allowing multiple views to be created with custom map function and reduce function.
|
||||
- `@N1qlPrimaryIndexed` can be used to ensure a general-purpose PRIMARY INDEX is available in N1QL.
|
||||
- `@N1qlSecondaryIndexed` will create a more specific N1QL index that does the same kind of filtering on entity type that the view does. It'll allow for efficient listing of all documents that correspond to a Repository's associated domain object.
|
||||
|
||||
Note that sometimes you only want this automatic creation in certain contexts (eg. in Dev but not in Production). To that end, you can customize the `IndexManager` bean in an env-specific `AbstractCouchbaseConfiguration` (eg. via `@Profile`) to ignore certain types of index creation annotations. This is done through the `IndexManager(boolean ignoreView, boolean ignoreN1qlPrimary, boolean ignoreN1qlSecondary)` constructor. You can even declare multiple beans and differentiate them using Spring annotations.
|
||||
|
||||
[[couchbase.repository.views.querying]]
|
||||
=== View based querying
|
||||
|
||||
@@ -436,7 +452,6 @@ Provide the implementation and directly use `queryView` and `queryN1QL` methods
|
||||
- for example for views `ViewQuery.stale(Stale.FALSE)`
|
||||
- for example for N1QL `Query.simple("SELECT * FROM default", QueryParams.build().consistency(ScanConsistency.REQUEST_PLUS));`
|
||||
|
||||
|
||||
[[couchbase.repository.multibucket]]
|
||||
== Working with multiple buckets
|
||||
The Java Config version allows you to define multiple `Bucket` and `CouchbaseTemplate`, but in order to have different
|
||||
|
||||
@@ -44,9 +44,13 @@ import org.springframework.data.couchbase.core.convert.translation.JacksonTransl
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.data.couchbase.core.view.Consistency;
|
||||
import org.springframework.data.couchbase.core.query.Consistency;
|
||||
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
|
||||
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
|
||||
import org.springframework.data.couchbase.core.query.ViewIndexed;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.FieldNamingStrategy;
|
||||
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
|
||||
@@ -227,6 +231,20 @@ public abstract class AbstractCouchbaseConfiguration {
|
||||
return new CustomConversions(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an {@link IndexManager} bean that will be used to process {@link ViewIndexed},
|
||||
* {@link N1qlPrimaryIndexed} and {@link N1qlSecondaryIndexed} annotations on repositories
|
||||
* to automatically create indexes.
|
||||
* <p/>
|
||||
* If this configuration is used in a context where such automatic creations are not desired (eg.
|
||||
* you want automatic index creation in Dev but not in Prod, and this configuration is the Prod one),
|
||||
* override the bean and use the {@link IndexManager#IndexManager(boolean, boolean, boolean)} constructor.
|
||||
*/
|
||||
@Bean(name = BeanNames.COUCHBASE_INDEX_MANAGER)
|
||||
public IndexManager indexManager() {
|
||||
return new IndexManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the mapping base package for classes annotated with {@link Document}.
|
||||
*
|
||||
|
||||
@@ -57,4 +57,9 @@ public class BeanNames {
|
||||
* The bean that stores custom mapping between repositories and their backing couchbaseOperations.
|
||||
*/
|
||||
public static final String REPO_OPERATIONS_MAPPING = "repositoryOperationsMapping";
|
||||
|
||||
/**
|
||||
* The bean that drives how some indexes are automatically created.
|
||||
*/
|
||||
public static final String COUCHBASE_INDEX_MANAGER = "couchbaseIndexManager";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.config;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* The XML parser for a {@link IndexManager} definition.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
public class CouchbaseIndexManagerParser extends AbstractSingleBeanDefinitionParser {
|
||||
|
||||
/**
|
||||
* Resolve the bean ID and assign a default if not set.
|
||||
*
|
||||
* @param element the XML element which contains the attributes.
|
||||
* @param definition the bean definition to work with.
|
||||
* @param parserContext encapsulates the parsing state and configuration.
|
||||
* @return the ID to work with.
|
||||
*/
|
||||
@Override
|
||||
protected String resolveId(final Element element, final AbstractBeanDefinition definition, final ParserContext parserContext) {
|
||||
String id = super.resolveId(element, definition, parserContext);
|
||||
return StringUtils.hasText(id) ? id : BeanNames.COUCHBASE_INDEX_MANAGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the bean class that will be constructed.
|
||||
*
|
||||
* @param element the XML element which contains the attributes.
|
||||
* @return the class type to instantiate.
|
||||
*/
|
||||
@Override
|
||||
protected Class getBeanClass(final Element element) {
|
||||
return IndexManager.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the bean definition and build up the bean.
|
||||
*
|
||||
* @param element the XML element which contains the attributes.
|
||||
* @param bean the builder which builds the bean.
|
||||
*/
|
||||
@Override
|
||||
protected void doParse(final Element element, final BeanDefinitionBuilder bean) {
|
||||
boolean ignoreViews = Boolean.parseBoolean(element.getAttribute("ignoreViews"));
|
||||
boolean ignorePrimary = Boolean.parseBoolean(element.getAttribute("ignorePrimary"));
|
||||
boolean ignoreSecondary = Boolean.parseBoolean(element.getAttribute("ignoreSecondary"));
|
||||
|
||||
bean.addConstructorArgValue(ignoreViews);
|
||||
bean.addConstructorArgValue(ignorePrimary);
|
||||
bean.addConstructorArgValue(ignoreSecondary);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,6 +44,7 @@ public class CouchbaseNamespaceHandler extends NamespaceHandlerSupport {
|
||||
registerBeanDefinitionParser("jmx", new CouchbaseJmxParser());
|
||||
registerBeanDefinitionParser("template", new CouchbaseTemplateParser());
|
||||
registerBeanDefinitionParser("translation-service", new CouchbaseTranslationServiceParser());
|
||||
registerBeanDefinitionParser("indexManager", new CouchbaseIndexManagerParser());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.view.Consistency;
|
||||
import org.springframework.data.couchbase.core.query.Consistency;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,7 @@ import com.couchbase.client.java.view.ViewResult;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
|
||||
import org.springframework.data.couchbase.core.view.Consistency;
|
||||
import org.springframework.data.couchbase.core.query.Consistency;
|
||||
|
||||
/**
|
||||
* Defines common operations on the Couchbase data source, most commonly implemented by {@link CouchbaseTemplate}.
|
||||
|
||||
@@ -38,8 +38,6 @@ import com.couchbase.client.java.error.TranscodingException;
|
||||
import com.couchbase.client.java.query.N1qlQuery;
|
||||
import com.couchbase.client.java.query.N1qlQueryResult;
|
||||
import com.couchbase.client.java.query.N1qlQueryRow;
|
||||
import com.couchbase.client.java.query.N1qlParams;
|
||||
import com.couchbase.client.java.query.consistency.ScanConsistency;
|
||||
import com.couchbase.client.java.util.features.CouchbaseFeature;
|
||||
import com.couchbase.client.java.view.SpatialViewQuery;
|
||||
import com.couchbase.client.java.view.SpatialViewResult;
|
||||
@@ -70,7 +68,7 @@ import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.BeforeDeleteEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent;
|
||||
import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent;
|
||||
import org.springframework.data.couchbase.core.view.Consistency;
|
||||
import org.springframework.data.couchbase.core.query.Consistency;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.view;
|
||||
package org.springframework.data.couchbase.core.query;
|
||||
|
||||
import com.couchbase.client.java.query.consistency.ScanConsistency;
|
||||
import com.couchbase.client.java.view.Stale;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.view;
|
||||
package org.springframework.data.couchbase.core.query;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.query;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
|
||||
/**
|
||||
* This annotation is targeted at {@link CouchbaseRepository Repository} interfaces, indicating that the framework
|
||||
* should ensure a N1QL Primary Index is present on the repository's associated bucket when the repository is created.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface N1qlPrimaryIndexed {
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.query;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
|
||||
/**
|
||||
* This annotation is targeted at {@link CouchbaseRepository Repository} interfaces, indicating that
|
||||
* the framework should ensure a N1QL Secondary Index is present when the repository is instantiated.
|
||||
* <p/>
|
||||
* Said index will relate to the "type" field (the one bearing type information) and restrict on documents
|
||||
* that match the repository's entity class.
|
||||
* <p/>
|
||||
* Be sure to also use {@link N1qlPrimaryIndexed} to make sure the PRIMARY INDEX is there as well.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface N1qlSecondaryIndexed {
|
||||
|
||||
/**
|
||||
* the name of the index to be created, in the repository's associated bucket namespace.
|
||||
*/
|
||||
String indexName();
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.view;
|
||||
package org.springframework.data.couchbase.core.query;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.view;
|
||||
package org.springframework.data.couchbase.core.query;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
@@ -22,8 +22,6 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.data.annotation.QueryAnnotation;
|
||||
|
||||
/**
|
||||
* Annotation to support the use of Views with Couchbase.
|
||||
*
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.core.query;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* This annotation is targeted at {@link CouchbaseRepository Repository} interfaces, indicating that
|
||||
* the framework should ensure a View is present when the repository is instantiated.
|
||||
* <p/>
|
||||
* The view must at least be described as a design document name and view name. Default map function
|
||||
* will filter documents on the type associated to the repository, and default reduce function is "_count".
|
||||
* <p/>
|
||||
* One can specify a custom reduce function as well as a non-default map function. This can be done on methods,
|
||||
* allowing for multiple views per repository to be created.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ViewIndexed {
|
||||
|
||||
/**
|
||||
* The design document in which to create/look for the view.
|
||||
*/
|
||||
String designDoc();
|
||||
|
||||
/**
|
||||
* The name of the view.
|
||||
*/
|
||||
String viewName();
|
||||
|
||||
/**
|
||||
* The map function to use (default is empty, which will trigger a default map function filtering on the
|
||||
* repository's associated entity type).
|
||||
*/
|
||||
String mapFunction() default "";
|
||||
|
||||
/**
|
||||
* The reduce function to use (default is built in "_count" reduce function).
|
||||
*/
|
||||
String reduceFunction() default "_count";
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import java.util.Set;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
|
||||
import org.springframework.data.couchbase.repository.support.IndexManager;
|
||||
import org.springframework.data.repository.cdi.CdiRepositoryBean;
|
||||
import org.springframework.data.repository.config.CustomRepositoryImplementationDetector;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -63,7 +64,8 @@ public class CouchbaseRepositoryBean<T> extends CdiRepositoryBean<T> {
|
||||
protected T create(CreationalContext<T> creationalContext, Class<T> repositoryType, Object customImplementation) {
|
||||
CouchbaseOperations couchbaseOperations = getDependencyInstance(couchbaseOperationsBean, CouchbaseOperations.class);
|
||||
RepositoryOperationsMapping couchbaseOperationsMapping = new RepositoryOperationsMapping(couchbaseOperations);
|
||||
return new CouchbaseRepositoryFactory(couchbaseOperationsMapping).getRepository(repositoryType, customImplementation);
|
||||
IndexManager indexManager = new IndexManager();
|
||||
return new CouchbaseRepositoryFactory(couchbaseOperationsMapping, indexManager).getRepository(repositoryType, customImplementation);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,8 +31,12 @@ import org.springframework.data.repository.config.XmlRepositoryConfigurationSour
|
||||
*/
|
||||
public class CouchbaseRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport {
|
||||
|
||||
/** The reference property to use in xml configuration to specify the template to use with a repository. */
|
||||
private static final String COUCHBASE_TEMPLATE_REF = "couchbase-template-ref";
|
||||
|
||||
/** The reference property to use in xml configuration to specify the index manager bean to use with a repository. */
|
||||
private static final String COUCHBASE_INDEX_MANAGER_REF = "couchbase-index-manager-ref";
|
||||
|
||||
@Override
|
||||
protected String getModulePrefix() {
|
||||
return "couchbase";
|
||||
@@ -46,11 +50,14 @@ public class CouchbaseRepositoryConfigurationExtension extends RepositoryConfigu
|
||||
public void postProcess(final BeanDefinitionBuilder builder, final XmlRepositoryConfigurationSource config) {
|
||||
Element element = config.getElement();
|
||||
ParsingUtils.setPropertyReference(builder, element, COUCHBASE_TEMPLATE_REF, "couchbaseOperations");
|
||||
ParsingUtils.setPropertyReference(builder, element, COUCHBASE_INDEX_MANAGER_REF, "indexManager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess(final BeanDefinitionBuilder builder, final AnnotationRepositoryConfigurationSource config) {
|
||||
builder.addDependsOn(BeanNames.REPO_OPERATIONS_MAPPING);
|
||||
builder.addDependsOn(BeanNames.COUCHBASE_INDEX_MANAGER);
|
||||
builder.addPropertyReference("couchbaseOperationsMapping", BeanNames.REPO_OPERATIONS_MAPPING);
|
||||
builder.addPropertyReference("indexManager", BeanNames.COUCHBASE_INDEX_MANAGER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ import java.lang.reflect.Method;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.view.Dimensional;
|
||||
import org.springframework.data.couchbase.core.view.Query;
|
||||
import org.springframework.data.couchbase.core.view.View;
|
||||
import org.springframework.data.couchbase.core.query.Dimensional;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.view.Dimensional;
|
||||
import org.springframework.data.couchbase.core.query.Dimensional;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
|
||||
@@ -20,18 +20,13 @@ import java.util.Iterator;
|
||||
|
||||
import com.couchbase.client.java.document.json.JsonArray;
|
||||
import com.couchbase.client.java.view.SpatialViewQuery;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.view.Dimensional;
|
||||
import org.springframework.data.couchbase.core.query.Dimensional;
|
||||
import org.springframework.data.couchbase.repository.query.support.GeoUtils;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.geo.Box;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.geo.Polygon;
|
||||
import org.springframework.data.geo.Shape;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
|
||||
@@ -52,7 +52,7 @@ public class StringN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
public static final String SPEL_PREFIX = "n1ql";
|
||||
|
||||
/**
|
||||
* Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.view.Query @Query}
|
||||
* Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query}
|
||||
* annotation's inline statement. This will be replaced by the correct <code>SELECT x FROM y</code> clause needed
|
||||
* for entity mapping. Eg. <code>"${{@value StringN1qlBasedQuery#SPEL_SELECT_FROM_CLAUSE}} WHERE test = true"</code>.
|
||||
* Note this only makes sense once, as the beginning of the statement.
|
||||
@@ -60,21 +60,21 @@ public class StringN1qlBasedQuery extends AbstractN1qlBasedQuery {
|
||||
public static final String SPEL_SELECT_FROM_CLAUSE = "#" + SPEL_PREFIX + ".selectEntity";
|
||||
|
||||
/**
|
||||
* Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.view.Query @Query}
|
||||
* Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query}
|
||||
* annotation's inline statement. This will be replaced by the (escaped) bucket name corresponding to the repository's
|
||||
* entity. Eg. <code>"SELECT * FROM ${{@value StringN1qlBasedQuery#SPEL_BUCKET}} LIMIT 3"</code>.
|
||||
*/
|
||||
public static final String SPEL_BUCKET = "#" + SPEL_PREFIX + ".bucket";
|
||||
|
||||
/**
|
||||
* Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.view.Query @Query}
|
||||
* Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query}
|
||||
* annotation's inline statement. This will be replaced by the fields allowing to construct the repository's entity
|
||||
* (SELECT clause). Eg. <code>"SELECT ${{@value StringN1qlBasedQuery#SPEL_ENTITY}} FROM test"</code>.
|
||||
*/
|
||||
public static final String SPEL_ENTITY = "#" + SPEL_PREFIX + ".fields";
|
||||
|
||||
/**
|
||||
* Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.view.Query @Query}
|
||||
* Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query}
|
||||
* annotation's inline statement WHERE clause. This will be replaced by the expression allowing to only select
|
||||
* documents matching the entity's class. Eg. <code>"SELECT * FROM test WHERE test = true AND ${{@value StringN1qlBasedQuery#SPEL_FILTER}}"</code>.
|
||||
*/
|
||||
|
||||
@@ -30,7 +30,7 @@ import com.couchbase.client.java.document.json.JsonObject;
|
||||
import com.couchbase.client.java.view.ViewQuery;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.view.View;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
|
||||
@@ -26,8 +26,11 @@ import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.UnsupportedCouchbaseFeatureException;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.view.Query;
|
||||
import org.springframework.data.couchbase.core.view.View;
|
||||
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
|
||||
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
import org.springframework.data.couchbase.core.query.ViewIndexed;
|
||||
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseQueryMethod;
|
||||
@@ -51,6 +54,7 @@ import org.springframework.util.Assert;
|
||||
* Factory to create {@link SimpleCouchbaseRepository} instances.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
@@ -61,6 +65,11 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
*/
|
||||
private final RepositoryOperationsMapping couchbaseOperationsMapping;
|
||||
|
||||
/**
|
||||
* Holds the reference to the {@link IndexManager}.
|
||||
*/
|
||||
private final IndexManager indexManager;
|
||||
|
||||
/**
|
||||
* Holds the mapping context.
|
||||
*/
|
||||
@@ -76,10 +85,12 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
*
|
||||
* @param couchbaseOperationsMapping the template for the underlying actions.
|
||||
*/
|
||||
public CouchbaseRepositoryFactory(final RepositoryOperationsMapping couchbaseOperationsMapping) {
|
||||
public CouchbaseRepositoryFactory(final RepositoryOperationsMapping couchbaseOperationsMapping, final IndexManager indexManager) {
|
||||
Assert.notNull(couchbaseOperationsMapping);
|
||||
Assert.notNull(indexManager);
|
||||
|
||||
this.couchbaseOperationsMapping = couchbaseOperationsMapping;
|
||||
this.indexManager = indexManager;
|
||||
mappingContext = this.couchbaseOperationsMapping.getDefault().getConverter().getMappingContext();
|
||||
viewPostProcessor = ViewPostProcessor.INSTANCE;
|
||||
|
||||
@@ -117,7 +128,14 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
protected Object getTargetRepository(final RepositoryInformation metadata) {
|
||||
CouchbaseOperations couchbaseOperations = couchbaseOperationsMapping.resolve(metadata.getRepositoryInterface(), metadata.getDomainType());
|
||||
boolean isN1qlAvailable = couchbaseOperations.getCouchbaseClusterInfo().checkAvailable(CouchbaseFeature.N1QL);
|
||||
checkFeatures(metadata, isN1qlAvailable);
|
||||
|
||||
ViewIndexed viewIndexed = AnnotationUtils.findAnnotation(metadata.getRepositoryInterface(), ViewIndexed.class);
|
||||
N1qlPrimaryIndexed n1qlPrimaryIndexed = AnnotationUtils.findAnnotation(metadata.getRepositoryInterface(), N1qlPrimaryIndexed.class);
|
||||
N1qlSecondaryIndexed n1qlSecondaryIndexed = AnnotationUtils.findAnnotation(metadata.getRepositoryInterface(), N1qlSecondaryIndexed.class);
|
||||
|
||||
checkFeatures(metadata, isN1qlAvailable, n1qlPrimaryIndexed, n1qlSecondaryIndexed);
|
||||
|
||||
indexManager.buildIndexes(metadata, viewIndexed, n1qlPrimaryIndexed, n1qlSecondaryIndexed, couchbaseOperations);
|
||||
|
||||
CouchbaseEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
|
||||
if (isN1qlAvailable) {
|
||||
@@ -132,10 +150,14 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFeatures(RepositoryInformation metadata, boolean isN1qlAvailable) {
|
||||
boolean needsN1ql = metadata.isPagingRepository();
|
||||
//paging repo will need N1QL, other repos might also if they don't have only @View methods
|
||||
private void checkFeatures(RepositoryInformation metadata, boolean isN1qlAvailable,
|
||||
N1qlPrimaryIndexed n1qlPrimaryIndexed, N1qlSecondaryIndexed n1qlSecondaryIndexed) {
|
||||
//paging repo will always need N1QL, also check if the repository requires a N1QL index
|
||||
boolean needsN1ql = metadata.isPagingRepository() || n1qlPrimaryIndexed != null || n1qlSecondaryIndexed != null;
|
||||
|
||||
//for other repos, they might also need N1QL if they don't have only @View methods
|
||||
if (!needsN1ql) {
|
||||
|
||||
for (Method method : metadata.getQueryMethods()) {
|
||||
|
||||
boolean hasN1ql = AnnotationUtils.findAnnotation(method, Query.class) != null;
|
||||
|
||||
@@ -38,6 +38,11 @@ public class CouchbaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID e
|
||||
*/
|
||||
private RepositoryOperationsMapping operationsMapping;
|
||||
|
||||
/**
|
||||
* Contains the reference to the IndexManager.
|
||||
*/
|
||||
private IndexManager indexManager;
|
||||
|
||||
/**
|
||||
* Set the template reference.
|
||||
*
|
||||
@@ -52,6 +57,15 @@ public class CouchbaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID e
|
||||
setMappingContext(operationsMapping.getDefault().getConverter().getMappingContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IndexManager reference.
|
||||
*
|
||||
* @param indexManager the IndexManager to use.
|
||||
*/
|
||||
public void setIndexManager(final IndexManager indexManager) {
|
||||
this.indexManager = indexManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a factory instance.
|
||||
*
|
||||
@@ -59,25 +73,28 @@ public class CouchbaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID e
|
||||
*/
|
||||
@Override
|
||||
protected RepositoryFactorySupport createRepositoryFactory() {
|
||||
return getFactoryInstance(operationsMapping);
|
||||
return getFactoryInstance(operationsMapping, indexManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the factory instance for the operations.
|
||||
*
|
||||
* @param operationsMapping the reference to the template.
|
||||
* @param indexManager the reference to the {@link IndexManager}.
|
||||
* @return the factory instance.
|
||||
*/
|
||||
private RepositoryFactorySupport getFactoryInstance(final RepositoryOperationsMapping operationsMapping) {
|
||||
return new CouchbaseRepositoryFactory(operationsMapping);
|
||||
private RepositoryFactorySupport getFactoryInstance(final RepositoryOperationsMapping operationsMapping,
|
||||
IndexManager indexManager) {
|
||||
return new CouchbaseRepositoryFactory(operationsMapping, indexManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the template is set and not null.
|
||||
* Make sure that the dependencies are set and not null.
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
Assert.notNull(operationsMapping, "operationsMapping must not be null!");
|
||||
Assert.notNull(indexManager, "indexManager must not be null!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.couchbase.repository.support;
|
||||
|
||||
import static com.couchbase.client.java.query.dsl.Expression.s;
|
||||
import static com.couchbase.client.java.query.dsl.Expression.x;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import com.couchbase.client.java.bucket.BucketManager;
|
||||
import com.couchbase.client.java.document.json.JsonObject;
|
||||
import com.couchbase.client.java.query.AsyncN1qlQueryResult;
|
||||
import com.couchbase.client.java.query.Index;
|
||||
import com.couchbase.client.java.query.Statement;
|
||||
import com.couchbase.client.java.query.dsl.path.index.IndexType;
|
||||
import com.couchbase.client.java.view.DefaultView;
|
||||
import com.couchbase.client.java.view.DesignDocument;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import rx.Observable;
|
||||
import rx.exceptions.CompositeException;
|
||||
import rx.functions.Action1;
|
||||
import rx.functions.Func1;
|
||||
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
|
||||
import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed;
|
||||
import org.springframework.data.couchbase.core.query.N1qlSecondaryIndexed;
|
||||
import org.springframework.data.couchbase.core.query.ViewIndexed;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
|
||||
/**
|
||||
* {@link IndexManager} is responsible for automatic index creation according to the provided metadata and
|
||||
* various index annotations (if not null).
|
||||
* <p/>
|
||||
* Index creation will be attempted in parallel using the asynchronous APIs, but the overall process is still blocking.
|
||||
*
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
public class IndexManager {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(IndexManager.class);
|
||||
|
||||
private static final String TEMPLATE_MAP_FUNCTION = "function (doc, meta) { if(doc.%s == \"%s\") { emit(null, null); } }";
|
||||
|
||||
private static final JsonObject SUCCESS_MARKER = JsonObject.empty();
|
||||
|
||||
/** True if this index manager should ignore view creation annotations */
|
||||
private boolean ignoreViews;
|
||||
/** True if this index manager should ignore N1QL PRIMARY creation annotations */
|
||||
private boolean ignoreN1qlPrimary;
|
||||
/** True if this index manager should ignore N1QL SECONDARY creation annotations */
|
||||
private boolean ignoreN1qlSecondary;
|
||||
|
||||
|
||||
/**
|
||||
* Construct an IndexManager that can be used as a Bean in a {@link Profile @Profile} annotated configuration
|
||||
* in order to ignore all or part of automatic index creations in some contexts (like activating it in Dev but
|
||||
* not in Prod).
|
||||
*
|
||||
* @param ignoreViews true to ignore {@link ViewIndexed} annotations.
|
||||
* @param ignoreN1qlPrimary true to ignore {@link N1qlPrimaryIndexed} annotations.
|
||||
* @param ignoreN1qlSecondary true to ignore {@link N1qlSecondaryIndexed} annotations.
|
||||
*/
|
||||
public IndexManager(boolean ignoreViews, boolean ignoreN1qlPrimary, boolean ignoreN1qlSecondary) {
|
||||
this.ignoreViews = ignoreViews;
|
||||
this.ignoreN1qlPrimary = ignoreN1qlPrimary;
|
||||
this.ignoreN1qlSecondary = ignoreN1qlSecondary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a default IndexManager that process all three types of automatic index creations.
|
||||
*/
|
||||
public IndexManager() {
|
||||
this(false, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this IndexManager ignores {@link ViewIndexed} annotations.
|
||||
*/
|
||||
public boolean isIgnoreViews() {
|
||||
return ignoreViews;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this IndexManager ignores {@link N1qlPrimaryIndexed} annotations.
|
||||
*/
|
||||
public boolean isIgnoreN1qlPrimary() {
|
||||
return ignoreN1qlPrimary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this IndexManager ignores {@link N1qlSecondaryIndexed} annotations.
|
||||
*/
|
||||
public boolean isIgnoreN1qlSecondary() {
|
||||
return ignoreN1qlSecondary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the relevant indexes according to the provided annotation and repository metadata, in parallel but blocking
|
||||
* until all relevant indexes are created. Existing indexes will be detected and skipped.
|
||||
* <p/>
|
||||
* Note that this IndexManager could be configured to ignore some of the annotation types.
|
||||
* In case of multiple errors, a {@link CompositeException} can be raised with up to 3 causes (one per type of index).
|
||||
*
|
||||
* @param metadata the repository's metadata (allowing to find out the type of entity stored, the key under which type
|
||||
* information is stored, etc...).
|
||||
* @param viewIndexed the annotation for creation of a View-based index.
|
||||
* @param n1qlPrimaryIndexed the annotation for creation of a N1QL-based primary index (generic).
|
||||
* @param n1qlSecondaryIndexed the annotation for creation of a N1QL-based secondary index (specific to the repository
|
||||
* stored entity).
|
||||
* @param couchbaseOperations the template to use for index creation.
|
||||
* @throws CompositeException when several errors (for multiple index types) have been raised.
|
||||
*/
|
||||
public void buildIndexes(RepositoryInformation metadata, ViewIndexed viewIndexed, N1qlPrimaryIndexed n1qlPrimaryIndexed,
|
||||
N1qlSecondaryIndexed n1qlSecondaryIndexed, CouchbaseOperations couchbaseOperations) {
|
||||
Observable<Void> viewAsync = Observable.empty();
|
||||
Observable<Void> n1qlPrimaryAsync = Observable.empty();
|
||||
Observable<Void> n1qlSecondaryAsync = Observable.empty();
|
||||
|
||||
if (viewIndexed != null && !ignoreViews) {
|
||||
viewAsync = buildAllView(viewIndexed, metadata, couchbaseOperations);
|
||||
}
|
||||
|
||||
if (n1qlPrimaryIndexed != null && !ignoreN1qlPrimary) {
|
||||
n1qlPrimaryAsync = buildN1qlPrimary(metadata, couchbaseOperations);
|
||||
}
|
||||
|
||||
if (n1qlSecondaryIndexed != null && !ignoreN1qlSecondary) {
|
||||
n1qlSecondaryAsync = buildN1qlSecondary(n1qlSecondaryIndexed, metadata, couchbaseOperations);
|
||||
}
|
||||
|
||||
//trigger the builds, wait for the last one, throw CompositeException if errors
|
||||
Observable.mergeDelayError(viewAsync, n1qlPrimaryAsync, n1qlSecondaryAsync)
|
||||
.toBlocking()
|
||||
.lastOrDefault(null);
|
||||
}
|
||||
|
||||
private Observable<Void> buildN1qlPrimary(final RepositoryInformation metadata, CouchbaseOperations couchbaseOperations) {
|
||||
final String bucketName = couchbaseOperations.getCouchbaseBucket().name();
|
||||
Statement createPrimary = Index.createPrimaryIndex()
|
||||
.on(bucketName)
|
||||
.using(IndexType.GSI);
|
||||
|
||||
LOGGER.debug("Creating N1QL primary index for repository {}", metadata.getRepositoryInterface().getSimpleName());
|
||||
return couchbaseOperations.getCouchbaseBucket().async().query(createPrimary)
|
||||
.flatMap(new Func1<AsyncN1qlQueryResult, Observable<JsonObject>>() {
|
||||
@Override
|
||||
public Observable<JsonObject> call(AsyncN1qlQueryResult asyncN1qlQueryResult) {
|
||||
return asyncN1qlQueryResult.errors();
|
||||
}
|
||||
})
|
||||
.defaultIfEmpty(SUCCESS_MARKER)
|
||||
.flatMap(new Func1<JsonObject, Observable<Void>>() {
|
||||
@Override
|
||||
public Observable<Void> call(JsonObject json) {
|
||||
if (json == SUCCESS_MARKER) {
|
||||
LOGGER.debug("N1QL primary index created for repository {}", metadata.getRepositoryInterface().getSimpleName());
|
||||
return Observable.empty();
|
||||
} else if (json.getString("msg").contains("Index #primary already exist")) {
|
||||
LOGGER.debug("Primary index already exist, skipping");
|
||||
return Observable.empty(); //ignore, the index already exist
|
||||
} else {
|
||||
return Observable.error(new CouchbaseQueryExecutionException(
|
||||
"Cannot create N1QL primary index on " + bucketName + ": " + json));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Observable<Void> buildN1qlSecondary(N1qlSecondaryIndexed config, final RepositoryInformation metadata, CouchbaseOperations couchbaseOperations) {
|
||||
final String bucketName = couchbaseOperations.getCouchbaseBucket().name();
|
||||
final String indexName = config.indexName();
|
||||
String typeKey = couchbaseOperations.getConverter().getTypeKey();
|
||||
final String type = metadata.getDomainType().getName();
|
||||
|
||||
Statement createIndex = Index.createIndex(indexName)
|
||||
.on(bucketName, x(typeKey))
|
||||
.where(x(typeKey).eq(s(type)))
|
||||
.using(IndexType.GSI);
|
||||
|
||||
LOGGER.debug("Creating N1QL secondary index for repository {}", metadata.getRepositoryInterface().getSimpleName());
|
||||
return couchbaseOperations.getCouchbaseBucket().async().query(createIndex)
|
||||
.flatMap(new Func1<AsyncN1qlQueryResult, Observable<JsonObject>>() {
|
||||
@Override
|
||||
public Observable<JsonObject> call(AsyncN1qlQueryResult asyncN1qlQueryResult) {
|
||||
return asyncN1qlQueryResult.errors();
|
||||
}
|
||||
})
|
||||
.defaultIfEmpty(SUCCESS_MARKER)
|
||||
.flatMap(new Func1<JsonObject, Observable<Void>>() {
|
||||
@Override
|
||||
public Observable<Void> call(JsonObject json) {
|
||||
if (json == SUCCESS_MARKER) {
|
||||
LOGGER.debug("N1QL secondary index created for repository {}", metadata.getRepositoryInterface().getSimpleName());
|
||||
return Observable.empty();
|
||||
} else if (json.getString("msg").contains("Index " + indexName + " already exist")) {
|
||||
LOGGER.debug("Secondary index already exist, skipping");
|
||||
return Observable.empty(); //ignore, the index already exist
|
||||
} else {
|
||||
return Observable.error(new CouchbaseQueryExecutionException(
|
||||
"Cannot create N1QL secondary index " + bucketName + "." + indexName + " for " + type + ": " + json));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Observable<Void> buildAllView(ViewIndexed config, final RepositoryInformation metadata, CouchbaseOperations couchbaseOperations) {
|
||||
if (config == null) return Observable.empty();
|
||||
LOGGER.debug("Creating View index index for repository {}", metadata.getRepositoryInterface().getSimpleName());
|
||||
|
||||
BucketManager manager = couchbaseOperations.getCouchbaseBucket().bucketManager();
|
||||
String viewName = config.viewName();
|
||||
String mapFunction = config.mapFunction();
|
||||
if (mapFunction.isEmpty()) {
|
||||
String typeKey = couchbaseOperations.getConverter().getTypeKey();
|
||||
String type = metadata.getDomainType().getName();
|
||||
|
||||
mapFunction = String.format(TEMPLATE_MAP_FUNCTION, typeKey, type);
|
||||
}
|
||||
String reduceFunction = config.reduceFunction();
|
||||
if ("".equals(reduceFunction)) {
|
||||
reduceFunction = null;
|
||||
}
|
||||
|
||||
com.couchbase.client.java.view.View view = DefaultView.create(viewName, mapFunction, reduceFunction);
|
||||
DesignDocument doc = manager.getDesignDocument(config.designDoc());
|
||||
if (doc != null) {
|
||||
for (com.couchbase.client.java.view.View existingView : doc.views()) {
|
||||
if (existingView.name().equals(viewName)) {
|
||||
LOGGER.debug("View index {}/{} already exist, skipping", config.designDoc(), viewName);
|
||||
return Observable.empty(); //abort, the view already exist
|
||||
}
|
||||
}
|
||||
doc.views().add(view);
|
||||
} else {
|
||||
doc = DesignDocument.create(config.designDoc(), Collections.singletonList(view));
|
||||
}
|
||||
return manager.async().upsertDesignDocument(doc)
|
||||
.map(new Func1<DesignDocument, Void>() {
|
||||
@Override
|
||||
public Void call(DesignDocument designDocument) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.doOnNext(new Action1<Void>() {
|
||||
@Override
|
||||
public void call(Void aVoid) {
|
||||
LOGGER.debug("View index created for repository {}", metadata.getRepositoryInterface().getSimpleName());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -26,12 +26,9 @@ import com.couchbase.client.java.view.ViewResult;
|
||||
import com.couchbase.client.java.view.ViewRow;
|
||||
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.view.View;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
import org.springframework.data.couchbase.repository.CouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.repository.support;
|
||||
|
||||
import org.springframework.data.couchbase.core.view.View;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
|
||||
/**
|
||||
* Interface to abstract {@link ViewMetadataProvider} that provides {@link View}s to be used for query execution.
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.couchbase.core.view.View;
|
||||
import org.springframework.data.couchbase.core.query.View;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor;
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ The reference to a CouchbaseConverter instance.
|
||||
</xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="direct">
|
||||
<tool:expected-type type="org.springframework.data.couchbase.core.view.Consistency"/>
|
||||
<tool:expected-type type="org.springframework.data.couchbase.core.query.Consistency"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
@@ -178,6 +178,18 @@ The name of the CouchbaseBucket object that determines what connection to monito
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:element name="indexManager">
|
||||
<xsd:complexType>
|
||||
<xsd:complexContent>
|
||||
<xsd:extension base="beans:identifiedType">
|
||||
<xsd:attribute name="ignoreViews" type="xsd:boolean" default="false" use="optional"/>
|
||||
<xsd:attribute name="ignorePrimary" type="xsd:boolean" default="false" use="optional"/>
|
||||
<xsd:attribute name="ignoreSecondary" type="xsd:boolean" default="false" use="optional"/>
|
||||
</xsd:extension>
|
||||
</xsd:complexContent>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:attributeGroup name="couchbase-repository-attributes">
|
||||
<xsd:attribute name="couchbase-template-ref" type="couchbaseTemplateRef" default="couchbaseTemplate">
|
||||
<xsd:annotation>
|
||||
@@ -186,6 +198,13 @@ The name of the CouchbaseBucket object that determines what connection to monito
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="couchbase-index-manager-ref" type="couchbaseIndexManagerRef" default="couchbaseIndexManager">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
The reference to an IndexManager. Will default to 'couchbaseIndexManager'.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:attributeGroup>
|
||||
|
||||
<xsd:simpleType name="converterRef">
|
||||
@@ -220,4 +239,15 @@ The name of the CouchbaseBucket object that determines what connection to monito
|
||||
</xsd:annotation>
|
||||
<xsd:union memberTypes="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="couchbaseIndexManagerRef">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:assignable-to type="org.springframework.data.couchbase.repository.support.IndexManager"/>
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
<xsd:union memberTypes="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
@@ -20,7 +20,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.WriteResultChecking;
|
||||
import org.springframework.data.couchbase.core.view.Consistency;
|
||||
import org.springframework.data.couchbase.core.query.Consistency;
|
||||
|
||||
@Configuration
|
||||
public class UnitTestApplicationConfig extends AbstractCouchbaseConfiguration {
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
|
||||
<!-- You can use these logger configurations to log more details:
|
||||
- log the Couchbase queries produced by the framework
|
||||
- log details of the parsing of SpEL in N1QL inline queries-->
|
||||
- log details of the parsing of SpEL in N1QL inline queries
|
||||
- log additional debug info during automatic index creation
|
||||
-->
|
||||
<logger name="org.springframework.data.couchbase.repository.query" level="debug"/>
|
||||
<logger name="org.springframework.data.couchbase.repository.query.StringN1qlBasedQuery" level="trace"/>
|
||||
<logger name="org.springframework.data.couchbase.repository.support.IndexManager" level="debug"/>
|
||||
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user