首页 > 其他分享 >多数据源切换

多数据源切换

时间:2023-06-08 16:55:14浏览次数:35  
标签:return 数据源 private public 切换 order DataSource

多数据源解决方案

mybatis-plus动态切换数据源

  • 该方法简单便捷,直接通过注解@DS("xxx")就可以切换数据源
  • 但是这边官方建议只添加在方法上或类上,所以在同一个方法中只能使用一种数据源

依赖配置

1.添加依赖
    <dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
			<version>3.6.0</version>
		</dependency>
2.yml文件
spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/test?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8&serverTimezone=GMT
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
        slave_1:
          url: jdbc:mysql://xxxx:3306/test?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8&serverTimezone=GMT
          username: root
          password: root
          driver-class-name: com.mysql.jdbc.Driver

controller

package com.wpc.springbootdynamicsourceswtich.controller;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.wpc.springbootdynamicsourceswtich.service.IndexServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @ClassName IndexController
 * @Description TODO
 * @Author wpc
 * @Date 2023/6/7 14:06
 */
@RestController
@RequestMapping("/index")
public class IndexController {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private IndexServiceImpl indexService;


    @PostMapping("/master")
    public List selectAllByMaster(){
        return jdbcTemplate.queryForList("select * from sys_user");
    }

    @PostMapping("/slave")
    public List selectAllBySlaver(){
        List list = indexService.getList();
        List list2 = indexService.getList2();
        System.out.println(list);
        System.out.println(list2);
        return indexService.getList();
    }
}

mapper


@Mapper
public interface IndexMapper extends BaseMapper<SysUser> {
		//这里直接使用mybatis-plus的方法
}

service

@Service
public class IndexServiceImpl {

    @Autowired
    private IndexMapper indexMapper;

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @DS("slave_1")
    public List getList() {
        return indexMapper.selectList(null);
    }

    @DS("master")
    public List getList2() {
        return jdbcTemplate.queryForList("select * from sys_user");
    }
}

AbstractRoutingDataSource手动切换数据源

  • 在程序运行时通过AOP切面动态切换当前线程绑定的数据源对象,即数据库事物上下文来实现的

源码

@Nullable
private Map<Object, Object> targetDataSources;   // 存放所有数据源
@Nullable
private Object defaultTargetDataSource;     //存放默认数据源
@Nullable
private Map<Object, DataSource> resolvedDataSources;   //targetDataSources 数据源集合的解析后的key-value对象
@Nullable
private DataSource resolvedDefaultDataSource;   	// 解析后的默认数据源对象

//由于该类实现了InitializingBean,在bean属性初始化之后执行该方法,spring的扩展点
//解析targetDataSources放入resolvedDataSources
public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        } else {
            this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
            this.targetDataSources.forEach((key, value) -> {
                Object lookupKey = this.resolveSpecifiedLookupKey(key);
                DataSource dataSource = this.resolveSpecifiedDataSource(value);
                this.resolvedDataSources.put(lookupKey, dataSource);
            });
            if (this.defaultTargetDataSource != null) {
                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
            }

        }
    }

//this.determineTargetDataSource()获取数据源,然后不同的数据源获取自己的连接    
public Connection getConnection(String username, String password) throws SQLException {
        return this.determineTargetDataSource().getConnection(username, password);
}    
    
// 这里根据key值获取对应的数据源    
protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }

        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        } else {
            return dataSource;
        }
    }

// 重写该方法,设置数据源对应的key
@Nullable
protected abstract Object determineCurrentLookupKey();

配置文件

// 这里的连接参数必须要和自己选择的数据源连接对象一致,比如MysqlDatasource使用的时user  DruidDataSource使用的是username
spring:
  datasource:
    order:
      url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC
      user: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
    storage:
      url: jdbc:mysql://xxx:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false&serverTimezone=UTC
      user: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

启动类

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@MapperScan(basePackages = {"com.wpc.springbootmanuswitch.mapper"})
public class SpringbootManuSwitchApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootManuSwitchApplication.class, args);
    }

}

配置类

@Configuration
public class DataSourceProxyConfig {

    /**
     * 这里使用MysqlDataSource数据源则,yml里面的用户名为user,要与    protected String user = null; 对应
     * 使用 DruidDataSource 数据源 ,yml里面则使用username
     * @return
     */
    @Bean("originOrder")
    @ConfigurationProperties(prefix = "spring.datasource.order")
    public DataSource dataSourceMaster() {
        return new MysqlDataSource();
    }

    @Bean("originStorage")
    @ConfigurationProperties(prefix = "spring.datasource.storage")
    public DataSource dataSourceStorage() {
        return new MysqlDataSource();
    }


    @Bean(name = "order")
    public DataSource masterDataSourceProxy(@Qualifier("originOrder") DataSource dataSource) {
        return dataSource;
    }

    @Bean(name = "storage")
    public DataSource storageDataSourceProxy(@Qualifier("originStorage") DataSource dataSource) {
        return dataSource;
    }


    @Bean("dynamicDataSource")
    public DataSource dynamicDataSource(@Qualifier("order") DataSource order,
                                        @Qualifier("storage") DataSource storage) {

        DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();

        // 数据源的集合
        Map<Object, Object> dataSourceMap = new HashMap<>(3);
        dataSourceMap.put("order", order);
        dataSourceMap.put("storage", storage);

        dynamicRoutingDataSource.setDefaultTargetDataSource(order);
        dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);

        return dynamicRoutingDataSource;
    }

    @Bean
    @ConfigurationProperties(prefix = "mybatis")
    public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("dynamicDataSource") DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);

        org.apache.ibatis.session.Configuration configuration=new org.apache.ibatis.session.Configuration();
        //使用jdbc的getGeneratedKeys获取数据库自增主键值
        configuration.setUseGeneratedKeys(true);
        //使用列别名替换列名
        configuration.setUseColumnLabel(true);
        //自动使用驼峰命名属性映射字段,如userId ---> user_id
        configuration.setMapUnderscoreToCamelCase(true);
        sqlSessionFactoryBean.setConfiguration(configuration);

        return sqlSessionFactoryBean;
    }

}
public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }

}
@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        log.info("当前数据源 [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}

controller

@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @PostMapping("/createOrder")
    public String createOrder() throws Exception {
        Order order = new Order();
        order.setCount(1);
        order.setMoney(100);
        order.setUserId(10000l);
        order.setProductId(1l);
        orderService.saveOrder(order);
        return "ok";
    }
    
}

实体

@Data
public class Order {
    @TableField("id")
    private Long id;

    @TableField("userId")
    private Long userId;

    @TableField("productId")
    private Long productId;

    @TableField("count")
    private Integer count;

    @TableField("money")
    private Integer money;

    @TableField("status")
    private Integer status;
}
@Data
public class Storage {

    @TableField("id")
    private Long id;

    @TableField("productId")
    private Long productId;

    @TableField("count")
    private Integer count;
    
}

mapper

@Mapper
public interface OrderMapper {

    @Insert("INSERT INTO `order`(userId, productId, count, status, money) VALUES (#{userId}, #{productId}, #{count}, #{status}, #{money})")
    @Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
    int insert(Order record);

    /**
     * 更新订单状态
     * @param id
     * @param status
     * @return
     */
    @Update("UPDATE `order` SET status = #{status} WHERE id = #{id}")
    int updateOrderStatus(@Param("id") Long id, @Param("status") int status);
}
@Mapper
public interface StorageMapper {

    /**
     * 获取库存
     * @param productId 商品编号
     * @return
     */
    @Select("SELECT id,productId,count FROM storage WHERE productId = #{productId}")
    Storage findByProductId(@Param("productId") Long productId);

    /**
     * 扣减库存
     * @param productId 商品编号
     * @param count  要扣减的库存
     * @return
     */
    @Update("UPDATE storage SET count = count - #{count} WHERE productId = #{productId}")
    int reduceStorage(@Param("productId") Long productId, @Param("count") Integer count);
}

service

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private StorageService storageService;

    @Override
    @Transactional
    public void saveOrder(Order order) {

        log.info("=============切换数据源=================");
        DynamicDataSourceContextHolder.setDataSourceKey("order");


        log.info("=============插入订单=================");
        orderMapper.insert(order);

        log.info("=============扣减库存=================");
        storageService.reduce(order);

        log.info("=============更新订单状态=================");
        //切换数据源
        log.info("=============切换数据源=================");
        DynamicDataSourceContextHolder.setDataSourceKey("order");
        //更新订单
        Integer updateOrderRecord = orderMapper.updateOrderStatus(order.getId(),1);
        log.info("更新订单id:{} {}", order.getId(), updateOrderRecord > 0 ? "成功" : "失败");
    }
}

@Service
@Slf4j
public class StorageServiceImpl implements StorageService {

    @Autowired
    private StorageMapper storageMapper;
    @Override
    @Transactional
    public void reduce(Order order) {
        log.info("切换数据源");
        DynamicDataSourceContextHolder.setDataSourceKey("storage");

        log.info("检查库存");
        Storage storage = storageMapper.findByProductId(order.getProductId());
        if(storage == null){
            throw new RuntimeException("未找到该商品库存");
        }
        if (storage.getCount() < order.getCount()) {
            log.warn("{} 库存不足,当前库存:{}", order.getProductId(), order.getCount());
            throw new RuntimeException("库存不足");
        }
        log.info("开始扣减库存");
        storageMapper.reduceStorage(order.getProductId(), order.getCount());

    }
}

标签:return,数据源,private,public,切换,order,DataSource
From: https://www.cnblogs.com/wangpc/p/17467010.html

相关文章

  • 使用ActivityOptions做Activity切换动画
    不知道大家有没有注意到startActivity(Intent,Bundle),那么ActivityOptions就是这个Bundle的原型,负责Activity跳转时的动画。publicvoidonClick(Viewview){Intentintent=newIntent(this,SecondActivity.class);ActivityOptionsoptions=Act......
  • 小程序实现滚动快递列表时快递分类字母跟随切换
    场景:快递列表是按照开头字母来分类的,我们可以做到滚动快递列表到下一个字母时,分类字母自动切换。商城的分类,例如很多时候滚动分类列表时,分类标题也会跟着切换到当前分类。实现思路先来看看下面简单的demo在开发者工具中预览效果 思路:页面时由两个scroll-view容器......
  • Kali安装JDK8以及JDK11、JDK17切换
    声明:本文分享的安全工具和项目均来源于网络,仅供安全研究与学习之用,如用于其他用途,由使用者承担全部法律及连带责任,与工具作者和本公众号无关。 瓜神学习网络安全公众号   背景 很久之前更新了一次kali,今天用的时候发现JDK变成17了 安装J......
  • KingbaseES数据库配置Hikari数据源
     Hikari是一个高性能的数据库连接池,它是SpringBoot2.x中的默认数据源。一、下载驱动打开下面网址:选择对应平台的jdbc驱动程序。人大金仓-成为世界卓越的数据库产品与服务提供商(kingbase.com.cn)这里以x86平台为例:下载完成后目录里面包含以下文件:根据项目的JDK版本选择......
  • uniapp主题切换功能的方式终结篇(全平台兼容)
    前面我已经给大家介绍了两种主题切换的方式,每种方式各有自己的优势与缺点,例如“scss变量+vuex”方式兼容好但不好维护与扩展,“scss变量+require”方式好维护但兼容不好,还不清楚的可点下面链接直达了解一下uniapp主题切换功能的第一种实现方式(scss变量+vuex)uniapp主题切换功能的......
  • 多线程中的上下文切换
    我们都知道,在并发编程中,并不是线程越多就效率越高,线程数太少可能导致资源不能充分利用,线程数太多可能导致竞争资源激烈,然后上下文切换频繁造成系统的额外开销。大量的超时报警,通过工具分析,cs指标很高,然后分析日志,发现有大量wait()相关的Exception,这个时候我们怀疑是在多线程并发处......
  • CS5290兼容CS5230防破音AB/D切换,5.2W单声道GF类音频功放IC
    CS5290E是一款采用CMOS工艺,电容式升压型GF类单声道音频功放,可以为4Q的负载提供最高5.2W的连续功率;CS5290E芯片内部固定的28倍增益,有效的减少了外围元器件的数量;功放集成了D类和AB类两种工作模式即可保证D类模式下强劲的功率输出,又可兼顾系统在有FM的情况下,消除功放对系统的干扰;CS52......
  • mmm手动切换IP,切换角色
    //mmm手动切换IP,切换角色//mmm切换IP[root@home-db4~]#mmm_controlset_passive [root@home-db4~]#mmm_controlset_ip10.200.3.119db1//mmm切换角色[root@home-db4~]#mmm_controlset_active[root@home-db4~]#mmm_controlmove_rolewriterdb1[root@home-db4~]......
  • uniapp主题切换功能的第一种实现方式(scss变量+vuex)
    随着用户端体验的不断提升,很多应用在上线的时候都要求做不同的主题,最基本的就是白天与夜间主题。就像b站app主题切换,像这样的uniapp因为能轻松实现多端发布而得到很多开发者的青睐,但每个端的实现也有可能不同,现我把已实现的功能一点点的大家分享给大家,须要的可以参考一下,可......
  • 在centos7升级nodejs存在的无法切换版本的问题解决
    1.安装n管理工具npminstall-gn安装最新版本nlatest安装指定版本 n8.11.3 2.切换nodejs版本n选择已安装的版本 ο node/8.11.3  node/10.4.1查看当前版本node-v,下面表示已切换成功v8.13.3但问题来了,切换后,查看版本还是原来的v6.13.3,看下面 使用n切换nodejs......