Java中@DS+@Transactional注解切換數(shù)據(jù)源失效解決方案
背景
項(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)題分析
代碼
依賴
<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)建人名稱 */ @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è)務(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)題。
到此這篇關(guān)于Java中@DS+@Transactional注解切換數(shù)據(jù)源失效解決方案的文章就介紹到這了,更多相關(guān)@DS+@Transactional數(shù)據(jù)源失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于JSON實(shí)現(xiàn)傳輸byte數(shù)組過(guò)程解析
這篇文章主要介紹了基于JSON實(shí)現(xiàn)傳輸byte數(shù)組過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06簡(jiǎn)單談?wù)刯ava中final,finally,finalize的區(qū)別
Java中final、finally、finalize的區(qū)別與用法,困擾了不少學(xué)習(xí)者,下面我們就這個(gè)問(wèn)題進(jìn)行一些探討,希望對(duì)大家的學(xué)習(xí)有所幫助。2016-05-05java循環(huán)遍歷無(wú)規(guī)則json的方式:gson、fastjson、orgjson
這篇文章主要介紹了java循環(huán)遍歷無(wú)規(guī)則json的方式:gson、fastjson、orgjson,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08一學(xué)即會(huì)之JDK版本快速切換方法(2024)
這篇文章主要介紹了一學(xué)即會(huì)之JDK版本快速切換方法,詳細(xì)給大家講解了如何下載、安裝和配置多個(gè)JDK版本,并通過(guò)設(shè)置環(huán)境變量和編寫(xiě)批處理腳本來(lái)切換JDK版本,需要的朋友可以參考下2025-03-03JAVA Calendar類(lèi)使用方面實(shí)例
這篇文章主要介紹了JAVA Calendar類(lèi)使用方面實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04解決pageHelper分頁(yè)失效以及如何配置問(wèn)題
這篇文章主要介紹了解決pageHelper分頁(yè)失效以及如何配置問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04WebUploader+SpringMVC實(shí)現(xiàn)文件上傳功能
WebUploader是由Baidu團(tuán)隊(duì)開(kāi)發(fā)的一個(gè)簡(jiǎn)單的以HTML5為主,F(xiàn)LASH為輔的現(xiàn)代文件上傳組件。這篇文章主要介紹了WebUploader+SpringMVC實(shí)現(xiàn)文件上傳功能,需要的朋友可以參考下2017-06-06