1

I use spring boot 2.1.1.RELEASE and spring-data-jdbc 1.0.3.RELEASE, Kotlin 1.3.10.

I have the following simple class definitions in kotlin:

@Table(value = "CUSTOMER")
data class Customer(
    @Id
    var id: Long?,
    @Column("NAME")
    var name: String,
    @Column("PUBLICATION_NAME")
    var publicationName: String,
    @Column("START_DATE")
    var startDate: String,
    @Column("END_DATE")
    var endDate: String,

    var items: MutableSet<Item>

)

@Table(value = "ITEM")
data class Item  (
    @Id
    var id: Long?,
    @Column("name")
    var name: String,
    @Column("title")
    var title: String,
    @Column("weight")
    var weight: Double
)

And the following database table definitions:

CREATE TABLE customer(
  id                SERIAL PRIMARY KEY,
  name              VARCHAR(256) NOT NULL,
  publication_name  VARCHAR(128) NOT NULL,
  start_date        VARCHAR(10),
  end_date          VARCHAR(10)
);

CREATE TABLE item(
  id               SERIAL PRIMARY KEY,
  customer         INTEGER,
  name             VARCHAR(256)     NOT NULL,
  title            VARCHAR(128),
  weight           DOUBLE PRECISION NOT NULL
);

I get the following error:

org.springframework.data.mapping.MappingException: Could not read property @org.springframework.data.annotation.Id()private java.lang.Long net.service.model.query.Item.id from result set!

    at org.springframework.data.jdbc.core.EntityRowMapper.readFrom(EntityRowMapper.java:130)
    at org.springframework.data.jdbc.core.EntityRowMapper.readEntityFrom(EntityRowMapper.java:143)
    at org.springframework.data.jdbc.core.EntityRowMapper.readFrom(EntityRowMapper.java:124)
    at org.springframework.data.jdbc.core.EntityRowMapper.lambda$createInstance$0(EntityRowMapper.java:167)
    at org.springframework.data.relational.core.conversion.BasicRelationalConverter$ConvertingParameterValueProvider.getParameterValue(BasicRelationalConverter.java:251)
    at org.springframework.data.convert.KotlinClassGeneratingEntityInstantiator$DefaultingKotlinClassInstantiatorAdapter.extractInvocationArguments(KotlinClassGeneratingEntityInstantiator.java:230)
    at org.springframework.data.convert.KotlinClassGeneratingEntityInstantiator$DefaultingKotlinClassInstantiatorAdapter.createInstance(KotlinClassGeneratingEntityInstantiator.java:204)
    at org.springframework.data.convert.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:84)
    at org.springframework.data.relational.core.conversion.BasicRelationalConverter.createInstance(BasicRelationalConverter.java:141)
    at org.springframework.data.jdbc.core.EntityRowMapper.createInstance(EntityRowMapper.java:160)
    at org.springframework.data.jdbc.core.EntityRowMapper.mapRow(EntityRowMapper.java:71)
    at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:94)
    at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:61)
    at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:679)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:617)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:669)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:694)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:748)
    at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.queryForObject(NamedParameterJdbcTemplate.java:235)
    at org.springframework.data.jdbc.core.DefaultDataAccessStrategy.findById(DefaultDataAccessStrategy.java:204)
    at org.springframework.data.jdbc.core.JdbcAggregateTemplate.findById(JdbcAggregateTemplate.java:135)
    at org.springframework.data.jdbc.repository.support.SimpleJdbcRepository.findById(SimpleJdbcRepository.java:66)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359)
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:99)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy95.findById(Unknown Source)
    at net.service.model.query.repository.CustomerRepositoryV2Test.should save Customer with all Items(CustomerRepositoryV2Test.kt:24)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:515)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:171)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:167)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:114)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:59)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:105)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:95)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:71)
    at java.util.ArrayList.forEach(ArrayList.java:1249)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:110)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:95)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:71)
    at java.util.ArrayList.forEach(ArrayList.java:1249)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:110)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:72)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:95)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:71)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:220)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:188)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:202)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:181)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.h2.jdbc.JdbcSQLException: Column "items_id" not found [42122-197]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:357)
    at org.h2.message.DbException.get(DbException.java:179)
    at org.h2.message.DbException.get(DbException.java:155)
    at org.h2.jdbc.JdbcResultSet.getColumnIndex(JdbcResultSet.java:3148)
    at org.h2.jdbc.JdbcResultSet.get(JdbcResultSet.java:3247)
    at org.h2.jdbc.JdbcResultSet.getObject(JdbcResultSet.java:529)
    at org.springframework.data.jdbc.core.EntityRowMapper.readFrom(EntityRowMapper.java:127)
    ... 88 more

From the spring documentation: "The table of the referenced entity is expected to have an additional column named the same as the table of the referencing entity. You can change this name by implementing NamingStrategy.getReverseColumnName(RelationalPersistentProperty property)."

What am I doing wrong?

Update: Adding the java set collection did not help either:

var items: java.util.HashSet<Item> = java.util.HashSet()

Update 2: Still no fix, added a reproducible example project on github:

https://github.com/ielkhalloufi/spring-data-jdbc-example
ielkhalloufi
  • 652
  • 1
  • 10
  • 27
  • The exception doesn't seem to match the classes you show. Please show the matching entities and the complete stack trace. If you are using `MutableSet` in the relevant classes as well, could you replace it with `java.util.Set` and check if this changes anything? – Jens Schauder Dec 06 '18 at 06:06
  • Hi Jens, I added the complete stacktrace, and use the Java set instead, without result. Hope you have some more insight. – ielkhalloufi Dec 06 '18 at 11:55
  • Could you create a small complete project for reproducing this? I'm not seeing where the problem is coming from. – Jens Schauder Dec 06 '18 at 12:32
  • Added a reproducible example project on github: https://github.com/ielkhalloufi/spring-data-jdbc-example – ielkhalloufi Dec 06 '18 at 13:11

3 Answers3

3

Try moving the set out of the constructor and to the body like

@Table(value = "CUSTOMER")
data class Customer(
    @Id
    var id: Long?,
    @Column("NAME")
    var name: String,
    @Column("PUBLICATION_NAME")
    var publicationName: String,
    @Column("START_DATE")
    var startDate: String,
    @Column("END_DATE")
    var endDate: String
){
    var items: MutableSet<Item> = HashSet()
}

And edit your test like

val gasLineItem = Customer(
            id = null,
            name = "name",
            publicationName = "",
            startDate = "2018-12-01",
            endDate = "2019-02-01"

    ).apply {
        items = mutableSetOf(
                gasCreative1,
                gasCreative2
        )
    }
Seanvd
  • 186
  • 1
  • 9
1

This seems to be a bug related to collection valued properties that are set via a constructor.

Kotlin creates a constructor with all the attributes, including the Set. Unfortunately, this triggers a bug in which Spring Data JDBC tries to load the contained entity from the ResultSet of the outer entity. Instead, it should issue another select, which it does when the property is only settable via setter or "wither".

Please file an issue at https://jira.spring.io/projects/DATAJDBC

As a workaround exclude collection valued attributes from the constructor. Since I know almost nothing about Kotlin, I can't tell you how to do that.

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
1

You can also use @PersistenceConstructor to tell Spring Data JDBC explicitly a constructor that doesn't include the Set (which is the cause of the bug). For example:

@Table(value = "CUSTOMER")
data class Customer(
    @Id
    var id: Long?,
    @Column("NAME")
    var name: String,
    @Column("PUBLICATION_NAME")
    var publicationName: String,
    @Column("START_DATE")
    var startDate: String,
    @Column("END_DATE")
    var endDate: String,
    var items: MutableSet<Item>
) {

    @PersistenceConstructor
    constructor(
        id: Long?, name: String, publicationName: String, startDate: String, endDate: String
    ) : this(
        id,
        name,
        publicationName,
        startDate,
        endDate,
        hashSetOf()
    )
}
David Montaño
  • 371
  • 1
  • 10