I am using SpringBoot 3 to access a SQLite database. My problem is, that when fetching entities parallely, I get a random ArrayIndexOutOfBoundsException
in a Java class that parses a date.
SpringBoot 3.2.5
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.42.2.0</version>
</dependency>
The dialect is set to spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
My Java class looks like this:
public class SomeTable implements Serializable {
private static final String COLUMN_NAME_CREATED = "CREATED";
private static final String COLUMN_NAME_MODIFIED = "MODIFIED";
private static final String COLUMN_NAME_IP = "IP";
private static final String COLUMN_NAME_USERNAME = "USERNAME";
@Id
@Column(length = 36)
protected String id;
@NotNull
@Column(name = "created")
private Instant created;
@NotNull
@Column(name = "modified")
private Instant modified;
}
The table looks like this:
CREATE TABLE "some_table" (
"id" varchar(36) NOT NULL,
"created" timestamp NOT NULL,
"modified" timestamp NOT NULL
);
This is the stacktrace I am getting:
java.lang.ArrayIndexOutOfBoundsException: Index 18 out of bounds for length 13
at java.base/sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(Unknown Source)
at java.base/java.util.GregorianCalendar.computeFields(Unknown Source)
at java.base/java.util.GregorianCalendar.computeFields(Unknown Source)
at java.base/java.util.Calendar.setTimeInMillis(Unknown Source)
at org.sqlite.jdbc3.JDBC3ResultSet.getTimestamp(JDBC3ResultSet.java:490)
at com.zaxxer.hikari.pool.HikariProxyResultSet.getTimestamp(HikariProxyResultSet.java)
at org.hibernate.type.descriptor.jdbc.TimestampUtcAsJdbcTimestampJdbcType$2.doExtract(TimestampUtcAsJdbcTimestampJdbcType.java:103)
at org.hibernate.type.descriptor.jdbc.BasicExtractor.extract(BasicExtractor.java:44)
at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.getCurrentRowValue(JdbcValuesResultSetImpl.java:302)
at org.hibernate.sql.results.internal.RowProcessingStateStandardImpl.getJdbcValue(RowProcessingStateStandardImpl.java:119)
at org.hibernate.sql.results.graph.basic.BasicResultAssembler.extractRawValue(BasicResultAssembler.java:52)
at org.hibernate.sql.results.graph.basic.BasicResultAssembler.assemble(BasicResultAssembler.java:59)
at org.hibernate.sql.results.graph.DomainResultAssembler.assemble(DomainResultAssembler.java:33)
at org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.extractConcreteTypeStateValues(AbstractEntityInitializer.java:1081)
at org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.initializeEntityInstance(AbstractEntityInitializer.java:838)
at org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.initializeEntity(AbstractEntityInitializer.java:813)
at org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.initializeInstance(AbstractEntityInitializer.java:799)
at org.hibernate.sql.results.internal.InitializersList.initializeInstance(InitializersList.java:70)
at org.hibernate.sql.results.internal.StandardRowReader.coordinateInitializers(StandardRowReader.java:109)
at org.hibernate.sql.results.internal.StandardRowReader.readRow(StandardRowReader.java:86)
at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:181)
at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:33)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:209)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:83)
at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:76)
at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:65)
at org.hibernate.loader.ast.internal.SingleIdLoadPlan.load(SingleIdLoadPlan.java:145)
at org.hibernate.loader.ast.internal.SingleIdLoadPlan.load(SingleIdLoadPlan.java:117)
at org.hibernate.loader.ast.internal.SingleIdEntityLoaderStandardImpl.load(SingleIdEntityLoaderStandardImpl.java:75)
at org.hibernate.persister.entity.AbstractEntityPersister.doLoad(AbstractEntityPersister.java:3748)
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3737)
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:604)
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromCacheOrDatasource(DefaultLoadEventListener.java:590)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:560)
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:544)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:207)
at org.hibernate.event.internal.DefaultLoadEventListener.loadWithRegularProxy(DefaultLoadEventListener.java:290)
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:242)
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:111)
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:68)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138)
at org.hibernate.internal.SessionImpl.fireLoadNoChecks(SessionImpl.java:1222)
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1068)
at org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer.initializeInstance(EntitySelectFetchInitializer.java:195)
at org.hibernate.sql.results.internal.InitializersList.initializeInstance(InitializersList.java:70)
at org.hibernate.sql.results.internal.StandardRowReader.coordinateInitializers(StandardRowReader.java:109)
at org.hibernate.sql.results.internal.StandardRowReader.readRow(StandardRowReader.java:86)
at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:203)
at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:33)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:209)
at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:83)
at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:76)
at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:65)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$2(ConcreteSqmSelectQueryPlan.java:137)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:362)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:303)
at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:509)
at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:427)
at org.hibernate.query.Query.getResultList(Query.java:120)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:383)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:354)
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:516)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:392)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:164)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
at jdk.proxy2/jdk.proxy2.$Proxy229.findAll(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:354)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
at jdk.proxy2/jdk.proxy2.$Proxy229.findAll(Unknown Source)
at com.my.project.services.SomeTableService.getSomeTable(AbstractNetworkRoutingService.java:97)
Caused by: java.lang.ArrayIndexOutOfBoundsException: Index 27 out of bounds for length 13
at java.base/sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(Unknown Source)
at java.base/java.util.GregorianCalendar.computeFields(Unknown Source)
at java.base/java.util.GregorianCalendar.computeFields(Unknown Source)
at java.base/java.util.Calendar.setTimeInMillis(Unknown Source)
at org.sqlite.jdbc3.JDBC3ResultSet.getTimestamp(JDBC3ResultSet.java:490)
at com.zaxxer.hikari.pool.HikariProxyResultSet.getTimestamp(HikariProxyResultSet.java)
at org.hibernate.type.descriptor.jdbc.TimestampUtcAsJdbcTimestampJdbcType$2.doExtract(TimestampUtcAsJdbcTimestampJdbcType.java:103)
at org.hibernate.type.descriptor.jdbc.BasicExtractor.extract(BasicExtractor.java:44)
at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.getCurrentRowValue(JdbcValuesResultSetImpl.java:302)
at org.hibernate.sql.results.internal.RowProcessingStateStandardImpl.getJdbcValue(RowProcessingStateStandardImpl.java:119)
at org.hibernate.sql.results.graph.basic.BasicResultAssembler.extractRawValue(BasicResultAssembler.java:52)
at org.hibernate.sql.results.graph.basic.BasicResultAssembler.assemble(BasicResultAssembler.java:59)
at org.hibernate.sql.results.graph.DomainResultAssembler.assemble(DomainResultAssembler.java:33)
at org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.extractConcreteTypeStateValues(AbstractEntityInitializer.java:1081)
at org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.initializeEntityInstance(AbstractEntityInitializer.java:838)
at org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.initializeEntity(AbstractEntityInitializer.java:813)
at org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.initializeInstance(AbstractEntityInitializer.java:799)
at org.hibernate.sql.results.internal.InitializersList.initializeInstance(InitializersList.java:70)
at org.hibernate.sql.results.internal.StandardRowReader.coordinateInitializers(StandardRowReader.java:109)
at org.hibernate.sql.results.internal.StandardRowReader.readRow(StandardRowReader.java:86)
...
com.my.project.services.SomeTableService.getSomeTable(AbstractNetworkRoutingService.java:97)
The dates in the table look like this:
We tried to use a custom converter that uses Instant.ofEpochMillis()
but the problem still appeared.
My current assumption is a race condition. Limiting the number of connections to 1 via Spring settings solves the problem - but having to do that because of this seems just wrong, performance-wise this is not optimal so I was thinking someone might have an idea on some other way.
This is a bug now reported to Hibernate: HHH-18239. We implemented a custom type as a temporary solution:
/**
* This is a custom implementation of a Hibernate type, that maps a {@link Types#TIMESTAMP} to a
* {@link java.time.Instant}. This is necessary, because of this bug in Hibernate:
* <a href="https://hibernate.atlassian.net/browse/HHH-18239">Hibernate ORM Jira HHH-18239</a>.
*/
public class CustomThreadSafeInstantType implements UserType<Instant> {
@Override
public int getSqlType() {
return Types.TIMESTAMP;
}
@Override
public Class<Instant> returnedClass() {
return Instant.class;
}
@Override
public boolean equals(Instant instant, Instant j1) {
return Objects.equals(instant, j1);
}
@Override
public int hashCode(Instant instant) {
return instant.hashCode();
}
@Override
public Instant nullSafeGet(ResultSet resultSet, int i, SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws SQLException {
var timestamp = resultSet.getTimestamp(i);
if (timestamp == null) {
return null;
}
return timestamp.toInstant();
}
@Override
public void nullSafeSet(PreparedStatement preparedStatement, Instant instant, int i, SharedSessionContractImplementor sharedSessionContractImplementor)
throws SQLException {
if (instant == null) {
preparedStatement.setNull(i, Types.TIMESTAMP);
return;
}
preparedStatement.setTimestamp(i, Timestamp.from(instant));
}
@Override
public Instant deepCopy(Instant instant) {
return instant;
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Instant instant) {
return instant;
}
@Override
public Instant assemble(Serializable serializable, Object o) {
return (Instant) serializable;
}
}
This type is used like so:
public class SomeTable implements Serializable {
private static final String COLUMN_NAME_CREATED = "CREATED";
private static final String COLUMN_NAME_MODIFIED = "MODIFIED";
private static final String COLUMN_NAME_IP = "IP";
private static final String COLUMN_NAME_USERNAME = "USERNAME";
@Id
@Column(length = 36)
protected String id;
@NotNull
@Column(name = "created")
@Type(CustomThreadSafeInstantType.class)
private Instant created;
@NotNull
@Column(name = "modified")
@Type(CustomThreadSafeInstantType.class)
private Instant modified;
}