数据源 订阅
数据源是指数据库应用程序所使用的数据库或者数据库服务器。数据源(Data Source)顾名思义,数据的来源,是提供某种所需要数据的器件或原始媒体。在数据源中存储了所有建立数据库连接的信息。就像通过指定文件名称可以在文件系统中找到文件一样,通过提供正确的数据源名称,你可以找到相应的数据库连接。 展开全文
数据源是指数据库应用程序所使用的数据库或者数据库服务器。数据源(Data Source)顾名思义,数据的来源,是提供某种所需要数据的器件或原始媒体。在数据源中存储了所有建立数据库连接的信息。就像通过指定文件名称可以在文件系统中找到文件一样,通过提供正确的数据源名称,你可以找到相应的数据库连接。
信息
外文名
Data Source
中文名
数据源
数据源分类
信息系统的数据源必需可靠且具备更新能力,常用的数据源有:①观测数据,即现场获取的实测数据,它们包括野外实地勘测、量算数据,台站的观测记录数据,遥测数据等。②分析测定数据,即利用物理和化学方法分析测定的数据。③图形数据,各种地形图和专题地图等。④统计调查数据,各种类型的统计报表、社会调查数据等。⑤遥感数据,由地面、航空或航天遥感获得的数据。中国的数据源数量庞大。如:全国范围的土地资源清查及详查数据,航空摄影测量图像和国土普查卫星资料已覆盖全国,定位、半定位观测站网遍布全国,有地面调查、地图测绘等大量数据。上面提到的数据源例子只是很小一部 分,事实上数据源可以是任何数据类型。
收起全文
精华内容
下载资源
问答
  • springboot mybatis 集成多数据源 两种实现方式,一种静态添加,一种动态添加,后者能够支持很好的拓展。使用注解的方式实现,项目打包了一个module
  • springboot + mybatis(通用mapper) + druid多数据源
  • Spring动态切换多数据源Demo

    千次下载 热门讨论 2015-02-03 15:24:55
    请自行修改com/resources/datasource.properties中数据库配置,Demo中配置的两个数据源,一个是Mysql,一个是Oracle。 运行之前请自行建立数据库的表。
  • SpringBoot的jpa多数据源的动态切换项目源代码,适用于不同的用户操作不同的数据库,但是其对应的repository是一样的,可以进行http请求的动态数据源切换
  • Spring+SpringMVC+Mybatis多数据源

    千次下载 热门讨论 2015-12-15 15:14:18
    Spring+SpringMVC+Mybatis多数据源整合。基于spring3.0和mybatis3.2的
  • 1、多数据源与动态数据源 当项目不只是要用到一个数据库的时候就需要使用到多个数据源了,这种场景很多,比如要查找的数据不在同一个数据库库中,或者是要做数据库读写分离。应对上面的问题主要有两种解决方法。 ...

    1、多数据源与动态数据源

    当项目不只是要用到一个数据库的时候就需要使用到多个数据源了,这种场景很多,比如要查找的数据不在同一个数据库库中,或者是要做数据库读写分离。应对上面的问题主要有两种解决方法。

    第一种:在Spring中注入多个DataSource,然后根据不同的DataSource生成不同的SqlSessionFactory,把要操作不同数据库的mapper接口放在不同的包下,并且使用MyBatis的@MapperScan注解,使用不同的SqlSessionFactory扫描不同包下的mapper接口。

    例如:

    第一步:向Spring中注入多个数据源
    @Bean("db1-DataSource")
    @Primary//注意这个@Primary注解是必须的,必须要有一个数据源加上这个注解,否则SpringBoot无法启动
    @ConfigurationProperties(prefix = "datasource.db1")
    fun db1DataSource(): DataSource {
        return DataSourceBuilder.create().build()
    }
    
    @Bean("db2-DataSource")
    @ConfigurationProperties(prefix = "datasource.db2")
    fun db2DataSource(): DataSource {
        return DataSourceBuilder.create().build()
    }
    
    第二步:生成多个SqlSessionFactory并注入Spring
    @Bean("db1-SqlSessionFactory")
    fun db1SqlSessionFactory(@Qualifier("db1-DataSource") ds: DataSource): SqlSessionFactory {
        val fb = SqlSessionFactoryBean()
        fb.setDataSource(ds)
        fb.setMapperLocations("你的mapper的xml文件位置")
        return fb.getObject()
    }
    
    @Bean("db2-SqlSessionFactory")
    fun db2SqlSessionFactory(@Qualifier("db2-DataSource") ds: DataSource): SqlSessionFactory {
        val fb = SqlSessionFactoryBean()
        fb.setDataSource(ds)
        fb.setMapperLocations("你的mapper的xml文件位置")
        return fb.getObject()
    }
    
    第三步:@MapperScan使用不同的SqlSessionFactory扫描不同包下的mapper接口生成代理对象,假设要操作db1数据库的mapper接口放在app.mapper.db1包下,要操作db2数据库的mapper接口放在app.mapper.db2包下
    
    @MapperScan(value = ["app.mapper.db1"], sqlSessionFactoryRef = "db1-SqlSessionFactory")
    
    @MapperScan(value = ["app.mapper.db2"], sqlSessionFactoryRef = "db2-SqlSessionFactory")
    
    这样不同包下的mapper接口就可以操作不同的数据库了。

    接下来详细介绍第二种方法。

    第二种:使用Spring提供的动态数据源AbstractRoutingDataSource

    AbstractRoutingDataSource是一个抽象类,他实现了DataSource接口,内部可以存放多个DataSource,可以在需要的时候返回不同的DataSource。

    接下来解析AbstractRoutingDataSource的部分源码:

    //AbstractRoutingDataSource的内部使用了一个map存放多个数据源,key是数据源的唯一名字(可以任意命名,但是要保证唯一),value是对应的DataSource
    
    private Map<Object, Object> targetDataSources;
    //提供一个默认使用的数据源
    private Object defaultTargetDataSource;
    
    //这个是我们要实现的一个抽象方法,返回值是DataSource的唯一名字,表示使用该名字对应的DataSource
    protected abstract Object determineCurrentLookupKey();
    //这个是决定使用哪个数据源的方法,根据determineCurrentLookupKey的返回值来决定
    protected DataSource determineTargetDataSource() {
        Object lookupKey = determineCurrentLookupKey();
    	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 + "]");
    	}
    
    	return dataSource;
    }
    //可以设置存放多个数据源的map
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
       this.targetDataSources = targetDataSources;
    }
    
    //设置默认的数据源
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
    	this.defaultTargetDataSource = defaultTargetDataSource;
    }

    我们要做的就是继承这个AbstractRoutingDataSource抽象类,并且实现determineCurrentLookupKey() 这个方法,通过返回值来动态改变需要使用的数据源。

    现在来假设一种应用场景,我们需要拦截标注了@Transactional注解的方法,如果这个方法是以"add"、"update"、 "delete",作为前缀,那么就使用db1-DataSource,如果是以 "get" 做为前缀,则使用db2-DataSource。这相当于进行了一个简单的读写分离。

    也就是说我们需要在标注了@Transactional注解的事务方法执行之前,根据方法签名去动态改变使用的DataSource。

    这里可以编写一个切面去拦截该要执行的事务方法,然后在切面当中去判断执行的方法名,将获取的结果信息保存在一个ThreradLocal当中,这样就可以在AbstractRoutingDataSource的determineCurrentLookupKey方法中从ThreadLocal中获取这个信息并且返回对应的数据源的名字。使用ThreadLocal的主要原因是因为事务方法总是并发执行的,为了防止相互的干扰。

    具体的示例代码如下:

    我编写了一个DynamicDataSource去继承了AbstractRoutingDataSource,并且实现了determineCurrentLookupKey方法。

    class DynamicDataSource : AbstractRoutingDataSource() {
        override fun determineCurrentLookupKey(): Any {
            //这个MultipleDataSourceConfig是配置了多个数据源、sqlSessionFactory等的一个配置类
            val key = MultipleDataSourceConfig.threadLocal.get()
    
            println(" DynamicDataSource 当前使用的数据库为 : ${key} ")
    
            if (key == null) {
                println("error dataSource key ")
                return "db1"//这个是默认数据源的key
            }
    
            return key
        }
    }

    这个是配置类。

    EnableTransactionManagement
    @Configuration
    open class MultipleDataSourceConfig {
    
        companion object {
            val threadLocal: ThreadLocal<String> = ThreadLocal()
        }
    
        @Bean("db1-DataSource")
        @Primary
        open fun masterDataSource(): DataSource? {
            根据配置信息返回一个数据源
        }
    
        @Bean("db2-DataSource")
        open fun masterDataSource(): DataSource? {
            根据配置信息返回一个数据源
        }
    
    
        @Bean("dataSource")
        open fun dataSource(@Qualifier("db1-DataSource") db1: DataSource, @Qualifier("db2-DataSource") db2: DataSource): DynamicDataSource {
            val map = 把db1与db2放入一个map当中,key是db1和db2,value是对应的数据源
            return DynamicDataSource().apply {
                this.setTargetDataSources(map)
                this.setDefaultTargetDataSource(db1)
            }
        }
    
        @Bean("sqlSessionFactory")
        open fun sqlSessionFactory(@Qualifier("dataSource") ds: DataSource): SqlSessionFactory {
            val bean = SqlSessionFactoryBean()
            bean.setDataSource(ds)
            return bean.getObject()
        }
    
        @Bean
        open fun transactionManager(@Qualifier("dataSource") ds: DataSource): DataSourceTransactionManager {
            return DataSourceTransactionManager(ds)
        }
    }

    这个是拦截事务方法的切面。

    @Component
    @Aspect
    @Order(1)//这个注解的作用稍后会进行讲解
    class SelectDataSourceAspect {
    
       @Before("@annotation(org.springframework.transaction.annotation.Transactional)")
        fun before(jp: JoinPoint) {
            println(" SelectDataSourceAspect 拦截的方法名为 ${jp.signature.name} ")
    
            try {
                println(" SelectDataSourceAspect 执行${jp.signature.name}方法 : 事务已经开启 -> " + TransactionInterceptor.currentTransactionStatus().toString())
                //println(TransactionInterceptor.currentTransactionStatus().toString() + " " + txm)
            } catch (e: Exception) {
                println(" SelectDataSourceAspect 执行${jp.signature.name}方法 : 事务没有开启 ")
            }
    
            if (如果 jp.signature.name 是以add、update、delete作为前缀) {
                MultipleDataSourceConfig.threadLocal.set("db1")
            } else {
                MultipleDataSourceConfig.threadLocal.set("db2")
            }
        }
    
    }

    到这里好像看起来没问题,但是有一个重大的bug。

    Spring当中有一个事务切面来帮我们处理事务方法,他主要是在事务方法执行之前从数据源中拿取connection,设置开启事务,如果成功执行则提交,抛出异常则回滚(具体的源码在Spring的事务管理器DataSourceTransactionManager中)。

    而我们的切面是要在事务方法执行之前进行使用数据源判断的,也就是是说这两个切面是有执行的先后顺序的。

    例如:假设开始的时候Spring的事务切面先执行,他从数据源中拿取connection,因为ThreadLocal当中没有值,所以拿取到的connection是默认数据源db1的,在他拿取connection之后我们才改变了使用的数据源,这个显然是错误的。

    那么如何改变切面的执行顺序呢?

    查阅官方文档有这样的一段描述:

    点我跳转:Spring关于切面执行顺序的描述

    大致内容就是可以给切面加上@Order注解,@Order注解内部有一个int类型的值表示优先级,该值越小则切面越优先被执行。

    因此给我们配置的切面加上@Order(1)注解,就可以保证我们的优先执行了。

    这就是第二种方法的全部内容以及一些注意事项。

    说明:

    1、并不是一定要使用AOP来判断使用的数据源,你可以在filter、controller、controller的拦截器等等 当中进行判断。

    2、示例当中通过方法名来判断使用的数据源显然不是很优美,你可以编写一个自己的注解,例如@Read、@Write,然后在AOP中获取Method,利用反射拿出注解内容再进行判断,这样的话代码也会好看很多。注意:只有环绕的通知(@Around)才能拿到Method,像前置通知@Before只能拿到方法签名。

    2、运行时动态添加数据源

    针对上面使用的spring动态数据源,我有一个突发奇想,能不能在项目运行的时候添加数据源呢?也就是在不需要停止web应用的情况的通过网络传输一些数据源的配置信息给web应用,然后web应用就可以使用这个新加数据源操作对应的数据库。

    我们前面提到的AbstractRoutingDataSource中使用了[Map<Object, Object> targetDataSources]来保存多个数据源,考虑在运行的时候往这个map里面put新的数据源就可以。

    在具体的实现上,发送数据源配置信息我决定使用apollo,在apollo上添加的配置会同步到web应用中,当然你可以使用其他的方法,比如通过controller接受数据源配置或者通过dubbo这样的rpc框架、或者是写个listener监听端口使用原生的socket进行信息传输等等,喜欢那种就用那种。我使用apollo主要因为,我的项目的配置信息全部放在apollo上进行了统一管理。

    使用一个ConcureentHashMap来存放这些数据源,把它注册进Spring的容器当中,并且通过setTargetDataSources方法set进AbstractRoutingDataSource中。使用ConcureentHashMap而不使用HashMap的主要是因为增加数据源操作和事务切面获取数据源操作之间可能会有并发执行的情况,为了线程安全考虑。

    代码示例如下:

    向Spring中注入一个ConcureentHashMap

    @Bean("slaves")
    open fun runtimeDataSource(): ConcurrentHashMap<Any, Any> {
        val map = ConcurrentHashMap<Any, Any>()
        val cfg = ConfigService.getAppConfig()
        val set = cfg.propertyNames
        for (key in set) {
            //配置的前缀是db.slave.,这里是获取已经配置了的数据源
            if (key.startsWith("db.slave.")) {
                val value = cfg.getProperty(key, "")
                //编写一个getDataSource方法解析自定义的配置,kv中key表示数据源的名字,value表示数据源。
                val kv = getDataSource(key, value)
                if (kv != null) {
                    map.put(kv.key, kv.value)
                }
            }
        }
    
        //这里是监听随时通过网络发送过来的数据源配置
        cfg.addChangeListener(ConfigChangeListener { changeEvent ->
            //使用同步方法的主要原因是,把更新后的数据及时刷新回主存当中。
            //因为执行synchronized之前,需要的数据会从主存中获取而不是缓存中获取,执行完成之后会及时的刷新回主存。(周志明的深入理解jvm中有提到)
            //而且也是防止一次性添加多个数据源并发的时候发生一些错误。
            synchronized(this) {
                for (key in changeEvent.changedKeys()) {
                    val change = changeEvent.getChange(key)
                    if (change.changeType.equals(PropertyChangeType.ADDED) && change.propertyName.startsWith("db.slave.")) {
                        //编写一个getDataSource方法解析自定义的配置,kv中key表示数据源的名字,value表示数据源。
                        val kv = getDataSource(change.propertyName, change.newValue)
                        if (kv != null) {
                            //把数据源put进行map当中
                            map.put(kv.key, kv.value)
                        }
                    }
                }
            }
        })
    
        return map
    }

    在我们的切面当中注入这个map。

    @Component
    @Aspect
    @Order(1)
    class SelectDataSourceAspect {
        @Autowired
        @Qualifier("slaves")
        lateinit var dbs: ConcurrentHashMap<Any, Any>
    
        @Before("@annotation(org.springframework.transaction.annotation.Transactional)")
        fun before(jp: JoinPoint) {
            println(" SelectDataSourceAspect 拦截的方法名为 ${jp.signature.name} ")
    
            try {
                println(" SelectDataSourceAspect 执行${jp.signature.name}方法 : 事务已经开启 -> " + TransactionInterceptor.currentTransactionStatus().toString())
            } catch (e: Exception) {
                println(" SelectDataSourceAspect 执行${jp.signature.name}方法 : 事务没有开启 ")
            }
    
            val methodName = jp.signature.name
    
            if (methodName 是一个add、updat、delete方法 || dbs.size == 1) {
                MultipleDataSourceConfig.threadLocal.set("db1")
            } else {
                val li = mutableListOf<String>()
                for (k in dbs.keys) {
                    if (!"db1".equals(k.toString())) {
                        li.add(k.toString())
                    }
                }
    
                //如果是读取方法则随便从其他的数据源当中返回一个。
                //读写分离,写的话必须走主库,读的话随便挑一个从库。
                //当然可以编写更有效率的路由选择算法来替换这个随机选择算法。
                val idx = Math.abs(random.nextInt()) % li.size
                MultipleDataSourceConfig.threadLocal.set(li[idx])
            }
    
    
        }
    
    }

    DynamicDataSource中的代码保持不变,只需要从ThreadLocal中获取只然后返回就可以了。

    但是!测试的发现并不会选择到动态添加的数据源,只有提前配置好的数据源才可以,但是DynamicDataSource中已经返回了正确的数据源的key。

    打开AbstractRoutingDataSource源码查看选择数据源的过程:

    //可以看到,获取connection是从一个determineTargetDataSource返回的数据源中拿的
    @Override
    public Connection getConnection() throws SQLException {
    	return determineTargetDataSource().getConnection();
    }
    
    //这个determineTargetDataSource是根据determineCurrentLookupKey返回的key来获取数据源
    //但是!他是从resolvedDataSources中获取的,那么这个resolvedDataSources又是从哪里来的呢?
    protected DataSource determineTargetDataSource() {
    	Object lookupKey = determineCurrentLookupKey();
    	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 + "]");
    	}
    	return dataSource;
    }
    
    //原来resolvedDataSources只是从targetDataSources中copy了一份过去而已
    @Override
    public void afterPropertiesSet() {
    	if (this.targetDataSources == null) {
    		throw new IllegalArgumentException("Property 'targetDataSources' is required");
    	}
    	this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
    	for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
    		Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
    		DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
    		this.resolvedDataSources.put(lookupKey, dataSource);
    	}
    	if (this.defaultTargetDataSource != null) {
    		this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
    	}
    }
    

    知道原因之后就可以进行修改了,干脆在DynamicDataSource把determineTargetDataSource方法也重写了,然后从注入的map当中选择数据源,就不用通过resolvedDataSources获取了。

    修改后的DynamicDataSource代码如下:

    class DynamicDataSource : AbstractRoutingDataSource() {
    
        @Autowired
        @Qualifier("slaves")
        lateinit var dbs: ConcurrentHashMap<Any, Any>
    
        override fun determineCurrentLookupKey(): Any {
            val key = MultipleDataSourceConfig.threadLocal.get()
    
            if (key == null) {
                println("error dataSource key ")
                return "db1"
            }
    
            return key
        }
    
        //重写这个方法,其实这个在父类中是protect,这是一个很明显的提示
        override fun determineTargetDataSource(): DataSource? {
            val lookupKey = determineCurrentLookupKey()
            var dataSource = dbs[lookupKey]
    
            if (dataSource is DataSource) {
                return dataSource
            }
    
            return null
        }
    
    }

    到此整个过程就分享完毕了,我进行了一些场景的测试都得到了正确的结果。

    3、工程的源代码

    代码托管在github上面:工程源码

    展开全文
  • SpringBoot静态数据源指的是将多个数据源信息配置在配置文件中,在项目启动时加载配置文件中的多个数据源,并实例化多个数据源Bean,再通过分包/Aop达到切换数据源的目的 如果想要新增或者修改数据源,必须修改配置文件,...

    SpringBoot动态多数据源

    1.简介

    SpringBoot静态数据源指的是将多个数据源信息配置在配置文件中,在项目启动时加载配置文件中的多个数据源,并实例化多个数据源Bean,再通过分包/Aop达到切换数据源的目的

    如果想要新增或者修改数据源,必须修改配置文件,并修改对应的代码(增加对应的DataSource Bean)重启项目,重新实例化数据源,才能使用

    动态数据源指的是将数据源信息配置在关系型数据库中或者缓存数据库中,在项目启动时只初始化一个默认数据源,在项目运行过程中动态的从数据库中读取数据源信息,实例化为DataSource,并使用该数据源获取连接,执行SQL

    此处研究的ORM框架为Mybatis

    2.实现方法

    动态数据源的实现方法有两种

    2.1 重写DataSource中的getConnection方法

    Mybatis中数据库的连接都是在执行sql的时候才触发并创建连接

    由此可以通过重写数据源的getConnection()方法,当Mybatis需要创建对应的数据库连接时,跟据要使用的数据源,修改当前使用的数据源,达到动态数据源的目的

    2.2 通过AbstractRoutingDataSource类

    AbstractRoutingDataSource类是jdbc提供的轻量级的切换数据源的方案,内部维护了一个数据源的集合,提供了维护这个数据源集合的方法,这样我们在动态的创建完对应的数据源后,就可以通过这个类提供的方法,将数据源维护进这个集合,再通过重写 determineCurrentLookupKey()方法,来告诉 AbstractRoutingDataSource我们要的是哪个数据源,最终达到切换数据源的目的

    3.具体代码

    3.1 方案1:重写DataSource中的getConnection方法

    3.1.1 主配置类 DataSourceConfig

    @Configuration
    @MapperScan(basePackages = "com.zhangyao.springboot.mapper",sqlSessionFactoryRef = "sqlSessionFactory")
    public class DataSourceConfig {
    
        /**
         * 主数据源配置
         * @return
         */
        @Bean(name = "primaryDataSource")
        @Primary
        @ConfigurationProperties(prefix = "spring.datasource.test1")
        public DataSource getDataSource1(){
            HikariDataSource datasource =  DataSourceBuilder.create().type(MyDynamicDataSource.class).build();
            if(datasource==null){
                datasource = new MyDynamicDataSource().initDataSource("default");
            }
            //设置默认的数据源
            DataSourceCache.put("default", datasource);
            ThreadLocalDataSource.setLocalSource("default");
            return datasource;
        }
        @Bean("sqlSessionFactory")
        @Primary
        public SqlSessionFactory getSqlSessionFactory(@Qualifier("primaryDataSource") DataSource primaryDataSource) throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(primaryDataSource);
            return sqlSessionFactoryBean.getObject();
        }
    }
    

    这个类里配置了默认的数据源default,这个数据源是从配置文件中读取到的,并且实例化了Mybatis的SqlSessionFactory,默认注入default数据源

    3.1.2 MyDynamicDataSource 覆盖HikariDataSource的getConnection()

    package com.zhangyao.springboot.config;
    
    import com.zaxxer.hikari.HikariDataSource;
    import com.zhangyao.springboot.domin.Databaseinfo;
    import com.zhangyao.springboot.service.DataBaseService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.core.io.support.EncodedResource;
    import org.springframework.core.io.support.PropertiesLoaderUtils;
    
    import java.io.IOException;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.Map;
    import java.util.Properties;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * @author: zhangyao
     * @create:2020-11-03 21:07
     * @Description:
     **/
    public class MyDynamicDataSource extends HikariDataSource {
    
        @Autowired
        DataBaseService dataBaseService;
    
    
    
        /**
         * 定义缓存数据源的变量
         */
        public static final Map<Object, Object> DataSourceCache = new ConcurrentHashMap<Object, Object>();
    
        @Override
        public Connection getConnection() throws SQLException {
            String localSourceKey = ThreadLocalDataSource.getLocalSource();
            HikariDataSource dataSource = (HikariDataSource) DataSourceCache.get(localSourceKey);
            if(dataSource==null){
    	        try {
    	            dataSource = initDataSource(localSourceKey);
    	        } catch (IOException e) {
    	            e.printStackTrace();
    	        }
            }
            return dataSource.getConnection();
        }
    
        /**
         * 初始化DataSource
         * 当缓存中没有对应的数据源时,需要去默认数据源查询数据库
         *
         * @param key
         * @return
         */
        public HikariDataSource initDataSource(String key) throws IOException {
            HikariDataSource dataSource = new HikariDataSource();
            if ("default".equals(key)) {
                Properties properties = PropertiesLoaderUtils.loadProperties(new EncodedResource(new ClassPathResource("application.properties"), "UTF-8"));
                dataSource.setJdbcUrl(properties.getProperty("spring.datasource.test1.jdbc-url"));
                dataSource.setUsername(properties.getProperty("spring.datasource.test1.username"));
                dataSource.setPassword(properties.getProperty("spring.datasource.test1.password"));
                dataSource.setDriverClassName(properties.getProperty("spring.datasource.test1.driver-class-name"));
            } else {
                //查询数据库
                ThreadLocalDataSource.setLocalSource("default");
                Databaseinfo dataBaseInfo = dataBaseService.getDataBaseInfo(key);
                dataSource.setJdbcUrl(dataBaseInfo.getUrl());
                dataSource.setUsername(dataBaseInfo.getUserName());
                dataSource.setPassword(dataBaseInfo.getPassword());
                dataSource.setDriverClassName(dataBaseInfo.getDriverClassName());
                ThreadLocalDataSource.setLocalSource(key);
            }
            DataSourceCache.put(key, dataSource);
            return dataSource;
        }
    }
    

    这个类重写了HikariDatasource类的getConnection()方法,当Mybatis使用连接时,就会调用MyDynamicDataSource的getConnection()方法,然后通过获取ThreadLoacal中存放的当前使用的数据源的key,进而从自定义的缓存变量 DataSourceCache 中获取对应的数据源,如果获取不到,就使用默认数据源查询数据库,如果再获取不到,就抛出异常,查询到数据源后,初始化完再放入到缓存中DataSourceCache

    3.1.3 ThreadLocalDataSource 存放当前线程使用数据源的key

    package com.zhangyao.springboot.config;
    
    import lombok.extern.slf4j.Slf4j;
    
    import javax.xml.crypto.Data;
    
    /**
     * ThreadLocal保存数据源的key,并切换清除
     * @author: zhangyao
     * @create:2020-04-07 09:24
     **/
    @Slf4j
    public class ThreadLocalDataSource {
    
        //使用threadLocal保证切换数据源时的线程安全 不会在多线程的情况下导致切换错数据源
        private static final ThreadLocal<String> TYPE = new ThreadLocal<String>();
    
        /**
         * 修改当前线程内的数据源id
         * @param key
         */
        public static void setLocalSource(String key){
            TYPE.set(key);
        }
    
        /**
         * 获取当前线程内的数据源类型
         * @return
         */
        public static String getLocalSource(){
            return TYPE.get();
        }
    
        /**
         * 清空ThreadLocal中的TYPE
         */
        public void clear(){
            TYPE.remove();
        }
    
    }
    

    提供了对ThreadLocal的set/get操作方法,ThreadLocal中存放的是数据源的key,这个key与 MyDynamicDataSource中的DataSourceCache中的key一致,ThreadLocal的作用就是保证在当前线程内可以取到唯一的数据源的key

    3.1.4 DataSourceAop 切面 用于解析请求中的数据源的key

    @Aspect
    @Component
    @Slf4j
    public class DataSourceAop {
        /**
         * 定义切入点
         * 切入点为有该注解的方法
         * 此注解用于数据源TEST1
         */
        @Pointcut("@annotation(com.zhangyao.springboot.annotation.DataSourceServiceAop)")
        public void serviceTest1DatasourceAspect(){};
    
        /**
         * 在切入service方法之前执行
         * 设置数据源
         */
        @Before("serviceTest1DatasourceAspect()")
        public void beforeAspect(){
            log.info("切入方法,开始设置数据源");
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            String database_key = attributes.getRequest().getHeader("database_key");
            ThreadLocalDataSource.setLocalSource(database_key);
    
    
        }
        /**
         * 在切入service方法之后执行
         * 设置回默认数据源
         */
        @After("serviceTest1DatasourceAspect()")
        public void afterAspect(){
            log.info("切入方法后,开始切换默认数据源");
            ThreadLocalDataSource.setLocalSource("default");
        }
    }
    

    需要自定义一个注解@DataSourceServiceAop,标识在使用动态数据源的方法上

    这里是跟据前台传输的数据源的key来设置ThreadLocal中的key,如果前台传输的数据源的key不在header中,再跟据实际情况调整

    切完方法之后,切换回default数据源

    3.2 方案2: 通过AbstractRoutingDataSource类

    3.2.1 主配置类 DataSourceConfig

    package com.zhangyao.springboot.config;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.zaxxer.hikari.HikariDataSource;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.SqlSessionTemplate;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import tk.mybatis.spring.annotation.MapperScan;
    
    import javax.sql.DataSource;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    
    import static com.zhangyao.springboot.config.MyDynamicDataSource.DataSourceCache;
    
    
    /**
     *
     * aop多数据源动态切换配置
     * @author: zhangyao
     * @create:2020-04-06 22:17
     **/
    
    @Configuration
    @MapperScan(basePackages = "com.zhangyao.springboot.mapper",sqlSessionFactoryRef = "sqlSessionFactory")
    public class DataSourceConfig {
    
        /**
         * 主数据源配置
         * @return
         */
        @Bean(name = "primaryDataSource")
        @Primary
        @ConfigurationProperties(prefix = "spring.datasource.test1")
        public DataSource getDataSource1(){
            HikariDataSource datasource =  DataSourceBuilder.create().type(HikariDataSource.class).build();
            //设置默认的数据源
            DataSourceCache.put("default", datasource);
            ThreadLocalDataSource.setLocalSource("default");
            return datasource;
        }
        /**
         * 动态装配所有的数据源
         * @param primaryDataSource
         * @return
         */
        @Bean("dynamicDataSource")
        public DynamicChangeDataSourceConfig setDynamicDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource){
            //定义所有的数据源
            Map<Object,Object> allDataSource = new HashMap<Object, Object>();
            //把配置的多数据源放入map
            allDataSource.put("default", primaryDataSource);
    
            //定义实现了AbstractDataSource的自定义aop切换类
            DynamicChangeDataSourceConfig dynamicChangeDataSourceConfig = new DynamicChangeDataSourceConfig();
            //把上面的所有的数据源的map放进去
            dynamicChangeDataSourceConfig.setTargetDataSources(allDataSource);
            //设置默认的数据源
            dynamicChangeDataSourceConfig.setDefaultTargetDataSource(primaryDataSource);
    
            return dynamicChangeDataSourceConfig;
        }
    
        @Bean("sqlSessionFactory")
        @Primary
        public SqlSessionFactory getSqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dynamicDataSource);
            return sqlSessionFactoryBean.getObject();
        }
    
    }
    

    与方案一一样需要先实例化默认数据源default,但是MyBatis的SqlSessionFactory中注入的就不是默认数据源了,而是AbstractRoutingDataSource的实现类,并将默认数据源放入AbstractRoutingDataSource的targetDataSources中

    3.2.2 DynamicChangeDataSourceConfig AbstractRoutingDataSource的子类

    package com.zhangyao.springboot.config;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * 继承AbStractRoutingDataSource
     * 动态切换数据源
     * @author: zhangyao
     * @create:2020-04-07 09:23
     **/
    public class DynamicChangeDataSourceConfig extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            return ThreadLocalDataSource.getLocalSource();
        }
    
    }
    

    重写了AbstractRoutingDataSource的determineCurrentLookupKey方法,改为返回ThreadLocal中的数据源的key

    3.2.3 ThreadLocalDataSource 存放当前线程使用数据源的key

    与方案1一摸一样

    3.2.3 DataSourceAop 切面 用于解析请求中的数据源的key

    package com.zhangyao.springboot.config;
    
    import com.zaxxer.hikari.HikariDataSource;
    import com.zhangyao.springboot.domin.Databaseinfo;
    import com.zhangyao.springboot.service.DataBaseService;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestAttributes;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.annotation.Resource;
    
    import static com.zhangyao.springboot.config.MyDynamicDataSource.DataSourceCache;
    
    /**
     * @author: zhangyao
     * @create:2020-04-07 11:20
     **/
    @Aspect
    @Component
    @Slf4j
    public class DataSourceAop {
        @Autowired
        DataBaseService dataBaseService;
    
        @Resource(name = "dynamicDataSource")
        DynamicChangeDataSourceConfig dynamicChangeDataSourceConfig;
        /**
         * 定义切入点
         * 切入点为有该注解的方法
         * 此注解用于数据源TEST1
         */
        @Pointcut("@annotation(com.zhangyao.springboot.annotation.DataSourceServiceAop)")
        public void serviceTest1DatasourceAspect(){};
    
        /**
         * 在切入service方法之前执行
         * 设置数据源
         */
        @Before("serviceTest1DatasourceAspect()")
        public void beforeAspect(){
            log.info("切入方法,开始设置数据源");
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            String database_key = attributes.getRequest().getHeader("database_key");
            initDataSource(database_key);
            ThreadLocalDataSource.setLocalSource(database_key);
    
    
        }
        /**
         * 在切入service方法之后执行
         * 设置回默认数据源
         */
        @After("serviceTest1DatasourceAspect()")
        public void afterAspect(){
            log.info("切入方法后,开始切换默认数据源");
            ThreadLocalDataSource.setLocalSource("default");
        }
    
    
        public HikariDataSource initDataSource(String key) {
            HikariDataSource dataSource = new HikariDataSource();
            if ("default".equals(key)) {
                 Properties properties = PropertiesLoaderUtils.loadProperties(new EncodedResource(new ClassPathResource("application.properties"), "UTF-8"));
                dataSource.setJdbcUrl(properties.getProperty("spring.datasource.test1.jdbc-url"));
                dataSource.setUsername(properties.getProperty("spring.datasource.test1.username"));
                dataSource.setPassword(properties.getProperty("spring.datasource.test1.password"));
                dataSource.setDriverClassName(properties.getProperty("spring.datasource.test1.driver-class-name"));
            } else {
                //查询数据库
                ThreadLocalDataSource.setLocalSource("default");
                Databaseinfo dataBaseInfo = dataBaseService.getDataBaseInfo(key);
                dataSource.setJdbcUrl(dataBaseInfo.getUrl());
                dataSource.setUsername(dataBaseInfo.getUserName());
                dataSource.setPassword(dataBaseInfo.getPassword());
                dataSource.setDriverClassName(dataBaseInfo.getDriverClassName());
                DataSourceCache.put(key, dataSource);
                dynamicChangeDataSourceConfig.setTargetDataSources(DataSourceCache);
                dynamicChangeDataSourceConfig.afterPropertiesSet();
                ThreadLocalDataSource.setLocalSource(key);
            }
            return dataSource;
        }
    }
    

    当进入切点方法后,获取到前台传输的数据源key,去缓存中取,如果取不到,就查询数据库,并实例化放置到缓存中,并设置ThreadLocal的key为前台传输的key

    4.总结

    其实两种方案本质上是一种方法,第一种方法相当于自己把AbstractRoutingDataSource这个类的功能再手动的实现一遍,好处是更加灵活,可以针对自己的业务做定制

    展开全文
  • //产生主自定义的数据源,默认数据源配置在jdbc.properties中的数据源 initCustomDataSources(env); //将主数据员,自定义数据源注入到动态数据源DynamicDataSource , //再将DynamicDataSource 以名称...

    _**使用场景:
    假设需要做一个工具,由于保密需要只描述场景。 A下面有 B、C、D三家或者更多分公司,人员信息统一在总公司的A数据库,分公司各自使用各自的业务数据库,用户登陆访问A数据库校验成功后将用户所属分公司的KEY保存session中(此文章以存放session为例,其他存放方式参考即可),当然本文重点在与还可以动态增加EFG。。。。分公司,无需重启应用。
    _**设计思路:
    因为系统登录离不开A数据库,我成A为主数据库。
    B、C、D、E…..并非系统运行必须数据库,所以简称自定义数据库。
    B、C、D都为分公司,数据库结构完全一致,支持BCD与A库表结构不一致。案例以SpringBoot项目为例,Spring按照这个思路,更简单。

    新建项目依赖的主数据库A 配置主数据源,以及公共数据源属性,application.properties
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.druid.url=
    spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.druid.username=root
    spring.datasource.druid.password=root
    spring.datasource.druid.maxWait=5000
    spring.datasource.druid.。。。。。。
    配置自定义数据源jdbc.properties(也可以不在配置文件中配置,项目有提供页面配置,保存数据库或者配置文件)
    *数据源的key–可以理解为分公司代码
    spring.datasource.keys=dev,stg,prd
    spring.datasource.names=开发,测试,生产
    *个数据源前缀为key
    dev.spring.datasource.druid.driver-class-name=
    dev.spring.datasource.druid.url=
    dev.spring.datasource.druid.username=
    dev.spring.datasource.druid.password=

    stg.spring.datasource.druid.driver-class-name=
    stg.spring.datasource.druid.url=
    stg.spring.datasource.druid.username=
    stg.spring.datasource.druid.password=
    支持动态切换数据源的思路
    包装一个DataSource 从当前线程中拿到需要访问的key,根据key得到需要使用哪个数据源,参考目前spring已经提供的AbstractRoutingDataSource,因此只需要自己写个类继承AbstractRoutingDataSource实现determineCurrentLookupKey()方法,这个方法就是获取当前线程中的key。
    如下:
    public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
    
    /**
     * 动态更新自定义数据源
     * @param defaultDataSource
     * @param customDataSources
     */
    public void updateTargetDataSource(Map<String,DataSource> customDataSources){
        Map<Object,Object> customDS=new HashMap<Object, Object>();
        customDS.putAll(customDataSources);
        setTargetDataSources(customDS);
        afterPropertiesSet();
    }
    

    }
    由于要支持动态添加数据源所以又添加updateTargetDataSource()方法。

    现在配置主以及自定义DataSource,SpringBoot方式
    /**
    * @ClassName DynamicDataSourceRegister
    * @Description 动态数据源注册
    */
    public class DynamicDataSourceRegister implements EnvironmentAware {
    /**
    * 加载多数据源配置
    */
    @Override
    public void setEnvironment(Environment env) {
    DynamicDataSourceRegisterUtil.initAndRegisterDataSource(env);
    }
    }

    DynamicDataSourceRegisterUtil中的方法
    public static void initAndRegisterDataSource(Environment env){
    //将自定义的数据源的信息加载到spring 的Environment中
    try {
    initCustomEnvironment(env);
    } catch (IOException e) {
    logger.error(“初始化自定义数据库配置文件出错”,e);
    }
    //产生主数据源,默认数据源配置在application.properties中的数据源
    initDefaultDataSource(env);
    //产生主自定义的数据源,默认数据源配置在jdbc.properties中的数据源
    initCustomDataSources(env);
    //将主数据员,自定义数据源注入到动态数据源DynamicDataSource ,
    //再将DynamicDataSource 以名称“dataSource”注入到spring中
    registerDataSource(DATA_SOURCE);
    }
    DynamicDataSourceRegisterUtil具体代码如下(各个方法作用见注释):
    /**
    * @ClassName DynamicDataSourceRegister
    * @Description 动态数据源注册
    */
    public class DynamicDataSourceRegisterUtil{
    public final static String DATASOURCE_PARAM_PREF =”spring.datasource.”;
    public final static String DATASOURCE_PARAM_PRPO_KEY_DRIVER_CLASS_NAME =”driver-class-name”;
    public final static String DRUID_DATASOURCE_PARAM_PREF =”spring.datasource.druid.”;
    public final static String DATASOURCE_PARAM_TYPE =”druid.”;
    public final static String DATASOURCE_PARAM_SPLIT =”.”;

    public final static  String DEFAULT_TARGET_DATA_SOURCE ="defaultTargetDataSource";
    public final static  String TARGET_DATA_SOURCES  ="targetDataSources";
    public final static  String  DATA_SOURCE  ="dataSource";
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegisterUtil.class);
    private static final ConversionService conversionService = new DefaultConversionService();
    private static final String JDBC_FILE_NAME="jdbc.properties";
    private static final String CUSTOM_DATA_SOURCES_MAP_NAME="customDataSourcesMap";
    
    private static PropertyValues dataSourcePropertyValues;
    // 数据源
    private static DataSource defaultDataSource;
    private static Map<String,DataSource> customDataSources = new HashMap<String,DataSource>();
    
    /**
     * 创建DataSource
     *
     * @param type
     * @param driverClassName
     * @param url
     * @param username
     * @param password
     * @return
     */
    private static DataSource buildDataSource(Map<String,Object> dsMap)  {
        DataSource dataSource=null;
        try {
            dataSource=DruidDataSourceFactory.createDataSource(dsMap);
            if(dataSource instanceof DruidDataSource){
                //注意:这一设置是为解决Druid 在获取连接时由于连接配置出错会一直等待获取连接,比较重要
                ((DruidDataSource) dataSource) .setBreakAfterAcquireFailure(true);
                //((DruidDataSource) dataSource).init();
            }
            return dataSource;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    public  static void initAndRegisterDataSource(Environment env){
        //将自定义的数据源的信息加载到spring 的Environment中
        try {
            initCustomEnvironment(env);
        } catch (IOException e) {
            logger.error("初始化自定义数据库配置文件出错",e);
        }
        //产生主数据源,默认数据源配置在application.properties中的数据源
        initDefaultDataSource(env);
        //产生主自定义的数据源,默认数据源配置在jdbc.properties中的数据源
        initCustomDataSources(env);
        //将主数据员,自定义数据源注入到动态数据源DynamicDataSource ,再将DynamicDataSource 以名称“dataSource”注入到spring中
        registerDataSource(DATA_SOURCE);
    }
    
    /**
     * 更新配置文件,并热加载到Environment中
     * @param properties
     * @throws IOException
     */
    public synchronized static void refreshDataSoureProperties(Properties properties) throws IOException {
        //将属性持久化到配置文件
        OutputStream out=new FileOutputStream(URLEncoder.encode(DynamicDataSourceRegisterUtil.class.getClassLoader().getResource(JDBC_FILE_NAME).getPath(),"utf-8"));
        properties.store(out,"更新数据库");
        out.flush();
        out.close();
        //将属性热加载到环境中去
        Environment env =EnvironmentUtils.getEnvironment();
        PropertySource<?> source = new PropertiesPropertySource(CUSTOM_DATA_SOURCES_MAP_NAME, properties);
        ((StandardEnvironment) env).getPropertySources().addLast(source);
        refreshDataSource(env);
    }
    /**
     * 初始化主数据源
     */
    private static void initDefaultDataSource(Environment env) {
        // 读取主数据源
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, DATASOURCE_PARAM_PREF);
        Map<String,Object> dsMap = propertyResolver.getSubProperties(DATASOURCE_PARAM_TYPE);
        defaultDataSource = buildDataSource(dsMap);
        dataBinder(defaultDataSource,env);
    }
    
    /**
     * 关闭老的数据源
     */
    private static void closeOldCustomDataSources(){
        if(customDataSources!=null&&customDataSources.size()>0){
            for (String key:customDataSources.keySet()){
                DataSource dataSource =customDataSources.get(key);
                if(dataSource instanceof DruidDataSource){
                    ((DruidDataSource)dataSource).close();
                    logger.info("closed datasource "+key);
                }
            }
        }
        if(customDataSources!=null){
            customDataSources.clear();
        }
    }
    
    public synchronized static void initCustomEnvironment(Environment env) throws IOException {
        Properties properties = new Properties();
        InputStream in =null;
        try {
            in = new FileInputStream(URLEncoder.encode(DynamicDataSourceRegisterUtil.class.getClassLoader().getResource(JDBC_FILE_NAME).getPath(), "utf-8"));
            properties.load(in);
            PropertySource<?> source = new PropertiesPropertySource(CUSTOM_DATA_SOURCES_MAP_NAME, properties);
            ((StandardEnvironment) env).getPropertySources().addLast(source);
        }finally {
            in.close();
        }
    }
    /**
     * 初始化更多数据源
     *
     */
    private static void initCustomDataSources(Environment env) {
        //初始化之前要先将老的数据源关闭
        closeOldCustomDataSources();
        // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, DATASOURCE_PARAM_PREF);
        String dsPrefixs = propertyResolver.getProperty("keys");
        if(dsPrefixs==null){
            return;
        }
        for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
            RelaxedPropertyResolver propertys = new RelaxedPropertyResolver(env, dsPrefix.trim()+DATASOURCE_PARAM_SPLIT+DATASOURCE_PARAM_PREF);
            Map<String,Object> dsMap = propertys.getSubProperties(DATASOURCE_PARAM_TYPE);
            DataSource dataSource= buildDataSource(dsMap);
            dataBinder(dataSource,env);
            customDataSources.put(dsPrefix, dataSource);
            try{
                //向库中插入信息
                String password = (String) dsMap.get(DruidDataSourceFactory.PROP_PASSWORD);
                String url = (String) dsMap.get(DruidDataSourceFactory.PROP_URL);
                String username = (String) dsMap.get(DruidDataSourceFactory.PROP_USERNAME);
                String strs[]=url.split(";");
                String dbName="";
                for(String s: strs){
                    if(s!=null&&s.trim()!=null){
                        if(s.toUpperCase().contains("DATABASENAME")){
                            dbName=s.split("=")[1];
                        }
                    }
                }
                //Data Source=192.168.5.92;Initial Catalog=MS_CUBE_TEST;User ID=sa;Password=root;
                String connectionStr="Data Source="+strs[0].replaceAll(".*(\\d{3}(\\.\\d{1,3}){3}).*","$1")
                        +";Initial Catalog="+dbName+";User ID="+username+";Password="+password+";";
    
                updateConnectionStr2Db(dsMap,connectionStr);
            } catch (Exception e) {
                logger.error("将数据源配置信息写入到数据库失败!",e);
            }
        }
    }
    
    private static void updateConnectionStr2Db(Map<String, Object> dataSource, String connectionStr) throws SQLException {
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        try {
            Class.forName((String)dataSource.get(DATASOURCE_PARAM_PRPO_KEY_DRIVER_CLASS_NAME));
            connection =DriverManager.getConnection((String)dataSource.get(DruidDataSourceFactory.PROP_URL),
                    (String)dataSource.get(DruidDataSourceFactory.PROP_USERNAME),
                    (String)dataSource.get(DruidDataSourceFactory.PROP_PASSWORD));
            preparedStatement=connection.prepareStatement("merge into ssas_base_config_prop p using(\n" +
                    "\tselect 'clrSSASConfigurationConnectionString' as  properties_key\n" +
                    ") t on (t.properties_key=p.properties_key)\n" +
                    "when matched then\n" +
                    "update set  properties_value=?\n" +
                    "when not matched then\n" +
                    "insert (properties_key,properties_value,properties_name,display_type)values('clrSSASConfigurationConnectionString',?,'当前环境数据库','text');\n");
            preparedStatement.setString(1,connectionStr);
            preparedStatement.setString(2,connectionStr);
            preparedStatement.executeUpdate();
            connection.commit();
        }catch (Exception e){
            logger.error("保存数据库连接信息失败",e);
        }finally {
            preparedStatement.close();
            connection.close();
        }
    }
    /**
     * 更新配置之后要更新DynamicDataSource
     * @param dataSourceName
     */
    private static void refreshDataSource(Environment environment) {
        initCustomDataSources(environment);
        DynamicDataSource dynamicDataSource =ApplicationContextUtil.getBean(DATA_SOURCE);
        dynamicDataSource.updateTargetDataSource(defaultDataSource,customDataSources);
        DynamicDataSourceContextHolder.dataSourceIds.clear();
        DynamicDataSourceContextHolder.dataSourceIds.addAll(customDataSources.keySet());
    }
    /**
     * 将动态数据源注册到spring中
     * @param dataSourceName
     */
    private static void registerDataSource(String dataSourceName) {
        Map<String,Object> targetDataSources = new HashMap<String,Object>();
        // 将主数据源添加到更多数据源中
        targetDataSources.put(DEFAULT_TARGET_DATA_SOURCE, defaultDataSource);
        // 添加更多数据源
        targetDataSources.putAll(customDataSources);
        DynamicDataSourceContextHolder.dataSourceIds.addAll(customDataSources.keySet());
        Map<String,Object> paramValues=new HashMap<String, Object>();
        paramValues.put(DEFAULT_TARGET_DATA_SOURCE, defaultDataSource);
        paramValues.put(TARGET_DATA_SOURCES, targetDataSources);
        ApplicationContextUtil.registerSingletonBean(dataSourceName,DynamicDataSource.class,paramValues);
        logger.info("Dynamic DataSource Registry");
    }
    /**
     * 为DataSource绑定更多数据
     * @param dataSource
     * @param env
     */
    private static void dataBinder(DataSource dataSource, Environment env) {
        RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
        dataBinder.setConversionService(conversionService);
        dataBinder.setIgnoreNestedProperties(false);//false
        dataBinder.setIgnoreInvalidFields(false);//false
        dataBinder.setIgnoreUnknownFields(true);//true
        if (dataSourcePropertyValues == null) {
            Map<String, Object> rpr = new RelaxedPropertyResolver(env, DATASOURCE_PARAM_PREF).getSubProperties(DATASOURCE_PARAM_TYPE);
            Map<String, Object> values = new HashMap<>(rpr);
            // 排除已经设置的属性
            values.remove("type");
            values.remove(DATASOURCE_PARAM_PRPO_KEY_DRIVER_CLASS_NAME);
            values.remove("url");
            values.remove("username");
            values.remove("password");
            dataSourcePropertyValues = new MutablePropertyValues(values);
        }
        dataBinder.bind(dataSourcePropertyValues);
    }
    

    }
    相关工具类ApplicationContextUtil、EnvironmentUtil
    @Component
    public class ApplicationContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtil.applicationContext = applicationContext;
    }
    
    /**
     * 取得存储在静态变量中的ApplicationContext.
     */
    public static ApplicationContext getApplicationContext() {
        checkApplicationContext();
        return applicationContext;
    }
    
    /**
     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(String name) {
        checkApplicationContext();
        if (applicationContext.containsBean(name)) {
            return (T) applicationContext.getBean(name);
        }
        return null;
    }
    
    /**
     * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(Class<T> clazz) {
        checkApplicationContext();
        return (T) applicationContext.getBeansOfType(clazz);
    }
    
    private static void checkApplicationContext() {
        if (applicationContext == null)
            throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextUtil");
    }
    public synchronized static void registerSingletonBean(String beanName,Class clzz,Map<String,Object> original) {
        checkApplicationContext();
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ApplicationContextUtil.getApplicationContext().getAutowireCapableBeanFactory();
        if(beanFactory.containsBean(beanName)){
            removeBean(beanName);
        }
        GenericBeanDefinition definition = new GenericBeanDefinition();
        //类class
        definition.setBeanClass(clzz);
        //属性赋值
        definition.setPropertyValues(new MutablePropertyValues(original));
        //注册到spring上下文
        beanFactory.registerBeanDefinition(beanName, definition);
    }
    public synchronized static void registerSingletonBean(String beanName,Object obj,Map<String,Object> original) {
        checkApplicationContext();
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ApplicationContextUtil.getApplicationContext().getAutowireCapableBeanFactory();
        if(beanFactory.containsBean(beanName)){
            removeBean(beanName);
        }
        GenericBeanDefinition definition = new GenericBeanDefinition();
        //类class
        definition.setBeanClass(obj.getClass());
        //属性赋值
        definition.setPropertyValues(new MutablePropertyValues(original));
        //注册到spring上下文
        beanFactory.registerBeanDefinition(beanName, definition);
    }
    public synchronized static void registerSingletonBean(String beanName,Object obj) {
        registerSingletonBean(beanName,obj,BeanUtils.transBean2Map(obj));
    }
    /**
     * 删除spring中管理的bean
     * @param beanName
     */
    public static void removeBean(String beanName){
        ApplicationContext ctx = ApplicationContextUtil.getApplicationContext();
        DefaultListableBeanFactory acf = (DefaultListableBeanFactory) ctx.getAutowireCapableBeanFactory();
        if(acf.containsBean(beanName)) {
            acf.removeBeanDefinition(beanName);
        }
    }
    

    }

    @Component
    public class EnvironmentUtils implements EnvironmentAware {
    private static Environment environment ;
    @Override
    public void setEnvironment(Environment environment) {
    EnvironmentUtils.environment=environment;
    }
    public static Environment getEnvironment(){
    return EnvironmentUtils.environment;
    }
    }

    动态数据源注入完成,配置事务之类的照旧 数据源一律使用DynamicDataSource。
    在springBoot启动类加上注解@Import(DynamicDataSourceRegister.class)
    这样数据源加载就完成。
    接下来配置切面实现动态切换数据源,原理:将要访问的数据源放到session中(不一定是session,可以根据自定义业务需要变化)。每次切面都从session中拿到数据源的key (dev、 stg、prd)将这个key设置到当前线程
    用ThreadLocal来实现(见DynamicDataSourceContextHolder)
    /**
    * @ClassName DynamicDataSourceContextHolder
    * @Description 判断当前数据源是否存在(上下文)
    */
    public class DynamicDataSourceContextHolder {
    private static final ThreadLocal contextHolder = new ThreadLocal();
    public static List dataSourceIds = new ArrayList<>();
    //使用setDataSourceType设置当前的
    public static void setDataSourceType(String dataSourceType) {
    contextHolder.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return contextHolder.get();
    }
    
    public static void clearDataSourceType() {
        contextHolder.remove();
    }
     //判断指定DataSrouce当前是否存在
    public static boolean containsDataSource(String dataSourceId){
        return dataSourceIds.contains(dataSourceId);
    }
    

    }
    这也是DynamicDataSource的方法determineCurrentLookupKey()中从当前线程中取数据源的key
    @Override
    protected Object determineCurrentLookupKey() {
    return DynamicDataSourceContextHolder.getDataSourceType();
    }

    因此接下来的切面要做的事就是在业务方法之前将数据源key设置到当前线程中,在访问结束后再清楚。理论上可以配置环绕切面,为避免很多不必要麻烦这里采用前置、和后置配合使用
    项目结构 Controller层 service层 dao(mapper)层
    所以切面直接切service层
    @Component
    @Aspect
    public class MultipleDataSourceAspectAdvice implements Ordered {
    private static final Logger logger = Logger.getLogger(MultipleDataSourceAspectAdvice.class);

    @Before("execution(* com..mapper..*.*(..))")
    public void changeDataSource(JoinPoint point) throws Throwable {
        String environmentCode = SessionUtil.getSessionAttr(SessionUtil.ENVIRONMENT_CODE,"dev").toString();
        logger.info("Use DataSource : "+ environmentCode+"-"+ point.getSignature());
        DynamicDataSourceContextHolder.setDataSourceType(environmentCode);
    }
    
    @After("execution(* com..service..*.*(..))")
    public void clearDataSource2(JoinPoint point) {
        //方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
    @After("execution(* com..mapper..*.*(..))")
    public void clearDataSource(JoinPoint point) {
        //方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
    @Before("execution(* com..service..*.*(..))")
    public void switchDataSource(JoinPoint point) throws Throwable {
        String environmentCode = SessionUtil.getSessionAttr(SessionUtil.ENVIRONMENT_CODE,"dev").toString();
        logger.info("Use DataSource : "+ environmentCode+"-"+ point.getSignature());
        DynamicDataSourceContextHolder.setDataSourceType(environmentCode);
    }
    @Override
    public int getOrder() {
        return 1;
    }
    

    }
    但是如果只切service层会有问题存在,如果在service层调用service 就会导致里面的service调用完成之后将当前线程中的key清除了,最终导致接下来的dao获取不到key而走默认库
    因此这里又对dao层也切入一次做同样操作,就避免刚才情况。
    实际上到这里并不完美,因为如果事务切面先执行,即使现在切换了数据源也没有用了,事务会缓存数据源。所以必须要让切面在事务切面之前执行因此实现Ordered 接口的getOrder()方法。
    在启动类添加注解@EnableTransactionManagement(order = 2,proxyTargetClass=true)将事务切面的order设置为2或者更大。
    到此动态切换以及动态添加完成。

    这是动态添加数据源的接口的实现,非常简单
    @ResponseBody
    @RequestMapping(method = RequestMethod.POST,value=”submitEnvironmentConfig”)
    public ResponseData submitEnvironmentConfig(@RequestBody List environmentConfigs) throws Exception{
    if(environmentConfigs==null||environmentConfigs.size()==0){
    return new ResponseData.Builder().error(“没有数据!”);
    }
    //参数校验
    String codes=”“;
    String names=”“;
    //生成prop
    Properties properties=new Properties();
    for (EnvironmentConfig e:environmentConfigs){
    if(StringUtils.isEmpty(e.getCode())||
    StringUtils.isEmpty(e.getName())||
    StringUtils.isEmpty(e.getDriverClassName())||
    StringUtils.isEmpty(e.getPassword())||
    StringUtils.isEmpty(e.getUsername())||
    StringUtils.isEmpty(e.getUrl())){
    return new ResponseData.Builder().error(“所有参数不能为空!”);
    }
    codes=codes+”,”+e.getCode().trim();
    names=names+”,”+e.getName().trim();
    properties.setProperty(e.getCode().trim()+DynamicDataSourceRegisterUtil.DATASOURCE_PARAM_SPLIT+DynamicDataSourceRegisterUtil.DRUID_DATASOURCE_PARAM_PREF+DynamicDataSourceRegisterUtil.DATASOURCE_PARAM_PRPO_KEY_DRIVER_CLASS_NAME,e.getDriverClassName());
    properties.setProperty(e.getCode().trim()+DynamicDataSourceRegisterUtil.DATASOURCE_PARAM_SPLIT+DynamicDataSourceRegisterUtil.DRUID_DATASOURCE_PARAM_PREF+DruidDataSourceFactory.PROP_URL,e.getUrl());
    properties.setProperty(e.getCode().trim()+DynamicDataSourceRegisterUtil.DATASOURCE_PARAM_SPLIT+DynamicDataSourceRegisterUtil.DRUID_DATASOURCE_PARAM_PREF+DruidDataSourceFactory.PROP_USERNAME,e.getUsername());
    properties.setProperty(e.getCode().trim()+DynamicDataSourceRegisterUtil.DATASOURCE_PARAM_SPLIT+DynamicDataSourceRegisterUtil.DRUID_DATASOURCE_PARAM_PREF+DruidDataSourceFactory.PROP_PASSWORD,e.getPassword());
    }
    codes=codes.substring(1);
    names=names.substring(1);
    properties.setProperty(DynamicDataSourceRegisterUtil.DATASOURCE_PARAM_PREF+”keys”,codes);
    properties.setProperty(DynamicDataSourceRegisterUtil.DATASOURCE_PARAM_PREF+”names”,names);
    DynamicDataSourceRegisterUtil.refreshDataSoureProperties(properties);
    return new ResponseData.Builder(null).success();
    }

    谢谢大家,由于时间仓促可能比较混乱,希望给大家带来思路就可以了。欢迎交流。

    展开全文
  • springboot整合mybatisplus配置多数据源

    千次阅读 2019-11-24 22:57:48
    业务中,有一个需求,需要定时将一个库的部分业务表的数据同步到另一个库中,由于在现有的项目中进行改造比较麻烦,因此索性使用springboot和mybatisplus完成一个多数据源的环境搭建 简单说明一下,使用mybatis也是...

    前言

    业务中,有一个需求,需要定时将一个库的部分业务表的数据同步到另一个库中,由于在现有的项目中进行改造比较麻烦,因此索性使用springboot和mybatisplus完成一个多数据源的环境搭建

    简单说明一下,使用mybatis也是可以的,但项目中有技术要求,同时使用过mybatisplus之后,发现功能更加强大,而且使用很灵活,因此就选择了这个组件

    搭建流程

    开发步骤:

    • 新建springboot项目。
    • 导入依赖 --> devtools,lombok,web,thymeleaf,mysql,aop,mybatisplus,druid,swagger。
    • maven多环境配置。
    • 编写application.yml --> 项目端口,项目路径名,mybatisplus配置,mysql多数据源配置。
    • 新建DataSourceContextHolder 用于设置,获取,清空 当前线程内的数据源变量。
    • 新建 MultipleDataSource 实现 AbstractRoutingDataSource 类。重写determineCurrentLookupKey(),通过DataSourceContextHolder 获取数据源变量,用于当作lookupKey取出指定的数据源。
    • 新建DataSourceEnum 用于存放数据源名称。
    • 新建注解 DataSource,用于aop类中当作切入点来选择数据源。
    • 编写aop类 --> DataSourceAspect.java
    展开全文
  • SpringBoot配置数据源 1.创建项目 创建好项目以后,pom文件应该是这样的: <?xml version="1.0" encoding="UTF-8"?> <project xmlns=...
  • 不同数据源之间,事物管理,多个数据源之间同时commit或者同时rollback 兼容不同的连接池(dbpc,druid) 兼容mybatis,JPA 等不同的方式 (spring- boot -starter) 以上就是该项目实现的所有功能,因代码量和篇幅...
  • Springboot整合Mybatis多数据源配置

    万次阅读 热门讨论 2019-06-09 14:06:44
    数据源配置类: 次数据源配置类: 三、项目结构(重点是mapper结构和配置类里的路径对应) 四、启动类——启动类需要取消加载数据源自动配置 五、测试: controller: Service: Mapper: 数据...
  • 本文介绍两种切换数据库的方法。 方法1:数据库信息都配置在spring xml中,适用于一般数据库切换。...方法2:将数据库信息配置在默认数据源中,适用于切换数据库操作同一方法,相当于批量执行方法。...
  • 最近项目需要指出多数据源,同时支持事务回滚,这里记录一下 1、多数据源方式介绍 主要方式有以下两种: 通过配置多个SqlSessionFactory 来实现多数据源,这么做的话,未免过于笨重,而且无法实现动态添加数据源...
  • JasperReport中使用多个数据源的方法

    热门讨论 2012-04-17 17:31:53
    几经探索,终于寻找到在一个报表中使用多个数据源的方法,现在分享出来以便后来者少走弯路!网上很多资料说多数据源只能使用子报表方式,呵呵!
  • 其中 master 数据源一定是要配置 作为我们的默认数据源,其次cluster集群中,其他的数据不配置也不会影响程序员运行,如果你想添加新的一个数据源 就在cluster下新增一个数据源即可,其中key为必须项,用于数据源的...
  • 【JAVA】JAVA数据源

    千次阅读 多人点赞 2018-11-16 15:09:19
    文章目录前言连接池、数据源、JNDI连接池、数据源连接池思想JNDI直连数据源 前言 我们在进行数据访问,声明模板和repository之前【JAVA】Spring对JDBC的支持,都需要配置数据源用来连接数据库。数据源又可以分为两大...
  • 以前写过一篇教程,Springboot AOP方式切换多数据源(主从两库类似情况使用最佳): https://blog.csdn.net/qq_35387940/article/details/100122788 网上大多流传的springboot系列的切换多数据源都是以上那种写死...
  • 数据源的配置

    万次阅读 2018-10-23 20:14:06
    页面查询实在太慢,所以准备把dau_baseinfo表迁移到clickhouse,此时就需要再引入一个数据源,即clickhouse对应的数据源 下面开始配置多数据源 第一步:创建一个DynamicDataSource的类,继...
  • DBCP 数据源jar包

    千次下载 热门讨论 2012-08-13 17:44:34
    其中包括两个jar包,commons-dbcp.jar,commons-pool.jar
  • 为了提高数据库的查询效率,利用数据库主从机制,写走主库,查询走从库。如果只是实现一主一从类似简单的主从...分析AbstractRoutingDataSource可知,defaultTargetDataSource,表示默认的数据源;targetDataSour...
  • springboot+jpa多数据源配置实例

    千次阅读 2019-07-22 18:00:16
    springboot+jpa配置多数据源一直都在听说,没有实际动手演练,今天动手一试,发现有一些麻烦,麻烦的地方在于,需要严格区分多种数据源带来的变化,实体需要区分,dao层需要区分,service一般来说是事务控制的入口,...
  • 数据源与连接池

    千次阅读 2018-06-27 21:31:06
    因此需要对所有用到的JPA实现做一下整理,包括数据源。我们知道JDBC查询的时候都是需要四个步骤:1. 加载数据库驱动2. 获取数据库连接3. 查询4. 释放连接,遍历查询结果。我们要做的就是在第3步查询前,先call 一次...
  • 动态数据源切换

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

    千次阅读 2019-01-14 15:00:53
    springboot1.x与springboot2.x的不同版本还是有不少区别的,本文主要介绍在springboot2.1.1动态切换数据源的案例. 二.配置 1.引入依赖 &amp;lt;dependency&amp;gt; &amp;lt;groupId&amp;gt;...
  • 1、独立数据库,通过动态切换数据源来实现多租户,安全性最高,但成本也高。 2、共享数据库,隔离数据架构,比如使用oracle用多个schema。 3、共享数据库,共享数据库表,使用字段来区分不同租户,此...
  • spring + druid 配置动态配置数据源以及多数据源切换功能实现 数据源连接池使用druid 其他的数据源基本原理相同 spring中配置默认数据源连接池如下: <!-- 数据源配置, 使用 BoneCP 数据库连接池 --> &...
  • springboot 链接多数据库,动态获取数据源信息

    千次阅读 热门讨论 2019-04-09 18:12:32
    springboot 链接多数据库,动态获取数据源信息 前言 最近公司有个需求需要后端访问多个数据库,在网上查了半天资料,结果发现大部分都是配置的主从库这种数据源固定的情况,不符合我的需求,假设我们有这样一种...
  • Spring AOP实现多数据源切换

    千次阅读 2018-11-30 22:44:07
    有时候我们会遇到这样的场景:一个应用系统中存在多个数据源,需要根据不同业务场景进行临时切换。比如读写分离(也可以考虑使用Mycat等数据库中间件)等。 Spring提供了动态数据源的功能,可以让我们实现在对...
  • SSM多数据源配置(配置两个数据源) 前言:SSM项目需要配置多数据源,以达到操作不同数据库的数据,网上看到大多是通过动态切换数据源的方式,但在项目中总会出现问题,这里通过配置两个spring-mybatis.xml...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,343,468
精华内容 937,387
关键字:

数据源