spring-projects / spring-data-relational

Spring Data Relational. Home of Spring Data JDBC and Spring Data R2DBC.
https://spring.io/projects/spring-data-jdbc
Apache License 2.0
774 stars 348 forks source link

Upgrade HSQL to 2.7.4 #1929

Open schauder opened 3 weeks ago

schauder commented 3 weeks ago

Simply upgrading the version property leads to

[ERROR] org.springframework.data.jdbc.repository.JdbcRepositoryEmbeddedWithReferenceIntegrationTests.deleteById -- Time elapsed: 0.001 s <<< ERROR!
org.springframework.data.relational.core.conversion.DbActionExecutionException: Failed to execute BatchWithValue{actions=[Insert{entity=org.springframework.data.jdbc.repository.JdbcRepositoryEmbeddedWithReferenceIntegrationTests$DummyEntity2@6cddb04c, propertyPath=embeddable.dummyEntity2, dependingOn=InsertRoot{entity=org.springframework.data.jdbc.repository.JdbcRepositoryEmbeddedWithReferenceIntegrationTests$DummyEntity@1c6d65f7, idValueSource=GENERATED}, idValueSource=GENERATED, qualifiers={}}], batchValue=GENERATED}
    at org.springframework.data.jdbc.core.AggregateChangeExecutor.execute(AggregateChangeExecutor.java:118)
    at org.springframework.data.jdbc.core.AggregateChangeExecutor.lambda$executeSave$0(AggregateChangeExecutor.java:61)
    at org.springframework.data.relational.core.conversion.BatchedActions$InsertCombiner.lambda$forEach$2(BatchedActions.java:170)
    at java.base/java.util.HashMap.forEach(HashMap.java:1429)
    at org.springframework.data.relational.core.conversion.BatchedActions$InsertCombiner.lambda$forEach$3(BatchedActions.java:170)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
    at org.springframework.data.relational.core.conversion.BatchedActions$InsertCombiner.forEach(BatchedActions.java:169)
    at org.springframework.data.relational.core.conversion.BatchedActions.forEach(BatchedActions.java:79)
    at org.springframework.data.relational.core.conversion.SaveBatchingAggregateChange.forEachAction(SaveBatchingAggregateChange.java:71)
    at org.springframework.data.jdbc.core.AggregateChangeExecutor.executeSave(AggregateChangeExecutor.java:61)
    at org.springframework.data.jdbc.core.JdbcAggregateTemplate.performSave(JdbcAggregateTemplate.java:505)
    at org.springframework.data.jdbc.core.JdbcAggregateTemplate.save(JdbcAggregateTemplate.java:171)
    at org.springframework.data.jdbc.repository.support.SimpleJdbcRepository.save(SimpleJdbcRepository.java:69)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359)
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277)
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170)
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158)
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:515)
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:284)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:752)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:174)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
    at org.springframework.data.jdbc.repository.$Proxy162.save(Unknown Source)
    at org.springframework.data.jdbc.repository.JdbcRepositoryEmbeddedWithReferenceIntegrationTests.deleteById(JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java:165)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [INSERT INTO "DUMMY_ENTITY2" ("ID", "TEST") VALUES (?, ?)]; SQL state [24501]; error code [-3601]; invalid cursor state: identified cursor is not open
    at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1557)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:677)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:695)
    at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:1033)
    at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.batchUpdate(NamedParameterJdbcTemplate.java:421)
    at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.batchUpdate(NamedParameterJdbcTemplate.java:399)
    at org.springframework.data.jdbc.core.convert.IdGeneratingBatchInsertStrategy.execute(IdGeneratingBatchInsertStrategy.java:77)
    at org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy.insert(DefaultDataAccessStrategy.java:125)
    at org.springframework.data.jdbc.core.JdbcAggregateChangeExecutionContext.executeBatchInsert(JdbcAggregateChangeExecutionContext.java:109)
    at org.springframework.data.jdbc.core.AggregateChangeExecutor.execute(AggregateChangeExecutor.java:91)
    ... 40 more
Caused by: java.sql.SQLException: invalid cursor state: identified cursor is not open
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source)
    at org.hsqldb.jdbc.JDBCResultSet.checkClosed(Unknown Source)
    at org.hsqldb.jdbc.JDBCResultSet.next(Unknown Source)
    at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:93)
    at org.springframework.jdbc.core.JdbcTemplate.storeGeneratedKeys(JdbcTemplate.java:1591)
    at org.springframework.jdbc.core.JdbcTemplate.lambda$getPreparedStatementCallback$7(JdbcTemplate.java:1619)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:658)
    ... 48 more
Caused by: org.hsqldb.HsqlException: invalid cursor state: identified cursor is not open
    at org.hsqldb.error.Error.error(Unknown Source)
    at org.hsqldb.error.Error.error(Unknown Source)
    ... 55 more

and similar

schauder commented 2 weeks ago

Since I was able to reproduce this directly with JDBC I opened an issue with HSQLDB: https://sourceforge.net/p/hsqldb/bugs/1725/

schauder commented 2 weeks ago

Somehow the ResultSet used for returning generated values is based on a closed cursor on the second attempt.

Code for reproduction:

    @Test
    @Order(1)
    void dsOne() {
        insertDataSource();
    }

    @Test
    @Order(2)
    void dsTwo() {
        insertDataSource();
    }

    private void insertDataSource() {
        try (Connection connection = dataSource.getConnection()) {

            PreparedStatement preparedStatement = connection.prepareStatement(INSERT_MAIN + "(?, ?)", Statement.RETURN_GENERATED_KEYS);
            preparedStatement.setInt(1, 4711);
            preparedStatement.setString(2, "text1");
            preparedStatement.executeUpdate();

            try (ResultSet resultSet = preparedStatement.getGeneratedKeys()) {
                while (resultSet.next()) {
                    System.out.println("rs element");
                }
            }
        } catch (SQLException ex) {
            throw new RuntimeException(ex);
        }
    }