Apply addiitional polishing to the Inline Caching with Apache Cassandra Integration Tests.
The refactorings and code changes are based on feedback from Mark Paluch regarding Apache Cassandra and Spring Data for Apache Cassandra. Note: a developer must quote the CqlIdentifier (e.g. table names) when the case is mixed.
This commit is contained in:
@@ -27,6 +27,7 @@ import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
@@ -38,6 +39,7 @@ import org.springframework.data.cassandra.core.cql.RowMapper;
|
||||
import org.springframework.data.cassandra.core.cql.session.init.KeyspacePopulator;
|
||||
import org.springframework.data.cassandra.core.cql.session.init.ResourceKeyspacePopulator;
|
||||
import org.springframework.data.cassandra.core.cql.session.init.SessionFactoryInitializer;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import example.app.crm.model.Customer;
|
||||
import example.app.crm.repo.CustomerRepository;
|
||||
@@ -64,6 +66,8 @@ public abstract class TestCassandraConfiguration {
|
||||
private static final boolean CONTINUE_ON_ERROR = false;
|
||||
private static final boolean IGNORE_FAILED_DROPS = true;
|
||||
|
||||
private static final Customer pieDoe = Customer.newCustomer(16L, "Pie Doe");
|
||||
|
||||
private static final String CQL_SCRIPT_ENCODING = null;
|
||||
|
||||
protected static final int CASSANDRA_DEFAULT_PORT = CqlSessionFactoryBean.DEFAULT_PORT;
|
||||
@@ -71,8 +75,21 @@ public abstract class TestCassandraConfiguration {
|
||||
protected static final String CASSANDRA_DATA_CQL = "cassandra-data.cql";
|
||||
protected static final String CASSANDRA_INIT_CQL = "cassandra-init.cql";
|
||||
protected static final String CASSANDRA_SCHEMA_CQL = "cassandra-schema.cql";
|
||||
protected static final String LOCAL_DATA_CENTER = "datacenter1";
|
||||
protected static final String DEBUGGING_PROFILE = "debugging";
|
||||
protected static final String KEYSPACE_NAME = "CustomerService";
|
||||
protected static final String TABLE_NAME = "Customers";
|
||||
|
||||
protected @NonNull Resource newCassandraDataCqlScriptResource() {
|
||||
return new ClassPathResource(CASSANDRA_DATA_CQL);
|
||||
}
|
||||
|
||||
protected @NonNull Resource newCassandraInitCqlScriptResource() {
|
||||
return new ClassPathResource(CASSANDRA_INIT_CQL);
|
||||
}
|
||||
|
||||
protected @NonNull Resource newCassandraSchemaCqlScriptResource() {
|
||||
return new ClassPathResource(CASSANDRA_SCHEMA_CQL);
|
||||
}
|
||||
|
||||
@Bean
|
||||
SessionFactoryInitializer sessionFactoryInitializer(SessionFactory sessionFactory) {
|
||||
@@ -87,24 +104,12 @@ public abstract class TestCassandraConfiguration {
|
||||
return sessionFactoryInitializer;
|
||||
}
|
||||
|
||||
protected Resource newCassandraDataCqlScriptResource() {
|
||||
return new ClassPathResource(CASSANDRA_DATA_CQL);
|
||||
}
|
||||
|
||||
protected Resource newCassandraInitCqlScriptResource() {
|
||||
return new ClassPathResource(CASSANDRA_INIT_CQL);
|
||||
}
|
||||
|
||||
protected Resource newCassandraSchemaCqlScriptResource() {
|
||||
return new ClassPathResource(CASSANDRA_SCHEMA_CQL);
|
||||
}
|
||||
|
||||
protected KeyspacePopulator newKeyspacePopulator(Resource... cqlScripts) {
|
||||
protected @NonNull KeyspacePopulator newKeyspacePopulator(Resource... cqlScripts) {
|
||||
return new ResourceKeyspacePopulator(CONTINUE_ON_ERROR, IGNORE_FAILED_DROPS, CQL_SCRIPT_ENCODING, cqlScripts);
|
||||
//return cqlScript -> {};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Profile(DEBUGGING_PROFILE)
|
||||
BeanPostProcessor cassandraTemplatePostProcessor() {
|
||||
|
||||
return new BeanPostProcessor() {
|
||||
@@ -114,11 +119,12 @@ public abstract class TestCassandraConfiguration {
|
||||
|
||||
if (bean instanceof CassandraTemplate cassandraTemplate) {
|
||||
|
||||
Consumer<CassandraTemplate> cassandraTemplateConsumer = noopCassandraTemplateConsumer();
|
||||
//.andThen(entityObjectInsertingCassandraTemplateConsumer())
|
||||
//.andThen(entityObjectAssertingCassandraTemplateConsumer());
|
||||
//.andThen(keyspaceNameAssertingCassandraTemplateConsumer())
|
||||
//.andThen(tableNameAssertingCassandraTemplateConsumer());
|
||||
Consumer<CassandraTemplate> cassandraTemplateConsumer = noopCassandraTemplateConsumer()
|
||||
.andThen(insertEntityObjectCassandraTemplateConsumer())
|
||||
.andThen(assertEntityCountCassandraTemplateConsumer())
|
||||
.andThen(assertEntityObjectCassandraTemplateConsumer())
|
||||
.andThen(assertKeyspaceNameCassandraTemplateConsumer())
|
||||
.andThen(assertTableNameCassandraTemplateConsumer());
|
||||
|
||||
cassandraTemplateConsumer.accept(cassandraTemplate);
|
||||
}
|
||||
@@ -132,35 +138,32 @@ public abstract class TestCassandraConfiguration {
|
||||
return cassandraTemplate -> {};
|
||||
}
|
||||
|
||||
private Consumer<CassandraTemplate> entityCountAssertingCassandraTemplateConsumer() {
|
||||
private Consumer<CassandraTemplate> assertEntityCountCassandraTemplateConsumer() {
|
||||
return cassandraTemplate -> assertThat(cassandraTemplate.count(Customer.class)).isOne();
|
||||
}
|
||||
|
||||
private Consumer<CassandraTemplate> entityObjectAssertingCassandraTemplateConsumer() {
|
||||
private Consumer<CassandraTemplate> assertEntityObjectCassandraTemplateConsumer() {
|
||||
|
||||
return cassandraTemplate -> {
|
||||
|
||||
String cql = "SELECT id, name FROM Customers";
|
||||
String cql = "SELECT id, name FROM \"Customers\"";
|
||||
|
||||
RowMapper<Customer> customerRowMapper = (row, rowNumber) ->
|
||||
Customer.newCustomer(row.getLong("id"), row.getString("name"));
|
||||
|
||||
Customer actualCustomer = cassandraTemplate.getCqlOperations().queryForObject(cql, customerRowMapper);
|
||||
Customer expectedCustomer = Customer.newCustomer(16L, "Pie Doe");
|
||||
|
||||
assertThat(actualCustomer).isEqualTo(expectedCustomer);
|
||||
//assertThat(cassandraTemplate.selectOneById(16L, Customer.class)).isEqualTo(expectedCustomer);
|
||||
//assertThat(cassandraTemplate.query(Customer.class).stream().findFirst().orElse(null))
|
||||
// .isEqualTo(expectedCustomer);
|
||||
assertThat(actualCustomer).isEqualTo(pieDoe);
|
||||
assertThat(cassandraTemplate.selectOneById(16L, Customer.class)).isEqualTo(pieDoe);
|
||||
assertThat(cassandraTemplate.query(Customer.class).stream().findFirst().orElse(null)).isEqualTo(pieDoe);
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Why does this work and the CQL data script not work!
|
||||
private Consumer<CassandraTemplate> entityObjectInsertingCassandraTemplateConsumer() {
|
||||
return cassandraTemplate -> cassandraTemplate.insert(Customer.newCustomer(16L, "Pie Doe"));
|
||||
private Consumer<CassandraTemplate> insertEntityObjectCassandraTemplateConsumer() {
|
||||
return cassandraTemplate -> cassandraTemplate.insert(pieDoe);
|
||||
}
|
||||
|
||||
private Consumer<CassandraTemplate> keyspaceNameAssertingCassandraTemplateConsumer() {
|
||||
private Consumer<CassandraTemplate> assertKeyspaceNameCassandraTemplateConsumer() {
|
||||
|
||||
return cassandraTemplate -> {
|
||||
|
||||
@@ -177,7 +180,7 @@ public abstract class TestCassandraConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
private Consumer<CassandraTemplate> tableNameAssertingCassandraTemplateConsumer() {
|
||||
private Consumer<CassandraTemplate> assertTableNameCassandraTemplateConsumer() {
|
||||
|
||||
return cassandraTemplate -> {
|
||||
|
||||
@@ -198,7 +201,10 @@ public abstract class TestCassandraConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
ApplicationListener<ContextRefreshedEvent> populateCassandraDatabaseUsingRepository(CustomerRepository customerRepository) {
|
||||
return event -> customerRepository.save(Customer.newCustomer(16L, "Pie Doe"));
|
||||
@Profile(DEBUGGING_PROFILE)
|
||||
ApplicationListener<ContextRefreshedEvent> populateCassandraDatabaseUsingRepository(
|
||||
CustomerRepository customerRepository) {
|
||||
|
||||
return event -> customerRepository.save(pieDoe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,18 @@ package example.app.crm.config;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.datastax.oss.driver.api.core.CqlSession;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.cassandra.CassandraProperties;
|
||||
import org.springframework.boot.autoconfigure.cassandra.CqlSessionBuilderCustomizer;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.data.cassandra.core.CassandraTemplate;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
@@ -57,29 +60,29 @@ import example.app.crm.model.Customer;
|
||||
public class TestcontainersCassandraConfiguration extends TestCassandraConfiguration {
|
||||
|
||||
private static final String CASSANDRA_DOCKER_IMAGE_NAME = "cassandra:latest";
|
||||
private static final String LOCAL_DATACENTER_NAME = "datacenter1";
|
||||
|
||||
@Bean("CassandraContainer")
|
||||
@SuppressWarnings("rawtypes")
|
||||
GenericContainer cassandraContainer() {
|
||||
GenericContainer<?> cassandraContainer(Environment environment) {
|
||||
|
||||
GenericContainer cassandraContainer = newEnvironmentTunedCassandraContainer();
|
||||
GenericContainer<?> cassandraContainer = newEnvironmentOptimizedCassandraContainer();
|
||||
|
||||
cassandraContainer.start();
|
||||
|
||||
return withCassandraServer(cassandraContainer);
|
||||
return withCassandraServer(cassandraContainer, environment);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private @NonNull GenericContainer newCassandraContainer() {
|
||||
return new CassandraContainer(CASSANDRA_DOCKER_IMAGE_NAME)
|
||||
private @NonNull GenericContainer<?> newCassandraContainer() {
|
||||
|
||||
return new CassandraContainer<>(CASSANDRA_DOCKER_IMAGE_NAME)
|
||||
.withInitScript(CASSANDRA_SCHEMA_CQL)
|
||||
//.withInitScript(CASSANDRA_INIT_CQL)
|
||||
.withExposedPorts(CASSANDRA_DEFAULT_PORT)
|
||||
.withReuse(true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private @NonNull GenericContainer newEnvironmentTunedCassandraContainer() {
|
||||
// Information (feedback) received from Sergei Egorov.
|
||||
private @NonNull GenericContainer<?> newEnvironmentOptimizedCassandraContainer() {
|
||||
|
||||
return newCassandraContainer()
|
||||
.withEnv("CASSANDRA_SNITCH", "GossipingPropertyFileSnitch")
|
||||
@@ -96,19 +99,22 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura
|
||||
|
||||
return CqlSession.builder()
|
||||
.addContactPoint(resolveContactPoint(cassandraContainer))
|
||||
.withLocalDatacenter(LOCAL_DATA_CENTER)
|
||||
.withLocalDatacenter(LOCAL_DATACENTER_NAME)
|
||||
.build();
|
||||
}
|
||||
|
||||
private @NonNull GenericContainer<?> withCassandraServer(@NonNull GenericContainer<?> cassandraContainer) {
|
||||
private @NonNull GenericContainer<?> withCassandraServer(@NonNull GenericContainer<?> cassandraContainer,
|
||||
@NonNull Environment environment) {
|
||||
|
||||
//cassandraContainer = initializeCassandraServer(cassandraContainer);
|
||||
cassandraContainer = assertCassandraServerSetup(cassandraContainer);
|
||||
if (Arrays.asList(environment.getActiveProfiles()).contains(DEBUGGING_PROFILE)) {
|
||||
cassandraContainer = initializeCassandraServer(cassandraContainer);
|
||||
cassandraContainer = assertCassandraServerSetup(cassandraContainer);
|
||||
}
|
||||
|
||||
return cassandraContainer;
|
||||
}
|
||||
|
||||
private GenericContainer<?> initializeCassandraServer(GenericContainer<?> cassandraContainer) {
|
||||
private @NonNull GenericContainer<?> initializeCassandraServer(@NonNull GenericContainer<?> cassandraContainer) {
|
||||
|
||||
try (CqlSession session = newCqlSession(cassandraContainer)) {
|
||||
newKeyspacePopulator(newCassandraSchemaCqlScriptResource()).populate(session);
|
||||
@@ -117,7 +123,7 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura
|
||||
return cassandraContainer;
|
||||
}
|
||||
|
||||
private GenericContainer<?> assertCassandraServerSetup(GenericContainer<?> cassandraContainer) {
|
||||
private @NonNull GenericContainer<?> assertCassandraServerSetup(@NonNull GenericContainer<?> cassandraContainer) {
|
||||
|
||||
try (CqlSession session = newCqlSession(cassandraContainer)) {
|
||||
|
||||
@@ -126,16 +132,16 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura
|
||||
|
||||
assertThat(keyspaceMetadata.getName().toString()).isEqualToIgnoringCase(KEYSPACE_NAME);
|
||||
|
||||
keyspaceMetadata.getTable("Customers")
|
||||
keyspaceMetadata.getTable(TABLE_NAME)
|
||||
.map(tableMetadata -> {
|
||||
|
||||
assertThat(tableMetadata.getName().toString()).isEqualToIgnoringCase("Customers");
|
||||
assertThat(tableMetadata.getName().toString()).isEqualTo(TABLE_NAME);
|
||||
assertThat(tableMetadata.getKeyspace().toString()).isEqualToIgnoringCase(KEYSPACE_NAME);
|
||||
//assertCustomersTableHasSizeOne(session);
|
||||
|
||||
return tableMetadata;
|
||||
})
|
||||
.orElseThrow(() -> new IllegalStateException("Table [Customers] not found"));
|
||||
.orElseThrow(() -> new IllegalStateException(String.format("Table [%s] not found", TABLE_NAME)));
|
||||
|
||||
return keyspaceMetadata;
|
||||
})
|
||||
@@ -145,25 +151,26 @@ public class TestcontainersCassandraConfiguration extends TestCassandraConfigura
|
||||
return cassandraContainer;
|
||||
}
|
||||
|
||||
private void assertCustomersTableHasSizeOne(CqlSession session) {
|
||||
private void assertCustomersTableHasSizeOne(@NonNull CqlSession session) {
|
||||
|
||||
CassandraTemplate template = newCassandraTemplate(session);
|
||||
|
||||
assertThat(template.getCqlOperations().execute(String.format("USE %s;", KEYSPACE_NAME))).isTrue();
|
||||
assertThat(template.getCqlOperations().queryForObject("SELECT count(*) FROM Customers", Long.class)).isOne();
|
||||
assertThat(template.getCqlOperations().queryForObject("SELECT count(*) FROM \"Customers\"", Long.class)).isOne();
|
||||
//assertThat(template.count(Customer.class)).isOne(); // Table Customers not found; needs to use the Keyspace
|
||||
}
|
||||
|
||||
@Bean
|
||||
CqlSessionBuilderCustomizer cqlSessionBuilderCustomizer(
|
||||
CqlSessionBuilderCustomizer cqlSessionBuilderCustomizer(CassandraProperties properties,
|
||||
@Qualifier("CassandraContainer") GenericContainer<?> cassandraContainer) {
|
||||
|
||||
return cqlSessionBuilder -> cqlSessionBuilder.addContactPoint(resolveContactPoint(cassandraContainer))
|
||||
.withLocalDatacenter(LOCAL_DATA_CENTER)
|
||||
.withKeyspace(KEYSPACE_NAME);
|
||||
return cqlSessionBuilder -> cqlSessionBuilder
|
||||
.addContactPoint(resolveContactPoint(cassandraContainer))
|
||||
.withLocalDatacenter(properties.getLocalDatacenter())
|
||||
.withKeyspace(properties.getKeyspaceName());
|
||||
}
|
||||
|
||||
private InetSocketAddress resolveContactPoint(GenericContainer<?> cassandraContainer) {
|
||||
private @NonNull InetSocketAddress resolveContactPoint(@NonNull GenericContainer<?> cassandraContainer) {
|
||||
return new InetSocketAddress(cassandraContainer.getHost(),
|
||||
cassandraContainer.getMappedPort(CASSANDRA_DEFAULT_PORT));
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package example.app.crm.model;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.cassandra.core.mapping.Indexed;
|
||||
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
|
||||
import org.springframework.data.gemfire.mapping.annotation.Region;
|
||||
@@ -34,7 +33,8 @@ import lombok.NoArgsConstructor;
|
||||
* @author John Blum
|
||||
* @see jakarta.persistence.Entity
|
||||
* @see jakarta.persistence.Table
|
||||
* @see org.springframework.data.annotation.Id
|
||||
* @see org.springframework.data.cassandra.core.mapping.Indexed
|
||||
* @see org.springframework.data.cassandra.core.mapping.PrimaryKey
|
||||
* @see org.springframework.data.cassandra.core.mapping.Table
|
||||
* @see org.springframework.data.gemfire.mapping.annotation.Region
|
||||
* @since 1.1.0
|
||||
@@ -48,9 +48,8 @@ import lombok.NoArgsConstructor;
|
||||
@AllArgsConstructor(staticName = "newCustomer")
|
||||
public class Customer {
|
||||
|
||||
@Id
|
||||
@jakarta.persistence.Id
|
||||
@PrimaryKey
|
||||
@jakarta.persistence.Id
|
||||
private Long id;
|
||||
|
||||
@Indexed
|
||||
|
||||
@@ -1 +1 @@
|
||||
INSERT INTO Customers (id, name) VALUES (16, 'Pie Doe');
|
||||
INSERT INTO "Customers" (id, name) VALUES (16, 'Pie Doe');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
CREATE KEYSPACE IF NOT EXISTS CustomerService WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1 };
|
||||
CREATE TABLE IF NOT EXISTS CustomerService.Customers (id BIGINT PRIMARY KEY, name TEXT);
|
||||
CREATE INDEX IF NOT EXISTS CustomerNameIdx ON CustomerService.Customers(name);
|
||||
INSERT INTO CustomerService.Customers (id, name) VALUES (16, 'Pie Doe');
|
||||
USE CustomerService;
|
||||
CREATE TABLE IF NOT EXISTS "Customers" (id BIGINT PRIMARY KEY, name TEXT);
|
||||
CREATE INDEX IF NOT EXISTS CustomerNameIdx ON "Customers"(name);
|
||||
INSERT INTO "Customers" (id, name) VALUES (16, 'Pie Doe');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
CREATE KEYSPACE IF NOT EXISTS CustomerService WITH replication = { 'class':'SimpleStrategy', 'replication_factor':1 };
|
||||
USE CustomerService;
|
||||
CREATE TABLE IF NOT EXISTS Customers (id BIGINT PRIMARY KEY, name TEXT);
|
||||
CREATE INDEX IF NOT EXISTS CustomerNameIdx ON Customers(name);
|
||||
CREATE TABLE IF NOT EXISTS "Customers" (id BIGINT PRIMARY KEY, name TEXT);
|
||||
CREATE INDEX IF NOT EXISTS CustomerNameIdx ON "Customers"(name);
|
||||
|
||||
Reference in New Issue
Block a user