From 8d713fb6c78d3eedde5cbd7c5f6e6b940f9030a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Mon, 17 Feb 2025 09:14:26 +0100 Subject: [PATCH] Polish "Auto-configure CqlTemplate and ReactiveCqlTemplate" See gh-44291 --- .../CassandraDataAutoConfiguration.java | 12 +-- ...assandraReactiveDataAutoConfiguration.java | 14 +-- .../CassandraDataAutoConfigurationTests.java | 90 +++++++++---------- ...draReactiveDataAutoConfigurationTests.java | 68 ++++++-------- .../modules/reference/pages/data/nosql.adoc | 2 +- 5 files changed, 87 insertions(+), 99 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java index 8fa09aaaf3..f88a487bbe 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.java @@ -116,18 +116,18 @@ public class CassandraDataAutoConfiguration { return session; } - @Bean - @ConditionalOnMissingBean(CassandraOperations.class) - public CassandraTemplate cassandraTemplate(SessionFactory sessionFactory, CassandraConverter converter) { - return new CassandraTemplate(sessionFactory, converter); - } - @Bean @ConditionalOnMissingBean(CqlOperations.class) public CqlTemplate cqlTemplate(SessionFactory sessionFactory) { return new CqlTemplate(sessionFactory); } + @Bean + @ConditionalOnMissingBean(CassandraOperations.class) + public CassandraTemplate cassandraTemplate(CqlTemplate cqlTemplate, CassandraConverter converter) { + return new CassandraTemplate(cqlTemplate, converter); + } + @Bean @ConditionalOnMissingBean public CassandraCustomConversions cassandraCustomConversions() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraReactiveDataAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraReactiveDataAutoConfiguration.java index e218317fb1..911bd2cb11 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraReactiveDataAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraReactiveDataAutoConfiguration.java @@ -60,17 +60,17 @@ public class CassandraReactiveDataAutoConfiguration { return new DefaultReactiveSessionFactory(reactiveCassandraSession); } - @Bean - @ConditionalOnMissingBean(ReactiveCassandraOperations.class) - public ReactiveCassandraTemplate reactiveCassandraTemplate(ReactiveSession reactiveCassandraSession, - CassandraConverter converter) { - return new ReactiveCassandraTemplate(reactiveCassandraSession, converter); - } - @Bean @ConditionalOnMissingBean(ReactiveCqlOperations.class) public ReactiveCqlTemplate reactiveCqlTemplate(ReactiveSessionFactory reactiveCassandraSessionFactory) { return new ReactiveCqlTemplate(reactiveCassandraSessionFactory); } + @Bean + @ConditionalOnMissingBean(ReactiveCassandraOperations.class) + public ReactiveCassandraTemplate reactiveCassandraTemplate(ReactiveCqlTemplate reactiveCqlTemplate, + CassandraConverter converter) { + return new ReactiveCassandraTemplate(reactiveCqlTemplate, converter); + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationTests.java index 6d3057077a..e6f4f4a82e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationTests.java @@ -19,13 +19,13 @@ package org.springframework.boot.autoconfigure.data.cassandra; import java.util.Collections; import com.datastax.oss.driver.api.core.CqlSession; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; import org.springframework.boot.autoconfigure.data.cassandra.city.City; import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -36,9 +36,6 @@ import org.springframework.data.cassandra.core.convert.CassandraCustomConversion import org.springframework.data.cassandra.core.cql.CqlTemplate; import org.springframework.data.cassandra.core.mapping.CassandraMappingContext; import org.springframework.data.cassandra.core.mapping.SimpleUserTypeResolver; -import org.springframework.data.domain.ManagedTypes; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.util.ObjectUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -51,80 +48,81 @@ import static org.assertj.core.api.Assertions.assertThat; */ class CassandraDataAutoConfigurationTests { - private AnnotationConfigApplicationContext context; + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.cassandra.keyspaceName=boot_test") + .withUserConfiguration(CassandraMockConfiguration.class) + .withConfiguration( + AutoConfigurations.of(CassandraAutoConfiguration.class, CassandraDataAutoConfiguration.class)); - @AfterEach - void close() { - if (this.context != null) { - this.context.close(); - } + @Test + void cqlTemplateExists() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(CqlTemplate.class)); } @Test void templateExists() { - load(CassandraMockConfiguration.class); - assertThat(this.context.getBeanNamesForType(CassandraTemplate.class)).hasSize(1); + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(CassandraTemplate.class)); } @Test - void cqlTemplateExists() { - load(CassandraMockConfiguration.class); - assertThat(this.context.getBeanNamesForType(CqlTemplate.class)).hasSize(1); + void templateUsesCqlTemplate() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(CassandraTemplate.class); + assertThat(context.getBean(CassandraTemplate.class).getCqlOperations()) + .isSameAs(context.getBean(CqlTemplate.class)); + }); } @Test void entityScanShouldSetManagedTypes() { - load(EntityScanConfig.class); - CassandraMappingContext mappingContext = this.context.getBean(CassandraMappingContext.class); - ManagedTypes managedTypes = (ManagedTypes) ReflectionTestUtils.getField(mappingContext, "managedTypes"); - assertThat(managedTypes.toList()).containsOnly(City.class); + this.contextRunner.withUserConfiguration(EntityScanConfig.class).run((context) -> { + assertThat(context).hasSingleBean(CassandraMappingContext.class); + CassandraMappingContext mappingContext = context.getBean(CassandraMappingContext.class); + assertThat(mappingContext.getManagedTypes()).singleElement() + .satisfies((typeInformation) -> assertThat(typeInformation.getType()).isEqualTo(City.class)); + }); } @Test void userTypeResolverShouldBeSet() { - load(); - CassandraConverter cassandraConverter = this.context.getBean(CassandraConverter.class); - assertThat(cassandraConverter).extracting("userTypeResolver").isInstanceOf(SimpleUserTypeResolver.class); + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(CassandraConverter.class); + assertThat(context.getBean(CassandraConverter.class)).extracting("userTypeResolver") + .isInstanceOf(SimpleUserTypeResolver.class); + }); } @Test void codecRegistryShouldBeSet() { - load(); - CassandraConverter cassandraConverter = this.context.getBean(CassandraConverter.class); - assertThat(cassandraConverter.getCodecRegistry()) - .isSameAs(this.context.getBean(CassandraMockConfiguration.class).codecRegistry); + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(CassandraConverter.class); + assertThat(context.getBean(CassandraConverter.class).getCodecRegistry()) + .isSameAs(context.getBean(CassandraMockConfiguration.class).codecRegistry); + }); } @Test void defaultConversions() { - load(); - CassandraTemplate template = this.context.getBean(CassandraTemplate.class); - assertThat(template.getConverter().getConversionService().canConvert(Person.class, String.class)).isFalse(); + this.contextRunner.run((context) -> { + CassandraTemplate template = context.getBean(CassandraTemplate.class); + assertThat(template.getConverter().getConversionService().canConvert(Person.class, String.class)).isFalse(); + }); } @Test void customConversions() { - load(CustomConversionConfig.class); - CassandraTemplate template = this.context.getBean(CassandraTemplate.class); - assertThat(template.getConverter().getConversionService().canConvert(Person.class, String.class)).isTrue(); + this.contextRunner.withUserConfiguration(CustomConversionConfig.class).run((context) -> { + CassandraTemplate template = context.getBean(CassandraTemplate.class); + assertThat(template.getConverter().getConversionService().canConvert(Person.class, String.class)).isTrue(); + }); } @Test void clusterDoesNotExist() { - this.context = new AnnotationConfigApplicationContext(CassandraDataAutoConfiguration.class); - assertThat(this.context.getBeansOfType(CqlSession.class)).isEmpty(); - } - - void load(Class... config) { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); - TestPropertyValues.of("spring.cassandra.keyspaceName:boot_test").applyTo(ctx); - if (!ObjectUtils.isEmpty(config)) { - ctx.register(config); + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + CassandraDataAutoConfiguration.class)) { + assertThat(context.getBeansOfType(CqlSession.class)).isEmpty(); } - ctx.register(CassandraMockConfiguration.class, CassandraAutoConfiguration.class, - CassandraDataAutoConfiguration.class); - ctx.refresh(); - this.context = ctx; } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraReactiveDataAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraReactiveDataAutoConfigurationTests.java index 818f2cacab..d7bb019514 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraReactiveDataAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraReactiveDataAutoConfigurationTests.java @@ -16,22 +16,19 @@ package org.springframework.boot.autoconfigure.data.cassandra; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; import org.springframework.boot.autoconfigure.data.cassandra.city.City; import org.springframework.boot.autoconfigure.domain.EntityScan; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Configuration; import org.springframework.data.cassandra.core.ReactiveCassandraTemplate; import org.springframework.data.cassandra.core.convert.CassandraConverter; import org.springframework.data.cassandra.core.cql.ReactiveCqlTemplate; import org.springframework.data.cassandra.core.mapping.CassandraMappingContext; import org.springframework.data.cassandra.core.mapping.SimpleUserTypeResolver; -import org.springframework.data.domain.ManagedTypes; -import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -44,56 +41,49 @@ import static org.assertj.core.api.Assertions.assertThat; */ class CassandraReactiveDataAutoConfigurationTests { - private AnnotationConfigApplicationContext context; + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.cassandra.keyspaceName=boot_test") + .withUserConfiguration(CassandraMockConfiguration.class) + .withConfiguration(AutoConfigurations.of(CassandraAutoConfiguration.class, CassandraDataAutoConfiguration.class, + CassandraReactiveDataAutoConfiguration.class)); - @AfterEach - void close() { - if (this.context != null) { - this.context.close(); - } + @Test + void reactiveCqlTemplateExists() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ReactiveCqlTemplate.class)); } @Test void templateExists() { - load("spring.cassandra.keyspaceName:boot_test"); - assertThat(this.context.getBeanNamesForType(ReactiveCassandraTemplate.class)).hasSize(1); + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ReactiveCassandraTemplate.class)); } @Test - void reactiveCqlTemplateExists() { - load("spring.cassandra.keyspaceName:boot_test"); - assertThat(this.context.getBeanNamesForType(ReactiveCqlTemplate.class)).hasSize(1); + void templateUsesReactiveCqlTemplate() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(ReactiveCassandraTemplate.class); + assertThat(context.getBean(ReactiveCassandraTemplate.class).getReactiveCqlOperations()) + .isSameAs(context.getBean(ReactiveCqlTemplate.class)); + }); } @Test void entityScanShouldSetManagedTypes() { - load(EntityScanConfig.class, "spring.cassandra.keyspaceName:boot_test"); - CassandraMappingContext mappingContext = this.context.getBean(CassandraMappingContext.class); - ManagedTypes managedTypes = (ManagedTypes) ReflectionTestUtils.getField(mappingContext, "managedTypes"); - assertThat(managedTypes.toList()).containsOnly(City.class); + this.contextRunner.withUserConfiguration(EntityScanConfig.class).run((context) -> { + assertThat(context).hasSingleBean(CassandraMappingContext.class); + CassandraMappingContext mappingContext = context.getBean(CassandraMappingContext.class); + assertThat(mappingContext.getManagedTypes()).singleElement() + .satisfies((typeInformation) -> assertThat(typeInformation.getType()).isEqualTo(City.class)); + }); } @Test void userTypeResolverShouldBeSet() { - load("spring.cassandra.keyspaceName:boot_test"); - CassandraConverter cassandraConverter = this.context.getBean(CassandraConverter.class); - assertThat(cassandraConverter).extracting("userTypeResolver").isInstanceOf(SimpleUserTypeResolver.class); - } - - private void load(String... environment) { - load(null, environment); - } - - private void load(Class config, String... environment) { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); - TestPropertyValues.of(environment).applyTo(ctx); - if (config != null) { - ctx.register(config); - } - ctx.register(CassandraMockConfiguration.class, CassandraAutoConfiguration.class, - CassandraDataAutoConfiguration.class, CassandraReactiveDataAutoConfiguration.class); - ctx.refresh(); - this.context = ctx; + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(CassandraConverter.class); + assertThat(context).hasSingleBean(CassandraConverter.class); + assertThat(context.getBean(CassandraConverter.class)).extracting("userTypeResolver") + .isInstanceOf(SimpleUserTypeResolver.class); + }); } @Configuration(proxyBeanMethods = false) diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/nosql.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/nosql.adoc index 245dac77f7..3710fcf6c3 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/nosql.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/data/nosql.adoc @@ -440,7 +440,7 @@ There is a `spring-boot-starter-data-cassandra` starter for collecting the depen [[data.nosql.cassandra.connecting]] === Connecting to Cassandra -You can inject an auto-configured javadoc:org.springframework.data.cassandra.core.CassandraTemplate[] or a Cassandra `CqlSession` instance as you would with any other Spring Bean. +You can inject an auto-configured javadoc:org.springframework.data.cassandra.core.cql.CqlTemplate[], javadoc:org.springframework.data.cassandra.core.CassandraTemplate[], or a Cassandra `CqlSession` instance as you would with any other Spring Bean. The `spring.cassandra.*` properties can be used to customize the connection. Generally, you provide `keyspace-name` and `contact-points` as well the local datacenter name, as shown in the following example: