Description
Bug description
Scenario: We're upgrading out solution from Spring boot 2.7.x to 3.0.x passing so from Spring Batch: 4.3.x to 5.0.1
That means that our current application is already in production and it leverage in a JobRepository that is on an Oracle Database and it's filled by job executed in the past on top of version 4.3.x
Upgrading to version 5.0.1 we're now facing this exception while trying to access to job parameters' stored in the repository by v. 4.3.x :
java.lang.ClassNotFoundException: STRING
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:375)
at org.springframework.batch.core.repository.dao.JdbcJobExecutionDao$2.processRow(JdbcJobExecutionDao.java:424)
at org.springframework.jdbc.core.JdbcTemplate$RowCallbackHandlerResultSetExtractor.extractData(JdbcTemplate.java:1688)
at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:723)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:713)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:744)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:773)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:789)
at org.springframework.batch.core.repository.dao.JdbcJobExecutionDao.getJobParameters(JdbcJobExecutionDao.java:440)
at org.springframework.batch.core.repository.dao.JdbcJobExecutionDao$JobExecutionRowMapper.mapRow(JdbcJobExecutionDao.java:469)
at org.springframework.batch.core.repository.dao.JdbcJobExecutionDao$JobExecutionRowMapper.mapRow(JdbcJobExecutionDao.java:451)
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:723)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:713)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:744)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:757)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:815)
at org.springframework.batch.core.repository.dao.JdbcJobExecutionDao.findJobExecutions(JdbcJobExecutionDao.java:172)
at org.springframework.batch.core.explore.support.SimpleJobExplorer.getJobExecutions(SimpleJobExplorer.java:90)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218)
I'm digging a bit into it in order to collect more details.
Right now what I can share is that this is the code that throws the exception:
Class<?> parameterType = null;
try {
parameterType = Class.forName(rs.getString("PARAMETER_TYPE"));
}
catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
And I think that it's due to the fact that: rs.getString("PARAMETER_TYPE")
will return STRING if the record in BATCH_JOB_EXECUTION_PARAMS was add by a version <5.x.x. Instead if the record is add by a version >= 5.x.x this will work because on the DB the PARAMETER_TYPE will be: java.lang.String
(right now we're facing this issue only for STRING but if this supposition is confirmed other types: DATE, LONG, will face the same issue I guess)
Environment
Spring Batch version: from 4.3.x to 5.0.1
Java version: 17
Oracle 19 and Oracle 21
Steps to reproduce
- Run a job on top of Spring Batch 4.3.x with a long-term repository (like on Oracle)
- Access though Java, on top of Spring Batch 5.0.1, to the previous job's parameters that are stored in the long-term repository.
Expected behavior
Library should be able to access the data stored in a long-term repository by and older version
Minimal Complete Reproducible example
Unzip file: spring-batch-4352-mre.zip
- Run an oracle DB instance with a TEST schema with Spring batch's tables at v. 4.3.x :
docker run --name test -p 1521:1521 -e ORACLE_PASSWORD="iamapassword" -v `pwd`/init_db:/container-entrypoint-initdb.d gvenzl/oracle-xe:18.4.0-full
-
Execute job with Spring batch 4.3.x:
cd batch-processing4.3.x/batch-processing ; ./gradlew clean bootRun
-
Execute job with Spring batch 5.0.x:
cd ../../batch-processing5.0.x/batch-processing ; ./gradlew clean bootRun
This step at point 3. will fail due to: java.lang.ClassNotFoundException: STRING