Java中@DS+@Transactional注解切換數(shù)據(jù)源失效解決方案
背景
項目中使用了MySQL數(shù)據(jù)庫,并按照功能模塊采用了分庫的策略。因此,一個業(yè)務邏輯類中可能涉及多個MySQL數(shù)據(jù)庫的操作。
我們項目中是采用@DS("xxx")來實現(xiàn)數(shù)據(jù)源切換。
當注解添加到類上,意味著此類里的方法都使用此數(shù)據(jù)源; 當注解添加到方法上時,意味著此方法上使用的數(shù)據(jù)源優(yōu)先級高于其他一切配置;
問題分析
代碼
依賴
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>yml配置
spring:
datasource:
dynamic:
primary: master #設置默認的數(shù)據(jù)源或者數(shù)據(jù)源組,默認值即為master
strict: false #嚴格匹配數(shù)據(jù)源,默認false. true未匹配到指定數(shù)據(jù)源時拋異常,false使用默認數(shù)據(jù)源
datasource:
master:
url: jdbc:mysql://localhost:3306/demo_01?useSSL=false&autoReconnect=true&characterEncoding=utf8
username: root
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0開始支持SPI可省略此配置
slave:
url: jdbc:mysql://172.23.168.70:3306/dynamic?useSSL=false&autoReconnect=true&characterEncoding=utf8
username: root
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver對象實體
/**
* @author itender
* @date 2023/4/28 11:01
* @desc
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_dynamic_template")
public class DynamicTemplateEntity {
?
@TableId(type = IdType.AUTO)
private Integer id;
?
/**
* 語言
*/
private String language;
?
/**
* 語言編碼
*/
@TableField("language_code")
private String languageCode;
?
/**
* 創(chuàng)建時間
*/
@TableField("created_time")
private Date createdTime;
?
/**
* 創(chuàng)建人
*/
@TableField("created_by")
private Integer createdBy;
?
/**
* 創(chuàng)建人名稱
*/
@TableField("created_by_name")
private String createdByName;
}/**
* @author itender
* @date 2023/4/28 10:57
* @desc
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@TableName("t_user")
public class UserEntity {
?
/**
* 主鍵id
*/
@TableId(type = IdType.AUTO)
private Integer id;
?
/**
* 用戶名稱
*/
private String username;
}
?controller代碼
/**
* @author itender
* @date 2023/4/28 10:34
* @desc
*/
@RestController
@RequestMapping("template")
public class DynamicTemplateController {
?
private final DynamicTemplateService dynamicTemplateService;
?
@Autowired
public DynamicTemplateController(DynamicTemplateService dynamicTemplateService) {
this.dynamicTemplateService = dynamicTemplateService;
}
?
@GetMapping
public List<DynamicTemplateEntity> list() {
return dynamicTemplateService.list();
}
?
@PostMapping
public Integer add(@RequestBody DynamicTemplateEntity template) {
return dynamicTemplateService.add(template);
}
}service
/**
* @author itender
* @date 2023/4/28 10:36
* @desc
*/
public interface DynamicTemplateService {
?
/**
* 查詢模板集合
*
* @return
*/
List<DynamicTemplateEntity> list();
?
/**
* 添加模板
*
* @param template
* @return
*/
Integer add(DynamicTemplateEntity template);
}mapper
/**
* @author itender
* @date 2023/4/28 11:09
* @desc
*/
@DS("slave")
@Mapper
@Repository
public interface DynamicTemplateMapper extends BaseMapper<DynamicTemplateEntity> {
}/**
* @author itender
* @date 2023/4/28 11:08
* @desc
*/
@Mapper
@Repository
@DS("master")
public interface UserMapper extends BaseMapper<UserEntity> {
}業(yè)務代碼
/**
?* @author itender
?* @date 2023/4/28 11:15
?* @desc
?*/
@Service
public class DynamicTemplateServiceImpl implements DynamicTemplateService {
? ? private final DynamicTemplateMapper dynamicTemplateMapper;
? ? private final UserMapper userMapper;
? ? private final UserService userService;
? ? @Autowired
? ? public DynamicTemplateServiceImpl(DynamicTemplateMapper dynamicTemplateMapper, UserMapper userMapper, UserService userService) {
? ? ? ? this.dynamicTemplateMapper = dynamicTemplateMapper;
? ? ? ? this.userMapper = userMapper;
? ? ? ? this.userService = userService;
? ? }
? ? @Override
? ? public List<DynamicTemplateEntity> list() {
? ? ? ? List<DynamicTemplateEntity> templateList = dynamicTemplateMapper.selectList(new QueryWrapper<>());
? ? ? ? if (CollectionUtils.isEmpty(templateList)) {
? ? ? ? ? ? return Lists.newArrayList();
? ? ? ? }
? ? ? ? List<UserEntity> userList = userMapper.selectList(new QueryWrapper<>());
? ? ? ? if (CollectionUtils.isEmpty(userList)) {
? ? ? ? ? ? return templateList;
? ? ? ? }
? ? ? ? Map<Integer, String> userMap = userList.stream().collect(Collectors.toMap(UserEntity::getId, UserEntity::getUsername, (key1, key2) -> key1));
? ? ? ? templateList.forEach(template -> template.setCreatedByName(userMap.get(template.getCreatedBy())));
? ? ? ? return templateList;
? ? }
? ? @Transactional(rollbackFor = Exception.class)
? ? @Override
? ? public Integer add(DynamicTemplateEntity template) {
? ? ? ? List<UserEntity> userList = userMapper.selectList(new QueryWrapper<>());
? ? ? ? if (CollectionUtils.isEmpty(userList)) {
? ? ? ? ? ? template.setCreatedByName("");
? ? ? ? }
? ? ? ? Map<Integer, String> userMap = userList.stream().collect(Collectors.toMap(UserEntity::getId, UserEntity::getUsername, (key1, key2) -> key1));
? ? ? ? template.setCreatedByName(userMap.get(template.getCreatedBy()));
? ? ? ? template.setCreatedTime(new Date());
? ? ? ? dynamicTemplateMapper.insert(template);
? ? ? ? return template.getId();
? ? }
}測試
當方法沒有@Transactional注解時,可以正常切換數(shù)據(jù)源
[ { "id": 1, "language": "中文", "languageCode": "chinese", "createdTime": "2023-04-27T18:56:25.000+00:00", "createdBy": 1, "createdByName": "itender" }]
可以正常切換數(shù)據(jù)源。
當方法有@Transactional注解時,切換數(shù)據(jù)源失敗
### Error updating database. Cause: java.sql.SQLSyntaxErrorException: Table 'demo_01.t_dynamic_template' doesn't exist
### The error may exist in com/itender/threadpool/mapper/DynamicTemplateMapper.java (best guess)
### The error may involve com.itender.threadpool.mapper.DynamicTemplateMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO t_dynamic_template ( language, language_code, created_time, created_by, created_by_name ) VALUES ( ?, ?, ?, ?, ? )
### Cause: java.sql.SQLSyntaxErrorException: Table 'demo_01.t_dynamic_template' doesn't exist
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Table 'demo_01.t_dynamic_template' doesn't exist] with root cause
java.sql.SQLSyntaxErrorException: Table 'demo_01.t_dynamic_template' doesn't exist
分析
- spring 的@Transactional聲明式事務管理時通過動態(tài)代理實現(xiàn)的。
- @DS注解加到mapper接口、service接口、service方法里都不生效,獲取的還是默認的主數(shù)據(jù)源。猜測是由于spring的aop切面機制導致攔截不到@DS注解,進而不能切換數(shù)據(jù)源,正確的做法是添加到service實現(xiàn)類或者實現(xiàn)類里具體的方法上。
- 在事務方法內(nèi)調(diào)用@DS注解的方法,@DS注解同樣不生效,原因是spring只能攔截到最外層方法的@Transactional注解,此時加載該事務的數(shù)據(jù)源,在事務方法內(nèi)即使調(diào)用了@DS注解的方法,獲取的是外層事務的數(shù)據(jù)源,導致@DS失效。
- 在同一個實現(xiàn)類中,一個非DS注解的常規(guī)方法里調(diào)用@DS注解的方法,同樣存在@DS失效的情況,原因同2,是由spring的aop機制導致的,如果確有這種業(yè)務需要,可以將該DS注解方法定義在不同的類中,通過bean注入的方式調(diào)用,就不會出現(xiàn)這個問題。
解決方案
把查詢user的邏輯放到另外一個單獨的業(yè)務邏輯類里面
/**
?* @author itender
?* @date 2023/4/28 14:25
?* @desc
?*/
public interface UserService {
? ? /**
? ? ?* 查詢用戶集合
? ? ?*
? ? ?* @return
? ? ?*/
? ? List<UserEntity> list();
}/**
?* @author itender
?* @date 2023/4/28 14:27
?* @desc
?*/
@Service
public class UserServiceImpl implements UserService {
? ? private final UserMapper userMapper;
? ? @Autowired
? ? public UserServiceImpl(UserMapper userMapper) {
? ? ? ? this.userMapper = userMapper;
? ? }
? ? @DS("master")
? ? @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
? ? @Override
? ? public List<UserEntity> list() {
? ? ? ? return userMapper.selectList(new QueryWrapper<>());
? ? }
}修改template業(yè)務類
@DS("slave")
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
@Override
public Integer add(DynamicTemplateEntity template) {
// List<UserEntity> userList = userMapper.selectList(new QueryWrapper<>());
List<UserEntity> userList = userService.list();
if (CollectionUtils.isEmpty(userList)) {
template.setCreatedByName("");
}
Map<Integer, String> userMap = userList.stream().collect(Collectors.toMap(UserEntity::getId, UserEntity::getUsername, (key1, key2) -> key1));
template.setCreatedByName(userMap.get(template.getCreatedBy()));
template.setCreatedTime(new Date());
dynamicTemplateMapper.insert(template);
return template.getId();
}測試成功插入一條數(shù)據(jù)。
總結
- spring 的@Transactional聲明式事務管理時通過動態(tài)代理實現(xiàn)的。
- @DS注解加到mapper接口、service接口、service方法里都不生效,獲取的還是默認的主數(shù)據(jù)源。猜測是由于spring的aop切面機制導致攔截不到@DS注解,進而不能切換數(shù)據(jù)源,正確的做法是添加到service實現(xiàn)類或者實現(xiàn)類里具體的方法上。
- 在事務方法內(nèi)調(diào)用@DS注解的方法,@DS注解同樣不生效,原因是spring只能攔截到最外層方法的@Transactional注解,此時加載該事務的數(shù)據(jù)源,在事務方法內(nèi)即使調(diào)用了@DS注解的方法,獲取的是外層事務的數(shù)據(jù)源,導致@DS失效。
- 在同一個實現(xiàn)類中,一個非DS注解的常規(guī)方法里調(diào)用@DS注解的方法,同樣存在@DS失效的情況,原因同2,是由spring的aop機制導致的,如果確有這種業(yè)務需要,可以將該DS注解方法定義在不同的類中,通過bean注入的方式調(diào)用,就不會出現(xiàn)這個問題。
到此這篇關于Java中@DS+@Transactional注解切換數(shù)據(jù)源失效解決方案的文章就介紹到這了,更多相關@DS+@Transactional數(shù)據(jù)源失效內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
springboot集成WebSockets廣播消息(推薦)
這篇文章主要介紹了springboot-集成WebSockets廣播消息,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-12-12
Spring Gateway集成 Nacos注冊中心不能夠發(fā)現(xiàn)服務的解決方案
文章描述了在將Eureka替換為Nacos后,Spring Cloud Gateway在調(diào)用Nacos注冊的服務時出現(xiàn)問題,通過調(diào)試和分析,發(fā)現(xiàn)Spring Cloud Gateway在初始化時沒有正確加載Nacos的ReactiveDiscoveryClient,導致服務調(diào)用失敗,感興趣的朋友跟隨小編一起看看吧2026-01-01
Java實現(xiàn)泡泡堂對戰(zhàn)版游戲的示例代碼
本文將利用Java制作經(jīng)典游戲《泡泡堂》,文中使用了MVC模式,分離了模型、視圖和控制器,使得項目結構清晰易于擴展,感興趣的可以了解一下2022-04-04
解決微服務下Mybatis?xml無效綁定問題及分析Invalid?bound?statement
這篇文章主要介紹了解決微服務下Mybatis?xml無效綁定問題及分析Invalid?bound?statement,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
JVM線上調(diào)優(yōu)參數(shù)配置實踐完全指南
JVM調(diào)優(yōu)的基本思路涉及多個方面,旨在提升Java應用的性能、穩(wěn)定性和響應速度,下面這篇文章主要介紹了JVM線上調(diào)優(yōu)參數(shù)配置的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2025-08-08
Spring boot使用logback實現(xiàn)日志管理過程詳解
這篇文章主要介紹了Spring boot使用logback實現(xiàn)日志管理過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-06-06

