Spring boot2.X專案,使用data jpa 框架,普通方法可以實作資料源的切換,但是新建執行緒和定時任務里面執行方法,卻不能插入和修改資料庫,并且控制端無日志輸出。可以查詢資料。
相同的多資料源配置,使用mybatis框架沒有這樣的問題。
uj5u.com熱心網友回復:
人呢?有人遇見這個問題嗎?uj5u.com熱心網友回復:
你選擇資料源的配置是怎么配置的?代碼和組態檔從現象上看你的選擇資料源邏輯中有Session相關的東西。當執行一個Session的邏輯時,某些資訊放在ThreadLocal中了,而在新建的執行緒和定時任務的執行緒中,不會有初始化ThreadLocal的程序,所以失敗了。
提供思路,希望可以有幫助
uj5u.com熱心網友回復:
下面是主要配置和方法
組態檔
spring:
application:
name: service-system
hikari:
first:
jdbc-url: jdbc:mysql:// ?useUnicode=true&characterEncoding=utf8&useSSL=false
username:
password:
driver-class-name: com.mysql.jdbc.Driver
minimum-idle: 50
maximum-pool-size: 200
idle-timeout: 30000
max-lifetime: 300000
connection-timeout: 18000
connection-test-query: select 1
second:
jdbc-url: jdbc:mysql:// ?useUnicode=true&characterEncoding=utf8&useSSL=false
username:
password:
driver-class-name: com.mysql.jdbc.Driver
third:
jdbc-url: jdbc:mysql:// ?useUnicode=true&characterEncoding=utf8&useSSL=false
username:
password:
driver-class-name: com.mysql.jdbc.Driver
minimum-idle: 50
maximum-pool-size: 200
idle-timeout: 30000
max-lifetime: 300000
connection-timeout: 18000
connection-test-query: select 1
方法:
/**
* 實際資料源配置
*/
@Data
@Component
@ConfigurationProperties(prefix = "spring.hikari")
public class DBProperties {
private HikariDataSource first;
private HikariDataSource second;
private HikariDataSource third;
}
/**
* 資料源配置
*/
@Configuration
public class DataSourceConfig {
@Autowired
private DBProperties properties;
@Bean(name = "dataSource")
public DataSource dataSource() {
//按照目標資料源名稱和目標資料源物件的映射存放在Map中
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("first", properties.getFirst());
targetDataSources.put("second", properties.getSecond());
targetDataSources.put("third", properties.getThird());
//采用是想AbstractRoutingDataSource的物件包裝多資料源
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);
//設定默認的資料源,當拿不到資料源時,使用此配置
dataSource.setDefaultTargetDataSource(properties.getFirst());
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
public class DynamicDataSourceHolder {
/**
* 本地執行緒共享物件
*/
private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
public static void putDataSource(String name) {
THREAD_LOCAL.set(name);
}
public static String getDataSource() {
return THREAD_LOCAL.get();
}
public static void removeDataSource() {
THREAD_LOCAL.remove();
}
}
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
//從共享執行緒中獲取資料源名稱
return DynamicDataSourceHolder.getDataSource();
}
}
如果查詢可以成功的話,代表資料源是存在的。
一直沒有解決,希望能幫忙看一下問題在哪里
uj5u.com熱心網友回復:
當前問題出在DynamicDataSourceHolder上面運行正常的邏輯,在執行determineCurrentLookupKey之前一定會執行putDataSource。
但是新啟動的執行緒和定時任務,沒有執行putDataSource,定時任務結束或者執行緒結束也不會執行removeDataSource。
正確的做法
新啟動執行緒場景
class DataSourceThread extend Thread {
private String dataSource;
private Runnable runable;
public void run() {
DynamicDataSourceHolder.putDataSource(dataSource);
try {
runable.run();
} finally {
DynamicDataSourceHolder.removeDataSource();
}
}
}
Thread t = new DataSourceThread("first", new Runable(){.....});
t.start();
對于定時任務的場景只能在定時任務的邏輯中來裁決使用什么資料源,而且和DataSourceThread.run的邏輯類似
public void schedule() {
String dataSource = findDataSource();// 計算使用哪個DataSource
DynamicDataSourceHolder.putDataSource(dataSource);
try {
runSelfSchedule();
} finally {
DynamicDataSourceHolder.removeDataSource();
}
}
uj5u.com熱心網友回復:
測驗了,和之前的結果一樣,還是沒有插入資料。
把定時器里方法放到控制器里,呼叫可以成功。
uj5u.com熱心網友回復:
沒有插入資料的執行緒,是怎么啟動的?你是怎么呼叫DynamicDataSourceHolder.putDataSource(dataSource);的
uj5u.com熱心網友回復:
當前問題出在DynamicDataSourceHolder上面
運行正常的邏輯,在執行determineCurrentLookupKey之前一定會執行putDataSource。
但是新啟動的執行緒和定時任務,沒有執行putDataSource,定時任務結束或者執行緒結束也不會執行removeDataSource。
正確的做法
新啟動執行緒場景
class DataSourceThread extend Thread {
private String dataSource;
private Runnable runable;
public void run() {
DynamicDataSourceHolder.putDataSource(dataSource);
try {
runable.run();
} finally {
DynamicDataSourceHolder.removeDataSource();
}
}
}
Thread t = new DataSourceThread("first", new Runable(){.....});
t.start();
對于定時任務的場景只能在定時任務的邏輯中來裁決使用什么資料源,而且和DataSourceThread.run的邏輯類似
public void schedule() {
String dataSource = findDataSource();// 計算使用哪個DataSource
DynamicDataSourceHolder.putDataSource(dataSource);
try {
runSelfSchedule();
} finally {
DynamicDataSourceHolder.removeDataSource();
}
}
測驗了,和之前的結果一樣,還是沒有插入資料。
把定時器里方法放到控制器里,呼叫可以成功。
沒有插入資料的執行緒,是怎么啟動的?你是怎么呼叫DynamicDataSourceHolder.putDataSource(dataSource);的
定時器方法:
@Scheduled(cron = "0 0/1 * * * ?")
public void aioCallMethod() {
System.out.println("timer start");
Calendar calendar = Calendar.getInstance();
int hour = calendar.get(Calendar.HOUR_OF_DAY);
if (hour == 21) {
calendar.set(Calendar.HOUR_OF_DAY, 9);
} else {
calendar.set(Calendar.HOUR_OF_DAY, 21);
calendar.add(Calendar.DATE, -1);
}
calendar.set(Calendar.MINUTE, 30);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
Date date1 = calendar.getTime();
calendar.add(Calendar.HOUR_OF_DAY, 12);
Date date2 = calendar.getTime();
Date date = DateUtil.toDate(new Date(), "yyyy-MM-dd");
List<DiamondWireRecord> diamondWireRecords = new ArrayList<>();
DiamondWireRecord diamondWireRecord;
String deviceNo;
List<EquipmentAccessory> equipmentAccessories = equipmentAccessoryService.findAll();
for (EquipmentAccessory equipmentAccessory : equipmentAccessories) {
//設備編號
deviceNo = equipmentAccessory.getDeviceNo();
diamondWireRecord = new DiamondWireRecord();
//記錄時間
diamondWireRecord.setCreateDate(date);
diamondWireRecord.setDeviceNo(deviceNo);
diamondWireRecords.add(diamondWireRecord);
}
System.out.println("update start");
if (diamondWireRecords.size() > 0) {
diamondWireRecordService.saveAll(diamondWireRecords);
}
System.out.println("update end");
}
saveAll方法沒有日志輸出
uj5u.com熱心網友回復:
你的這個aioCallMethod方法,沒有呼叫putDataSource。定時器應該這樣用
@Scheduled(cron = "0 0/1 * * * ?")
public void schedule() {
String dataSource = findDataSource();// 計算使用哪個DataSource
DynamicDataSourceHolder.putDataSource(dataSource);
try {
aioCallMethod();
} finally {
DynamicDataSourceHolder.removeDataSource();
}
}
uj5u.com熱心網友回復:
你的這個aioCallMethod方法,沒有呼叫putDataSource。
定時器應該這樣用
@Scheduled(cron = "0 0/1 * * * ?")
public void schedule() {
String dataSource = findDataSource();// 計算使用哪個DataSource
DynamicDataSourceHolder.putDataSource(dataSource);
try {
aioCallMethod();
} finally {
DynamicDataSourceHolder.removeDataSource();
}
}
改成這種了,結果沒變
DynamicDataSourceHolder.putDataSource("first");
try {
method();
} finally {
DynamicDataSourceHolder.removeDataSource();
}
uj5u.com熱心網友回復:
diamondWireRecordService.saveAll(diamondWireRecords);是怎么寫的呢
uj5u.com熱心網友回復:
diamondWireRecordService.saveAll(diamondWireRecords);
是怎么寫的呢
@Service
@Transactional
public class DiamondWireRecordServiceImpl extends BaseServiceImpl<DiamondWireRecord> implements DiamondWireRecordService {
DiamondWireRecordDao dao;
@Autowired
public void setDao(DiamondWireRecordDao dao) {
super.setBaseDao(dao);
this.dao = dao;
}
@Override
public void saveAll(List<DiamondWireRecord> diamondWireRecords) {
dao.saveAll(diamondWireRecords);
}
}
方法執行了
uj5u.com熱心網友回復:
現在問題已經不是多資料源的問題了。當前問題是SpringBoot事務沒有提交的問題
增加如下代碼
@Inject
private PlatformTransactionManager platformTransactionManager;
final TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult()
{
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
String dataSource = findDataSource();// 計算使用哪個DataSource
DynamicDataSourceHolder.putDataSource(dataSource);
try {
aioCallMethod();
} finally {
DynamicDataSourceHolder.removeDataSource();
}
}
});
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/134470.html
標籤:Java相關
上一篇:Docker中的SrpingBoot專案無法訪問redis
下一篇:SQLNestedException:Cannot create PoolableConnectionFactory(IO例外:Invalid connect
