问题
To the enum field of my entity I have added @CassandraType(type = DataType.Name.INT). However not the ordinal of the enum, but the string representation instead, is used in the statement sent to Cassandra. Thus I get the following error:
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)
Below you can find a minimal example, reproducing the problem.
What am I doing wrong?
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'
}
}
Here is the full version of the minimal example as a download to faciliate experimentation: https://drive.google.com/open?id=1zzIDhbWycaj4WXrze2sAmw8xRPacA8Js
Edit: Since it seems to be a bug, I just opened a Jira issue.
回答1:
I've been trying to get this working for quite awhile and it seems I finally got it!
I was running into the same issue you were with the codec...I have no idea why that's not working. According to their documentation you were doing it exactly right.
So I implemented my own Cassandra Write Converter. See below
@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]
}
}
This should on every write you do to Cassandra convert all enums it sees of type MyEnum to an Int using the overridden converter. This opens you up to the possibility of having multiple of these for different types of Enums where maybe for some reason you would like to write other custom values from them instead of always converting all enums.
Hope this works!
EDIT Note the change in removing { } for INSTANCE on each converter, and registering the ReadingConverter with the CassandraCustomConversions
回答2:
This is fixed since Spring Boot version 2.1.5.
However, the @CassandraType needs to be placed at the getter explicitly in Kotlin, because otherwise it is not seen at runtime.
In practice this simply means replacing this:
@CassandraType(type = DataType.Name.INT)
var someEnum: SomeEnum
with that:
@get: CassandraType(type = DataType.Name.INT)
var someEnum: SomeEnum
来源:https://stackoverflow.com/questions/55915849/how-to-persist-enums-as-ordinals-with-spring-boot-and-cassandra