Spring 具有异步行为的 Hikari 数据源批量更新的 JDBCTemplate
Spring JDBCTemplate with Hikari datasource batch update behaving asynchronously
我正在开发一个网络应用程序,我在其中显示一些用户可以根据其采取行动的信息。采取行动后,列表应更新并在网页中反映这些变化。我已经让所有单独的部分正常工作,但将它们连接在一起会导致问题。
基本上是检索代码:
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);
}
模板是 SpringJDBCNamedParameterTemplate,由 Spring Boot 管理,数据源是 com.zaxxer.hikari.HikariDataSource.
的实例
在页面加载和 return 获取正确数据时调用检索方法。当运行expire方法时,正确的记录都过期了,两种方法都执行成功。最初我使用 excludeMap (rxjs/angular 7) 在前端链接它们,但它不一致。有时过期后的检索会 return 就好像过期没有发生一样,有时它会。但是,无论结果如何,如果我刷新页面,结果将如预期的那样是空的。我将其切换为在后端链接调用以查看它是否有任何帮助,并且它开始 return 处理结果,就好像过期并没有一直发生一样,但是在页面刷新时,它起作用了不出所料。
这是后端的链接:
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: about to delete
2019-09-19T13:33:51.050: It's done deleting
2019-09-19T13:33:51.246: about to retrieve
13:33:51.465 [http-nio-8080-exec-3] INFO EntryDAO - query for parent 27 with status=NA returned 364 and took 219 milliseconds
13:33:51.466 [http-nio-8080-exec-3] INFO EntryService - query for parent 27 and status NA returned 364
2019-09-19T13:33:51.466: it's done retrieving
工作
2019-09-19T13:38:13.752: about to delete
2019-09-19T13:38:13.798: It's done deleting
2019-09-19T13:38:14.112: about to retrieve
13:38:14.120 [http-nio-8080-exec-5] INFO EntryDAO - query for parent 27 with status=NA returned 0 and took 8 milliseconds
13:38:14.120 [http-nio-8080-exec-5] INFO EntryService - query for parent 27 and status NA returned 0
2019-09-19T13:38:14.120: it's done retrieving
这些是在前端链接时的,但是移动到后端后,还是一样的,只是 "It's done deleting" 和 "about to retrieve" 时间更近了,一般在毫秒或二。在更新和检索之间添加 2 秒的睡眠似乎可以解决问题。我尝试锁定行以进行更新并使用 Spring 的 @Transactional 注释使方法具有事务性,但似乎都无济于事。
据我所知,似乎正在触发批量更新,但 Java 方法 returns 在更新之前实际上已在数据库中完成。因此,当检索查询执行时,它会获取更新前的数据。有没有人能够确认这是这种行为,如果是,有没有办法修复它而不仅仅是用 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);
}
事实证明这是一个想太多的案例,解决方案其实很简单。读取查询使用了 sysdate between create and delete
。事实证明 between 是包含在内的,因为查询发生在任何 sysdate 的最小时间增量内,因为 sysdate = delete 而返回结果。将它切换到 where systimestamp >= create and systimestamp < delete
解决了这个问题。
我正在开发一个网络应用程序,我在其中显示一些用户可以根据其采取行动的信息。采取行动后,列表应更新并在网页中反映这些变化。我已经让所有单独的部分正常工作,但将它们连接在一起会导致问题。
基本上是检索代码:
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);
}
模板是 SpringJDBCNamedParameterTemplate,由 Spring Boot 管理,数据源是 com.zaxxer.hikari.HikariDataSource.
的实例在页面加载和 return 获取正确数据时调用检索方法。当运行expire方法时,正确的记录都过期了,两种方法都执行成功。最初我使用 excludeMap (rxjs/angular 7) 在前端链接它们,但它不一致。有时过期后的检索会 return 就好像过期没有发生一样,有时它会。但是,无论结果如何,如果我刷新页面,结果将如预期的那样是空的。我将其切换为在后端链接调用以查看它是否有任何帮助,并且它开始 return 处理结果,就好像过期并没有一直发生一样,但是在页面刷新时,它起作用了不出所料。
这是后端的链接:
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: about to delete
2019-09-19T13:33:51.050: It's done deleting
2019-09-19T13:33:51.246: about to retrieve
13:33:51.465 [http-nio-8080-exec-3] INFO EntryDAO - query for parent 27 with status=NA returned 364 and took 219 milliseconds
13:33:51.466 [http-nio-8080-exec-3] INFO EntryService - query for parent 27 and status NA returned 364
2019-09-19T13:33:51.466: it's done retrieving
工作
2019-09-19T13:38:13.752: about to delete
2019-09-19T13:38:13.798: It's done deleting
2019-09-19T13:38:14.112: about to retrieve
13:38:14.120 [http-nio-8080-exec-5] INFO EntryDAO - query for parent 27 with status=NA returned 0 and took 8 milliseconds
13:38:14.120 [http-nio-8080-exec-5] INFO EntryService - query for parent 27 and status NA returned 0
2019-09-19T13:38:14.120: it's done retrieving
这些是在前端链接时的,但是移动到后端后,还是一样的,只是 "It's done deleting" 和 "about to retrieve" 时间更近了,一般在毫秒或二。在更新和检索之间添加 2 秒的睡眠似乎可以解决问题。我尝试锁定行以进行更新并使用 Spring 的 @Transactional 注释使方法具有事务性,但似乎都无济于事。
据我所知,似乎正在触发批量更新,但 Java 方法 returns 在更新之前实际上已在数据库中完成。因此,当检索查询执行时,它会获取更新前的数据。有没有人能够确认这是这种行为,如果是,有没有办法修复它而不仅仅是用 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);
}
事实证明这是一个想太多的案例,解决方案其实很简单。读取查询使用了 sysdate between create and delete
。事实证明 between 是包含在内的,因为查询发生在任何 sysdate 的最小时间增量内,因为 sysdate = delete 而返回结果。将它切换到 where systimestamp >= create and systimestamp < delete
解决了这个问题。