小工具知识网

当前位置: 首页 > 行业知识 > 【Java多数据源实现教程】实现动态数据源、多数据源切换方式

行业知识

【Java多数据源实现教程】实现动态数据源、多数据源切换方式

2026-10-26 12:51:43 来源:互联网转载

前言

本文为 【Java多数据源实现教程】 相关知识,由于自己最近在做导师的项目的时候需要使用这种技术,于是自学了相关技术原理与实现,并将其整理如下,具体包含:多数据源的典型使用场景(包含业务复杂场景、读写分离场景),多数据源实现原理及实现方法(包含通过AbstractRoutingDataSource实现动态数据源、多数据源切换方式、Spring集成多个MyBatis框架实现多数据源),多数据源事务控制(包含只使用主库TransactionManger、一个方法开启2个事务),dynamic-datasource多数源组件等~

博主主页:小新要变强 的主页

Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~

算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~

Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)


目录

文章标题

  • 前言
  • 目录
  • 一、多数据源的典型使用场景
    • 1️⃣业务复杂(数据量大)
    • 2️⃣读写分离
    • 二、多数据源实现原理及实现方法
      • 1️⃣通过AbstractRoutingDataSource实现动态数据源
      • 2️⃣多数据源切换方式
      • 3️⃣Spring集成多个MyBatis框架实现多数据源
      • 后记

        一、多数据源的典型使用场景

        在实际开发中,经常可能遇到在一个应用中可能需要访问多个数据库的情况。以下是两种典型场景:

        1️⃣业务复杂(数据量大)

        数据分布在不同的数据库中,数据库拆了, 应用没拆。 一个公司多个子项目,各用各的数据库,涉及数据共享…

        2️⃣读写分离

        • 为了解决数据库的读性能瓶颈(读比写性能更高, 写锁会影响读阻塞,从而影响读的性能)。
        • 很多数据库拥主从架构。也就是,一台主数据库服务器,是对外提供增删改业务的生产服务器;另一(多)台从数据库服务器,主要进行读的操作。ꞏ
        • 可以通过中间件(ShardingSphere、mycat、mysql-proxy 、TDDL …),但是有一些规模较小的公司,没有专门的中间件团队搭建读写分离基础设施,因此需要业务开发人员自行实现读写分离。

          这里的架构与上图类似。不同的是,在读写分离中,主库和从库的数据库是一致的(不考虑主从延迟)。数据更新操作(insert、update、delete)都是在主库上进行,主库将数据变更信息同步给从库。在查询时,可以在从库上进行,从而分担主库的压力。

          二、多数据源实现原理及实现方法

          原理:

          对于大多数的java应用,都使用了spring框架,spring-jdbc模块提供AbstractRoutingDataSource,其内部可以包含了多个DataSource,然后在运行时来动态的访问哪个数据库。这种方式访问数据库的架构图如下所示:

          应用直接操作的是AbstractRoutingDataSource的实现类,告诉AbstractRoutingDataSource访问哪个数据库,然后由AbstractRoutingDataSource从事先配置好的数据源(ds1、ds2)选择一个,来访问对应的数据库。

          • (1)当执行数据库持久化操作,只要集成了Spring就一定会通过DataSourceUtils获取Connection
          • (2)通过Spring注入的DataSource获取Connection即可执行数据库操作。所以思路就是:只需配置一个实现了DataSource的Bean, 然后根据业务动态提供Connection即可
          • (3)其实Spring已经提供一个DataSource实现类用于动态切换数据源——AbstractRoutingDataSource
          • (4)分析AbstractRoutingDataSource即可实现动态数据源切换。

            1️⃣通过AbstractRoutingDataSource实现动态数据源

            通过这个类可以实现动态数据源切换。如下是这个类的成员变量:

            private Map targetDataSources;private Object defaultTargetDataSource;private Map resolvedDataSources;
            • targetDataSources保存了key和数据库连接的映射关系
            • defaultTargetDataSource标识默认的连接
            • resolvedDataSources这个数据结构是通过targetDataSources构建而来,存储结构也是数据库标识和数据源的映射关系

              而AbstractRoutingDataSource实现了InitializingBean接口,并实现了afterPropertiesSet方法。afterPropertiesSet方法是初始化bean的时候执行,通常用作数据初始化。(resolvedDataSources就是在这里赋值)

              @Overridepublic void afterPropertiesSet() {...this.resolvedDataSources = new HashMapObject lookupKey = resolveSpecifiedLookupKey(entry.getKey());DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());this.resolvedDataSources.put(lookupKey, dataSource);}...}    @Bean    @ConfigurationProperties("spring.datasource.datasource1")    public DataSource firstDataSource(){        return DruidDataSourceBuilder.create().build();    }    @Bean    @ConfigurationProperties("spring.datasource.datasource2")    public DataSource secondDataSource(){        return DruidDataSourceBuilder.create().build();    }    @Bean    @Primary    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {        Map    /**    * ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。    * 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。    */    private static final ThreadLocal        super.setDefaultTargetDataSource(defaultTargetDataSource);        super.setTargetDataSources(targetDataSources);        super.afterPropertiesSet();    }    @Override    protected Object determineCurrentLookupKey() {        return getDataSource();    }    public static void setDataSource(String dataSource) {        CONTEXT_HOLDER.set(dataSource);    }    public static String getDataSource() {        return CONTEXT_HOLDER.get();    }    public static void clearDataSource() {        CONTEXT_HOLDER.remove();    }}ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface WR {    String value() default "W";}    // 前置通知    @Before("within(com.tuling.dynamic.datasource.service.impl.*) && @annotation(wr)")    public void before(JoinPoint joinPoint, WR wr){        System.out.println(wr.value());    }}    @Autowired    FrendMapper frendMapper;    @Override    @WR("R") // 库2    public List        return frendMapper.list();    }    @Override    @WR("W") // 库1    public void save(Frend frend) {        frendMapper.save(frend);    }}@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class})})public class DynamicDataSourcePlugin implements Interceptor {    @Override    public Object intercept(Invocation invocation) throws Throwable {        Object[] objects = invocation.getArgs();        MappedStatement ms = (MappedStatement) objects[0];        // 读方法        if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {            DynamicDataSource.name.set("R");        } else {            // 写方法            DynamicDataSource.name.set("W");        }        // 修改当前线程要选择的数据源的key        return invocation.proceed();    }    @Override    public Object plugin(Object target) {        if (target instanceof Executor) {            return Plugin.wrap(target, this);        } else {            return target;        }    }    @Override    public void setProperties(Properties properties) {    }}    @Bean    @Primary    public SqlSessionFactory wSqlSessionFactory(@Qualifier("dataSource1") DataSource dataSource1) throws Exception {        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();        sessionFactory.setDataSource(dataSource1);        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/w/*.xml"));        /*主库设置sql控制台打印*/        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();        configuration.setLogImpl(StdOutImpl.class);        sessionFactory.setConfiguration(configuration);        return sessionFactory.getObject();    }}    @Bean    public SqlSessionFactory rSqlSessionFactory(@Qualifier("dataSource2") DataSource dataSource2) throws Exception {        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();        sessionFactory.setDataSource(dataSource2);        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/r/*.xml"));        /*从库设置sql控制台打印*/        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();        configuration.setLogImpl(StdOutImpl.class);        sessionFactory.setConfiguration(configuration);        return sessionFactory.getObject();    }}

上一篇:织梦模块管理秘籍,核心操作技巧轻松掌握

下一篇:轻松查看Win10版本信息的秘诀揭秘!