spring-projects / spring-data-cassandra

Provides support to increase developer productivity in Java when using Apache Cassandra. Uses familiar Spring concepts such as a template classes for core API usage and lightweight repository style data access.
https://spring.io/projects/spring-data-cassandra/
Apache License 2.0
374 stars 307 forks source link

AbstractCassandraConfiguration Not Working with @ServiceConnection CassandraContainer for Tests #1406

Closed murphye closed 1 year ago

murphye commented 1 year ago

I am trying to use CassandraContainer with @ServiceConnection in Spring Boot 3.1. This is a supported configuration, and a Cassandra container spins up no problem.

Summary

  1. @ServiceConnection, no AbstractCassandraConfiguration: WORKS
  2. @ServiceConnection, has AbstractCassandraConfiguration: DOES NOT WORK
  3. @DynamicPropertySource, has AbstractCassandraConfiguration: WORKS

Details

    companion object {
        @JvmStatic
        @Container
        @ServiceConnection
        val cassandra: CassandraContainer<*> = CassandraContainer("cassandra:3.11.2")
    }

However, there seems to be a problem using this with AbstractCassandraConfiguration, for example:

@Configuration
@EnableCassandraRepositories(basePackages = ["com.acme"])
class CassandraConfig : AbstractCassandraConfiguration() {

   @Value("\${spring.cassandra.port:0}")
    private val port = 0

    override fun getPort(): Int {
        return port
    }

    @Value("\${cassandra.keyspace-name:default}")
    private val cassandraKeyspace: String? = null

    override fun getKeyspaceName(): String {
        return cassandraKeyspace!!
    }

    override fun getKeyspaceCreations(): MutableList<CreateKeyspaceSpecification> {
        return mutableListOf(
            CreateKeyspaceSpecification.createKeyspace(
                cassandraKeyspace!!
            )
            .ifNotExists()
            .with(KeyspaceOption.DURABLE_WRITES, true)
            .withSimpleReplication()
        )
    }
}

When using this and running mvn test I will get this error:

Connection refused: localhost/127.0.0.1:0

However, when NOT using AbstractCassandraConfiguration, there is NOT a connection error, as the port is propagated correctly for Spring Data Cassandra.

I also tried @DynamicPropertySource, and it DID work successfully.

    companion object {
        @JvmStatic
        @Container
        //@ServiceConnection
        val cassandra: CassandraContainer<*> = CassandraContainer("cassandra:3.11.2")

        @DynamicPropertySource
        fun registerCassandraProperties(registry: DynamicPropertyRegistry) {
            registry.add("spring.cassandra.port", cassandra::getFirstMappedPort)
        }
    }
mp911de commented 1 year ago

@ServiceConnection is a Spring Boot feature, paging @mhalbritter to provide guidance over whether there is something we can do in Spring Data Cassandra to work better with service connections.

Generally, AbstractCassandraConfiguration concepts predate Spring Boot and there should be no reason to use AbstractCassandraConfiguration. Instead, what is preventing you from using Spring Boot's auto-configuration for Cassandra?

murphye commented 1 year ago

@mp911de Thank you for taking a look at this. I am new to using Spring Data Cassandra.

All I need is a way to automatically create the keyspace like getKeyspaceCreations can (see my example code). If you know a better way to do it through Spring Boot auto-configuration, I am definitely willing to change the implementation.

mp911de commented 1 year ago

You should get the keyspace creation out of the box with test containers. The only remaining thing would be the creation of CQL objects (tables, indices, UDT's). For CQL, you can either use SessionFactoryInitializer or SessionFactoryFactoryBean if you want to create tables from mapped entities.

Let me know how this goes for you.

murphye commented 1 year ago

Good news! I was able to get this working:

@SpringBootTest
@Testcontainers
class CassandraTests {

    companion object {
        @JvmStatic
        @ServiceConnection
        @Container
        val cassandra: CassandraContainer<*> = CassandraContainer("cassandra:3.11.15")
            .withInitScript("bootstrap-test.cql")
    }

    @Test
    fun contextLoads() {

    }

}

src/test/resources/bootstrap-test.cql:

CREATE KEYSPACE demo
    WITH replication = {
        'class' : 'NetworkTopologyStrategy',
        'datacenter1' : 1
        };

USE demo;

I believe this is a good solution, so I will close this issue. Thanks!

sathishnune commented 1 year ago

After Spring Boot 3 upgrade, failed to create a template with different bean name. Any help would be appreciated.

`@Configuration @EnableCassandraRepositories public class TestCassandraConfig extends AbstractCassandraConfiguration {

@Override
protected String getKeyspaceName() {
    return "keyspace";
}

@Override
protected String getContactPoints() {
    return "localhost:9042";
}

@Override
protected String getLocalDataCenter() {
    return "datacenter1";
}

@Override
@Bean("cassandraHealthCheckSession")
public CqlSessionFactoryBean cassandraSession() {
    CqlSessionFactoryBean cassandraSession = super.cassandraSession();
    cassandraSession.setUsername("username");
    cassandraSession.setPassword("password");

    return cassandraSession;
}

@Bean("cassandraHealthCheckTemplate")
@Override
public CassandraAdminTemplate cassandraTemplate() {
    return new CassandraAdminTemplate(getRequiredSession(), requireBeanOfType(CassandraConverter.class));
}

@Bean
@Override
public SessionFactoryFactoryBean cassandraSessionFactory(CqlSession cassandraHealthCheckSession) {
    return super.cassandraSessionFactory(cassandraHealthCheckSession);
}

@Override
protected CqlSession getRequiredSession() {
    return getBeanFactory().getBean("cassandraHealthCheckSession", CqlSession.class);
}

@Override
protected SessionFactory getRequiredSessionFactory() {
    return this::getRequiredSession;
}

}`

Error: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cassandraTemplate' defined in class path resource [com/example/cassandratest/config/TestCassandraConfig.class]: No matching factory method found on class [com.example.cassandratest.config.TestCassandraConfig]: factory bean 'testCassandraConfig'; factory method 'cassandraTemplate()'. Check that a method with the specified name exists and that it is non-static.

@mp911de I need multiple templates as I need to connect to multiple key spaces. Is there a way to make it work?