精华内容
下载资源
问答
  • 动态数据源
    千次阅读
    2022-04-08 15:32:38

    遇到一个问题

    配置动态数据源之后,同一个请求,不同事务无法多次切换数据源

    解决方案:spring.jpa.open-in-view=false

    原因:spring.jpa.open-in-view(默认为true),JPA方法执行完成之后,并没有释放数据库连接(需要等到Controller方法执行完成才会释放)

    这里还有一点要注意数据源的切换是通过AbstractRoutingDataSource 的 determineCurrentLookupKey()实现,数据库建立连接时getConnection才会触发此方法,所以同一个事务中应该是触发不了多次的。

    determineCurrentLookupKey()无数据源时,默认使用主数据源(MASTER),所以多数据源的实现方式可以为,切换其它数据源执行后释放,系统会继续使用主数据源

    更多相关内容
  • Mybatis动态数据源

    2018-12-20 09:08:42
    读写库分离,动态数据源切换,mybitis动态数据源,SSM动态数据源,多数据源
  • 创建动态数据源2.把动态数据源植入MyBatis3. 如何使用 前言 项目背景:我们做的是一个分布式数据库运维平台,项目会配置自己的数据源,同时因为是数据库运维平台,可以动态接入新的数据库集群,需要到这些集群节点...

    项目背景

    1. 原有实现方案

    我们做的是一个分布式数据库运维平台,项目会配置自己的数据源,同时因为是数据库运维平台,可以动态接入新的数据库集群,需要到这些集群节点系统表查询一些运维数据。接手项目前,他们的做法是手动创建JDBC连接,拼接sql语句去查询。
    实现如下:

    			MgrHost host = new MgrHost();
                StringBuilder querySql = new StringBuilder();
                querySql.append("select t.hostaddr, t.hostagentport");
                querySql.append(" from mgr_host t");
                querySql.append(" where t.oid = ").append(node.getNodehost());
                try (Connection conn = clusterConnService.createSpecifiedClusterConn(clusterInfo);
                     Statement statement = conn.createStatement();
                     ResultSet resultSet = statement.executeQuery(querySql.toString())) {
                    //获取连接
                    while (resultSet.next()) {
                        host.setHostaddr(resultSet.getString(1));
                        host.setHostagentport(resultSet.getInt(2));
                    }
                } catch (Exception e) {
                    // 查询节点失败
                    logger.error("query mgr_host failed", e);
                    throw new com.ai.dbops.api.commons.DBOpsException("query mgr_host failed");
                }
    
    public Connection createSpecifiedClusterConn(ClusterInfo clusterInfo) throws ClassNotFoundException, SQLException {
            logger.debug("start create specified connection");
            String driverClassName = "org.postgresql.Driver";
            String url = "jdbc:postgresql://" + clusterInfo.getMgrAddr() + "/"
                    + antDbConfig.getNodeConnectDatabase() + "?useUnicode=true";
    
            Class.forName(driverClassName);
            Connection conn = DriverManager.getConnection(url, clusterInfo.getClusterConnInfo().getDbUser(), clusterInfo.getClusterConnInfo().getDbPasswd());
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("set command_mode to sql");
            } catch (Exception e) {
                try {
                    if (conn != null) {
                        conn.close();
                    }
                } catch (Exception e1) {
                    logger.error(ExceptionUtils.getStackTrace(e1));
                }
            }
            return conn;
        }
    

    2. 这样实现的弊端

    1. 手动创建jdbc连接,繁琐复杂,忘记关闭连接还可能造成数据库连接滥用
    2. Sql语句都是在代码里硬编码,影响代码整洁可读性
    3. 可能会无限创建连接,造成数据库连接过多;每次都需要新建连接,连接耗时影响性能。

    针对以上问题,解决方案是利用数据源来管理连接,可解决1 3两个问题;通过实现spring提供的AbstractRoutingDataSource接口配置动态数据源,并植入MyBatis,在mapper写sql ,可以解决问题2。

    解决方案

    1.创建动态数据源

    @Component
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Resource(name = "dataSource")
        private DataSource sqlModeDataSource;
    
        //1.首先自定义DynamicDataSourceContextHolder来持有dataSource的key,
        //这边是ThreadLocal实现,多线程线程隔离的,下面贴实现。
        //2.查看AbstractRoutingDataSource源码, getConnection()方法会通过这个方法决定获取哪个数据源,这个方法是动态数据源的关键
        @Override
        public Object determineCurrentLookupKey() {
            return DynamicDataSourceContextHolder.getDataSourceKey();
        }
    
    	//这里是系统默认数据源 初始化时注入进来 
        @PostConstruct
        public void initDefaultDataSource() {
            log.info("dynamicDataSource postConstruct ...");
            Map<Object, Object> targetDataSource = new HashMap<>();
            targetDataSource.put(DynamicDataSourceContextHolder.DEFAULT_DADA_SOURCE, sqlModeDataSource);
            this.setTargetDataSources(targetDataSource);
        }
    
        //系统动态添加数据源进来,通过这个方法,并缓存对应的key
        public void addDataSource(String key, DataSource dataSource) {
            Map<Object, Object> targetDataSources = new HashMap<>(this.getResolvedDataSources());
            targetDataSources.put(key, dataSource);
            this.setTargetDataSources(targetDataSources);
    
            DynamicDataSourceContextHolder.addDataSourceKey(key);
            this.afterPropertiesSet();
        }
    }
    

    DynamicDataSourceContextHolder实现,很简单,通过ThreadLocal实现

    public class DynamicDataSourceContextHolder {
        
        private static final ThreadLocal<String> contextHolder = ThreadLocal.withInitial(() -> DEFAULT_DATA_SOURCE);
    
        /**
         * 数据源的 key集合,用于切换时判断数据源是否存在
         */
        private static final CopyOnWriteArrayList<Object> dataSourceKeys = new CopyOnWriteArrayList<>();
    
        /**
         * 切换数据源
         *
         * @param key key
         */
        public static void setDataSourceKey(String key) {
            contextHolder.set(key);
        }
    
        /**
         * 获取数据源
         *
         * @return key
         */
        public static String getDataSourceKey() {
            return contextHolder.get();
        }
    
        /**
         * 重置数据源
         */
        public static void clearDataSourceKey() {
            contextHolder.remove();
        }
    
        /**
         * 判断是否包含数据源
         *
         * @param key 数据源key
         * @return true
         */
        public static boolean containsDataSourceKey(String key) {
            return dataSourceKeys.contains(key);
        }
    
        /**
         * 添加数据源keys
         *
         * @param keys keys
         * @return true
         */
        public static boolean addDataSourceKeys(Collection<String> keys) {
            return dataSourceKeys.addAll(keys);
        }
    
        public static void addDataSourceKey(String key) {
            dataSourceKeys.add(key);
        }
    }
    

    2.把动态数据源植入MyBatis

    这边就是MyBatis的配置,把sqlSessionFactory的数据源配置成动态数据源

    @Bean("sqlSessionFactory")
            public SqlSessionFactory antdbSqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource)
                    throws Exception {
                    }
    

    3. 如何使用

    1. 因为DynamicDataSource我们把项目应用数据库配成了默认,所以我们项目中访问默认数据库的操作,如往常一样,不需要额外的操作
    2. 需要访问我们后续在应用中动态添加进来的其他数据库集群时,只需要通过如下的工具类,把mapper的访问放到DataSourceUtils工具里即可
      示例
       ClusterInfo clusterInfo = new ClusterInfo();
       clusterInfo.setClusterId(111);
       XXXNode node = DataSourceUtils.execute(clusterInfo, () -> {
       		xxxMapper.queryNodes(param1, parma2);
       })
    

    下面看一下DataSourceUtils的实现,分四步:

    1. 根据ClusterInfo去查看是否已缓存这个数据源,没有就创建新的,并加入到我们之前创建的动态数据源缓存
    2. 利用DynamicDataSourceContextHolder.setDataSourceKey切换数据源
    3. sql语句的执行
    4. 这里注意执行完记得清除key,因为是放在ThreadLocal的,项目中使用线程池,不清除会影响后面使用同个线程的操作
    @Component
    @Slf4j
    public class DataSourceUtils {
        private static DataSourceProperties properties;
    
        private static DynamicDataSource dynamicDataSource;
    
        private static ClusterConnService clusterConnService;
    
    
        @PostConstruct
        private void init() {
            properties = ApplicationContextUtils.getBean(DataSourceProperties.class);
            dynamicDataSource = ApplicationContextUtils.getBean("dynamicDataSource");
            clusterConnService = ApplicationContextUtils.getBean(ClusterConnService.class);
           
        }
        
       
        public static <T> T execute(ClusterInfo clusterInfo, Supplier<T> supplier) {
            //如果没提供集群id 则按默认数据源查询
            if (Objects.isNull(clusterInfo) || Objects.isNull(clusterInfo.getClusterId())) {
                return supplier.get();
            }
            
            //这是去获取数据源,如果没有被缓存,就初始化数据源
            String datasourceKey = DynamicDataSourceContextHolder.DS_KEY_PREFIX + clusterInfo.getClusterId();
            if (!DynamicDataSourceContextHolder.containsDataSourceKey(datasourceKey)) {
                synchronized (DataSourceUtils.class) {
                    if (!DynamicDataSourceContextHolder.containsDataSourceKey(datasourceKey)) {
                        initDataSource(datasourceKey, clusterInfo, true);
                    }
                }
            }
    
            return doExec(supplier, datasourceKey);
        }
    
        //初始化完数据源,就通过DynamicDataSourceContextHolder设置数据源的key,然后执行sql语句
        private static <T> T doExec(Supplier<T> supplier, String datasourceKey) {
            log.info("switch to dataSource[{}]", datasourceKey);
            DynamicDataSourceContextHolder.setDataSourceKey(datasourceKey);
            T t = supplier.get();
            DynamicDataSourceContextHolder.clearDataSourceKey();
            log.info("dataSource[{}] finished, switch to default", datasourceKey);
            return t;
        }
    
        //这里就是初始化数据源,并且根据对应的key缓存到动态数据源里
        private static void initDataSource(String datasourceKey, ClusterInfo clusterInfo, boolean sqlMode) {
            String address = clusterInfo.getMgrAddr();
            String username = clusterInfo.getClusterConnInfo().getDbUser();
            String password = clusterInfo.getClusterConnInfo().getDbPasswd();
            String dbName = antDbConfig.getNodeConnectDatabase();
    
            log.info("init dataSource , poolName:{}", datasourceKey);
            String url = "jdbc:postgresql://" + address + "/" + dbName + "?useUnicode=true";
            Class<? extends HikariDataSource> clazz = sqlMode ? SqlModeDataSource.class : HikariDataSource.class;
            HikariDataSource dataSource = properties.initializeDataSourceBuilder()
                    .type(clazz)
                    .driverClassName("org.postgresql.Driver")
                    .url(url)
                    .username(username)
                    .password(password)
                    .build();
            dataSource.setPoolName(datasourceKey);
            dynamicDataSource.addDataSource(datasourceKey, dataSource);
        }
    
    }
    

    上面切换完数据源后,有人可能还会有疑问为什么这样执行mapper就会使用我们切过的数据源执行?
    看过Mybatis源码的应该可以理解,这里mapper是Mybatis动态代理类,调用mapper方法最终是通过刚才配置的数据源getConnection来获取数据库连接,动态数据源getConnection方法上面我们解释过了,是通过我们动态改变key来获取的。

    展开全文
  • SpringBoot 实现动态数据源

    千次阅读 2022-05-12 23:02:05
    SpringBoot 实现动态数据源 功能: 前端请求接口时携带用户信息,后端拦截器获取用户信息后切换数据源查询数据。 使用场景:多租户,sass,pass等项目。 实现原理:主要通过SpringBoot提供的...

    SpringBoot 实现动态数据源

    功能: 前端请求接口时携带用户信息,后端拦截器获取用户信息后切换数据源查询数据。

    使用场景:多租户,sass,pass等项目。

    实现原理:主要通过SpringBoot提供的AbstractRoutingDataSource类,该类允许我们存入一个map,然后在通过数据源获取数据前允许我们指定一个key,然后自动的根据key从map内取对应的数据源。

    1 创建 DataSourceHolder类管理数据源的创建和存储

    @Component
    @NoArgsConstructor
    @Slf4j
    public class DataSourceHolder {
    
        @Autowired
        private AbstractRoutingDataSource routingDataSource;
    
        @Resource
        private DataSourceInfoMapper dataSourceInfoMapper;
    
        /**
         * 存放所有数据源,其实 key = dataSourceKey , value = DataSource
         */
        protected final ConcurrentHashMap<Object, Object> dataSourceMap = new ConcurrentHashMap<>(64);
    
        /**
         * 用于初始化数据源时加锁
         */
        private final ConcurrentHashMap<String, Object> dataSourceInitLock = new ConcurrentHashMap<>(64);
    
        /**
         * 保存当前的 数据源key
         */
        private static ThreadLocal<String> currDataSourceKey = new ThreadLocal<>();
    
        /**
         * 是否使用默认的数据源
         */
        private static ThreadLocal<Boolean> useDefaultDs = new ThreadLocal<>();
    
        public static void setDataSourceKey(String dataSourceKey){
            log.info("数据源切换:{} -> {}", currDataSourceKey.get(), dataSourceKey  );
            currDataSourceKey.set(dataSourceKey);
            unUseDefaultDataSource();
        }
    
        public static void useDefaultDataSource(){
            useDefaultDs.set(true);
        }
        public static void unUseDefaultDataSource(){
            useDefaultDs.remove();
        }
    
        /**
         * 获取当前的数据源 key
         * @return 当前 dataSourcekey
         */
        public String getDataSourceKey(){
    
            //获取指定的数据源 KEY
            final String currentDsKey = currDataSourceKey.get();
    
            //如果指定使用默认数据源 或者 未指定数据源,返回null
            if( Objects.equals(useDefaultDs.get(),true) || StrUtil.isBlank( currentDsKey ) ){
                log.info("采用默认数据源");
                return null;
            }
    
            //如果指定的数据源已经被初始化
            if( dataSourceMap.containsKey(currentDsKey) ){
                log.info("采用动态数据源,dataSourceKey:{}",currentDsKey);
                return currentDsKey;
            }
    
            //如果数据源未被初始化,尝试初始化数据源
            return initDataSource( currentDsKey );
        }
    
    
        /**
         * 初始化数据源
         * @return 返回数据源key
         */
        private String initDataSource( final String dataSourceKey ) {
    
            //如果不存在,设置一个对象,主要用于加锁
            Object lockObj = dataSourceInitLock.computeIfAbsent(dataSourceKey, key -> new Object());
    
    
            synchronized ( lockObj ){
                log.info("开始初始化数据源,dataSourceKey:{}",dataSourceKey);
    
                if( dataSourceMap.containsKey(dataSourceKey) ){
                    log.info("数据源已被其他线程初始化,dataSourceKey:{}",dataSourceKey);
                    return dataSourceKey;
                }
    
                try {
                    //使用默认的数据源
                    useDefaultDataSource();
    
                    //根据dataSourceKey,查询数据库配置信息
                    LambdaQueryWrapper<DataSourceInfo> wrapper = Wrappers.lambdaQuery();
                    wrapper.eq(DataSourceInfo::getDataSourceKey,dataSourceKey);
                    wrapper.last( "limit 1");
    
                    DataSourceInfo dataSourceInfo = dataSourceInfoMapper.selectOne(wrapper);
    
                    if( dataSourceInfo == null ){
                        //这里要抛异常,否则会使用默认的数据源
                        log.error("加载数据源失败,数据源配置信息不存在,dataSourceKey:{}",dataSourceKey);
                        return null;
                    }
    
                    Class dataSourceType = Class.forName( dataSourceInfo.getDataSourceType() );
    
                    //创建数据源
                    DataSource dataSource = DataSourceBuilder.create()
                        .driverClassName(dataSourceInfo.getDriverClassName())
                        .username(dataSourceInfo.getUsername())
                        .password(dataSourceInfo.getPassword())
                        .url(dataSourceInfo.getUrl())
                        .type(dataSourceType)
                        .build();
    
                    //放入数据源map
                    dataSourceMap.put(dataSourceKey,dataSource);
                    //数据源改变后需要刷新
                    refreshDataSource();
    
                    //创建成功后返回
                    return dataSourceKey;
                }catch( ClassNotFoundException ignored){
                } finally {
                    unUseDefaultDataSource();
                }
    
            }
            return null;
        }
    
        /**
         * 删除数据源
         * @param dataSourceKey 数据源Key
         */
        public void deleteDataSource( String dataSourceKey ){
            dataSourceMap.remove(dataSourceKey);
            refreshDataSource();
        }
    
    
        /**
         * 刷新数据源
         */
        public void refreshDataSource(){
            log.info("刷新动态数据源");
            routingDataSource.afterPropertiesSet();
        }
    
    }
    

    2 继承AbstractRoutingDataSource实现动态数据源

    @NoArgsConstructor
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
    
        @Resource
        private DataSourceHolder dataSourceHolder;
    
    
        /**
         * 主要用于获取当前使用的数据源的key
         * @return
         */
        @Override
        protected Object determineCurrentLookupKey() {
            return dataSourceHolder.getDataSourceKey();
        }
    
    }
    

    3 配置数据源

    @Configuration
    @AutoConfigureAfter(DataSourceAutoConfiguration.class)
    public class DynamicDataSourceConfiguration {
    
        @Autowired
        private DataSourceHolder dataSourceHolder;
    
    
        @Bean("defaultDataSource")
        public DataSource defaultDataSource(DataSourceProperties dataSourceProperties){
    
            //创建数据源
            return DataSourceBuilder.create()
                    .driverClassName(dataSourceProperties.getDriverClassName())
                    .username(dataSourceProperties.getUsername())
                    .password(dataSourceProperties.getPassword())
                    .url(dataSourceProperties.getUrl())
                    .type(dataSourceProperties.getType())
                    .build();
        }
    
        @Primary
        @Bean("dataSource")
        public DynamicDataSource dynamicDataSource(){
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
    
            // 设置默认的数据源
            dynamicDataSource.setDefaultTargetDataSource( SpringUtil.getBean("defaultDataSource") );
            //设置数据源map
            dynamicDataSource.setTargetDataSources( dataSourceHolder.dataSourceMap );
    
            return dynamicDataSource;
        }
    
        /**
         * 配置@Transactional注解事物
         */
        @Bean
        public PlatformTransactionManager transactionManager() {
            return new DataSourceTransactionManager(dynamicDataSource());
        }
    
    }
    

    4 创建过滤器 根据请求动态切换数据源

    @Configuration
    public class WebConfiguration implements WebMvcConfigurer {
    
    
        /**
         * 注册filter
         */
        @Bean
        public FilterRegistrationBean filterRegistrationBean(){
    
            FilterRegistrationBean bean = new FilterRegistrationBean();
    
            bean.setFilter((servletRequest, servletResponse, filterChain) -> {
                //默认使用默认的数据源
                DataSourceHolder.useDefaultDataSource();
    
                Object dsKey = servletRequest.getParameter("dsKey");
                if( dsKey!=null ){
                    //如果指定了数据源,使用指定的数据源
                    DataSourceHolder.setDataSourceKey(dsKey.toString());
                }
                filterChain.doFilter(servletRequest,servletResponse);
            });
            bean.setOrder(1);
            bean.addUrlPatterns("/*");
            return bean;
        }
    
    }
    

    代码已经放大Gitee上,代码地址:https://gitee.com/GJW520/dynamic-datasource.git

    展开全文
  • Spring-动态数据源

    千次阅读 2022-02-26 18:17:45
    Spring-动态数据源 动态数据源的原理得先说清。 原理 平常在使用Mysql的时候是通过JDBC的,得给一个url,userName,和password,如下: jdbc:mysql://localhost:3306/t_db1?useSSL=false&...

    Spring-动态数据源

    动态数据源的原理得先说清。

    原理

    平常在使用Mysql的时候是通过JDBC的,得给一个url,userName,和password,如下:

     jdbc:mysql://localhost:3306/t_db1?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    

    一个url对应一个Connection对象,需要在url中指定需要连接的库。

    之后的Mysql的CRUD的操作都是通过Connection对象来做的。所以,动态,就是在这个时候操作的,在获取Connection的时候来通过指定的key来判断要用哪个数据源。这得有一个前提,得先建立Connection,之后在获取Connection的时候在判断,

    1. 通过什么判断呢?存放key和Connection的数据结构是什么?

      只要指定一个回调方法,key随便,存放key和Connection的数据结构是Map。在获取Connection的时候,先确定Key,在获取Connection就好了。

    再看Spring

    Java连接Mysql指定了接口,需要实现DataSource接口,Spring中提供了抽象类(AbstractDataSource

    在这里插入图片描述

    解释说明

    DriverManagerDataSource

    这个没有什么可说,只是封装了一下连接mysql需要用的一些属性,比如userName,password,url,driver,就是就老一套的获取Connection封装了一下

    AbstractRoutingDataSource

    Spring提供的动态数据源的抽象类

    首先,它里面有一个Map,Map的key是Object(这是自定义的),V是Object(虽然是Object,代码里面规定了,只能为String,和DataSource),其实它本质就应该是DataSource,这里是String的目的是为了可以通过DataSourceLookup来获取数据源。(比如,可以在配置数据源的时候,直接配置数据源的bean的名字,然后自己写一个DataSourceLookup的实现类,从BeanFactory中获取DataSource)。

    还提供了一个determineCurrentLookupKey()方法来判断当前数据源的key,其实就是map中的key。在最终获取Connection的时候通过当前的key获取DataSource,在通过DataSource获取真正的Connection。

    代码分析
    1. 属性

    在这里插入图片描述

    1. 实现接口

    在这里插入图片描述

    实现了InitializingBean那它肯定在afterPropertiesSet会做操作。等会看看

    1. 重要方法

      • afterPropertiesSet

      在这里插入图片描述

      • 获取Connection之前确定Key

    在这里插入图片描述

     `determineCurrentLookupKey`方法是留给子类拓展的。determineCurrentLookupKey怎么来确定key呢?它是一个无参的方法,一般来说,都是放在ThreadLocal中,在执行sql操作之前,在对应的ThreadLocal放这次需要的key,就可以在determineCurrentLookupKey中获取ThreadLocal中的值,确定key。
    
     <font color='red'>Spring提供了一个动态数据源的实现类,`IsolationLevelDataSourceRouter`,key是事务的隔离级别。意思就是可以根据不同的隔离级别来选择DataSource</font>
    
     还有,一般都是用切面来操作ThreadLocal的
    

    例子

    我这里为了简单,就不写切面了。直接代码cv。

    1. 配置类

      package datasource.dynamic;
      
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.jdbc.core.JdbcTemplate;
      import org.springframework.jdbc.datasource.DriverManagerDataSource;
      import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
      
      import javax.sql.DataSource;
      import java.util.HashMap;
      import java.util.Map;
      
      @Configuration
      public class Config {
      	@Bean
      	public DriverManagerDataSource dataSource1(){
      		String url = "jdbc:mysql://localhost:3306/t_db1?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
      		String userName = "root";
      		String password = "123456";
      		return new DriverManagerDataSource(url,userName,password);
      	}
      	@Bean
      	public DriverManagerDataSource dataSource2(){
      		String url = "jdbc:mysql://localhost:3306/t_db2?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
      		String userName = "root";
      		String password = "123456";
      		return new DriverManagerDataSource(url,userName,password);
      	}
      	@Bean
      	public DriverManagerDataSource dataSource3(){
      		String url = "jdbc:mysql://localhost:3306/t_db3?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
      		String userName = "root";
      		String password = "123456";
      		return new DriverManagerDataSource(url,userName,password);
      	}
      	@Bean
      	public MyDynamicDataSource dynamicDataSource(Map<String,DriverManagerDataSource> map){
      		HashMap<Object, Object> original = new HashMap<>(map);
      		MyDynamicDataSource myDynamicDataSource = new MyDynamicDataSource();
      		myDynamicDataSource.setTargetDataSources(original);
      		return myDynamicDataSource;
      	}
      
      	@Bean
      	public JdbcTemplate jdbcTemplate(AbstractRoutingDataSource dataSource){
      		return new JdbcTemplate(dataSource);
      	}
      }
      

      配置了三个数据库,也就是三个数据源,利用JdbcTemplate来快捷的执行sql,JdbcTemplate需要指定用自己定义的动态数据源(MyDynamicDataSource)。

      此外,之前说了,动态数据源里面有个map,它得需要数据源,此外还需要key,这里用bean的名字做为key,创建MyDynamicDataSource之后,设置TargetDataSources。当MyDynamicDataSource被Spring创建的时候,InitializingBean#afterPropertiesSet()会通过DataSourceLookup转换。

    2. ThreadLocal

      package datasource.dynamic;
      
      import org.springframework.util.Assert;
      
      public class DataSourceKeyHolder {
      	private static final ThreadLocal<String> keyHolder = new ThreadLocal<>();
      
      	public static void setKey(String key) {
      		keyHolder.remove();
      		keyHolder.set(key);
      	}
      
      	public static String getKey() {
      		String s = keyHolder.get();
      		Assert.notNull(s, "key 不能为空");
      		return s;
      	}
      	public static void clear(){
      		keyHolder.remove();
      	}
      }
      
    3. 动态数据源实现类

      package datasource.dynamic;
      
      import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
      
      public class MyDynamicDataSource extends AbstractRoutingDataSource {
      	@Override
      	protected Object determineCurrentLookupKey() {
      		return DataSourceKeyHolder.getKey();
      	}
      }
      

      只是从ThreadLocal中获取当前的key就好了。

    4. 实体对象

      public class TestBean {
      	private Integer id;
      	private String name;
      	private Double age;
      
      	public Integer getId() {
      		return id;
      	}
      
      	public void setId(Integer id) {
      		this.id = id;
      	}
      
      	public String getName() {
      		return name;
      	}
      
      	public void setName(String name) {
      		this.name = name;
      	}
      
      	public Double getAge() {
      		return age;
      	}
      
      	public void setAge(Double age) {
      		this.age = age;
      	}
      
      	@Override
      	public String toString() {
      		return "TestBean{" +
      				"id=" + id +
      				", name='" + name + '\'' +
      				", age=" + age +
      				'}';
      	}
      }
      
    5. sql

      -- auto-generated definition
      create table t_test
      (
          id   int auto_increment
              primary key,
          name text   null,
          age  double null
      );
      

      创建三个数据库,在三个数据库里面都创建这个表,填写点数据。

    6. 主要测试类

      package datasource.dynamic;
      
      import org.springframework.context.annotation.AnnotationConfigApplicationContext;
      import org.springframework.jdbc.core.BeanPropertyRowMapper;
      import org.springframework.jdbc.core.JdbcTemplate;
      
      
      public class MainTest {
      	public static void main(String[] args) {
      		try {
      			AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
      			JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); // 这里从头到尾用的都是一个JdbcTemplate
      			dataSource1(jdbcTemplate); // 动态数据源1
      			dataSource2(jdbcTemplate);// 动态数据源2
      			dataSource3(jdbcTemplate);// 动态数据源3
      		}catch (Exception e){
      			e.printStackTrace();
      		}
      
      	}
      	public static void dataSource1(	JdbcTemplate jdbcTemplate ){
      		DataSourceKeyHolder.setKey("dataSource1");
      		try {
      			TestBean testBean = jdbcTemplate.queryForObject("select * from t_test where id=?", new BeanPropertyRowMapper<>(TestBean.class), 1);
      			System.out.println(testBean);
      		}finally {
      			DataSourceKeyHolder.clear();
      		}
      	}
      	public static void dataSource2(	JdbcTemplate jdbcTemplate ){
      		DataSourceKeyHolder.setKey("dataSource2");
      		try {
      			TestBean testBean = jdbcTemplate.queryForObject("select * from t_test where id=?", new BeanPropertyRowMapper<>(TestBean.class), 1);
      			System.out.println(testBean);
      		}finally {
      			DataSourceKeyHolder.clear();
      		}
      	}
      	public static void dataSource3(	JdbcTemplate jdbcTemplate ){
      		DataSourceKeyHolder.setKey("dataSource3");
      		try {
      			TestBean testBean = jdbcTemplate.queryForObject("select * from t_test where id=?", new BeanPropertyRowMapper<>(TestBean.class), 1);
      			System.out.println(testBean);
      		}finally {
      			DataSourceKeyHolder.clear();
      		}
      	}
      }
      
    7. 结果

    在这里插入图片描述


    关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。

    展开全文
  • 1、多数据源与动态数据源 当项目不只是要用到一个数据库的时候就需要使用到多个数据源了,这种场景很多,比如要查找的数据不在同一个数据库库中,或者是要做数据库读写分离。应对上面的问题主要有两种解决方法。 ...
  • 昨天在B站看了基于SpringBoot和MyBatis-Plus多数据源分析的视频,评论区有小伙伴想实现页面级的数据源切换,经过初步的分析就有了下面的想法。 通过分析源码可知道,MyBatis-Plus提供了dynamic-datasource-spring-...
  • 最简单的动态数据源配置

    千次阅读 2022-03-30 11:44:16
    动态数据源配置
  • 动态数据源配置druid+mybatis

    千次阅读 多人点赞 2021-01-10 21:06:21
    本方案不限数据库数量完全动态配置,支持不同的数据库部署在不同的服务器上。(mybatis-plus没测试,下个版本用oracle配的时候尝试plus) 如图 二、下一步我们看一下Druid继承关系 我们可以看到想要配置...
  • SpringBoot 动态数据源

    千次阅读 2021-11-15 18:32:23
    /** * 添加动态数据源 * @param dataSourceProperty */ public void addDataSource(DataSourceProperty dataSourceProperty) { DataSource dataSource = druidDataSourceCreator.createDataSource...
  • Spring Boot 配置动态数据源

    千次阅读 2020-11-08 20:58:36
    Spring Boot 配置动态数据源 Spring Framework 为 SQL 数据库提供了广泛的支持。从直接使用 JdbcTemplate 进行 JDBC 访问到完 全的对象关系映射(object relational mapping)技术,比如 Hibernate。Spring Data ...
  • mybatis-plus动态数据源

    千次阅读 2022-03-04 10:01:56
    mybatis-plus动态数据源
  • 使用sharding-jdbc解决SaaS系统动态数据源问题

    千次阅读 热门讨论 2021-02-16 21:41:17
    设计思路2.1 SaaS数据隔离的三种方案2.1.1 共享数据表(tenant_id)2.1.2 独立schema2.1.3 独立数据库2.2 创建租户思路2.2.1 创建租户时指定数据源2.2.2 创建租户流程3. 使用sharding-jdbc实现4. 总结 1. 需求背景 ...
  • 目录 ...上文中对动态数据源的二次开发方案进行了表述,本文将从代码的层面,实现Trino动态数据源的管理。 代码实现 代码实现上,我们依旧从开发方案中提到的4个步骤入手,对核心代码进行刨析。
  • Mybatis实现动态数据源

    千次阅读 2020-04-05 17:22:17
    动态数据源 需求:就是不同模块要存在不同的数据库中,就要实现动态数据源 基本思路: 1.创建一个动态的数据源DynamicDataSource,同事创建一个配置类DynamicDataSourceConfig配置类,这个配置类在Spring初始化的...
  • springboot动态数据源切换

    千次阅读 2022-04-10 18:21:17
    1,通过jdbc的方式动态切换数据源,实现既关即停,即开即用 @Component @Slf4j public class MySqlConfiguration { /** * mysql数据源 */ private Map<String, HikariDataSource> mySqlDataSources = ...
  • 配置druid动态连接池按数据源配置连接池参数
  • Mybatis动态数据源切换

    千次阅读 2020-08-23 17:03:28
    Mybatis动态数据源切换 1. 结构设计 首先看一下这个功能的架构设计 ![Component 1.png]...
  • 多数据源与动态数据源

    千次阅读 2018-03-30 16:49:54
    最近在网上找了一些资料,分别配置多数据和动态数据源。下面谈谈我的个人理解,有不对的地方希望大家指正,相互学习。1、多数据源: 主要可以实现多个数据源的配置,比如数据源A是oracle的做为会员库,B是mysql的...
  • springboot实现动态数据源访问多个数据库1. 简介和实现类介绍2. 项目创建实现类2.1添加数据源配置 DateSourceConfig2.2.定义动态数据源2.2.1 数据库标识管理类 DBIdentifier2.2.2.定义动态数据源派生类 ...
  • 本文记录下通过注解动态配置数据源的实现方式,可以是不同的数据库(如读写分离),也可以是mycat代理的分表数据源。废话不多,上干货—— 一、数据库配置文件 #mysql本地数据源1 spring.datasource.driver-class...
  • Hibernate动态数据源

    千次阅读 2017-03-20 10:54:50
    使用目的出于在审计厅项目建设的需求,我们在...后来在老师的决策下将数据库分库存储,不同地区的数据利用切分工具进行数据的切分,然后使用ETL、dts配合自己写的脚本完成数据的迁移和各种角色、存储过程、权限的设置。
  • baomidou 动态数据源

    千次阅读 2020-10-19 11:06:49
    官方文档 方便迅速的集成多数据源到您的系统中 源码 spring.factories 中有自动配置类 DynamicDataSourceAutoConfiguration
  • 动态数据源切换

    千次阅读 2019-07-03 15:49:27
    目的:在Springboot中实现多数据源,并动态切换数据源,写数据用主数据库,查数据用从数据库,实现读写分离。 原理: 首先要将主、从数据库都加入自定义的数据源库DynamicRouterDataSource中(继承自springboot...
  • MyBatis-Plus:创建动态数据源

    千次阅读 2020-10-22 19:11:52
    MyBatis-Plus:创建动态数据源简述工具类注意事项 简述 由于一些业务场景需要,我们可能需要使用MyBatis-Plus动态连接数据库,进行数据交互。 工具类 import ...
  • SpringBoot多数据源切换,AOP实现动态数据源切换 操作数据一般都是在DAO层进行处理,可以选择直接使用JDBC进行编程 或者是使用多个DataSource 然后创建多个SessionFactory,在使用Dao层的时候通过不同的...
  • 出入职场,菜鸟程序员接到个小任务,其中有部分功能是需要动态添加数据源的,着实有些头痛,经过网上一些资料的查找和整理,现将最后的结果与大家进行分享。 一、不配置默认数据源 刚开始需求所需不配置默认数据源...
  • 我们在使用spring中动态数据源的时候,往往在业务代码中需要指定数据源,在进行业务操作的时候就会去指定的数据源操作数据:代码如下 数据源,两个数据源,ds1,ds2: @Bean public DataSource ...
  • 根据Mybatis-plus配置动态数据源
  • Springboot+MyBatis-Plus实现多租户动态数据源模式

    千次阅读 热门讨论 2020-05-19 13:59:04
    Springboot+MyBatis-Plus实现多租户动态数据源模式一、先实现动态数据源上下文模式代码,保证在多租户模式下,能自动根据租户Id切换数据源二、实现动态数据源添加和设置,并继承自AbstractRoutingDataSource类,实现...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 710,272
精华内容 284,108
关键字:

动态数据源

友情链接: LCDcontador.rar