【问题标题】:Spring Batch not deserialising datesSpring Batch 不反序列化日期
【发布时间】:2019-04-06 14:04:15
【问题描述】:

我在 Spring Batch 的 JobExecution 上下文中添加了一个对象,其中包含一个 Instant 字段。

序列化如下:

{
  "startFrom": {
    "nano": 0,
    "epochSecond": 1541116800
   }
 }

但是,Spring Batch 似乎无法对其进行反序列化。

Caused by: java.lang.IllegalArgumentException: Unable to deserialize the execution context
    at org.springframework.batch.core.repository.dao.JdbcExecutionContextDao$ExecutionContextRowMapper.mapRow(JdbcExecutionContextDao.java:325)
    at org.springframework.batch.core.repository.dao.JdbcExecutionContextDao$ExecutionContextRowMapper.mapRow(JdbcExecutionContextDao.java:309)
    at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:93)
    at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:60)
    at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:667)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:605)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:657)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:688)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:700)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:756)
    at org.springframework.batch.core.repository.dao.JdbcExecutionContextDao.getExecutionContext(JdbcExecutionContextDao.java:112)
    at org.springframework.batch.core.explore.support.SimpleJobExplorer.getJobExecutionDependencies(SimpleJobExplorer.java:202)
    at org.springframework.batch.core.explore.support.SimpleJobExplorer.getJobExecutions(SimpleJobExplorer.java:83)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
    at org.springframework.aop.framework.ReflectiveMethod

在进行一些研究时,我发现 Jackson 有一个 JavaTimeModule 来序列化/反序列化 Instant 和其他日期类。 但是,在Jackson2ExecutionContextStringSerializer 类中,它会按如下方式创建ObjectMapper,而不是注册正确的模块:

public Jackson2ExecutionContextStringSerializer() {
    this.objectMapper = new ObjectMapper();
    this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
    this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
    this.objectMapper.enableDefaultTyping();
    this.objectMapper.registerModule(new JobParametersModule());
}

Spring Batch 是否有理由不使用自动装配的 ObjectMapper?还是他们不注册JavaTimeModule 的原因? 有没有办法解决这个问题?

谢谢!

编辑:

我找到了如何覆盖这个对象映射器:

  @Bean
  public JobRepository createJobRepository() throws Exception {
    ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()).findAndRegisterModules();

    Jackson2ExecutionContextStringSerializer defaultSerializer = new Jackson2ExecutionContextStringSerializer();
    defaultSerializer.setObjectMapper(objectMapper);

    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(dataSource);
    factory.setTransactionManager(transactionManager);
    factory.setSerializer(defaultSerializer);
    factory.afterPropertiesSet();
    return factory.getObject();
  }

但是,即使这样,问题仍然存在。

【问题讨论】:

  • 这真的不是 Spring Batch 问题,因为我们只是委托给 Jackson 并且已经配置好了。

标签: spring jackson spring-batch jackson2


【解决方案1】:

以下内容对我有用。我正在扩展 Mahmoud Ben Hassine 上面的答案,这对 Nicolas Widart 不起作用。 (我没有足够的声誉来发表评论)。

@Bean
public BatchConfigurer configurer(DataSource dataSource, PlatformTransactionManager transactionManager,
  ObjectMapper objectMapper) {

    return new DefaultBatchConfigurer(dataSource) {
        final Jackson2ExecutionContextStringSerializer serializer = new Jackson2ExecutionContextStringSerializer();

        @Override
        protected JobRepository createJobRepository() throws Exception {
            serializer.setObjectMapper(objectMapper);

            JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
            factory.setDataSource(dataSource);
            factory.setTransactionManager(transactionManager);
            factory.setSerializer(serializer);
            factory.afterPropertiesSet();
            return factory.getObject();
        }

        @Override
        protected JobExplorer createJobExplorer() throws Exception {
            serializer.setObjectMapper(objectMapper);

            JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
            jobExplorerFactoryBean.setSerializer(serializer);
            jobExplorerFactoryBean.setDataSource(dataSource);
            jobExplorerFactoryBean.afterPropertiesSet();
            return jobExplorerFactoryBean.getObject();
        }
    };
}

(我在另一个@Configuration 类中定义了一个ObjectMapper bean。)这里重要的是还要覆盖createJobExplorer() 方法并在那里设置正确的ExecutionContextSerializer,因为此方法将调用getTarget()JobExplorerFactoryBean 上,如果没有设置,它将设置一个香草ExecutionContextSerializer,这会导致您描述的错误。

【讨论】:

    【解决方案2】:

    您展示的代码是序列化程序的默认初始化。您可以通过提供一个自定义对象映射器来覆盖它,该映射器使用您需要的模块进行预配置。这是一个例子:

    @Bean
    public JobRepository jobRepository() throws Exception {
        ObjectMapper objectMapper = null; // configure the object mapper as required
        Jackson2ExecutionContextStringSerializer serializer = new Jackson2ExecutionContextStringSerializer();
        serializer.setObjectMapper(objectMapper);
    
        JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
        jobRepositoryFactoryBean.setSerializer(serializer);
        // set other properties on the jobRepositoryFactoryBean
        jobRepositoryFactoryBean.afterPropertiesSet();
        return jobRepositoryFactoryBean.getObject();
    }
    

    希望这会有所帮助。

    【讨论】:

    • 真的谢谢你,在我看到你的评论之前我也这样做了。我已经相应地更新了我的问题。即使使用此覆盖,它也无法正确反序列化日期。
    【解决方案3】:

    更新

    Spring batch 现在有一个修复程序,允许您自定义您的 ObjectMapper,同时还可以获取 Jackson2ExecutionContextStringSerialier 在解决此问题后在内部执行的所有默认设置:https://jira.spring.io/browse/BATCH-2828

    修复程序位于4.2.0,在撰写本文时当前位于RC1

    原答案

    我注意到了类似的问题。我需要自定义ObjectMapper 来添加KotlinModule 来处理kotlin 数据类。不幸的是,Jackson2ExecutionContextStringSerializer 目前的编写方式不是很可扩展。正如您从创建的默认 ObjectMapper 中看到的那样,添加了一些默认值,包括针对作业参数的反序列化问题的修复:https://jira.spring.io/browse/BATCH-2680

    他们设置的JobParametersModuleprivate 类,因此我们无法设置具有相同功能的ObjectMapper 并添加到它。

    我在 Jira 中提出了一个问题:https://jira.spring.io/browse/BATCH-2828

    在解决此问题之前,最糟糕的解决方法是复制并粘贴 JobParmetersModule 的源代码,以便您可以在覆盖中注册相同的代码。

    【讨论】: