【问题标题】:Spring JDBCTemplate with Hikari datasource batch update behaving asynchronously带有 Hikari 数据源批量更新的 Spring JDBCTemplate 异步行为
【发布时间】:2019-09-19 19:34:47
【问题描述】:

我正在开发一个 web 应用程序,我在其中显示一些用户可以操作的信息。采取行动后,列表应更新并在网页中反映这些更改。我已经让所有单独的部分都在工作,但是将它们连接在一起会导致问题。

这基本上是检索代码:

public List<Entry> findEntriesForIdByStatus(Long id, Status status) {

    MapSqlParameterSource paramSource = new MapSqlParameterSource();

    paramSource.addValue("id", id);

    if (null == status) {
        return template.query(FIND_ALL_ENTRIES_QUERY, paramSource, entryResultSetExtractor);
    }

    paramSource.addValue("status", status.getCode());

    List<Entry> entryWithStatus = springJdbcTemplate.query(FIND_ENTRIES_FOR_STATUS_QUERY, 
            paramSource,
            entryResultSetExtractor);

    return equipmentWithStatus;
}

以及更新代码:

@Transactional("myTransactionManager")
public void expire(CustomObject customObj) {
    Timestamp expirationTime = Timestamp.valueOf(dateTimeFactory.now());

    Long id = customObj.getId();
    List<Entry> entryList = customObj.getEntryList();

    SqlParameterSource[] params = new MapSqlParameterSource[entryList.size()];
    for (int i = 0; i < entryList.size(); i++) {
        Entry entry = entryList.get(i);
        MapSqlParameterSource paramSource = new MapSqlParameterSource("id", id)
                .addValue("fieldA", entry.getFieldA())
                .addValue("fieldB", entry.getFieldB())
                .addValue("expirationTime", expirationTime);
        params[i] = paramSource;
    }

    springJdbcTemplate.batchUpdate(EXPIRE_QUERY, params);
}

模板是由 Spring Boot 管理的 SpringJDBCNamedParameterTemplate,数据源是 com.zaxxer.hikari.HikariDataSource 的实例。

在页面加载并返回正确数据时调用检索方法。运行 expire 方法时,正确的记录过期,两个方法都执行成功。最初我使用 excludeMap (rxjs/angular 7) 将它们链接在前端,但它不一致。有时,过期后的检索会返回,就好像过期没有发生一样,有时会。但是,无论结果如何,如果我刷新页面,结果将是空的,正如预期的那样。我将其切换为在后端链接调用以查看它是否有帮助,它开始返回结果,好像过期并没有一直发生,但在页面刷新时,它按预期工作。

这是后端的链接:

public @ResponseBody List<Entry> expireEntries(@RequestBody CustomObj customObj) {
    entryService.expire(customObj, WebUtil.getCurrentUser());
    System.out.println(LocalDateTime.now().toString() + ": about to retrieve");
    List<Entry> entries = entryService.findEntriesByStatus(customObj.getId(), Status.NA);
    System.out.println(LocalDateTime.now().toString() + ": it's done retrieving");
    return equips;
}

服务几乎只是调用 DAO,中间没有太多。日志输出类似于以下内容:

没用

2019-09-19T13:33:50.998:即将删除
2019-09-19T13:33:51.050:已删除
2019-09-19T13:33:51.246:即将检索
13:33:51.465 [http-nio-8080-exec-3] INFO EntryDAO - 查询父级 27,状态 = NA 返回 364,耗时 219 毫秒
13:33:51.466 [http-nio-8080-exec-3] INFO EntryService - 查询父级 27 和状态 NA 返回 364
2019-09-19T13:33:51.466:检索完毕

工作

2019-09-19T13:38:13.752:即将删除
2019-09-19T13:38:13.798:已完成删除
2019-09-19T13:38:14.112: 即将检索
13:38:14.120 [http-nio-8080-exec-5] INFO EntryDAO - 查询父级 27,状态 = NA 返回 0,耗时 8 毫秒
13:38:14.120 [http-nio-8080-exec-5] INFO EntryService - 查询父级 27 和状态 NA 返回 0
2019-09-19T13:38:14.120:检索完毕

这些是从前端链接的时候开始的,但是移动到后端之后,它是一样的,只是“删除完成”和“即将检索”的时间更接近,通常在一两毫秒内。在更新和检索之间添加 2 秒睡眠似乎可以解决问题。我尝试锁定更新行并使用 Spring 的 @Transactional 注释使方法具有事务性,但似乎都没有帮助。

据我所知,批量更新似乎已被触发,但 Java 方法在数据库中实际完成更新之前返回。因此,当检索查询执行时,它会获取更新前的数据。有没有人能够确认这是这种行为,如果是,有没有办法修复它而不仅仅是用 Thread.sleep 破解它?

编辑:这是事务管理器的 bean 定义。

@Bean(name = "myDbProperties")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.mydb")
public DataSourceProperties dbProperties() {
    return new DataSourceProperties();
}

@Primary
@Bean(name = "myDataSource")
@ConfigurationProperties(prefix = "spring.datasource.mydb.configuration")
public DataSource dataSource(@Qualifier("myDbProperties") DataSourceProperties dbProperties) {
    return dbProperties.initializeDataSourceBuilder().build();
}

@Bean
@Primary
@Qualifier("myTransactionManager")
public PlatformTransactionManager loadauthTransactionManager(
        @Qualifier("myDataSource") DataSource datasource) {
    return new DataSourceTransactionManager(datasource);
}

【问题讨论】:

  • 如何提交交易?
  • 春天会照顾它。在这种情况下,@Transactional 注释告诉它在方法返回后提交。
  • 没错,但我在您的代码示例中没有看到任何 @Transactional 注释
  • 我把它省略了,因为它看起来并不太相关,我把它在几个地方打乱了,希望它会有所帮助。目前它在 DAO 方法上。我会重新添加它以帮助清晰。
  • 其实再看一遍,是在第二个DAO方法,更新方法上,但不是第一个。那会影响什么吗?我的理解是,如果注释中没有指定,Spring 会为那个 DB 调用打开一个新事务。

标签: java spring spring-boot spring-jdbc hikaricp


【解决方案1】:

事实证明,这是一个过度思考的案例,解决方案实际上非常简单。读取查询使用了sysdate between create and delete。结果是介于两者之间,并且由于查询发生在由于 sysdate = delete 而返回的任何 sysdate 的最小时间增量结果内。将其切换到 where systimestamp &gt;= create and systimestamp &lt; delete 解决了这个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-09
    • 1970-01-01
    • 1970-01-01
    • 2020-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-25
    相关资源
    最近更新 更多