@DS+@Transactional注解切換數(shù)據(jù)源失效問(wèn)題及解決
背景
項(xiàng)目中使用了MySQL數(shù)據(jù)庫(kù),并按照功能模塊采用了分庫(kù)的策略。因此,一個(gè)業(yè)務(wù)邏輯類(lèi)中可能涉及多個(gè)MySQL數(shù)據(jù)庫(kù)的操作。
我們項(xiàng)目中是采用@DS(“xxx”)來(lái)實(shí)現(xiàn)數(shù)據(jù)源切換。
- 當(dāng)注解添加到類(lèi)上,意味著此類(lèi)里的方法都使用此數(shù)據(jù)源;
- 當(dāng)注解添加到方法上時(shí),意味著此方法上使用的數(shù)據(jù)源優(yōu)先級(jí)高于其他一切配置;
問(wèn)題分析
代碼
- 依賴(lài)
<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è)置默認(rèn)的數(shù)據(jù)源或者數(shù)據(jù)源組,默認(rèn)值即為master
strict: false #嚴(yán)格匹配數(shù)據(jù)源,默認(rèn)false. true未匹配到指定數(shù)據(jù)源時(shí)拋異常,false使用默認(rèn)數(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開(kāi)始支持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- 對(duì)象實(shí)體
/**
* @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;
/**
* 語(yǔ)言
*/
private String language;
/**
* 語(yǔ)言編碼
*/
@TableField("language_code")
private String languageCode;
/**
* 創(chuàng)建時(shí)間
*/
@TableField("created_time")
private Date createdTime;
/**
* 創(chuàng)建人
*/
@TableField("created_by")
private Integer createdBy;
/**
* 創(chuàng)建人名稱(chē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;
/**
* 用戶名稱(chēng)
*/
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è)務(wù)代碼
/**
* @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();
}
}測(cè)試
- 當(dāng)方法沒(méi)有@Transactional注解時(shí),可以正常切換數(shù)據(jù)源
[
{
"id": 1,
"language": "中文",
"languageCode": "chinese",
"createdTime": "2023-04-27T18:56:25.000+00:00",
"createdBy": 1,
"createdByName": "itender"
}
]可以正常切換數(shù)據(jù)源。
- 當(dāng)方法有@Transactional注解時(shí),切換數(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聲明式事務(wù)管理時(shí)通過(guò)動(dòng)態(tài)代理實(shí)現(xiàn)的。
- @DS注解加到mapper接口、service接口、service方法里都不生效,獲取的還是默認(rèn)的主數(shù)據(jù)源。猜測(cè)是由于spring的aop切面機(jī)制導(dǎo)致攔截不到@DS注解,進(jìn)而不能切換數(shù)據(jù)源,正確的做法是添加到service實(shí)現(xiàn)類(lèi)或者實(shí)現(xiàn)類(lèi)里具體的方法上。
- 在事務(wù)方法內(nèi)調(diào)用@DS注解的方法,@DS注解同樣不生效,原因是spring只能攔截到最外層方法的@Transactional注解,此時(shí)加載該事務(wù)的數(shù)據(jù)源,在事務(wù)方法內(nèi)即使調(diào)用了@DS注解的方法,獲取的是外層事務(wù)的數(shù)據(jù)源,導(dǎo)致@DS失效。
- 在同一個(gè)實(shí)現(xiàn)類(lèi)中,一個(gè)非DS注解的常規(guī)方法里調(diào)用@DS注解的方法,同樣存在@DS失效的情況,原因同2,是由spring的aop機(jī)制導(dǎo)致的,如果確有這種業(yè)務(wù)需要,可以將該DS注解方法定義在不同的類(lèi)中,通過(guò)bean注入的方式調(diào)用,就不會(huì)出現(xiàn)這個(gè)問(wèn)題。
解決方案
- 把查詢user的邏輯放到另外一個(gè)單獨(dú)的業(yè)務(wù)邏輯類(lèi)里面
/**
* @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è)務(wù)類(lèi)
@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();
}測(cè)試成功插入一條數(shù)據(jù)。
總結(jié)
spring 的@Transactional聲明式事務(wù)管理時(shí)通過(guò)動(dòng)態(tài)代理實(shí)現(xiàn)的。
@DS注解加到mapper接口、service接口、service方法里都不生效,獲取的還是默認(rèn)的主數(shù)據(jù)源。猜測(cè)是由于spring的aop切面機(jī)制導(dǎo)致攔截不到@DS注解,進(jìn)而不能切換數(shù)據(jù)源,正確的做法是添加到service實(shí)現(xiàn)類(lèi)或者實(shí)現(xiàn)類(lèi)里具體的方法上。
在事務(wù)方法內(nèi)調(diào)用@DS注解的方法,@DS注解同樣不生效,原因是spring只能攔截到最外層方法的@Transactional注解,此時(shí)加載該事務(wù)的數(shù)據(jù)源,在事務(wù)方法內(nèi)即使調(diào)用了@DS注解的方法,獲取的是外層事務(wù)的數(shù)據(jù)源,導(dǎo)致@DS失效。
在同一個(gè)實(shí)現(xiàn)類(lèi)中,一個(gè)非DS注解的常規(guī)方法里調(diào)用@DS注解的方法,同樣存在@DS失效的情況,原因同2,是由spring的aop機(jī)制導(dǎo)致的,如果確有這種業(yè)務(wù)需要,可以將該DS注解方法定義在不同的類(lèi)中,通過(guò)bean注入的方式調(diào)用,就不會(huì)出現(xiàn)這個(gè)問(wèn)題。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
mybatis-flex實(shí)現(xiàn)多數(shù)據(jù)源操作
MyBaits-Flex內(nèi)置了功能完善的多數(shù)據(jù)源支持,本文主要介紹了mybatis-flex實(shí)現(xiàn)多數(shù)據(jù)源操作,具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06
java啟動(dòng)如何設(shè)置JAR包內(nèi)存大小
這篇文章主要介紹了java啟動(dòng)如何設(shè)置JAR包內(nèi)存大小問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
IDEA出現(xiàn)jdk版本相關(guān)問(wèn)題后的版本檢查及切換方式
文章指出Spring Boot項(xiàng)目中因spring-boot-starter-parent默認(rèn)使用Java 1.6導(dǎo)致編譯錯(cuò)誤,需檢查項(xiàng)目SDK、模塊Sources及Maven配置的語(yǔ)言級(jí)別,確保均設(shè)置為1.8以解決版本沖突問(wèn)題2025-09-09
Spring?MVC文件請(qǐng)求處理MultipartResolver詳解
這篇文章主要介紹了Spring?MVC文件請(qǐng)求處理詳解:MultipartResolver,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-11-11
intellij idea創(chuàng)建第一個(gè)動(dòng)態(tài)web項(xiàng)目的步驟方法
這篇文章主要介紹了intellij idea創(chuàng)建第一個(gè)動(dòng)態(tài)web項(xiàng)目的步驟方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10
SSM如何實(shí)現(xiàn)在Controller中添加事務(wù)管理
這篇文章主要介紹了SSM如何實(shí)現(xiàn)在Controller中添加事務(wù)管理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
詳解如何在spring boot中使用spring security防止CSRF攻擊
這篇文章主要介紹了詳解如何在spring boot中使用spring security防止CSRF攻擊,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05

