Java中@DS+@Transactional注解切换数据源失效解决方案
背景
项目中使用了MySQL数据库,并按照功能模块采用了分库的策略。因此,一个业务逻辑类中可能涉及多个MySQL数据库的操作。
我们项目中是采用@DS("xxx")来实现数据源切换。
当注解添加到类上,意味着此类里的方法都使用此数据源; 当注解添加到方法上时,意味着此方法上使用的数据源优先级高于其他一切配置;
问题分析
代码
依赖
<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 #设置默认的数据源或者数据源组,默认值即为master strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源 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; /** * 创建时间 */ @TableField("created_time") private Date createdTime; /** * 创建人 */ @TableField("created_by") private Integer createdBy; /** * 创建人名称 */ @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> { }
业务代码
/** * @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注解时,可以正常切换数据源
[ { "id": 1, "language": "中文", "languageCode": "chinese", "createdTime": "2023-04-27T18:56:25.000+00:00", "createdBy": 1, "createdByName": "itender" }]
可以正常切换数据源。
当方法有@Transactional注解时,切换数据源失败
### 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声明式事务管理时通过动态代理实现的。
- @DS注解加到mapper接口、service接口、service方法里都不生效,获取的还是默认的主数据源。猜测是由于spring的aop切面机制导致拦截不到@DS注解,进而不能切换数据源,正确的做法是添加到service实现类或者实现类里具体的方法上。
- 在事务方法内调用@DS注解的方法,@DS注解同样不生效,原因是spring只能拦截到最外层方法的@Transactional注解,此时加载该事务的数据源,在事务方法内即使调用了@DS注解的方法,获取的是外层事务的数据源,导致@DS失效。
- 在同一个实现类中,一个非DS注解的常规方法里调用@DS注解的方法,同样存在@DS失效的情况,原因同2,是由spring的aop机制导致的,如果确有这种业务需要,可以将该DS注解方法定义在不同的类中,通过bean注入的方式调用,就不会出现这个问题。
解决方案
把查询user的逻辑放到另外一个单独的业务逻辑类里面
/** * @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业务类
@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(); }
测试成功插入一条数据。
总结
- spring 的@Transactional声明式事务管理时通过动态代理实现的。
- @DS注解加到mapper接口、service接口、service方法里都不生效,获取的还是默认的主数据源。猜测是由于spring的aop切面机制导致拦截不到@DS注解,进而不能切换数据源,正确的做法是添加到service实现类或者实现类里具体的方法上。
- 在事务方法内调用@DS注解的方法,@DS注解同样不生效,原因是spring只能拦截到最外层方法的@Transactional注解,此时加载该事务的数据源,在事务方法内即使调用了@DS注解的方法,获取的是外层事务的数据源,导致@DS失效。
- 在同一个实现类中,一个非DS注解的常规方法里调用@DS注解的方法,同样存在@DS失效的情况,原因同2,是由spring的aop机制导致的,如果确有这种业务需要,可以将该DS注解方法定义在不同的类中,通过bean注入的方式调用,就不会出现这个问题。
到此这篇关于Java中@DS+@Transactional注解切换数据源失效解决方案的文章就介绍到这了,更多相关@DS+@Transactional数据源失效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
教你利用springboot集成swagger并生成接口文档
有很多小伙伴不会利用springboot集成swagger并生成接口文档,今天特地整理了这篇文章,文中有非常详细的代码图文介绍及代码示例,对不会这个方法的小伙伴们很有帮助,需要的朋友可以参考下2021-05-05Java中forEach使用lambda表达式,数组和集合的区别说明
这篇文章主要介绍了Java中forEach使用lambda表达式,数组和集合的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-07-07
最新评论