精华内容
下载资源
问答
  • 上两遍已经描述了动态多数据源的原理和基础实现了,上面两篇都是从applition.yml中配置多数据源的,这里再拓展补充一下其他场景,如何读取数据源不从applition.yml中配置,实现从数据库中读取数据源配置并动态切换...

    上两遍已经描述了动态多数据源的原理和基础实现了,前面的数据源配置都是从application.yml中配置多数据源的,这里再拓展补充一下其他场景,如何读取数据源不从application.yml中配置,实现从数据库中读取数据源配置并动态切换数据源。

    一、回顾上篇的动态多数据源配置

    上篇:springboot动态多数据源配置和使用(二)

    1. 继承AbstractRoutingDataSource,重写抽象方法determineCurrentLookupKey()
    /**
     * 多数据源
     *
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicContextHolder.peek();
        }
    
    }
    
    1. 注入spring容器
        @Bean
        public DynamicDataSource dynamicDataSource(DataSourceProperties dataSourceProperties) {
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            //设置多个数据源的map
            dynamicDataSource.setTargetDataSources(getDynamicDataSource())
            //默认数据源
            DruidDataSource defaultDataSource = DynamicDataSourceFactory.buildDruidDataSource(dataSourceProperties);
            dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
            return dynamicDataSource;
        }
    
        private Map<Object, Object> getDynamicDataSource(){
            Map<String, DataSourceProperties> dataSourcePropertiesMap = properties.getDatasource();
            Map<Object, Object> targetDataSources = new HashMap<>(dataSourcePropertiesMap.size());
            dataSourcePropertiesMap.forEach((k, v) -> {
                DruidDataSource druidDataSource = DynamicDataSourceFactory.buildDruidDataSource(v);
                targetDataSources.put(k, druidDataSource);
            });
    
            return targetDataSources;
        }
    
    
    1. 可以看到多数据源配置是从application.yml读取出来,然后设置到DynamicDataSource对象里的targetDataSources属性
      dynamicDataSource.setTargetDataSources();

    分析

    从上面分析可以知道,重要的是targetDataSources这个存放多数据源的map属性。
    那么我们只要把targetDataSources这个map由配置文件获取创建dataSource然后放入map改写成由数据读读取出来的配置,再创建dataSource再放入targetDataSources这个map变量就可以实现我们想要的功能了。

    二、从数据库获取配置创建数据源

    这一步说难也不难,就把数据库的配置保存在数据库的表里面,在切面类切换数据源时读取数据库的配置,然后创建数据源,把创建的数据源通过put方法放入targetDataSources这个map即可,最后在切面类DynamicContextHolder.push(key)改变数据源

    但是这样子就很没效率,每次都从数据库读取配置,然后创建dataSource数据源。所以实际上我们是懒加载的模式,再用一个数据源缓存池pool来保存dataSource,如果缓存有了dataSource就不再从数据库读取了,直接从数据源缓存池的pool来获取数据源。

    具体实现

    1.创建表

    CREATE TABLE `oa_quick_knife_datasource` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      `datasource_name` varchar(20) DEFAULT '' COMMENT '数据源名称',
      `datasource_url` varchar(200) DEFAULT '' COMMENT '数据源url',
      `datasource_account` varchar(50) DEFAULT '' COMMENT '数据源帐号',
      `datasource_password` varchar(64) DEFAULT '' COMMENT '数据源密码',
      `remark` varchar(200) DEFAULT NULL COMMENT '备注',
      `is_show_type` tinyint(1) DEFAULT NULL COMMENT '数据源可见类型(1-全部人可见,2-部分人可见)',
      `datasource_type` tinyint(1) DEFAULT NULL COMMENT '默认mysql,暂时只支持mysql',
      `update_name` varchar(20) DEFAULT '' COMMENT '更新人',
      `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      `create_code` int(11) unsigned DEFAULT '0' COMMENT '创建人工号',
      `create_name` varchar(20) DEFAULT '' COMMENT '创建人',
      `update_code` int(11) unsigned DEFAULT '0' COMMENT '更新人工号',
      `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `deleted_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='数据源 ';
    

    注:datasource_password这个字段我们不要明文保存数据库密码,我们加密后再放入这个字段里面

    2.数据源缓存池

    数据源缓存池的类代码

    /**
     * 数据源缓存池
     */
    public class DataSourceCachePool {
        /** 数据源连接池缓存【本地 class缓存 - 不支持分布式】 */
        private static Map<String, DruidDataSource> dbSources = new HashMap<>();
        private static RedisTemplate<String, Object> redisTemplate;
    
        private static RedisTemplate<String, Object> getRedisTemplate() {
            if (redisTemplate == null) {
                redisTemplate = (RedisTemplate<String, Object>) SpringContextUtils.getBean("redisTemplate");
            }
            return redisTemplate;
        }
    
        /**
         * 获取多数据源缓存
         *
         * @param dbKey
         * @return
         */
        public static DynamicDataSourceModel getCacheDynamicDataSourceModel(String dbKey) {
            String redisCacheKey = ConfigConstant.SYS_DYNAMICDB_CACHE + dbKey;
            if (getRedisTemplate().hasKey(redisCacheKey)) {
                String model = (String)getRedisTemplate().opsForValue().get(redisCacheKey);
                return  JSON.parseObject(model,DynamicDataSourceModel.class);
            }
            DatasourceDao datasourceDao = (DatasourceDao)SpringContextUtils.getBean("datasourceDao");
            DynamicDataSourceModel dbSource = datasourceDao.getDynamicDbSourceByCode(dbKey);
            try{
                dbSource.setDbPassword(AesUtil.decryptBySalt(dbSource.getDbPassword(),dbSource.getId()));
            }catch (Exception e){
                throw new RRException("动态数据源密钥解密失败,dbKey:"+dbKey);
            }
    
            if (dbSource != null) {
                getRedisTemplate().opsForValue().set(redisCacheKey, JSONObject.toJSONString(dbSource));
            }
            return dbSource;
        }
    
        public static DruidDataSource getCacheBasicDataSource(String dbKey) {
            return dbSources.get(dbKey);
        }
    
        /**
         * put 数据源缓存
         *
         * @param dbKey
         * @param db
         */
        public static void putCacheBasicDataSource(String dbKey, DruidDataSource db) {
            dbSources.put(dbKey, db);
        }
    
        /**
         * 清空数据源缓存
         */
        public static void cleanAllCache() {
            //关闭数据源连接
            for(Map.Entry<String, DruidDataSource> entry : dbSources.entrySet()){
                String dbkey = entry.getKey();
                DruidDataSource druidDataSource = entry.getValue();
                if(druidDataSource!=null && druidDataSource.isEnable()){
                    druidDataSource.close();
                }
                //清空redis缓存
                getRedisTemplate().delete(ConfigConstant.SYS_DYNAMICDB_CACHE + dbkey);
            }
            //清空缓存
            dbSources.clear();
        }
    
        public static void removeCache(String dbKey) {
            //关闭数据源连接
            DruidDataSource druidDataSource = dbSources.get(dbKey);
            if(druidDataSource!=null && druidDataSource.isEnable()){
                druidDataSource.close();
            }
            //清空redis缓存
            getRedisTemplate().delete(ConfigConstant.SYS_DYNAMICDB_CACHE + dbKey);
            //清空缓存
            dbSources.remove(dbKey);
        }
    
    }
    

    上面的数据源缓存池主要代码是下面getCacheDynamicDataSourceModel方法的这段

    这个方法的逻辑是先从redis缓存数据源配置,redis没有则从数据库获取,以及获取的配置的数据库密码是加密的,所以这里还要再解密

        /**
         * 获取多数据源缓存配置
         *
         * @param dbKey
         * @return
         */
        public static DynamicDataSourceModel getCacheDynamicDataSourceModel(String dbKey) {
            String redisCacheKey = ConfigConstant.SYS_DYNAMICDB_CACHE + dbKey;
            if (getRedisTemplate().hasKey(redisCacheKey)) {
                String model = (String)getRedisTemplate().opsForValue().get(redisCacheKey);
                return  JSON.parseObject(model,DynamicDataSourceModel.class);
            }
            DatasourceDao datasourceDao = (DatasourceDao)SpringContextUtils.getBean("datasourceDao");
            DynamicDataSourceModel dbSource = datasourceDao.getDynamicDbSourceByCode(dbKey);
            try{
                dbSource.setDbPassword(AesUtil.decryptBySalt(dbSource.getDbPassword(),dbSource.getId()));
            }catch (Exception e){
                throw new RRException("动态数据源密钥解密失败,dbKey:"+dbKey);
            }
    
            if (dbSource != null) {
                getRedisTemplate().opsForValue().set(redisCacheKey, JSONObject.toJSONString(dbSource));
            }
            return dbSource;
        }
    

    还有一个重要的方法,把数据源放入缓存池的dbSource这个map属性里面

        /**
         * put 数据源缓存
         *
         * @param dbKey
         * @param db
         */
        public static void putCacheBasicDataSource(String dbKey, DruidDataSource db) {
            dbSources.put(dbKey, db);
        }
    

    3.再写一个工具类DynamicDBUtil 操作数据源缓存池

    这个类的核心方法是getDbSourceByDbKey(),先判断缓存池有没有对应key的数据源,没有则读取数据源配置(先从redis读配置,没有再从数据库读配置),根据配置创建DruidDataSource数据源,再把数据源放入缓存池

    getDbSourceByDbKey这个方法的dbKey是指能根据这个key找到数据库对应的记录,这里指该表的id

    /**
     * Spring JDBC 实时数据库访问
     *
     */
    @Slf4j
    public class DynamicDBUtil {
    
        /**
         * 通过 dbKey ,获取数据源
         *
         * @param dbKey
         * @return
         */
        public static DruidDataSource getDbSourceByDbKey(final String dbKey) {
    
            //先判断缓存中是否存在数据库链接
            DruidDataSource cacheDbSource = DataSourceCachePool.getCacheBasicDataSource(dbKey);
            if (cacheDbSource != null && !cacheDbSource.isClosed()) {
                log.debug("--------getDbSourceBydbKey------------------从缓存中获取DB连接-------------------");
                return cacheDbSource;
            } else {
                //获取多数据源配置
                DynamicDataSourceModel dbSource = DataSourceCachePool.getCacheDynamicDataSourceModel(dbKey);
                DruidDataSource dataSource = getJdbcDataSource(dbSource);
                if(dataSource!=null && dataSource.isEnable()){
                    DataSourceCachePool.putCacheBasicDataSource(dbKey, dataSource);
                }else{
                    throw new RRException("动态数据源连接失败,dbKey:"+dbKey);
                }
                log.info("--------getDbSourceBydbKey------------------创建DB数据库连接-------------------");
                return dataSource;
            }
        }
    
        /**
         * 获取数据源【最底层方法,不要随便调用】
         *
         * @param dbSource
         * @return
         */
        private static DruidDataSource getJdbcDataSource(final DynamicDataSourceModel dbSource) {
            DruidDataSource dataSource = new DruidDataSource();
    
            String driverClassName = dbSource.getDbDriver();
            String url = dbSource.getDbUrl();
            String dbUser = dbSource.getDbUsername();
            String dbPassword = dbSource.getDbPassword();
            dataSource.setDriverClassName(driverClassName);
            dataSource.setUrl(url);
            //dataSource.setValidationQuery("SELECT 1 FROM DUAL");
            dataSource.setTestWhileIdle(true);
            dataSource.setTestOnBorrow(false);
            dataSource.setTestOnReturn(false);
            dataSource.setBreakAfterAcquireFailure(true);
            dataSource.setConnectionErrorRetryAttempts(0);
            dataSource.setUsername(dbUser);
            dataSource.setMaxWait(60000);
            dataSource.setPassword(dbPassword);
    
            log.info("******************************************");
            log.info("*                                        *");
            log.info("*====【"+dbSource.getCode()+"】=====Druid连接池已启用 ====*");
            log.info("*                                        *");
            log.info("******************************************");
            return dataSource;
        }
    
        /**
         * 关闭数据库连接池
         *
         * @param dbKey
         * @return
         */
        public static void closeDbKey(final String dbKey) {
            DruidDataSource dataSource = getDbSourceByDbKey(dbKey);
            try {
                if (dataSource != null && !dataSource.isClosed()) {
                    dataSource.getConnection().commit();
                    dataSource.getConnection().close();
                    dataSource.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
    
        private static JdbcTemplate getJdbcTemplate(String dbKey) {
            DruidDataSource dataSource = getDbSourceByDbKey(dbKey);
            return new JdbcTemplate(dataSource);
        }
    
        /**
         * 获取连接
         * @param url
         * @param username
         * @param password
         * @param driverName
         * @return
         */
        public static Connection getConn(String url,String username,String password,String driverName) {
            Connection conn = null;
            try {
                Class.forName(driverName);
                conn = DriverManager.getConnection(url, username, password);
            } catch (Exception e) {
                throw new RRException("无法连接,问题:"+e.getMessage(), e);
            }
    
            return conn;
        }
    
        /**
         * 关闭数据库连接
         * @param
         */
        public static void closeConnection(Connection conn) {
            try {
                if(conn!=null){
                    conn.close();
                }
            } catch (SQLException e) {
                throw new RRException("close connection failure", e);
            }
        }
    
    }
    

    4. 继承继承AbstractRoutingDataSource,重写抽象方法determineCurrentLookupKey()

    这里比上一篇的DynamicDataSource新增了targetDataSources静态变量和setDataSource()静态方法。

    targetDataSources这个属性用于存放多数据源

    /**
     * 多数据源
     *
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        public static Map<Object, Object> targetDataSources = new ConcurrentHashMap<>(10);
    
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicContextHolder.peek();
        }
    
        public static void setDataSource(String dbKey) throws Exception{
            if(!DynamicDataSource.targetDataSources.containsKey(dbKey)){
                DruidDataSource dataSource = DynamicDBUtil.getDbSourceByDbKey(dbKey);
                DynamicDataSource.targetDataSources.put(dbKey,dataSource);
            }
            //切换动态多数据源的dbKey
            DynamicContextHolder.push(dbKey);
            DynamicDataSource dynamicDataSource = (DynamicDataSource) SpringContextUtils.getBean("dynamicDataSource");
            //使得修改后的targetDataSources生效
            dynamicDataSource.afterPropertiesSet();
        }
    
    }
    

    5. 数据源配置类

    下面代码通过dynamicDataSource.setTargetDataSources(DynamicDataSource.targetDataSources)把值引用赋值给dynamicDataSource对象(即指向同一块内存,修改了静态变量targetDataSources,就相当于修改了dynamicDataSource对象里面的targetDataSources属性)

    @Configuration
    @EnableConfigurationProperties(DynamicDataSourceProperties.class)
    public class DynamicDataSourceConfig {
        @Autowired
        private DynamicDataSourceProperties properties;
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.druid")
        public DataSourceProperties dataSourceProperties() {
            return new DataSourceProperties();
        }
    
        @Bean
        public DruidDataSource defaultDataSource(DataSourceProperties dataSourceProperties) {
            //默认数据源,通过配置获取创建
            DruidDataSource defaultDataSource = DynamicDataSourceFactory.buildDruidDataSource(dataSourceProperties);
            return defaultDataSource;
        }
    
        @Bean
        @Primary
        @DependsOn({"defaultDataSource"})
        public DynamicDataSource dynamicDataSource(DruidDataSource defaultDataSource) {
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            //设置targetDataSources(通过数据库配置获取,首次创建没有数据源)
            dynamicDataSource.setTargetDataSources(DynamicDataSource.targetDataSources);
    
            //默认数据源
            dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
            return dynamicDataSource;
        }
    
    }
    

    6. 再写一个注解以及实现这个注解的切面类就可以了

    /**
     * 多数据源注解
     *
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface DataSource {
        String value() default "";
    }
    
    /**
     * 多数据源,切面处理类
     *
     */
    @Aspect
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public class DataSourceAspect {
        protected Logger logger = LoggerFactory.getLogger(getClass());
    
        @Pointcut("@annotation(io.renren.datasource.annotation.DataSource) " +
                "|| @within(io.renren.datasource.annotation.DataSource)")
        public void dataSourcePointCut() {
    
        }
    
        @Around("dataSourcePointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Class targetClass = point.getTarget().getClass();
            Method method = signature.getMethod();
    
            DataSource targetDataSource = (DataSource)targetClass.getAnnotation(DataSource.class);
            DataSource methodDataSource = method.getAnnotation(DataSource.class);
            if(targetDataSource != null || methodDataSource != null){
                String value;
                if(methodDataSource != null){
                    value = methodDataSource.value();
                }else {
                    value = targetDataSource.value();
                }
                //根据dbKey动态设置数据源
                DynamicDataSource.setDataSource(dbKey);
                logger.debug("set datasource is {}", value);
            }
    
            try {
                return point.proceed();
            } finally {
                DynamicContextHolder.poll();
                logger.debug("clean datasource");
            }
        }
    }
    

    到这一步就已完成了,然后把DataSource注解加到service的类或方法上,即可实现操作指定的多数据源。

    三、通过接口的入参来指定数据源

    上面的注解DataSource的value是写死在代码里面的,但是我们有这样的需求,前端根据接口入参来操作指定数据源的数据。

    所以我们在上面的基础上,再改造一下
    再写两个注解

    /**
     * 多数据源注解-注解数据源的dbKey
     *
     * @author ZhangXinLin
     */
    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface DbKey {
    }
    

    这个自定义注解DbKey,是作用在参数上的,标志该参数是用来指定数据源的dbKey

    /**
     * 多数据源注解
     *
     * @author ZhangXinLin
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface DynamicDataSource {
    
    }
    
    

    DynamicDataSource的自定义注解是用在controller的方法上

    DynamicDataSource注解的切面类

    这个切面类是根据方法的入参dbKey来动态切换数据源,核心代码是调用这行代码
    //根据dbKey动态设置数据源
    DynamicDataSource.setDataSource(dbKey);

    /**
     * @Description: 动态加载多数据源(启动后加载)
     **/
    @Aspect
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public class DynamicDataSourceAspect {
        protected Logger logger = LoggerFactory.getLogger(getClass());
    
        @Pointcut("@annotation(io.renren.datasource.annotation.DynamicDataSource) " +
                "|| @within(io.renren.datasource.annotation.DynamicDataSource)")
        public void dynamicdataSourcePointCut() {
    
        }
    
        @Around("dynamicdataSourcePointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            //获取参数,根据参数获取数据源
            String dbKey = getDbKey(point);
    
            if( dbKey != null){
                //根据dbKey动态设置数据源
                DynamicDataSource.setDataSource(dbKey);
            }
            try {
                return point.proceed();
            } finally {
                DynamicContextHolder.poll();
                logger.debug("clean datasource");
            }
        }
    
    
        /**
         * 根据@DbKey注解获取数据源的dbKey
         * @param point
         * @return
         */
        private String getDbKey(ProceedingJoinPoint point) {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            Object[] args = point.getArgs();
            String value = null;
            //参数注解,1维是参数,2维是注解
            Annotation[][] annotations = method.getParameterAnnotations();
            for (int i = 0; i < annotations.length; i++) {
                Object param = args[i];
                Annotation[] paramAnn = annotations[i];
                //参数为空,直接下一个参数
                if (param == null || paramAnn.length == 0) {
                    continue;
                }
                for (Annotation pAnnotation : paramAnn) {
                    if (pAnnotation.annotationType().equals(DbKey.class)) {
                        value =  param.toString();
                        break;
                    }
                }
            }
            return value;
        }
    }
    

    然后在controller的方法上加上注解@DynamicDataSource,以及入参加上注解@Dbkey

        /**
         * 查看数据源的所有表列表
         * @param id
         * @return
         */
        @DynamicDataSource
        @RequestMapping("/getTableList/{id}")
        public R getTableList(@PathVariable("id") @DbKey Integer id){
            List<Map<String, Object>> list = datasourceService.queryTableList(id);
            return R.ok().put("list", list);
        }
    

    看看实际效果

    1. 首先我们表的数据源的增删改写一个页面
      在这里插入图片描述

    在这里插入图片描述

    1. 在页面调用getTableList接口来切换数据源的数据

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    可以看到,前端页面选择不同的数据库,后端接口就会根据dbKey的入参来动态切换数据源,从而查询出不同数据源的表名列表



    源码

    源码在一个还没有写完的快速开发平台的项目里面(功能可以在线编写模版,线上配置数据源,不用改代码就可以编写开发模版,生成不同系统的基础代码);
    这个项目还没写完,后面写完也会开源出来,所以这里的源码暂时没有

    展开全文
  • 之前写过一篇博客《Spring+Mybatis+Mysql搭建分布式数据库访问框架》描述如何通过Spring+Mybatis配置动态数据源访问多个数据库。但是之前的方案有一些限制(原博客中也描述了):只适用于数据库数量不多且固定的情况。...

    之前写过一篇博客《Spring+Mybatis+Mysql搭建分布式数据库访问框架》描述如何通过Spring+Mybatis配置动态数据源访问多个数据库。但是之前的方案有一些限制(原博客中也描述了):只适用于数据库数量不多且固定的情况。针对数据库动态增加的情况无能为力。

    下面讲的方案能支持数据库动态增删,数量不限。

    数据库环境准备

    下面一Mysql为例,先在本地建3个数据库用于测试。需要说明的是本方案不限数据库数量,支持不同的数据库部署在不同的服务器上。如图所示db_project_001、db_project_002、db_project_003。

    05ea32aa4fd81cd581c975f5831b9f9c.png

    搭建Java后台微服务项目

    创建一个Spring Boot的maven项目:

    a455585d000ed41b93b864ff7e5cd784.png

    config:数据源配置管理类。

    datasource:自己实现的数据源管理逻辑。

    dbmgr:管理了项目编码与数据库IP、名称的映射关系(实际项目中这部分数据保存在redis缓存中,可动态增删)。

    mapper:数据库访问接口。

    model:映射模型。

    rest:微服务对外发布的restful接口,这里用来测试。

    application.yml:配置了数据库的JDBC参数。

    详细的代码实现

    1. 添加数据源配置

    package com.elon.dds.config;

    import javax.sql.DataSource;

    import org.apache.ibatis.session.SqlSessionFactory;

    import org.mybatis.spring.SqlSessionFactoryBean;

    import org.mybatis.spring.annotation.MapperScan;

    import org.springframework.beans.factory.annotation.Qualifier;

    import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;

    import org.springframework.boot.context.properties.ConfigurationProperties;

    import org.springframework.context.annotation.Bean;

    import org.springframework.context.annotation.Configuration;

    import com.elon.dds.datasource.DynamicDataSource;

    /**

    * 数据源配置管理。

    *

    * @author elon

    * @version 2018年2月26日

    */

    @Configuration

    @MapperScan(basePackages="com.elon.dds.mapper", value="sqlSessionFactory")

    public class DataSourceConfig {

    /**

    * 根据配置参数创建数据源。使用派生的子类。

    *

    * @return 数据源

    */

    @Bean(name="dataSource")

    @ConfigurationProperties(prefix="spring.datasource")

    public DataSource getDataSource() {

    DataSourceBuilder builder = DataSourceBuilder.create();

    builder.type(DynamicDataSource.class);

    return builder.build();

    }

    /**

    * 创建会话工厂。

    *

    * @param dataSource 数据源

    * @return 会话工厂

    */

    @Bean(name="sqlSessionFactory")

    public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) {

    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

    bean.setDataSource(dataSource);

    try {

    return bean.getObject();

    } catch (Exception e) {

    e.printStackTrace();

    return null;

    }

    }

    }

    2.定义动态数据源

    1)  首先增加一个数据库标识类,用于区分不同的数据库访问。

    由于我们为不同的project创建了单独的数据库,所以使用项目编码作为数据库的索引。而微服务支持多线程并发的,采用线程变量。

    package com.elon.dds.datasource;

    /**

    * 数据库标识管理类。用于区分数据源连接的不同数据库。

    *

    * @author elon

    * @version 2018-02-25

    */

    public class DBIdentifier {

    /**

    * 用不同的工程编码来区分数据库

    */

    private static ThreadLocal projectCode = new ThreadLocal();

    public static String getProjectCode() {

    return projectCode.get();

    }

    public static void setProjectCode(String code) {

    projectCode.set(code);

    }

    }

    2)  从DataSource派生了一个DynamicDataSource,在其中实现数据库连接的动态切换

    import java.lang.reflect.Field;

    import java.sql.Connection;

    import java.sql.SQLException;

    import org.apache.logging.log4j.LogManager;

    import org.apache.logging.log4j.Logger;

    import org.apache.tomcat.jdbc.pool.DataSource;

    import org.apache.tomcat.jdbc.pool.PoolProperties;

    import com.elon.dds.dbmgr.ProjectDBMgr;

    /**

    * 定义动态数据源派生类。从基础的DataSource派生,动态性自己实现。

    *

    * @author elon

    * @version 2018-02-25

    */

    public class DynamicDataSource extends DataSource {

    private static Logger log = LogManager.getLogger(DynamicDataSource.class);

    /**

    * 改写本方法是为了在请求不同工程的数据时去连接不同的数据库。

    */

    @Override

    public Connection getConnection(){

    String projectCode = DBIdentifier.getProjectCode();

    //1、获取数据源

    DataSource dds = DDSHolder.instance().getDDS(projectCode);

    //2、如果数据源不存在则创建

    if (dds == null) {

    try {

    DataSource newDDS = initDDS(projectCode);

    DDSHolder.instance().addDDS(projectCode, newDDS);

    } catch (IllegalArgumentException | IllegalAccessException e) {

    log.error("Init data source fail. projectCode:" + projectCode);

    return null;

    }

    }

    dds = DDSHolder.instance().getDDS(projectCode);

    try {

    return dds.getConnection();

    } catch (SQLException e) {

    e.printStackTrace();

    return null;

    }

    }

    /**

    * 以当前数据对象作为模板复制一份。

    *

    * @return dds

    * @throws IllegalAccessException

    * @throws IllegalArgumentException

    */

    private DataSource initDDS(String projectCode) throws IllegalArgumentException, IllegalAccessException {

    DataSource dds = new DataSource();

    // 2、复制PoolConfiguration的属性

    PoolProperties property = new PoolProperties();

    Field[] pfields = PoolProperties.class.getDeclaredFields();

    for (Field f : pfields) {

    f.setAccessible(true);

    Object value = f.get(this.getPoolProperties());

    try

    {

    f.set(property, value);

    }

    catch (Exception e)

    {

    log.info("Set value fail. attr name:" + f.getName());

    continue;

    }

    }

    dds.setPoolProperties(property);

    // 3、设置数据库名称和IP(一般来说,端口和用户名、密码都是统一固定的)

    String urlFormat = this.getUrl();

    String url = String.format(urlFormat, ProjectDBMgr.instance().getDBIP(projectCode),

    ProjectDBMgr.instance().getDBName(projectCode));

    dds.setUrl(url);

    return dds;

    }

    }

    3)  通过DDSTimer控制数据连接释放(超过指定时间未使用的数据源释放)

    package com.elon.dds.datasource;

    import org.apache.tomcat.jdbc.pool.DataSource;

    /**

    * 动态数据源定时器管理。长时间无访问的数据库连接关闭。

    *

    * @author elon

    * @version 2018年2月25日

    */

    public class DDSTimer {

    /**

    * 空闲时间周期。超过这个时长没有访问的数据库连接将被释放。默认为10分钟。

    */

    private static long idlePeriodTime = 10 * 60 * 1000;

    /**

    * 动态数据源

    */

    private DataSource dds;

    /**

    * 上一次访问的时间

    */

    private long lastUseTime;

    public DDSTimer(DataSource dds) {

    this.dds = dds;

    this.lastUseTime = System.currentTimeMillis();

    }

    /**

    * 更新最近访问时间

    */

    public void refreshTime() {

    lastUseTime = System.currentTimeMillis();

    }

    /**

    * 检测数据连接是否超时关闭。

    *

    * @return true-已超时关闭; false-未超时

    */

    public boolean checkAndClose() {

    if (System.currentTimeMillis() - lastUseTime > idlePeriodTime)

    {

    dds.close();

    return true;

    }

    return false;

    }

    public DataSource getDds() {

    return dds;

    }

    }

    4)      增加DDSHolder来管理不同的数据源,提供数据源的添加、查询功能

    package com.elon.dds.datasource;

    import java.util.HashMap;

    import java.util.Iterator;

    import java.util.Map;

    import java.util.Map.Entry;

    import java.util.Timer;

    import org.apache.tomcat.jdbc.pool.DataSource;

    /**

    * 动态数据源管理器。

    *

    * @author elon

    * @version 2018年2月25日

    */

    public class DDSHolder {

    /**

    * 管理动态数据源列表。

    */

    private Map ddsMap = new HashMap();

    /**

    * 通过定时任务周期性清除不使用的数据源

    */

    private static Timer clearIdleTask = new Timer();

    static {

    clearIdleTask.schedule(new ClearIdleTimerTask(), 5000, 60 * 1000);

    };

    private DDSHolder() {

    }

    /*

    * 获取单例对象

    */

    public static DDSHolder instance() {

    return DDSHolderBuilder.instance;

    }

    /**

    * 添加动态数据源。

    *

    * @param projectCode 项目编码

    * @param dds dds

    */

    public synchronized void addDDS(String projectCode, DataSource dds) {

    DDSTimer ddst = new DDSTimer(dds);

    ddsMap.put(projectCode, ddst);

    }

    /**

    * 查询动态数据源

    *

    * @param projectCode 项目编码

    * @return dds

    */

    public synchronized DataSource getDDS(String projectCode) {

    if (ddsMap.containsKey(projectCode)) {

    DDSTimer ddst = ddsMap.get(projectCode);

    ddst.refreshTime();

    return ddst.getDds();

    }

    return null;

    }

    /**

    * 清除超时无人使用的数据源。

    */

    public synchronized void clearIdleDDS() {

    Iterator> iter = ddsMap.entrySet().iterator();

    for (; iter.hasNext(); ) {

    Entry entry = iter.next();

    if (entry.getValue().checkAndClose())

    {

    iter.remove();

    }

    }

    }

    /**

    * 单例构件类

    * @author elon

    * @version 2018年2月26日

    */

    private static class DDSHolderBuilder {

    private static DDSHolder instance = new DDSHolder();

    }

    }

    5)      定时器任务ClearIdleTimerTask用于定时清除空闲的数据源

    package com.elon.dds.datasource;

    import java.util.TimerTask;

    /**

    * 清除空闲连接任务。

    *

    * @author elon

    * @version 2018年2月26日

    */

    public class ClearIdleTimerTask extends TimerTask {

    @Override

    public void run() {

    DDSHolder.instance().clearIdleDDS();

    }

    }

    3.       管理项目编码与数据库IP和名称的映射关系

    package com.elon.dds.dbmgr;

    import java.util.HashMap;

    import java.util.Map;

    /**

    * 项目数据库管理。提供根据项目编码查询数据库名称和IP的接口。

    * @author elon

    * @version 2018年2月25日

    */

    public class ProjectDBMgr {

    /**

    * 保存项目编码与数据名称的映射关系。这里是硬编码,实际开发中这个关系数据可以保存到redis缓存中;

    * 新增一个项目或者删除一个项目只需要更新缓存。到时这个类的接口只需要修改为从缓存拿数据。

    */

    private Map dbNameMap = new HashMap();

    /**

    * 保存项目编码与数据库IP的映射关系。

    */

    private Map dbIPMap = new HashMap();

    private ProjectDBMgr() {

    dbNameMap.put("project_001", "db_project_001");

    dbNameMap.put("project_002", "db_project_002");

    dbNameMap.put("project_003", "db_project_003");

    dbIPMap.put("project_001", "127.0.0.1");

    dbIPMap.put("project_002", "127.0.0.1");

    dbIPMap.put("project_003", "127.0.0.1");

    }

    public static ProjectDBMgr instance() {

    return ProjectDBMgrBuilder.instance;

    }

    // 实际开发中改为从缓存获取

    public String getDBName(String projectCode) {

    if (dbNameMap.containsKey(projectCode)) {

    return dbNameMap.get(projectCode);

    }

    return "";

    }

    //实际开发中改为从缓存中获取

    public String getDBIP(String projectCode) {

    if (dbIPMap.containsKey(projectCode)) {

    return dbIPMap.get(projectCode);

    }

    return "";

    }

    private static class ProjectDBMgrBuilder {

    private static ProjectDBMgr instance = new ProjectDBMgr();

    }

    }

    4.       定义数据库访问的mapper

    package com.elon.dds.mapper;

    import java.util.List;

    import org.apache.ibatis.annotations.Mapper;

    import org.apache.ibatis.annotations.Result;

    import org.apache.ibatis.annotations.Results;

    import org.apache.ibatis.annotations.Select;

    import com.elon.dds.model.User;

    /**

    * Mybatis映射接口定义。

    *

    * @author elon

    * @version 2018年2月26日

    */

    @Mapper

    public interface UserMapper

    {

    /**

    * 查询所有用户数据

    * @return 用户数据列表

    */

    @Results(value= {

    @Result(property="userId", column="id"),

    @Result(property="name", column="name"),

    @Result(property="age", column="age")

    })

    @Select("select id, name, age from tbl_user")

    List getUsers();

    }

    5.       定义查询对象模型

    package com.elon.dds.model;

    public class User

    {

    private int userId = -1;

    private String name = "";

    private int age = -1;

    @Override

    public String toString()

    {

    return "name:" + name + "|age:" + age;

    }

    public int getUserId()

    {

    return userId;

    }

    public void setUserId(int userId)

    {

    this.userId = userId;

    }

    public String getName()

    {

    return name;

    }

    public void setName(String name)

    {

    this.name = name;

    }

    public int getAge()

    {

    return age;

    }

    public void setAge(int age)

    {

    this.age = age;

    }

    }

    6.       定义查询用户数据的restful接口

    package com.elon.dds.rest;

    import java.util.List;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.web.bind.annotation.RequestMapping;

    import org.springframework.web.bind.annotation.RequestMethod;

    import org.springframework.web.bind.annotation.RequestParam;

    import org.springframework.web.bind.annotation.RestController;

    import com.elon.dds.datasource.DBIdentifier;

    import com.elon.dds.mapper.UserMapper;

    import com.elon.dds.model.User;

    /**

    * 用户数据访问接口。

    *

    * @author elon

    * @version 2018年2月26日

    */

    @RestController

    @RequestMapping(value="/user")

    public class WSUser {

    @Autowired

    private UserMapper userMapper;

    /**

    * 查询项目中所有用户信息

    *

    * @param projectCode 项目编码

    * @return 用户列表

    */

    @RequestMapping(value="/v1/users", method=RequestMethod.GET)

    public List queryUser(@RequestParam(value="projectCode", required=true) String projectCode)

    {

    DBIdentifier.setProjectCode(projectCode);

    return userMapper.getUsers();

    }

    }

    要求每次查询都要带上projectCode参数。

    7.       编写Spring Boot App的启动代码

    package com.elon.dds;

    import org.springframework.boot.SpringApplication;

    import org.springframework.boot.autoconfigure.SpringBootApplication;

    /**

    * Hello world!

    *

    */

    @SpringBootApplication

    public class App

    {

    public static void main( String[] args )

    {

    System.out.println( "Hello World!" );

    SpringApplication.run(App.class, args);

    }

    }

    8.       在application.yml中配置数据源

    其中的数据库IP和数据库名称使用%s。在查询用户数据中动态切换。

    spring:

    datasource:

    url: jdbc:mysql://%s:3306/%s?useUnicode=true&characterEncoding=utf-8

    username: root

    password:

    driver-class-name: com.mysql.jdbc.Driver

    logging:

    config: classpath:log4j2.xml

    测试方案

    1.       查询project_001的数据,正常返回

    6822cf7de1a650e3db25e8a6f6955936.png

    2.       查询project_002的数据,正常返回

    f09401ecc8a37675fca12f6aff3bf035.png

    展开全文
  • 通过SharePoint Designer 2010, 你可以使用三个不同数据提供程序中的任意一个连接到数据库。 这些数据提供程序不是相互排斥的。 例如, microsoft SQL Server 2000 和更高版本是 ole db 兼容的, 并且可以通过使用 ...

    希望使用哪种数据提供程序?

    通过SharePoint Designer 2010, 你可以使用三个不同数据提供程序中的任意一个连接到数据库。 这些数据提供程序不是相互排斥的。 例如, microsoft SQL Server 2000 和更高版本是 ole db 兼容的, 并且可以通过使用 Microsoft .net Framework 数据提供程序 for ole db 进行访问。 但是, SQL server 的 Microsoft .net Framework 数据提供程序专门设计用于处理 Microsoft SQL server 2000 和更高版本, 如果你的数据库位于这两个服务器产品中的任何一种, 则可能是最佳选择。

    数据提供程序是允许客户端 (在本例中为SharePoint Designer 2010 ) 与数据库通信的软件组件。 最初, 每个数据库管理系统都是唯一的, 每个数据库管理系统都有自己的用于访问和更新数据的编程语言。 ODBC 是对各种不同数据库管理系统进行标准化访问的第一次尝试。 数据视图不支持 ODBC 提供程序。 OLE DB 在 ODBC 后引入, 可提供更灵活的数据库访问。 许多符合 ODBC 的数据库也兼容 OLE DB。

    有关要使用的数据提供程序的详细信息, 请参阅以下图表。

    如果要连接到的数据库 .。。使用 .。。

    microsoft sql server 2000、microsoft sql server 2005 或 microsoft sql server 2008

    用于 SQL Server 的 Microsoft .net Framework 数据访问接口

    OLE DB 兼容

    用于 OLE DB 的 Microsoft .net Framework 数据访问接口

    Oracle

    适用于 Oracle 的 Microsoft .net Framework 数据提供程序

    ODBC 兼容

    适用于 ODBC 的 Microsoft .net Framework 数据访问接口

    注意数据视图不支持到任何类型的数据库 (包括 MySQL) 的数据库连接的 ODBC 提供程序。 在 "配置数据库连接" 对话框中, 应忽略使用 ODBC 提供程序的选项。

    展开全文
  • Spring多数据源事务

    2021-03-10 06:08:19
    前言接着上一篇文章Spring事务基础,本文主要是关于Spring多数据源的情况下如何保证事务正常回滚。这里也是使用大家广泛使用的jta-atomikos进行,我只是做一些总结方便以后自己直接拿来用。如果你非常着急,那么可以...

    前言

    接着上一篇文章Spring事务基础,本文主要是关于Spring多数据源的情况下如何保证事务正常回滚。这里也是使用大家广泛使用的jta-atomikos进行,我只是做一些总结方便以后自己直接拿来用。

    如果你非常着急,那么可以直接下载这个项目看看即可:

    总体思路

    网上已经有很多关于jta-atomikos的相关文章,本文可能有点绕,不容易看得懂,所以在此描述一下思路:1、配置mybatis以及druid使得其能够实现连接多个数据源。 2、通过自定义数据源,将多个数据源的事务整合成一个SqlSession,进而实现统一管理事务。 3、利用AOP以及自定义注解实现动态的切换数据源(即是A的dao应该连接A的数据源。)。

    更多详细了解可以查看源码,或者下面的简单介绍。

    添加依赖

    主要依赖就是jta-atomikos,其余的mybatis与druid的相关依赖就不粘贴了。

    org.springframework.boot

    spring-boot-starter-aop

    org.springframework.boot

    spring-boot-starter-jta-atomikos

    配置多个数据源

    1、首先,定义一个枚举来说明一下当前数据源实例key有哪些。

    public class DataSourceKey {

    /** 数据库源one*/

    public static final String ONE= "one";

    /** 数据库源two*/

    public static final String TWO= "two";

    }

    2、其次,使用ThreadLocal存储当前使用数据源实例的key。ThreadLocal实例化的时候给一个master的默认值,也就是默认数据源是master数据源。

    public class DynamicDataSourceContextHolder {

    private static ThreadLocal CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceKey.MASTER.getName());

    public static List dataSourceKeys = new ArrayList();

    public static void setDataSourceKey(String key){

    CONTEXT_HOLDER.set(key);

    }

    public static Object getDataSourceKey(){

    return CONTEXT_HOLDER.get();

    }

    public static void clearDataSourceKey(){

    CONTEXT_HOLDER.remove();

    }

    public static Boolean containDataSourceKey(String key){

    return dataSourceKeys.contains(key);

    }

    }

    3、重写AbstractRoutingDataSource的determineCurrentLookupKey方法,在访问数据库时会调用该类的 determineCurrentLookupKey() 方法获取数据库实例的 key。

    public class DynamicDataSource extends AbstractRoutingDataSource {

    /**

    * 取得当前使用那个数据源。

    */

    @Override

    protected Object determineCurrentLookupKey() {

    return DataSourceContextHolder.getDatasourceType();

    }

    }

    4、通过SqlSessionFactory 重新组装整合多个数据源,最终返回sqlSessionTemplate给到dao层。

    @Configuration

    @MapperScan(basePackages = MyBatisConfig.BASE_PACKAGE, sqlSessionTemplateRef = "sqlSessionTemplate")

    public class MyBatisConfig extends AbstractDataSourceConfig {

    //mapper模式下的接口层

    static final String BASE_PACKAGE = "cn.xbmchina.multidatasourceatomikos.mapper";

    //对接数据库的实体层

    static final String ALIASES_PACKAGE = "ccn.xbmchina.multidatasourceatomikos.domain";

    static final String MAPPER_LOCATION = "classpath:mapper/*.xml";

    @Primary

    @Bean(name = "dataSourceOne")

    public DataSource dataSourceOne(Environment env) {

    String prefix = "spring.datasource.druid.one.";

    return getDataSource(env,prefix,"one");

    }

    @Bean(name = "dataSourceTwo")

    public DataSource dataSourceTwo(Environment env) {

    String prefix = "spring.datasource.druid.two.";

    return getDataSource(env,prefix,"two");

    }

    @Bean("dynamicDataSource")

    public DynamicDataSource dynamicDataSource(@Qualifier("dataSourceOne")DataSource dataSourceOne, @Qualifier("dataSourceTwo")DataSource dataSourceTwo) {

    Map targetDataSources = new HashMap<>();

    targetDataSources.put("one",dataSourceOne);

    targetDataSources.put("two",dataSourceTwo);

    DynamicDataSource dataSource = new DynamicDataSource();

    dataSource.setTargetDataSources(targetDataSources);

    dataSource.setDefaultTargetDataSource(dataSourceOne);

    return dataSource;

    }

    @Bean(name = "sqlSessionFactoryOne")

    public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceOne") DataSource dataSource)

    throws Exception {

    return createSqlSessionFactory(dataSource);

    }

    @Bean(name = "sqlSessionFactoryTwo")

    public SqlSessionFactory sqlSessionFactoryTwo(@Qualifier("dataSourceTwo") DataSource dataSource)

    throws Exception {

    return createSqlSessionFactory(dataSource);

    }

    @Bean(name = "sqlSessionTemplate")

    public CustomSqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactoryOne")SqlSessionFactory factoryOne, @Qualifier("sqlSessionFactoryTwo")SqlSessionFactory factoryTwo) throws Exception {

    Map sqlSessionFactoryMap = new HashMap<>();

    sqlSessionFactoryMap.put("one",factoryOne);

    sqlSessionFactoryMap.put("two",factoryTwo);

    CustomSqlSessionTemplate customSqlSessionTemplate = new CustomSqlSessionTemplate(factoryOne);

    customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap);

    return customSqlSessionTemplate;

    }

    /**

    * 创建数据源

    * @param dataSource

    * @return

    */

    private SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws Exception{

    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

    bean.setDataSource(dataSource);

    bean.setVfs(SpringBootVFS.class);

    bean.setTypeAliasesPackage(ALIASES_PACKAGE);

    bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));

    return bean.getObject();

    }

    }

    5、使用AOP,以自定义注解注解在的方法为切点,动态切换数据源

    import cn.xbmchina.multidatasourceatomikos.annotations.TargetDataSource;

    import cn.xbmchina.multidatasourceatomikos.db.DataSourceContextHolder;

    import org.aspectj.lang.JoinPoint;

    import org.aspectj.lang.annotation.After;

    import org.aspectj.lang.annotation.Before;

    import org.aspectj.lang.annotation.Pointcut;

    import java.lang.reflect.Method;

    public class DataSourceAspect {

    protected static final ThreadLocal preDatasourceHolder = new ThreadLocal<>();

    /**

    * @param clazz

    * @param name

    * @return

    */

    private static Method findUniqueMethod(Class> clazz, String name) {

    Class> searchType = clazz;

    while (searchType != null) {

    Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods());

    for (Method method : methods) {

    if (name.equals(method.getName())) {

    return method;

    }

    }

    searchType = searchType.getSuperclass();

    }

    return null;

    }

    @Pointcut("@annotation(cn.xbmchina.multidatasourceatomikos.annotations.TargetDataSource)")

    protected void datasourceAspect() {

    }

    /**

    * 根据@TargetDataSource的属性值设置不同的dataSourceKey,以供DynamicDataSource

    */

    @Before("datasourceAspect()")

    public void changeDataSourceBeforeMethodExecution(JoinPoint jp) {

    String key = determineDatasource(jp);

    if (key == null) {

    DataSourceContextHolder.setDatasourceType(null);

    return;

    }

    preDatasourceHolder.set(DataSourceContextHolder.getDatasourceType());

    DataSourceContextHolder.setDatasourceType(key);

    }

    /**

    * @param jp

    * @return

    */

    public String determineDatasource(JoinPoint jp) {

    String methodName = jp.getSignature().getName();

    Class targetClass = jp.getSignature().getDeclaringType();

    String dataSourceForTargetClass = resolveDataSourceFromClass(targetClass);

    String dataSourceForTargetMethod = resolveDataSourceFromMethod(targetClass, methodName);

    String resultDS = determinateDataSource(dataSourceForTargetClass, dataSourceForTargetMethod);

    return resultDS;

    }

    /**

    *

    */

    @After("datasourceAspect()")

    public void restoreDataSourceAfterMethodExecution() {

    DataSourceContextHolder.setDatasourceType(preDatasourceHolder.get());

    preDatasourceHolder.remove();

    }

    /**

    * @param targetClass

    * @param methodName

    * @return

    */

    private String resolveDataSourceFromMethod(Class targetClass, String methodName) {

    Method m = findUniqueMethod(targetClass, methodName);

    if (m != null) {

    TargetDataSource choDs = m.getAnnotation(TargetDataSource.class);

    return resolveDataSourceName(choDs);

    }

    return null;

    }

    /**

    * @param classDS

    * @param methodDS

    * @return

    */

    private String determinateDataSource(String classDS, String methodDS) {

    return methodDS == null ? classDS : methodDS;

    }

    /**

    * @param targetClass

    * @return

    */

    private String resolveDataSourceFromClass(Class targetClass) {

    TargetDataSource classAnnotation = (TargetDataSource) targetClass.getAnnotation(TargetDataSource.class);

    return null != classAnnotation ? resolveDataSourceName(classAnnotation) : null;

    }

    /**

    * @param ds

    * @return

    */

    private String resolveDataSourceName(TargetDataSource ds) {

    return ds == null ? null : ds.value();

    }

    }

    参考文章

    最后

    更多文章可关注公众号**【爱编码】,回复2020**有实战视频资料哦。

    展开全文
  • 多数据源的目的在于一个代码模块可调用数据库的数据进行某些业务操作。官方文档部分截图:第三方集成的,基本上是目前比较主流的(用的比较)。一、添加Maven依赖...
  • 本项目使用 Spring Boot 和 MyBatis 实现多数据源,动态数据源的切换;有多种不同的实现方式,在学习的过程中发现没有文章将这些方式和常见的问题集中处理,所以将常用的方式和常见的问题都写在了在本项目的不同分支...
  • 大家跟着步骤操作也有些二丈摸不着头脑,看来简单的操作步骤和功能概念还是有必要普及的,那今天就来说一点简单的入门操作知识,那就是BI工具亿信ABI为例子展示如何连接数据库数据源,其他工具我不知道,但这款工具...
  • 很久之前写一篇静态的springboot...之前说的静态多数据源是每个数据源配置一套mapper,现在说说如何动态多数据源共用一套mapper,以及数据源不从applition.yml中配置,实现从数据库中读取数据源配置并动态切换数据源
  • Java项目多数据源配置

    2021-03-10 09:43:09
    由于种种原因,有的时候可能要连接别人的数据库,或者不同的数据库没法自动转换,重构起来数据量又太大了,我们不得不在一个项目中连接数据源。从网上找了各种资料,只有这位大神给出的解决方案一下子就成功了。...
  • 通常情况下应用系统访问的是主数据中心,只有主数据中心出现故障的时候才会切换至应急数据中心,主数据中心与应急数据中心数据保持增量备份。如下图。传统应急流程是当主数据库出现故障时,手工修改应用服务的设置,...
  • 异构数据库系统是相关的数据库系统的集合,可以实现数据的共享和透明访问,每个数据库系统在加入异构数据库系统之前本身就已经存在,拥有自己的DMBS。异构数据库的各个组成部分具有自身的自治性,实现数据共享的...
  • SpringBoot使用mybatis-plus配置数据源,实现项目中从两个不同的数据库读取数据 1、添加依赖包 <properties> <java.version>1.8</java.version> <mybatis.version>2.1.2</mybatis....
  • 其他网址 Spring Boot 多数据源(读写分离)方案实现_骏马逸动,心随你动的博客-CSDN博客 ShardingSphere jdbc集成多数据源_读钓的博客-CSDN博客_shardingsphere 多数据源
  • 假如我们要访问两个数据源,那么我们要添加两个数据源的配置类:XXXDataSourceConfig,如果是三个,就创建三个配置类。。。 @Configuration @MapperScan(basePackages = ADataSourceConfig.PACKAGE, ...
  • 在实际开发中,经常可能遇到在一个应用中可能需要访问多数据库的情况,在需要切换数据源Service或Mapper方法上添加@DataSource注解@DataSource(value = DataSourceType.MASTER),其中value用来表示数据源名称 ...
  • [toc]7.1 JDBC技术概述JDBC是Java程序访问数据库的标准,它是由一组Java语言编写的...7.1.1 数据库访问的两层和三层模型两层模型即客户-数据库服务器结构,也就是通常所说的C-S结构。在两层模型中,Java应用程序通过...
  • Abp连接数据源

    千次阅读 2020-12-19 18:46:26
    在使用ABP时会遇到新数据和旧数据相互结合,我们不单单要用到一个数据库,还会用到数据库,由于我们使用了ABP框架,所以就要学习框架中的知识点,进入代码环节。 平时我们都是继承EntityFramework下的DbContext...
  • spring多数据源的处理mybatis实现跨库查询(2012-12-10 09:33:29)标签:it实现Myibatis动态sql跨数据库的处理Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用数据库实例进行管理,这样可以有效...
  • 一、前言在开发一些小型应用系统时,由于系统及服务器环境的制约需要采用免安装或者小型的数据库Access。...二、配置ODBC数据源1.找到“数据源配置”选项首先选择【开始】->【控制面板】菜单,进入“控制面板”界...
  • Figure 1 数据分割及多数据库架构通常这种多数据源的逻辑会渗透到业务逻辑中,同时也会给我们使用的数据访问API诸如Hibernate和iBatis等带来不便(需要指定个SessionFactory或SqlMapClient实例来对应个DataSource...
  • 在我们的项目中遇到这样一个问题:我们的项目需要连接数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。所以就采用了多数据源的方式(可以根据客户的需求去连接客户所需要的真正的数据源,即...
  • 如何让进程线程访问数据库,而不会选择相同的数据,这在设计分布式程序的时候经常用到,台机器的个进程,每个进程都有个线程,每个线程要从数据库里取数据来处理,要实现不能漏取数据,也不能重复取数据,...
  • myweb.xml的名称是web应用的名字,文件中的内容应该是tomcat\webapps\myweb\META-INF\context.xml中的内容 tomcat5数据源 server.xml unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware...
  • 我需要编写一个基于守护进程的java进程(不是基于Web的),它将连接到Oracle 10G数据库,从中读取一些数据,然后连接到SQL Server数据库并将数据写入表.听起来很简单,但我对此有几个疑问.>我需要有两个jdbc驱动程序,一...
  • H2-H2Database数据库部署: 1.官方下载 Zip 包,如:h2-2019-10-14.zip 2.解压,直接执行 bin 目录下的 h2.bat、h2.sh 文件。 3.也可执行 service 目录下的命令安装为 Windows 系统服务。 注:默认启动服务后会在...
  • 进程线程访问数据库如何让进程线程访问数据库,而不会选择相同的数据,这在设计分布式程序的时候经常用到,台机器的个进程,每个进程都有个线程,每个线程要从数据库里取数据来处理,要实现不能漏取...
  • 进行数据库操作的时候,经常出现这些名词:jdbc、datasource数据源数据库驱动,mybatis、JPA、Hibernate。 区分他们的含义和所处位置十分必要,现在分析如下 1、数据库 数据库,就是我们常用的关系型数据库,比如...
  • 弹出“ODBC Microsoft Access安装”对话框,填写了数据源名称后,点击【选择】,选择...如下图:二、连接access数据源创建关系数据源,驱动程序类别选择"Access",数据库名填写上面设置的“aa”,若是设置了用户名及...
  • 【Java】SpringBoot整合多数据源JdbcTemplate、Mybatis、Jpa

    千次阅读 多人点赞 2021-11-30 00:04:21
    SpringBoot 整合 JdbcTemplate 多数据源 pom <!-- 引入阿里的数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId>...
  • Oracle通过odbc数据源连接代码数据库创建DBLINK操作一、安装unixODBC二、配置odbc.ini三、配置透明网关四、配置oracle监听五、在oracle数据库中创建dblink六、测试通过dblink查看达梦数据库数据 一、安装unixODBC 2...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 471,808
精华内容 188,723
关键字:

多数据源数据库访问