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 890b489541..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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -45,6 +45,8 @@ import org.springframework.data.cassandra.core.CassandraTemplate; import org.springframework.data.cassandra.core.convert.CassandraConverter; import org.springframework.data.cassandra.core.convert.CassandraCustomConversions; import org.springframework.data.cassandra.core.convert.MappingCassandraConverter; +import org.springframework.data.cassandra.core.cql.CqlOperations; +import org.springframework.data.cassandra.core.cql.CqlTemplate; import org.springframework.data.cassandra.core.mapping.CassandraMappingContext; import org.springframework.data.cassandra.core.mapping.SimpleUserTypeResolver; @@ -114,10 +116,16 @@ public class CassandraDataAutoConfiguration { return session; } + @Bean + @ConditionalOnMissingBean(CqlOperations.class) + public CqlTemplate cqlTemplate(SessionFactory sessionFactory) { + return new CqlTemplate(sessionFactory); + } + @Bean @ConditionalOnMissingBean(CassandraOperations.class) - public CassandraTemplate cassandraTemplate(SessionFactory sessionFactory, CassandraConverter converter) { - return new CassandraTemplate(sessionFactory, converter); + public CassandraTemplate cassandraTemplate(CqlTemplate cqlTemplate, CassandraConverter converter) { + return new CassandraTemplate(cqlTemplate, converter); } @Bean 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 00d83b5f76..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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2025 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. @@ -30,6 +30,8 @@ import org.springframework.data.cassandra.ReactiveSessionFactory; import org.springframework.data.cassandra.core.ReactiveCassandraOperations; import org.springframework.data.cassandra.core.ReactiveCassandraTemplate; import org.springframework.data.cassandra.core.convert.CassandraConverter; +import org.springframework.data.cassandra.core.cql.ReactiveCqlOperations; +import org.springframework.data.cassandra.core.cql.ReactiveCqlTemplate; import org.springframework.data.cassandra.core.cql.session.DefaultBridgedReactiveSession; import org.springframework.data.cassandra.core.cql.session.DefaultReactiveSessionFactory; @@ -58,11 +60,17 @@ public class CassandraReactiveDataAutoConfiguration { return new DefaultReactiveSessionFactory(reactiveCassandraSession); } + @Bean + @ConditionalOnMissingBean(ReactiveCqlOperations.class) + public ReactiveCqlTemplate reactiveCqlTemplate(ReactiveSessionFactory reactiveCassandraSessionFactory) { + return new ReactiveCqlTemplate(reactiveCassandraSessionFactory); + } + @Bean @ConditionalOnMissingBean(ReactiveCassandraOperations.class) - public ReactiveCassandraTemplate reactiveCassandraTemplate(ReactiveSession reactiveCassandraSession, + public ReactiveCassandraTemplate reactiveCassandraTemplate(ReactiveCqlTemplate reactiveCqlTemplate, CassandraConverter converter) { - return new ReactiveCassandraTemplate(reactiveCassandraSession, 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 932ba62caf..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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 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. @@ -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; @@ -33,11 +33,9 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.data.cassandra.core.CassandraTemplate; import org.springframework.data.cassandra.core.convert.CassandraConverter; import org.springframework.data.cassandra.core.convert.CassandraCustomConversions; +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; @@ -50,74 +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 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 5e0521c650..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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2025 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. @@ -16,21 +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; @@ -43,50 +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 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: