【Bug】定時任務中 Jpa Save 方法失效
首先說一下問題,在定時任務中調用 jpa 的 save 方法沒有效果,但是通過外界調用,比如 controller 中注入 service 來調用是可以的,真是巨巨巨離譜,我被折磨了好幾天。
我這個問題的根源在于多數據源的配置上,所以如果你的項目中沒有使用多數據源,那接下來的內容應該對你沒什么幫助了~
一般來講,在配置多數據源的時候,除了要繼承 AbstractRoutingDataSource 類
package com.xsdl.content;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class MultiDataSource extends AbstractRoutingDataSource {private static final ThreadLocal<String> keys = new ThreadLocal<String>();@Overrideprotected Object determineCurrentLookupKey() {return keys.get();}public static void setDataSource(String dataSource) {keys.set(dataSource);}public static String getDatasource() {return keys.get();}public static void clearDataSource() {keys.remove();}}
還會配置對應數據源的 datasource、jdbcTemplate 等
package com.xsdl.config;import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;@Configuration
public class MultiDataSourceConfig {@Bean(name = "dataSourceAAA")@Primary@ConfigurationProperties("spring.aaa.datasource")public DataSource dataSourceAAA() {return DataSourceBuilder.create().build();}@Bean(name = "dataSourceBBB")@ConfigurationProperties("spring.bbb.datasource")public DataSource dataSourceBBB() {return DataSourceBuilder.create().build();}@Bean(name = "multiDataSource")public MultiDataSource multipleDataSource(@Qualifier("dataSourceAAA") DataSource dataSourceA,@Qualifier("dataSourceBBB") DataSource dataSourceB) {Map<Object, Object> map = new HashMap<>();map.put("dataSourceA", dataSourceA);map.put("dataSourceB", dataSourceB);MultiDataSource multipleDataSource = new MultiDataSource();multipleDataSource.setDefaultTargetDataSource(dataSourceA);multipleDataSource.setTargetDataSources(map);return multipleDataSource;}@Beanpublic JdbcTemplate jdbcTemplate(MultiDataSource multipleDataSource) {return new JdbcTemplate(multipleDataSource);}@Bean@Primary@Description("事務管理器")public PlatformTransactionManager transactionManager(@Qualifier("multiDataSource") MultiDataSource multiDataSource) {return new DataSourceTransactionManager(multiDataSource);}}
事情到現在是很正常的,啟動項目也可以正常啟動,正常在 service 的業務邏輯里調用對應的 dao 或者 repository 去保存數據也可以
突然有一天,我接到一個需求,有一個接收記錄的表,我需要定時從中查詢執行失敗的,重試進行補償,那我補償完成后,當然要更新回去對吧,于是我寫了一個類似下面這樣的代碼:
package com.xsdl.job;import com.xsdl.dao.UserDao;
import com.xsdl.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.UUID;
import java.util.stream.IntStream;@Component("tsJob")
@Slf4j
public class TsJob {@Autowiredprivate UserDao userDao;@Scheduled(cron = "0 0/2 * * * ?")public void execute() {log.warn("定時任務開始執行");IntStream.rangeClosed(1, 10).forEach(i -> {User user = new User();user.setUuid(UUID.randomUUID().toString());user.setName("name" + i);userDao.save(user);});log.warn("定時任務執行結束");}}
具體邏輯不用關注,就是查數據庫,然后修改了幾個字段,重新 save 回去,這時候問題出現了,save 方法執行無效,數據庫里數據沒更新,我一路 debug,期間各種磕磕絆絆,就是找不到問題,反而找到了幾個同病相憐的人:
[在spring的定時任務中使用 jpa 的save 方法失效](https://forum.springdoc.cn/t/topic/776)
no transaction is in progress
我一開始以為是事務的開啟問題,包括但這樣嘗試:
@Scheduled(cron = "0 0/2 * * * ?")public void execute() {log.warn("定時任務開始執行");IntStream.rangeClosed(1, 10).forEach(i -> {User user = new User();user.setUuid(UUID.randomUUID().toString());user.setName("name" + i);getThis.doSave(user);});log.warn("定時任務執行結束");}@Transactional(propagation = Propagation.New)public void doSave(User user) {userDao.save(user);}private TsJob getThis() {return ApplicationContextUtil.getBean("tsJob",TsJob.class);}
然而什么用都沒有
最關鍵的是,這個方法如果在一個 controller 里手動調用是可以執行的,我覺得太離譜了,就想開個新項目復現一下,結果怎么都復現不出來,最后突然覺得區別也就是在多數據源上了,中間各種嘗試,直到我把這個代碼注釋掉,終于可以了~
@Bean@Primary@Description("事務管理器")public PlatformTransactionManager transactionManager(@Qualifier("multiDataSource") MultiDataSource multiDataSource) {return new DataSourceTransactionManager(multiDataSource);}
如果你的項目里也這樣配置了 transactionManager ,可能就是這個問題~