Как сохранить перечисления как ординалы с Spring Boot и Cassandra? - PullRequest
1 голос
/ 30 апреля 2019

В поле enum моей сущности я добавил @CassandraType(type = DataType.Name.INT).Однако в заявлении, отправленном Кассандре, используется не порядковый номер перечисления, а строковое представление.Таким образом я получаю следующую ошибку:

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

package enumtest

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication


@SpringBootApplication
class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args)
}

test/src/main/kotlin/enumtest/SomeEnum.kt

package enumtest


enum class SomeEnum {
    Foo,
    Bar
}

test/src/main/kotlin/enumtest/Thing.kt

package enumtest

import com.datastax.driver.core.DataType
import org.springframework.data.cassandra.core.cql.PrimaryKeyType
import org.springframework.data.cassandra.core.mapping.CassandraType
import org.springframework.data.cassandra.core.mapping.Column
import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn
import org.springframework.data.cassandra.core.mapping.Table


@Table("thing")
@Suppress("unused")
class Thing(

    @PrimaryKeyColumn(name = "thing_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
    var thingId: Long,

    @CassandraType(type = DataType.Name.INT)
    @Column("some_enum")
    var someEnum: SomeEnum

)

test/src/main/kotlin/enumtest/ThingRepository.kt

package enumtest

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

@Repository
interface ThingRepository : CassandraRepository<Thing, Long>

test/src/main/resources/application.yml

spring:
  data:
    cassandra:
      contact-points: localhost
      port: 9142
      keyspace_name: enumtest

test/src/test/kotlin/enumtest/PersistenceTest.kt

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)
    }
}

test/src/test/resources/cql/cassandra_schema.cql

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)
);

test/build.gradle

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'
    }
}

Вот полная версияминимального примера в качестве загрузки для облегчения экспериментов: https://drive.google.com/open?id=1zzIDhbWycaj4WXrze2sAmw8xRPacA8Js

Редактировать: Так как это похоже на ошибку, я только что открыл выпуск Jira .

Ответы [ 2 ]

1 голос
/ 23 мая 2019

Это исправлено с версии Spring Boot 2.1.5.

Однако @CassandraType необходимо явно поместить в геттер в Kotlin, потому что в противном случае он не виден ввремя выполнения.

На практике это просто означает заменить это:

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

на:

    @get: CassandraType(type = DataType.Name.INT)
    var someEnum: SomeEnum
1 голос
/ 04 мая 2019

Я довольно долго пытался заставить это работать, и, кажется, я наконец-то понял!

Я столкнулся с той же проблемой, что и с кодеком ... Я понятия не имею, почему это не работает. Согласно их документации, вы все делали правильно.

Итак, я реализовал свой собственный конвертер записи 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, используя переопределенный конвертер. Это открывает вам возможность иметь несколько из них для различных типов перечислений, где, возможно, по какой-то причине вы захотите записать из них другие пользовательские значения вместо того, чтобы всегда преобразовывать все перечисления.

Надеюсь, это работает!

EDIT Обратите внимание на изменение в удалении {} для INSTANCE для каждого конвертера и регистрации ReadingConverter с помощью CassandraCustomConversions

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...