Spring boot 如何使用Spring Boot和Cassandra将枚举持久化为序号?

Spring boot 如何使用Spring Boot和Cassandra将枚举持久化为序号?,spring-boot,kotlin,enums,cassandra,Spring Boot,Kotlin,Enums,Cassandra,我在实体的枚举字段中添加了@CassandraTypetype=DataType.Name.INT。但是,发送给Cassandra的语句中使用的不是枚举的序号,而是字符串表示。因此,我得到以下错误: org.springframework.data.cassandra.CassandraInvalidQueryException: SessionCallback; CQL [INSERT INTO thing (thing_id,some_enum) VALUES (1,'Foo');]; Ex

我在实体的枚举字段中添加了@CassandraTypetype=DataType.Name.INT。但是,发送给Cassandra的语句中使用的不是枚举的序号,而是字符串表示。因此,我得到以下错误:

org.springframework.data.cassandra.CassandraInvalidQueryException: SessionCallback; CQL [INSERT INTO thing (thing_id,some_enum) VALUES (1,'Foo');]; Expected 4 or 0 byte int (3); nested exception is com.datastax.driver.core.exceptions.InvalidQueryException: Expected 4 or 0 byte int (3)
下面你可以找到一个简单的例子,重现这个问题

我做错了什么

test/src/main/kotlin/enumtest/Application.kt test/src/main/kotlin/enumtest/Thing.kt test/src/main/kotlin/enumtest/ThingRepository.kt test/src/test/kotlin/enumtest/PersistenceTest.kt test/src/test/resources/cql/cassandra_schema.cql test/build.gradle 以下是简化示例的完整版本,可供下载以方便实验:


编辑:因为它似乎是一个bug,所以我刚刚打开了它。

我一直在尝试让它工作一段时间,似乎我终于找到了它

我遇到了和你使用编解码器时相同的问题…我不知道为什么这不起作用。根据他们的文件,你做得完全正确

所以我实现了自己的Cassandra写转换器。见下文

@Configuration
class CassandraConfig(val cluster: Cluster){

    @Bean
    fun setCustomCassandraConversions() = CassandraCustomConversions(listOf(EnumWriteConverter.INSTANCE, EnumReadConverter.INSTANCE))

    @WritingConverter
    enum class EnumWriteConverter : Converter<Enum<MyEnum>, Int> {
        INSTANCE;
        override fun convert(source: Enum<MyEnum>) = source.ordinal
    }
     @ReadingConverter
    enum class EnumReadConverter : Converter<Int, Enum<MyEnum>> {
        INSTANCE;
        override fun convert(source: Int) = MyEnum.values()[source]
    }
}
这应该在每次写入Cassandra时使用重写的转换器将它看到的MyEnum类型的所有枚举转换为Int。这使您可以为不同类型的枚举使用多个这样的值,其中可能出于某种原因,您希望从这些值中写入其他自定义值,而不是始终转换所有枚举

希望这能奏效

编辑 请注意,例如在每个转换器上删除{},并将ReadingConverter注册到CassandraCustomConversions中,这是自Spring Boot版本2.1.5以来所做的更改

但是,@CassandraType需要显式地放在Kotlin中的getter中,否则在运行时就看不到它

实际上,这仅仅意味着更换:

    @CassandraType(type = DataType.Name.INT)
    var someEnum: SomeEnum
因此:

    @get: CassandraType(type = DataType.Name.INT)
    var someEnum: SomeEnum

我不确定Spring,但您可以在Java驱动程序中执行以下操作:@AlexOtt谢谢您的提示。然而,我还没有让它工作。我的PersistenceTest.kt现在看起来像,但错误仍然是一样的。不幸的是,我不能说-它看起来像是Spring内部的一个问题。。。您真的需要使用Spring,而不是使用Java驱动程序中的对象映射器吗?此外,findAll之类的东西对Cassandra非常不利,一旦数据增长足够大,您就会开始遇到问题……谢谢。是的,我非常希望使用Spring Boot与我们车队的其他微服务保持一致。是的,我知道芬德尔的事。我只在这个最小的示例中使用它进行测试。谢谢!添加后保存到Cassandra效果很好。但是,现在加载部分val things=thingRepository.findAll失败,出现java.lang.IllegalArgumentException:No enum constant enumtest.SomeEnum.0。我尝试了所需的@ReadingConverter,但没有成功。还有其他想法吗?你很接近!但是您忘记在CassandraCustomConversions中注册读取转换器。虽然…当我这样做时,我仍然有一个错误,并通过稍微改变读写转换器来修复它。请参阅上面的编辑。再次感谢。实际上,我曾经注册过EnumReadConverter,只是没有在我粘贴到gist中的版本中注册。通过注册,我获得com.datastax.driver.core.exceptions.codeconotfoundexception:Codec未找到请求操作的编解码器:[ANY enumtest.Something]。您的新版本,once with实例。。。而不是实例{…,但是效果很好。我也有同样的错误,当我查看之前的示例时,我注意到这是唯一的区别。很高兴它成功了!给未来的读者一个小提示:如果你确实想在Cassandra中保持为tinyint而不是int,@ReadingConverter需要是enum类EnumReadConverter:Converter In读取枚举类EnumReadConverter:Converter。
package enumtest

import org.springframework.data.cassandra.repository.CassandraRepository
import org.springframework.stereotype.Repository

@Repository
interface ThingRepository : CassandraRepository<Thing, Long>
spring:
  data:
    cassandra:
      contact-points: localhost
      port: 9142
      keyspace_name: enumtest
package enumtest

import org.cassandraunit.spring.CassandraDataSet
import org.cassandraunit.spring.CassandraUnitDependencyInjectionTestExecutionListener
import org.cassandraunit.spring.EmbeddedCassandra
import org.junit.Assert
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.test.context.TestExecutionListeners
import org.springframework.test.context.junit4.SpringRunner

@RunWith(SpringRunner::class)
@SpringBootTest
@TestExecutionListeners(
    listeners = [CassandraUnitDependencyInjectionTestExecutionListener::class],
    mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
)
@CassandraDataSet(value = ["cql/cassandra_schema.cql"], keyspace = "enumtest")
@EmbeddedCassandra
class PersistenceTest {
    @Autowired
    lateinit var thingRepository: ThingRepository

    @Test
    fun `test save`() {
        thingRepository.save(Thing(1, SomeEnum.Foo))
        val things = thingRepository.findAll()
        Assert.assertEquals(1, things.size)
        val thing = things[0]
        Assert.assertEquals(SomeEnum.Foo, thing.someEnum)
    }
}
CREATE KEYSPACE IF NOT exists enumtest
WITH REPLICATION = {'class':'SimpleStrategy', 'replication_factor':1};

CREATE TABLE IF NOT exists enumtest.thing (
    thing_id     bigint,
    some_enum    int,
    PRIMARY KEY (thing_id)
);
plugins {
    id 'org.springframework.boot' version '2.1.4.RELEASE'
    id 'org.jetbrains.kotlin.jvm' version '1.3.30'
    id 'org.jetbrains.kotlin.plugin.spring' version '1.3.30'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
    maven { url "https://repository.apache.org/snapshots/" }
}

dependencies {
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter'
    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-cassandra'
    implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8'
    implementation group: 'org.jetbrains.kotlin', name: 'kotlin-reflect'

    testImplementation group: 'org.cassandraunit', name: 'cassandra-unit-spring', version: '3.5.0.1'
    testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test'
}

compileKotlin {
    kotlinOptions {
        freeCompilerArgs = ['-Xjsr305=strict']
        jvmTarget = '1.8'
    }
}

compileTestKotlin {
    kotlinOptions {
        freeCompilerArgs = ['-Xjsr305=strict']
        jvmTarget = '1.8'
    }
}
@Configuration
class CassandraConfig(val cluster: Cluster){

    @Bean
    fun setCustomCassandraConversions() = CassandraCustomConversions(listOf(EnumWriteConverter.INSTANCE, EnumReadConverter.INSTANCE))

    @WritingConverter
    enum class EnumWriteConverter : Converter<Enum<MyEnum>, Int> {
        INSTANCE;
        override fun convert(source: Enum<MyEnum>) = source.ordinal
    }
     @ReadingConverter
    enum class EnumReadConverter : Converter<Int, Enum<MyEnum>> {
        INSTANCE;
        override fun convert(source: Int) = MyEnum.values()[source]
    }
}
    @CassandraType(type = DataType.Name.INT)
    var someEnum: SomeEnum
    @get: CassandraType(type = DataType.Name.INT)
    var someEnum: SomeEnum