Java中@DS+@Transactional注解切换数据源失效解决方案

 更新时间:2023年06月11日 08:56:12   作者:ITender  
本文主要介绍了@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集成WebSockets广播消息(推荐)

    springboot集成WebSockets广播消息(推荐)

    这篇文章主要介绍了springboot-集成WebSockets广播消息,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-12-12
  • Spring Gateway集成 Nacos注册中心不能够发现服务的解决方案

    Spring Gateway集成 Nacos注册中心不能够发现服务的解决方案

    文章描述了在将Eureka替换为Nacos后,Spring Cloud Gateway在调用Nacos注册的服务时出现问题,通过调试和分析,发现Spring Cloud Gateway在初始化时没有正确加载Nacos的ReactiveDiscoveryClient,导致服务调用失败,感兴趣的朋友跟随小编一起看看吧
    2026-01-01
  • SpringBoot生成条形码的方案详解

    SpringBoot生成条形码的方案详解

    在Spring Boot, Spring Cloud 项目中整合ZXing库来生成条形码在特定行业也是一个常见需求,ZXing是google开源的一个功能强大的Java库,专门用于二维码/条形码等的生成与解析,所以本文给大家介绍了SpringBoot生成条形码的方案,需要的朋友可以参考下
    2024-08-08
  • Java实现泡泡堂对战版游戏的示例代码

    Java实现泡泡堂对战版游戏的示例代码

    本文将利用Java制作经典游戏《泡泡堂》,文中使用了MVC模式,分离了模型、视图和控制器,使得项目结构清晰易于扩展,感兴趣的可以了解一下
    2022-04-04
  • 解决微服务下Mybatis xml无效绑定问题及分析Invalid bound statement

    解决微服务下Mybatis xml无效绑定问题及分析Invalid bound statement

    这篇文章主要介绍了解决微服务下Mybatis xml无效绑定问题及分析Invalid bound statement,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • JVM线上调优参数配置实践完全指南

    JVM线上调优参数配置实践完全指南

    JVM调优的基本思路涉及多个方面,旨在提升Java应用的性能、稳定性和响应速度,下面这篇文章主要介绍了JVM线上调优参数配置的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-08-08
  • Java中MyBatis Plus知识点总结

    Java中MyBatis Plus知识点总结

    在本篇文章里小编给大家整理一篇关于Java中MyBatis Plus知识点总结,需要的朋友们参考下。
    2019-10-10
  • Java 新特性之Option示例详解

    Java 新特性之Option示例详解

    使用Optional开发时要注意正确使用Optional的“姿势”,特别注意不要使用3.2节提到的错误示范,谨慎使用isPresent()和get()方法,尽量多使用map()、filter()、orElse()等方法来发挥Optional的作用,对Java  Option相关知识感兴趣的朋友一起看看吧
    2024-02-02
  • Spring boot使用logback实现日志管理过程详解

    Spring boot使用logback实现日志管理过程详解

    这篇文章主要介绍了Spring boot使用logback实现日志管理过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • 详解JDBC对Mysql utf8mb4字符集的处理

    详解JDBC对Mysql utf8mb4字符集的处理

    这篇文章主要介绍了详解JDBC对Mysql utf8mb4字符集的处理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-11-11

最新评论