#203 - Revise Cassandra Examples.

Simplify examples by adopting Spring Boot 1.4 improvements. Upgrade Cassandra Java driver to 3.0.3. Add examples for Ingalls (Query derivation, projection, Java 8 feature support).

Cassandra example setup is now self-contained by requiring just a running Cassandra instance.
Keyspace and tables are created during the tests. Examples also check if Cassandra is running and some examples additionally check the required version. Test execution is skipped if conditions are not met.

Cassandra (2.x) is started with TravisCI.
This commit is contained in:
Mark Paluch
2016-07-25 16:40:41 +02:00
parent d2e5c4b28f
commit 95978416d1
38 changed files with 1708 additions and 112 deletions

View File

@@ -5,6 +5,7 @@ jdk:
services:
- redis-server
- cassandra
cache:
directories:
@@ -14,4 +15,4 @@ sudo: false
install: true
script: "mvn clean dependency:list test -Dsort"
script: "mvn clean dependency:list test -Dsort"

View File

@@ -6,8 +6,6 @@
Before we can start we have to install Cassandra, e.g. via brew on Max OS.
```
brew install cassandra
pip install cassandra-driver
brew install cassandra-driver
More details can be found here: https://wiki.apache.org/cassandra/GettingStarted
```
@@ -17,26 +15,5 @@ More details can be found here: https://wiki.apache.org/cassandra/GettingStarted
/usr/local/bin/cassandra -f
```
### Start a Cassandra shell cqlsh
```
/usr/local/bin/cqlsh
```
### Setup Keyspace and Tables
```
CREATE KEYSPACE example
WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };
USE example;
CREATE TABLE users (
user_id int PRIMARY KEY,
uname text,
fname text,
lname text
);
```
That should be enough to get you started.
Now you can simply type ```mvn clean install``` to run the example.
Now you can simply type ```mvn clean install``` to run the example.

View File

@@ -12,6 +12,22 @@
</parent>
<name>Spring Data Cassandra - Example</name>
<description>Small sample project showing the usage of Spring Data Cassandra.</description>
<description>Basic sample project showing the usage of Spring Data Cassandra.</description>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-cassandra-example-utils</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014 the original author or authors.
* Copyright 2013-2016 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.
@@ -13,37 +13,51 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra;
package example.springdata.cassandra.basic;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cassandra.config.java.AbstractCqlTemplateConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.config.java.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
import com.datastax.driver.core.Session;
/**
* Basic {@link Configuration} to create the necessary schema for the {@link User} table.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
*/
@Configuration
@EnableAutoConfiguration
class SimpleConfiguration {
class BasicConfiguration {
@Configuration
@EnableCassandraRepositories
static class CassandraConfig extends AbstractCqlTemplateConfiguration {
static class CassandraConfig extends AbstractCassandraConfiguration {
@Override
public String getKeyspaceName() {
return "example";
}
@Bean
public CassandraTemplate cassandraTemplate(Session session) {
return new CassandraTemplate(session);
}
@Override
public String[] getEntityBasePackages() {
return new String[] { User.class.getPackage().getName() };
}
@Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE;
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2013-2016 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 example.springdata.cassandra.basic;
import java.util.List;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.repository.CrudRepository;
/**
* Simple repository interface for {@link User} instances. The interface is used to declare so called query methods,
* methods to retrieve single entities or collections of them.
*
* @author Thomas Darimont
*/
public interface BasicUserRepository extends CrudRepository<User, Long> {
/**
* Sample method annotated with {@link Query}. This method executes the CQL from the {@link Query} value.
*
* @param id
* @return
*/
@Query("SELECT * from users where user_id in(?0)")
User findUserByIdIn(long id);
/**
* Derived query method. This query corresponds with {@code SELECT * FROM users WHERE uname = ?0}.
* {@link User#username} is not part of the primary so it requires a secondary index.
*
* @param username
* @return
*/
User findUserByUsername(String username);
/**
* Derived query method using SASI (SSTable Attached Secondary Index) features through the {@code LIKE} keyword. This
* query corresponds with {@code SELECT * FROM users WHERE uname LIKE '?0%'}. {@link User#username} is not part of the
* primary so it requires a secondary index.
*
* @param lastnamePrefix
* @return
*/
List<User> findUsersByLastnameStartsWith(String lastnamePrefix);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-2016 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra;
package example.springdata.cassandra.basic;
import lombok.Data;
import lombok.NoArgsConstructor;

View File

@@ -1,5 +1,5 @@
/**
* Package showing a simple repository interface to use basic query method execution functionality.
*/
package example.springdata.cassandra;
package example.springdata.cassandra.basic;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2016 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.
@@ -13,14 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.springdata.cassandra;
package example.springdata.cassandra.convert;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.mapping.Table;
import lombok.Data;
import java.util.List;
/**
* Simple repository interface for {@link User} instances. The interface is used to declare so called query methods,
* methods to retrieve single entities or collections of them.
* Sample Addressbook class.
*
* @author Thomas Darimont
* @author Mark Paluch
*/
public interface SimpleUserRepository extends CrudRepository<User, Long> {}
@Data
@Table
public class Addressbook {
@Id String id;
Contact me;
List<Contact> friends;
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2016 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 example.springdata.cassandra.convert;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Sample Contact class.
*
* @author Mark Paluch
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Contact {
String firstname;
String lastname;
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright 2016 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 example.springdata.cassandra.convert;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.config.java.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.convert.CustomConversions;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
import org.springframework.util.StringUtils;
import com.datastax.driver.core.Row;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* {@link Configuration} class to register custom converters.
*
* @author Mark Paluch
*/
@Configuration
@EnableCassandraRepositories
class ConverterConfiguration extends AbstractCassandraConfiguration {
@Override
public String getKeyspaceName() {
return "example";
}
@Override
public String[] getEntityBasePackages() {
return new String[] { Addressbook.class.getPackage().getName() };
}
@Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE;
}
@Override
public CustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new PersonWriteConverter());
converters.add(new PersonReadConverter());
converters.add(new CustomAddressbookReadConverter());
return new CustomConversions(converters);
}
/**
* Write a {@link Contact} into its {@link String} representation.
*/
static class PersonWriteConverter implements Converter<Contact, String> {
public String convert(Contact source) {
try {
return new ObjectMapper().writeValueAsString(source);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
/**
* Read a {@link Contact} from its {@link String} representation.
*/
static class PersonReadConverter implements Converter<String, Contact> {
public Contact convert(String source) {
if (StringUtils.hasText(source)) {
try {
return new ObjectMapper().readValue(source, Contact.class);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
return null;
}
}
/**
* Perform custom mapping by reading a {@link Row} into a custom class.
*/
static class CustomAddressbookReadConverter implements Converter<Row, CustomAddressbook> {
public CustomAddressbook convert(Row source) {
CustomAddressbook result = new CustomAddressbook();
result.setTheId(source.getString("id"));
result.setMyDetailsAsJson(source.getString("me"));
return result;
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2016 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 example.springdata.cassandra.convert;
import lombok.Data;
/**
* @author Mark Paluch
*/
@Data
public class CustomAddressbook {
String theId;
String myDetailsAsJson;
}

View File

@@ -0,0 +1,4 @@
/**
* Package showing conversion features.
*/
package example.springdata.cassandra.convert;

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2016 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 example.springdata.cassandra.projection;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.mapping.Table;
import lombok.Value;
/**
* @author Mark Paluch
*/
@Value
@Table
class Customer {
@Id String id;
String firstname, lastname;
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2016 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 example.springdata.cassandra.projection;
/**
* An example projection interface containing only the firstname.
*
* @author Mark Paluch
*/
interface CustomerProjection {
String getFirstname();
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2016 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 example.springdata.cassandra.projection;
import java.util.Collection;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.repository.CrudRepository;
/**
* Sample repository managing customers to show projecting functionality of Spring Data Cassandra.
*
* @author Mark Paluch
*/
interface CustomerRepository extends CrudRepository<Customer, String> {
/**
* Uses a projection interface to indicate the fields to be returned. As the projection doesn't use any dynamic
* fields, the query execution will be restricted to only the fields needed by the projection.
*
* @return
*/
Collection<CustomerProjection> findAllProjectedBy();
/**
* When a projection is used that contains dynamic properties (i.e. SpEL expressions in an {@link Value} annotation),
* the normal target entity will be loaded but dynamically projected so that the target can be referred to in the
* expression.
*
* @return
*/
Collection<CustomerSummary> findAllSummarizedBy();
/**
* Passes in the projection type dynamically.
*
* @param id
* @param projection
* @return
*/
<T> Collection<T> findById(String id, Class<T> projection);
/**
* Projection for a single entity.
*
* @param id
* @return
*/
CustomerProjection findProjectedById(String id);
/**
* Dynamic projection for a single entity.
*
* @param id
* @param projection
* @return
*/
<T> T findProjectedById(String id, Class<T> projection);
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2016 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 example.springdata.cassandra.projection;
import org.springframework.beans.factory.annotation.Value;
/**
* An example of using SpEL with projections.
*
* @author Mark Paluch
*/
interface CustomerSummary {
@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2016 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 example.springdata.cassandra.projection;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.config.java.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
import com.datastax.driver.core.Session;
import example.springdata.cassandra.basic.User;
/**
* Basic {@link Configuration} to create the necessary schema for the {@link Customer} table.
*
* @author Mark Paluch
*/
@Configuration
@EnableAutoConfiguration
class ProjectionConfiguration {
@Configuration
@EnableCassandraRepositories
static class CassandraConfig extends AbstractCassandraConfiguration {
@Override
public String getKeyspaceName() {
return "example";
}
@Override
public String[] getEntityBasePackages() {
return new String[] { Customer.class.getPackage().getName() };
}
@Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE;
}
}
}

View File

@@ -0,0 +1,4 @@
/**
* Package showing projection features.
*/
package example.springdata.cassandra.projection;

View File

@@ -1,65 +0,0 @@
/*
* Copyright 2013-2014 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 example.springdata.cassandra;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import example.springdata.cassandra.SimpleConfiguration;
import example.springdata.cassandra.SimpleUserRepository;
import example.springdata.cassandra.User;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Integration test showing the basic usage of {@link SimpleUserRepository}.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
*/
@Ignore
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SimpleConfiguration.class)
public class SimpleUserRepositoryTests {
@Autowired SimpleUserRepository repository;
User user;
@Before
public void setUp() {
user = new User();
user.setId(42L);
user.setUsername("foobar");
user.setFirstname("firstname");
user.setLastname("lastname");
}
@Test
public void findSavedUserById() {
user = repository.save(user);
assertThat(repository.findOne(user.getId()), is(user));
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2013-2014 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 example.springdata.cassandra.basic;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.util.Version;
import org.springframework.test.context.junit4.SpringRunner;
import com.datastax.driver.core.Session;
import example.springdata.cassandra.util.CassandraVersion;
import example.springdata.cassandra.util.RequiresCassandraKeyspace;
/**
* Integration test showing the basic usage of {@link BasicUserRepository}.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BasicConfiguration.class)
public class BasicUserRepositoryTests {
public final static Version CASSANDRA_3_4 = Version.parse("3.4");
@ClassRule public final static RequiresCassandraKeyspace CASSANDRA_KEYSPACE = RequiresCassandraKeyspace.onLocalhost();
@Autowired BasicUserRepository repository;
@Autowired Session session;
User user;
@Before
public void setUp() {
user = new User();
user.setId(42L);
user.setUsername("foobar");
user.setFirstname("firstname");
user.setLastname("lastname");
}
/**
* Saving an object using the Cassandra Repository will create a persistent representation of the object in Cassandra.
*/
@Test
public void findSavedUserById() {
user = repository.save(user);
assertThat(repository.findOne(user.getId()), is(user));
}
/**
* Cassandra can be queries by using query methods annotated with {@link @Query}.
*/
@Test
public void findByAnnotatedQueryMethod() {
repository.save(user);
assertThat(repository.findUserByIdIn(1000), is(nullValue()));
assertThat(repository.findUserByIdIn(42), is(equalTo(user)));
}
/**
* Spring Data Cassandra supports query derivation so annotating query methods with
* {@link org.springframework.data.cassandra.repository.Query} is optional. Querying columns other than the primary
* key requires a secondary index.
*/
@Test
public void findByDerivedQueryMethod() throws InterruptedException {
session.execute("CREATE INDEX IF NOT EXISTS user_username ON users (uname);");
/*
Cassandra secondary indexes are created in the background without the possibility to check
whether they are available or not. So we are forced to just wait. *sigh*
*/
Thread.sleep(1000);
repository.save(user);
assertThat(repository.findUserByUsername(user.getUsername()), is(user));
}
/**
* Spring Data Cassandra supports {@code LIKE} and {@code CONTAINS} query keywords to for SASI indexes.
*/
@Test
public void findByDerivedQueryMethodWithSASI() throws InterruptedException {
assumeTrue(CassandraVersion.getReleaseVersion(session).isGreaterThanOrEqualTo(CASSANDRA_3_4));
session.execute("CREATE CUSTOM INDEX ON users (lname) USING 'org.apache.cassandra.index.sasi.SASIIndex';");
/*
Cassandra secondary indexes are created in the background without the possibility to check
whether they are available or not. So we are forced to just wait. *sigh*
*/
Thread.sleep(1000);
repository.save(user);
assertThat(repository.findUsersByLastnameStartsWith("last"), hasItem(user));
}
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright 2016 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 example.springdata.cassandra.basic;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.data.cassandra.core.WriteListener;
import org.springframework.test.context.junit4.SpringRunner;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import example.springdata.cassandra.util.RequiresCassandraKeyspace;
/**
* Integration test showing the basic usage of {@link CassandraTemplate}.
*
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BasicConfiguration.CassandraConfig.class)
public class CassandraOperationsIntegrationTests {
@ClassRule public final static RequiresCassandraKeyspace CASSANDRA_KEYSPACE = RequiresCassandraKeyspace.onLocalhost();
@Autowired CassandraOperations template;
@Before
public void setUp() throws Exception {
template.truncate("users");
}
/**
* Cassandra {@link com.datastax.driver.core.Statement}s can be used together with {@link CassandraTemplate} and the
* mapping layer.
*/
@Test
public void insertAndSelect() {
Insert insert = QueryBuilder.insertInto("users").value("user_id", 42L) //
.value("uname", "heisenberg") //
.value("fname", "Walter") //
.value("lname", "White") //
.ifNotExists(); //
template.execute(insert);
User user = template.selectOneById(User.class, 42L);
assertThat(user.getUsername(), is(equalTo("heisenberg")));
List<User> users = template.select(QueryBuilder.select().from("users"), User.class);
assertThat(users, hasSize(1));
assertThat(users.get(0), is(equalTo(user)));
}
/**
* Objects can be inserted and updated using {@link CassandraTemplate}. What you {@code update} is what you
* {@code select}.
*/
@Test
public void insertAndUpdate() {
User user = new User();
user.setId(42L);
user.setUsername("heisenberg");
user.setFirstname("Walter");
user.setLastname("White");
template.insert(user);
user.setFirstname(null);
template.update(user);
User loaded = template.selectOneById(User.class, 42L);
assertThat(loaded.getUsername(), is(equalTo("heisenberg")));
assertThat(loaded.getFirstname(), is(nullValue()));
}
/**
* Asynchronous query execution using callbacks.
*/
@Test
public void insertAsynchronously() throws InterruptedException {
User user = new User();
user.setId(42L);
user.setUsername("heisenberg");
user.setFirstname("Walter");
user.setLastname("White");
final CountDownLatch countDownLatch = new CountDownLatch(1);
template.insertAsynchronously(user, new WriteListener<User>() {
@Override
public void onWriteComplete(Collection<User> entities) {
countDownLatch.countDown();
}
@Override
public void onException(Exception x) {}
});
countDownLatch.await(5, TimeUnit.SECONDS);
User loaded = template.selectOneById(User.class, user.getId());
assertThat(loaded, is(equalTo(user)));
}
/**
* {@link CassandraTemplate} allows selection of projections on template-level. All basic data types including
* {@link Row} can be selected.
*/
@Test
@SuppressWarnings("unchecked")
public void selectProjections() {
User user = new User();
user.setId(42L);
user.setUsername("heisenberg");
user.setFirstname("Walter");
user.setLastname("White");
template.insert(user);
Long id = template.selectOne(QueryBuilder.select("user_id").from("users"), Long.class);
assertThat(id, is(user.getId()));
Row row = template.selectOne(QueryBuilder.select("user_id").from("users"), Row.class);
assertThat(row.getLong(0), is(user.getId()));
Map<String, Object> map = template.selectOne(QueryBuilder.select().from("users"), Map.class);
assertThat(map, hasEntry("user_id", user.getId()));
assertThat(map, hasEntry("fname", "Walter"));
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2016 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 example.springdata.cassandra.convert;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import java.util.Arrays;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.cassandra.core.CassandraOperations;
import org.springframework.test.context.junit4.SpringRunner;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import example.springdata.cassandra.util.RequiresCassandraKeyspace;
/**
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConverterConfiguration.class)
public class ConversionIntegrationTests {
@ClassRule public final static RequiresCassandraKeyspace CASSANDRA_KEYSPACE = RequiresCassandraKeyspace.onLocalhost();
@Autowired CassandraOperations operations;
@Before
public void setUp() throws Exception {
operations.truncate("addressbook");
}
/**
* Creates and stores a new {@link Addressbook} inside of Cassandra. {@link Contact} classes are converted using the
* custom {@link example.springdata.cassandra.convert.ConverterConfiguration.PersonWriteConverter}.
*/
@Test
public void shouldCreateAddressbook() {
Addressbook addressbook = new Addressbook();
addressbook.setId("private");
addressbook.setMe(new Contact("Walter", "White"));
addressbook.setFriends(Arrays.asList(new Contact("Jesse", "Pinkman"), new Contact("Saul", "Goodman")));
operations.insert(addressbook);
Row row = operations.selectOne(QueryBuilder.select().from("addressbook"), Row.class);
assertThat(row, is(notNullValue()));
assertThat(row.getString("id"), is(equalTo("private")));
assertThat(row.getString("me"), containsString("\"firstname\":\"Walter\""));
assertThat(row.getList("friends", String.class), hasSize(2));
}
/**
* Creates and loads a new {@link Addressbook} inside of Cassandra. {@link Contact} classes are converted using the
* custom {@link example.springdata.cassandra.convert.ConverterConfiguration.PersonReadConverter}.
*/
@Test
public void shouldReadAddressbook() {
Addressbook addressbook = new Addressbook();
addressbook.setId("private");
addressbook.setMe(new Contact("Walter", "White"));
addressbook.setFriends(Arrays.asList(new Contact("Jesse", "Pinkman"), new Contact("Saul", "Goodman")));
operations.insert(addressbook);
Addressbook loaded = operations.selectOne(QueryBuilder.select().from("addressbook"), Addressbook.class);
assertThat(loaded.getMe(), is(equalTo(addressbook.getMe())));
assertThat(loaded.getFriends(), is(equalTo(addressbook.getFriends())));
}
/**
* Creates and stores a new {@link Addressbook} inside of Cassandra. The {@link Addressbook} is read back to a
* {@link CustomAddressbook} class using the
* {@link example.springdata.cassandra.convert.ConverterConfiguration.CustomAddressbookReadConverter}.
*/
@Test
public void shouldReadCustomAddressbook() {
Addressbook addressbook = new Addressbook();
addressbook.setId("private");
addressbook.setMe(new Contact("Walter", "White"));
operations.insert(addressbook);
CustomAddressbook loaded = operations.selectOne(QueryBuilder.select().from("addressbook"), CustomAddressbook.class);
assertThat(loaded.getTheId(), is(equalTo(addressbook.getId())));
assertThat(loaded.getMyDetailsAsJson(), containsString("\"firstname\":\"Walter\""));
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2016 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 example.springdata.cassandra.projection;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Collection;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.projection.TargetAware;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import example.springdata.cassandra.util.RequiresCassandraKeyspace;
/**
* Integration tests for {@link CustomerRepository} to show projection capabilities.
*
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ProjectionConfiguration.class)
public class CustomerRepositoryIntegrationTest {
@ClassRule public final static RequiresCassandraKeyspace CASSANDRA_KEYSPACE = RequiresCassandraKeyspace.onLocalhost();
@Autowired CustomerRepository customers;
Customer dave, carter;
@Before
public void setUp() {
customers.deleteAll();
this.dave = customers.save(new Customer("d", "Dave", "Matthews"));
this.carter = customers.save(new Customer("c", "Carter", "Beauford"));
}
@Test
public void projectsEntityIntoInterface() {
Collection<CustomerProjection> result = customers.findAllProjectedBy();
assertThat(result, hasSize(2));
assertThat(result.iterator().next().getFirstname(), is("Carter"));
}
@Test
public void projectsDynamically() {
Collection<CustomerProjection> result = customers.findById("d", CustomerProjection.class);
assertThat(result, hasSize(1));
assertThat(result.iterator().next().getFirstname(), is("Dave"));
}
@Test
public void projectsIndividualDynamically() {
CustomerSummary result = customers.findProjectedById(dave.getId(), CustomerSummary.class);
assertThat(result, is(notNullValue()));
assertThat(result.getFullName(), is("Dave Matthews"));
// Proxy backed by original instance as the projection uses dynamic elements
assertThat(((TargetAware) result).getTarget(), is(instanceOf(Customer.class)));
}
@Test
public void projectIndividualInstance() {
CustomerProjection result = customers.findProjectedById(dave.getId());
assertThat(result, is(notNullValue()));
assertThat(result.getFirstname(), is("Dave"));
assertThat(((TargetAware) result).getTarget(), is(instanceOf(Customer.class)));
}
}

36
cassandra/java8/README.md Normal file
View File

@@ -0,0 +1,36 @@
# Spring Data Cassandra - Java 8 examples
This project contains samples of Java 8 specific features of Spring Data (Cassandra).
## Support for JDK 8's `Stream` for repository methods
Repository methods can use a Java 8 `Stream` as a return type which will cause the reading of the results and the to-object-conversion of rows to happen while iterating over the stream.
```java
public interface PersonRepository extends CrudRepository<Person, String> {
@Override
List<Person> findAll();
//Custom Query method returning a Java 8 Stream
@Query("SELECT * FROM person")
Stream<Person> findAllByCustomQueryWithStream();
}
```
The test cases in `PersonRepositoryIntegrationTest` oppose a plain `List` based query method with one that uses a `Stream` and shows how the former pulls all data into memory first and the iteration is done over the pre-populated list. The execution of the `Stream`-based method in contrast shows that the individual elements are read and converted while iterating the stream.
## Preparation
### Install Cassandra
Before we can start we have to install Cassandra, e.g. via brew on Max OS.
More details can be found here: https://wiki.apache.org/cassandra/GettingStarted
### Start Cassandra
```
/usr/local/bin/cassandra -f
```
That should be enough to get you started.
Now you can simply type ```mvn clean install``` to run the example.

26
cassandra/java8/pom.xml Normal file
View File

@@ -0,0 +1,26 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-cassandra-examples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-data-cassandra-java8</artifactId>
<name>Spring Data Cassandra - Java 8 specific features</name>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-data-cassandra-example-utils</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2016 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 example.springdata.cassandra.java8;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.config.java.AbstractCassandraConfiguration;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
/**
* @author Mark Paluch
*/
@Configuration
@EnableAutoConfiguration
class CassandraConfiguration {
@Configuration
@EnableCassandraRepositories
static class CassandraConfig extends AbstractCassandraConfiguration {
@Override
public String getKeyspaceName() {
return "example";
}
@Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE;
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2016 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 example.springdata.cassandra.java8;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.ZoneId;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.mapping.Table;
/**
* @author Mark Paluch
*/
@Table("pizza_orders")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
@Id String id;
LocalDate orderDate;
ZoneId zoneId;
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2016 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 example.springdata.cassandra.java8;
import java.time.LocalDate;
import java.time.ZoneId;
import org.springframework.data.cassandra.mapping.CassandraType;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.repository.Repository;
/**
* Repository to manage {@link Order} instances.
*
* @author Mark Paluch
*/
public interface OrderRepository extends Repository<Order, String> {
/**
* Method parameters are converted according the registered Converters into Cassandra types.
*/
@Query("SELECT * from pizza_orders WHERE orderdate = ?0 and zoneid = ?1 ALLOW FILTERING")
Order findOrderByOrderDateAndZoneId(LocalDate orderDate, ZoneId zoneId);
/**
* Parameter conversion can be overridden by using the {@link CassandraType} annotation.
*/
@Query("SELECT * from pizza_orders WHERE orderdate = ?0 and zoneid = ?1 ALLOW FILTERING")
Order findOrderByDate(com.datastax.driver.core.LocalDate orderDate, String zoneId);
void deleteAll();
Order save(Order order);
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2016 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 example.springdata.cassandra.java8;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.mapping.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author Mark Paluch
*/
@Table
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
@Id String id;
String firstname, lastname;
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2016 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 example.springdata.cassandra.java8;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.Repository;
/**
* Repository to manage {@link Person} instances.
*
* @author Mark Paluch
*/
public interface PersonRepository extends Repository<Person, String> {
/**
* Special customization of {@link CrudRepository#findOne(java.io.Serializable)} to return a JDK 8 {@link Optional}.
*
* @param id
* @return
*/
Optional<Person> findOne(String id);
@Query("select * from person")
Stream<Person> streamAllPeople();
/**
* Sample method to derive a query from using JDK 8's {@link Optional} as return type.
*
* @param id
* @return
*/
@Query("select * from person where id = ?0")
Optional<Person> findById(String id);
/**
* Sample default method to show JDK 8 feature support.
*
* @param person
* @return
*/
default Optional<Person> findByPerson(Person person) {
return findById(person == null ? null : person.id);
}
void deleteAll();
Person save(Person person);
}

View File

@@ -0,0 +1,4 @@
/**
* Package showing Java 8 features.
*/
package example.springdata.cassandra.java8;

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2016 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 example.springdata.cassandra.java8;
import example.springdata.cassandra.util.RequiresCassandraKeyspace;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.util.Version;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Integration test to show the usage of Java 8 features with Spring Data Cassandra.
*
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CassandraConfiguration.class)
public class Java8IntegrationTests {
@ClassRule
public final static RequiresCassandraKeyspace CASSANDRA_KEYSPACE = RequiresCassandraKeyspace.onLocalhost().atLeast(Version.parse("3.0"));
@Autowired PersonRepository repository;
@Before
public void setUp() throws Exception {
repository.deleteAll();
}
@Test
public void providesFindOneWithOptional() {
Person homer = repository.save(new Person("1", "Homer", "Simpson"));
assertThat(repository.findOne(homer.id).isPresent(), is(true));
assertThat(repository.findOne(homer.id + 1), is(Optional.<Person> empty()));
}
@Test
public void invokesDefaultMethod() {
Person homer = repository.save(new Person("1", "Homer", "Simpson"));
Optional<Person> result = repository.findByPerson(homer);
assertThat(result.isPresent(), is(true));
assertThat(result.get(), is(homer));
}
/**
* Streaming data from the store by using a repository method that returns a {@link Stream}. Note, that since the
* resulting {@link Stream} contains state it needs to be closed explicitly after use!
*/
@Test
public void useJava8StreamsWithCustomQuery() {
Person homer = repository.save(new Person("1", "Homer", "Simpson"));
Person bart = repository.save(new Person("2", "Bart", "Simpson"));
try (Stream<Person> stream = repository.streamAllPeople()) {
assertThat(stream.collect(Collectors.toList()), hasItems(homer, bart));
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2016 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 example.springdata.cassandra.java8;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.time.LocalDate;
import java.time.ZoneId;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.util.Version;
import org.springframework.test.context.junit4.SpringRunner;
import example.springdata.cassandra.util.RequiresCassandraKeyspace;
/**
* Integration test to show the usage of JSR-310 date/time types with Spring Data Cassandra.
*
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CassandraConfiguration.class)
public class Jsr310IntegrationTests {
@ClassRule public final static RequiresCassandraKeyspace CASSANDRA_KEYSPACE = RequiresCassandraKeyspace.onLocalhost()
.atLeast(Version.parse("3.0"));
@Autowired OrderRepository repository;
@Before
public void setUp() throws Exception {
repository.deleteAll();
}
@Test
public void findOneByJsr310Types() {
Order order = new Order("42", LocalDate.now(), ZoneId.systemDefault());
repository.save(order);
assertThat(repository.findOrderByOrderDateAndZoneId(order.getOrderDate(), order.getZoneId()), is(equalTo(order)));
}
@Test
public void findOneByConvertedTypes() {
Order order = new Order("42", LocalDate.of(2010, 1, 2), ZoneId.systemDefault());
repository.save(order);
com.datastax.driver.core.LocalDate date = com.datastax.driver.core.LocalDate.fromYearMonthDay(2010, 1, 2);
String zoneId = order.getZoneId().getId();
assertThat(repository.findOrderByDate(date, zoneId), is(equalTo(order)));
}
}

View File

@@ -1,5 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-cassandra-examples</artifactId>
@@ -17,7 +17,9 @@
<inceptionYear>2014</inceptionYear>
<modules>
<module>util</module>
<module>example</module>
<module>java8</module>
</modules>
<dependencies>
@@ -27,9 +29,14 @@
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-cassandra</artifactId>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>
<dependency>
<groupId>com.datastax.cassandra</groupId>
<artifactId>cassandra-driver-core</artifactId>
</dependency>
<dependency>

22
cassandra/util/pom.xml Normal file
View File

@@ -0,0 +1,22 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data.examples</groupId>
<artifactId>spring-data-cassandra-examples</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-data-cassandra-example-utils</artifactId>
<name>Spring Data Cassandra - Example Utilities</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2016 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 example.springdata.cassandra.util;
import org.springframework.data.util.Version;
import org.springframework.util.Assert;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import lombok.experimental.UtilityClass;
/**
* Utility to retrieve the Cassandra release version.
*
* @author Mark Paluch
*/
@UtilityClass
public class CassandraVersion {
/**
* Retrieve the Cassandra release version.
*
* @param session must not be {@literal null}.
* @return the release {@link Version}.
*/
public static Version getReleaseVersion(Session session) {
Assert.notNull(session, "Session must not be null");
ResultSet resultSet = session.execute("SELECT release_version FROM system.local;");
Row row = resultSet.one();
return Version.parse(row.getString(0));
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2016 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 example.springdata.cassandra.util;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
import org.junit.AssumptionViolatedException;
import org.junit.rules.ExternalResource;
import org.springframework.data.util.Version;
import org.springframework.util.Assert;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.NettyOptions;
import com.datastax.driver.core.Session;
import io.netty.channel.EventLoopGroup;
/**
* Implementation of an {@link ExternalResource} to verify Apache Cassandra is running and listening on the given
* contact point. This rule also creates a keyspace if it not exists.
*
* @author Mark Paluch
*/
public class RequiresCassandraKeyspace extends ExternalResource {
private final int timeout = 30;
private final String host;
private final int port;
private final String keyspaceName;
private Version requiresVersion;
private RequiresCassandraKeyspace(String host, int port, String keyspaceName) {
this.host = host;
this.port = port;
this.keyspaceName = keyspaceName;
}
/**
* Require a running Cassandra instance on {@code localhost:9042}.
*
* @return the {@link RequiresCassandraKeyspace} rule
*/
public static RequiresCassandraKeyspace onLocalhost() {
return new RequiresCassandraKeyspace("localhost", 9042, "example");
}
/**
* Setup a version requirement.
*
* @param version must not be {@literal null}.
* @return the {@link RequiresCassandraKeyspace} rule
*/
public RequiresCassandraKeyspace atLeast(Version version) {
Assert.notNull(version, "Required version must not be null!");
this.requiresVersion = version;
return this;
}
/*
* (non-Javadoc)
* @see org.junit.rules.ExternalResource#before()
*/
@Override
protected void before() throws Throwable {
try (Socket socket = new Socket()) {
socket.setTcpNoDelay(true);
socket.setSoLinger(true, 0);
socket.connect(new InetSocketAddress(host, port), timeout);
} catch (Exception e) {
throw new AssumptionViolatedException(String.format("Seems as Cassandra is not running at %s:%s.", host, port),
e);
}
Cluster cluster = Cluster.builder().addContactPoint(host).withPort(port).withNettyOptions(new NettyOptions() {
@Override
public void onClusterClose(EventLoopGroup eventLoopGroup) {
eventLoopGroup.shutdownGracefully(0, 0, TimeUnit.MILLISECONDS).syncUninterruptibly();
}
}).build();
Session session = cluster.newSession();
try {
if (requiresVersion != null) {
Version cassandraReleaseVersion = CassandraVersion.getReleaseVersion(session);
if (cassandraReleaseVersion.isLessThan(requiresVersion)) {
throw new AssumptionViolatedException(
String.format("Cassandra at %s:%s runs in Version %s but we require at least %s", host, port,
cassandraReleaseVersion, requiresVersion));
}
}
session.execute(String.format("CREATE KEYSPACE IF NOT EXISTS %s \n"
+ "WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };", keyspaceName));
} finally {
session.close();
cluster.close();
}
}
}

View File

@@ -38,6 +38,7 @@
<spring-data-releasetrain.version>Ingalls-M1</spring-data-releasetrain.version>
<spring-hateoas.version>0.21.0.RELEASE</spring-hateoas.version>
<jedis.version>2.9.0</jedis.version>
<cassandra-driver.version>3.0.3</cassandra-driver.version>
</properties>