DATACOUCH-138 - Add Page/Slice/PagingAndSortingRepo

Add support for Page / Slice in N1QL query derived methods.
Add PagingAndSortingRepository support through a N1qlCouchbaseRepository implementation. It will only be used as a base if N1QL is actually available on the cluster.

Refactor out some steps of entity related query creation in a N1qlUtils class, so that they can be also done eg. in repository base classes.

Add tests for both N1qlUtils and the N1qlCouchbaseRepository.

Document that PagingAndSortingRepository is supported provided N1QL is available on the Couchbase cluster.
This commit is contained in:
Simon Baslé
2015-09-16 17:41:21 -04:00
parent ef4a79efff
commit 56ae8217e9
24 changed files with 919 additions and 98 deletions

View File

@@ -92,7 +92,7 @@ public class CouchbaseTemplateParserIntegrationTests {
assertEquals("javaXmlClass", converter.getTypeKey());
User u = new User("specialSaveUser", "John Locke");
User u = new User("specialSaveUser", "John Locke", 46);
template.save(u);
JsonDocument uJsonDoc = template.getCouchbaseBucket().get("specialSaveUser");
template.getCouchbaseBucket().remove("specialSaveUser");

View File

@@ -49,7 +49,7 @@ public class CouchbaseRepositoryViewListener extends DependencyInjectionTestExec
private void populateTestData(final Bucket client, ClusterInfo clusterInfo) {
CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client);
for (int i = 0; i < 100; i++) {
template.save(new User("testuser-" + i, "uname-" + i), PersistTo.MASTER, ReplicateTo.NONE);
template.save(new User("testuser-" + i, "uname-" + i, i), PersistTo.MASTER, ReplicateTo.NONE);
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.couchbase.client.java.Bucket;
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.CouchbaseTemplate;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
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 PaginAndSortingRepository features in the Couchbase connector.
*
* @author Simon Baslé
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(QueryDerivationConversionListener.class)
public class N1qlCouchbaseRepositoryTests {
@Autowired
private CouchbaseTemplate template;
private PartyPagingRepository repository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(template);
repository = factory.getRepository(PartyPagingRepository.class);
}
@Test
public void shouldFindAllWithSort() {
Iterable<Party> allByAttendanceDesc = repository.findAll(new Sort(Sort.Direction.DESC, "attendees"));
long previousAttendance = Long.MAX_VALUE;
for (Party party : allByAttendanceDesc) {
assertTrue(party.getAttendees() <= previousAttendance);
previousAttendance = party.getAttendees();
}
}
@Test
public void shouldSortOnRenamedFieldIfJsonNameIsProvidedInSort() {
Iterable<Party> parties = repository.findAll(new Sort(Sort.Direction.DESC, "desc"));
String previousDesc = null;
for (Party party : parties) {
if (previousDesc != null) {
assertTrue(party.getDescription().compareTo(previousDesc) <= 0);
}
previousDesc = party.getDescription();
}
}
@Test
public void shouldPageThroughEntities() {
Pageable pageable = new PageRequest(0, 8);
Page<Party> page1 = repository.findAll(pageable);
assertEquals(14, page1.getTotalElements()); //13 generated parties + 1 specifically crafted party
assertEquals(8, page1.getNumberOfElements());
}
}

View File

@@ -0,0 +1,89 @@
package org.springframework.data.couchbase.repository;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import com.couchbase.client.java.Bucket;
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.CouchbaseTemplate;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = IntegrationTestApplicationConfig.class)
@TestExecutionListeners(SimpleCouchbaseRepositoryListener.class)
public class PageAndSliceTests {
@Autowired
private Bucket client;
@Autowired
private CouchbaseTemplate template;
private UserRepository repository;
@Before
public void setup() throws Exception {
RepositoryFactorySupport factory = new CouchbaseRepositoryFactory(template);
repository = factory.getRepository(UserRepository.class);
}
@Test
public void shouldPageThroughResults() {
Page<User> page1 = repository.findByAgeGreaterThan(9, new PageRequest(0, 40)); //there are 90 matching users
Page<User> page2 = repository.findByAgeGreaterThan(9, page1.nextPageable());
Page<User> page3 = repository.findByAgeGreaterThan(9, page2.nextPageable());
assertEquals(90, page1.getTotalElements());
assertEquals(3, page1.getTotalPages());
assertTrue(page1.hasContent());
assertTrue(page1.hasNext());
assertEquals(40, page1.getNumberOfElements());
assertTrue(page2.hasContent());
assertTrue(page2.hasNext());
assertEquals(40, page2.getNumberOfElements());
assertTrue(page3.hasContent());
assertFalse(page3.hasNext());
assertEquals(10, page3.getNumberOfElements());
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowWhenPageableIsNullInPageQuery() {
repository.findByAgeGreaterThan(9, null);
}
@Test
public void shouldSliceThroughResults() {
int count = 0;
List<User> allMatching = new ArrayList<User>(10);
Slice<User> slice = repository.findByAgeLessThan(9, new PageRequest(0, 3)); //9 matching users (ages 0-8)
allMatching.addAll(slice.getContent());
while(slice.hasNext()) {
slice = repository.findByAgeLessThan(9, slice.nextPageable());
allMatching.addAll(slice.getContent());
assertEquals(3, slice.getContent().size());
}
assertEquals(9, allMatching.size());
}
@Test(expected = IllegalArgumentException.class)
public void shouldThrowWhenPageableIsNullSliceQuery() {
repository.findByAgeLessThan(9, null);
}
}

View File

@@ -0,0 +1,4 @@
package org.springframework.data.couchbase.repository;
public interface PartyPagingRepository extends CouchbasePagingAndSortingRepository<Party, String> {
}

View File

@@ -48,7 +48,7 @@ public class SimpleCouchbaseRepositoryListener extends DependencyInjectionTestEx
CouchbaseTemplate template = new CouchbaseTemplate(clusterInfo, client);
for (int i = 0; i < 100; i++) {
User u = new User("testuser-" + i, "uname-" + i);
User u = new User("testuser-" + i, "uname-" + i, i);
template.save(u, PersistTo.MASTER, ReplicateTo.NONE);
}

View File

@@ -34,6 +34,8 @@ import org.springframework.data.couchbase.IntegrationTestApplicationConfig;
import org.springframework.data.couchbase.core.CouchbaseQueryExecutionException;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
@@ -64,7 +66,7 @@ public class SimpleCouchbaseRepositoryTests {
@Test
public void simpleCrud() {
String key = "my_unique_user_key";
User instance = new User(key, "foobar");
User instance = new User(key, "foobar", 22);
repository.save(instance);
User found = repository.findOne(key);
@@ -168,5 +170,4 @@ public class SimpleCouchbaseRepositoryTests {
}
}
}
}

View File

@@ -29,9 +29,12 @@ public class User {
private final String username;
public User(String key, String username) {
private final int age;
public User(String key, String username, int age) {
this.key = key;
this.username = username;
this.age = age;
}
public String getUsername() {
@@ -42,6 +45,10 @@ public class User {
return key;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return this.key;

View File

@@ -22,6 +22,9 @@ 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.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
/**
* @author Michael Nitschinger
@@ -43,4 +46,8 @@ public interface UserRepository extends CouchbaseRepository<User, String> {
List<User> findByUsernameContains(String contains);
User findByUsernameNear(String place);//this is to check that there's a N1QL derivation AND it fails
Page<User> findByAgeGreaterThan(int minAge, Pageable pageable);
Slice<User> findByAgeLessThan(int maxAge, Pageable pageable);
}