• SpringBoot多数据源以及事务处理


    背景

    在高并发的项目中,单数据库已无法承载大数据量的访问,因此需要使用多个数据库进行对数据的读写分离,此外就是在微服化的今天,我们在项目中可能采用各种不同存储,因此也需要连接不同的数据库,居于这样的背景,这里简单分享实现的思路以及实现方案。

    如何实现

    多数据源实现思路有两种,一种是通过配置多个SqlSessionFactory实现多数据源; image.png 另外一种是通过Spring提供的AbstractRoutingDataSource抽象了一个DynamicDataSource实现动态切换数据源; image.png

    实现方案

    准备

    采用Spring Boot2.7.8框架,数据库Mysql,ORM框架采用Mybatis,整个Maven依赖如下:

        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
            <spring-boot.version>2.7.8spring-boot.version>
            <mysql-connector-java.version>5.1.46mysql-connector-java.version>
            <mybatis-spring-boot-starter.version>2.0.0mybatis-spring-boot-starter.version>
            <mybatis.version>3.5.1mybatis.version>
        properties>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-dependenciesartifactId>
                    <version>${spring-boot.version}version>
                    <type>pomtype>
                    <scope>importscope>
                dependency>
                <dependency>
                    <groupId>mysqlgroupId>
                    <artifactId>mysql-connector-javaartifactId>
                    <version>${mysql-connector-java.version}version>
                dependency>
                <dependency>
                    <groupId>org.mybatisgroupId>
                    <artifactId>mybatisartifactId>
                    <version>${mybatis.version}version>
                dependency>
                <dependency>
                    <groupId>org.mybatis.spring.bootgroupId>
                    <artifactId>mybatis-spring-boot-starterartifactId>
                    <version>${mybatis-spring-boot-starter.version}version>
                dependency>
            dependencies>
        dependencyManagement>
    
    

    指定数据源操作指定目录XML文件

    该种方式需要操作的数据库的Mapper层和Dao层分别建立一个文件夹,分包放置,整体项目结构如下图: image.png

    Maven依赖如下:
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
            dependency>
            <dependency>
                <groupId>com.zaxxergroupId>
                <artifactId>HikariCPartifactId>
                <version>4.0.3version>
            dependency>
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
            dependency>
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
            dependency>
            <dependency>
                <groupId>org.mybatisgroupId>
                <artifactId>mybatisartifactId>
            dependency>
            <dependency>
                <groupId>org.mybatis.spring.bootgroupId>
                <artifactId>mybatis-spring-boot-starterartifactId>
            dependency>
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    
    Yaml文件
    spring:
      datasource:
        user:
          jdbc-url: jdbc:mysql://127.0.0.1:3306/study_user?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
          type: com.zaxxer.hikari.HikariDataSource
          #hikari连接池配置
          hikari:
            #pool name
            pool-name: user
            #最小空闲连接数
            minimum-idle: 5
            #最大连接池
            maximum-pool-size: 20
            #链接超时时间  3秒
            connection-timeout: 3000
            # 连接测试query
            connection-test-query: SELECT 1
        soul:
          jdbc-url: jdbc:mysql://127.0.0.1:3306/soul?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
          type: com.zaxxer.hikari.HikariDataSource
          #hikari连接池配置
          hikari:
            #pool name
            pool-name: soul
            #最小空闲连接数
            minimum-idle: 5
            #最大连接池
            maximum-pool-size: 20
            #链接超时时间  3秒
            connection-timeout: 3000
            # 连接测试query
            connection-test-query: SELECT 1
    
    不同库的Mapper指定不同的SqlSessionFactory

    针对不同的库分别放置对用不同的SqlSessionFactory

    @Configuration
    @MapperScan(basePackages = "org.datasource.demo1.usermapper",
            sqlSessionFactoryRef = "userSqlSessionFactory")
    public class UserDataSourceConfiguration {
    
        public static final String MAPPER_LOCATION = "classpath:usermapper/*.xml";
    
        @Primary
        @Bean("userDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.user")
        public DataSource userDataSource() {
            return DataSourceBuilder.create().build();
        }
    
    
        @Bean(name = "userTransactionManager")
        @Primary
        public PlatformTransactionManager userTransactionManager(@Qualifier("userDataSource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    
    
        @Primary
        @Bean(name = "userSqlSessionFactory")
        public SqlSessionFactory userSqlSessionFactory(@Qualifier("userDataSource") DataSource dataSource) throws Exception {
            final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
            sessionFactoryBean.setDataSource(dataSource);
            sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(UserDataSourceConfiguration.MAPPER_LOCATION));
            return sessionFactoryBean.getObject();
        }
    
    }
    @Configuration
    @MapperScan(basePackages = "org.datasource.demo1.soulmapper",
            sqlSessionFactoryRef = "soulSqlSessionFactory")
    public class SoulDataSourceConfiguration {
    
        public static final String MAPPER_LOCATION = "classpath:soulmapper/*.xml";
    
    
        @Bean("soulDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.soul")
        public DataSource soulDataSource() {
            return DataSourceBuilder.create().build();
        }
    
    
        @Bean(name = "soulTransactionManager")
        public PlatformTransactionManager soulTransactionManager(@Qualifier("soulDataSource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    
    
        @Bean(name = "soulSqlSessionFactory")
        public SqlSessionFactory soulSqlSessionFactory(@Qualifier("soulDataSource") DataSource dataSource) throws Exception {
            final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
            sessionFactoryBean.setDataSource(dataSource);
            sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(SoulDataSourceConfiguration.MAPPER_LOCATION));
            return sessionFactoryBean.getObject();
        }
    
    }
    
    使用
    @Service
    public class AppAuthService {
    
        @Autowired
        private AppAuthMapper appAuthMapper;
    
        @Transactional(rollbackFor = Exception.class)
        public int getCount() {
            int a = appAuthMapper.listCount();
            int b = 1 / 0;
            return a;
        }
    
    }
    
    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class TestDataSource {
    
        @Autowired
        private AppAuthService appAuthService;
    
        @Autowired
        private SysUserService sysUserService;
    
        @Test
        public void test_dataSource1(){
            int b=sysUserService.getCount();
            int a=appAuthService.getCount();
        }
    }
    
    总结

    此种方式使用起来分层明确,不存在任何冗余代码,不足地方就是每个库都需要对应一个配置类,该配置类中实现方式都基本类似,该种解决方案每个配置类中都存在事务管理器,因此不需要单独再去额外的关注。

    AOP+自定义注解

    关于采用Spring AOP方式实现原理就是把多个数据源存储在一个 Map中,当需要使用某个数据源时,从 Map中获取此数据源进行处理。 image.png

    AbstractRoutingDataSource

    在Spring中提供了AbstractRoutingDataSource来实现此功能,继承AbstractRoutingDataSource类并覆写其determineCurrentLookupKey()方法就可以完成数据源切换,该方法只需要返回数据源key即可,也就是存放数据源的Map的key,接下来我们来看一下AbstractRoutingDataSource整体的继承结构,看他是如何做到的。 image.png 在整体的继承结构上我们会发现AbstractRoutingDataSource最终是继承于DataSource,因此当我们继承AbstractRoutingDataSource是我们自身也是一个数据源,对于数据源必然有连接数据库的动作,如下代码:

    public Connection getConnection() throws SQLException {
      return this.determineTargetDataSource().getConnection();
    }
    
    public Connection getConnection(String username, String password) throws SQLException {
      return this.determineTargetDataSource().getConnection(username, password);
    }
    

    只是AbstractRoutingDataSource的getConnection()方法里实际是调用determineTargetDataSource()返回的数据源的getConnection()方法。

    protected DataSource determineTargetDataSource() {
      Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
      Object lookupKey = this.determineCurrentLookupKey();
      DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
      if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
        dataSource = this.resolvedDefaultDataSource;
      }
    
      if (dataSource == null) {
        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
      } else {
        return dataSource;
      }
    }
    

    该方法通过determineCurrentLookupKey()方法获取一个key,通过key从resolvedDataSources中获取数据源DataSource对象。determineCurrentLookupKey()是个抽象方法,需要继承AbstractRoutingDataSource的类实现;而resolvedDataSources是一个Map,里面应该保存当前所有可切换的数据源,接下来我们来聊聊实现,我们首先来看下目录,与分包的不同的是将所有的Mapper文件都放到一起,其他Maven依赖以及配置文件都保持一致。 image.png

    DataSourceType

    该枚举用来存放数据源的名称,

    public enum DataSourceType {
    
        USERDATASOURCE("userDataSource"),
    
        SOULDATASOURCE("soulDataSource");
    
        private String name;
    
    
        DataSourceType(String name) {
            this.name=name;
        }
    
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    DynamicDataSourceConfiguration

    通过读取配置文件中的数据源配置信息,创建数据连接,将多个数据源放入Map中,注入到容器中:

    @Configuration
    @MapperScan(basePackages = "org.datasource.demo2.mapper")
    public class DynamicDataSourceConfiguration {
    
        @Primary
        @Bean(name = "userDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.user")
        public DataSource userDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean(name = "soulDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.soul")
        public DataSource soulDataSource() {
            return DataSourceBuilder.create().build();
        }
    
    
        @Bean(name = "dynamicDataSource")
        public DynamicDataSource DataSource(@Qualifier("userDataSource") DataSource userDataSource,
                                            @Qualifier("soulDataSource") DataSource soulDataSource) {
            //targetDataSource 集合是我们数据库和名字之间的映射
            Map targetDataSource = new HashMap<>();
            targetDataSource.put(DataSourceType.USERDATASOURCE.getName(), userDataSource);
            targetDataSource.put(DataSourceType.SOULDATASOURCE.getName(), soulDataSource);
            DynamicDataSource dataSource = new DynamicDataSource();
            dataSource.setTargetDataSources(targetDataSource);
            //设置默认对象
            dataSource.setDefaultTargetDataSource(userDataSource);
            return dataSource;
        }
    
    
        @Bean(name = "sqlSessionFactory")
        public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
                throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setTransactionFactory(new MultiDataSourceTransactionFactory());
            bean.setDataSource(dynamicDataSource);
            //设置我们的xml文件路径
            bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(
                            "classpath*:mapper/*.xml"));
            return bean.getObject();
        }
    }
    
    DataSourceContext

    DataSourceContext使用ThreadLocal存放当前线程使用的数据源类型信息;

    public class DataSourceContext {
    
        private final static ThreadLocal<StringLOCAL_DATASOURCE =
                new ThreadLocal<>();
    
        public static void set(String name) {
            LOCAL_DATASOURCE.set(name);
        }
    
        public static String get() {
            return LOCAL_DATASOURCE.get();
        }
    
        public static void remove() {
            LOCAL_DATASOURCE.remove();
        }
    }
    
    DynamicDataSource

    DynamicDataSource继承AbstractRoutingDataSource,重写determineCurrentLookupKey()方法,可以选择对应Key;

    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            return DataSourceContext.get();
        }
    
    }
    
    CurrentDataSource

    定义数据源的注解;

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface CurrentDataSource {
        DataSourceType value() default DataSourceType.USERDATASOURCE;
    }
    
    DataSourceAspect

    定义切面切点,用来切换数据源,

    @Aspect
    @Order(-1) 
    @Component
    public class DataSourceAspect {
    
        @Pointcut("@annotation(org.datasource.demo2.constant.CurrentDataSource)")
        public void dsPointCut() {
    
        }
    
        @Around("dsPointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature) point.getSignature();
    
            Method method = signature.getMethod();
    
            CurrentDataSource dataSource = method.getAnnotation(CurrentDataSource.class);
    
            if (Objects.nonNull(dataSource)) {
                System.out.println("切换数据源为" + dataSource.value().getName());
                DataSourceContext.set(dataSource.value().getName());
            }
    
            try {
                return point.proceed();
            } finally {
                // 销毁数据源 在执行方法之后
                System.out.println("销毁数据源" + dataSource.value().getName());
                DataSourceContext.remove();
            }
        }
    
    }
    
    多数据源切换以后事务问题

    Spring使用事务的方式有两种,一种是声明式事务,一种是编程式事务,我们讨论的都是关于声明式事务,这种方式很方便,也是大家常用的,这里为什么讨论这个问题,当我们想将不同库的表放在同一个事务使用的时候,这个是时候我们会报错,如下图: image.png 这部分也就是其他技术贴没讲解的部分,因此这里我们来补充一下这个话题,背过八股们的小伙伴都知道Spring事务是居于AOP实现,从这个角度很容易会理解到这个问题,当我们将两个Service方法放在同一个Transactional下的时候,这个代理对象就是当前类,因此导致数据源对象也是当前类下的DataSource,导致就出现表不存在问题,当Transactional分别放在不同Service的时候没有这种情况。

        @Transactional(rollbackFor = Exception.class)
        public void update(){
            sysUserMapper.updateSysUser("111");
            appAuthService.update("111");
        }
    

    有没有办法解决这个问题呢,当然是有的,这里我就不一步一步去探讨源码问题,我就直接直捣黄龙,把问题本质说一下,在Spring事务管理中有一个核心类DataSourceTransactionManager,该类是Spring事务核心的默认实现,AbstractPlatformTransactionManager是整体的Spring事务实现模板类,整体的继承结构如下图, image.png 在方案一中,我们针对每个DataSourece都创建对应的DataSourceTransactionManager实现,也可以看出DataSourceTransactionManager就是管理我们整体的事务的,当我们配置了事物管理器以及拦截Service中的方法后,每次执行Service中方法前会开启一个事务,并且同时会缓存DataSource、SqlSessionFactory、Connection,因为DataSource、Conneciton都是从缓存中拿的,因此我们怎么切换数据源也没用,因此就出现表不存在的报错,具体源码可参考下面截图部分: image.png image.png 看到这里我们大致明白了为什么会报错,那么我们该如何做才能实现这种情况呢?其实我们要做的事就是动态的根据DataSourceType获取不同的Connection,不从缓存中获取Connection。

    解决方案

    我们来自定义一个MultiDataSourceTransaction实现Mybatis的事务接口,使用Map存储Connection相关连接,所有事务都采用手动提交,之后将MultiDataSourceTransaction交给SpringManagedTransactionFactory处理。

    public class MultiDataSourceTransaction implements Transaction {
    
        private final DataSource dataSource;
    
        private ConcurrentMap concurrentMap;
    
        private boolean autoCommit;
    
    
        public MultiDataSourceTransaction(DataSource dataSource) {
            this.dataSource = dataSource;
            concurrentMap = new ConcurrentHashMap<>();
        }
    
    
        @Override
        public Connection getConnection() throws SQLException {
            String databaseIdentification = DataSourceContext.get();
            if (StringUtils.isEmpty(databaseIdentification)) {
                databaseIdentification = DataSourceType.USERDATASOURCE.getName();
            }
            //获取数据源
            if (!this.concurrentMap.containsKey(databaseIdentification)) {
                try {
                    Connection conn = this.dataSource.getConnection();
                    autoCommit=false;
                    conn.setAutoCommit(false);
                    this.concurrentMap.put(databaseIdentification, conn);
                } catch (SQLException ex) {
                    throw new CannotGetJdbcConnectionException("Could bot get JDBC otherConnection", ex);
                }
            }
            return this.concurrentMap.get(databaseIdentification);
        }
    
    
        @Override
        public void commit() throws SQLException {
            for (Connection connection : concurrentMap.values()) {
                if (!autoCommit) {
                    connection.commit();
                }
            }
        }
    
        @Override
        public void rollback() throws SQLException {
            for (Connection connection : concurrentMap.values()) {
                connection.rollback();
            }
        }
    
        @Override
        public void close() throws SQLException {
            for (Connection connection : concurrentMap.values()) {
                DataSourceUtils.releaseConnection(connection, this.dataSource);
            }
        }
    
        @Override
        public Integer getTimeout() throws SQLException {
            return null;
        }
    }
    
    public class MultiDataSourceTransactionFactory extends SpringManagedTransactionFactory {
        @Override
        public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
            return new MultiDataSourceTransaction(dataSource);
        }
    }
    
    为什么可以这么做

    在Mybatis自动装配式会将配置文件装配为Configuration对象,也就是在方案一种SqlSessionFactory配置的过程,其中SqlSessionFactoryBean类实现了InitializingBean接口,初始化后执行afterPropertiesSet()方法,在afterPropertiesSet()方法中会执行 BuildSqlSessionFactory() 方法生成一个SqlSessionFactory对象。在BuildSqlSessionFactory中,会创建SpringManagedTransactionFactory对象,该对象就是MyBatis跟 Spring的桥梁。

    image.png 在MapperScan自动扫描Mapper过程中,会通过ClassPathMapperScanner扫描器找到Mapper接口,封装成各自的BeanDefinition,然后循环遍历对Mapper的BeanDefinition修改beanClass为MapperFactoryBean。 image.png 由于MapperFactoryBean实现了FactoryBean,在Bean生命周期管理时会调用getObject方法,通过JDK动态代理生成代理对象MapperProxy,Mapper接口请求的时候,执行MapperProxy代理类的invoke方法,执行的过程中通过SqlSessionFactory创建的SqlSession去调用Executor执行器,进行数据库操作。下图是SqlSession创建的整个过程: image.png openSession方法是将Spring事务管理关联起来的核心代码,首先这里将通过 getTransactionFactoryFromEnvironment()方法获取TransactionFactory。这个操作会得到初始化时候注入的 SpringManagedTransactionFactory对象。然后将执行TransactionFactory#newTransaction() 方法,初始化 MyBatis的Transaction。 image.png这里通过Configuration.newExecutor()创建一个Executor,Configuration指定在Executor默认为Simple,因此这里会创建一个SimpleExecutor,并初始化Transaction属性。接下来我们来看下SimpleExecutor执行执行update方法时候执行prepareStatement方法,在prepareStatement方法中执行了getConnection方法, image.png image.png 这里我们可以看到Connection获取过程,是通过Transaction获取的getConnection(),也就是通过之前注入的Transaction来获取Connection,这个Transaction就是SpringManagedTransaction,整体的时序图如下: image.png image.png 在整个调用链过程中,我们看到在DataSourceUtils有我们熟悉的TransactionSynchronizationManager,在上面Spring事务的时候我们也提到这个类,在开始Spring事务以后就会把Connetion绑定到当前线程,在DataSourceUtils获取到的Connection对象就是Srping开启事务时候创建的对象,这样就保证了Spring Transaction中的Connection跟MyBatis中执行SQL语句用的Connection为同一个 Connection,也就可以通过Spring事务管理机制进行事务管理了。 image.png 明白了整个流程,我们要做的事也就很简单,也就是每次切换DataSoure的同时获取最新的Connection,然后用一个Map对象来记录整个过程中的Connection,出现回滚这个Map对象里面Connection对象都回滚就可以了,然后将我们自定义的Transaction,委托给Spring在进行管理。

    总结

    采用AOP的方式是切换数据源已经非常好了,唯一不太好的地方就在于依然要手动去创建DataSource,每次增加都需要增加一个Bean,那有没有办法解决呢?当然是有的,让我们来更上一层楼,解放双手。

    更上一层楼

    ImportBeanDefinitionRegistrar

    ImportBeanDefinitionRegistrar接口是Spring提供一个扩展点,主要用来注册BeanDefinition,常见的第三方框架在集成Spring的时候,都会通过该接口,实现扫描指定的类,然后注册到Spring容器中。比如 Mybatis中的Mapper接口,SpringCloud中的Feignlient接口,都是通过该接口实现的自定义注册逻辑。 我们要做的事情就是通过ImportBeanDefinitionRegistrar帮助我们动态的将DataSource扫描的到容器中去,不在采用增加Bean的方式,整体代码如下:

    public class DynamicDataSourceBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrarEnvironmentAware {
    
    
        /**
         * 默认dataSource
         */
        private DataSource defaultDataSource;
    
        /**
         * 数据源map
         */
        private Map<StringDataSource> dataSourcesMap = new HashMap<>();
    
    
        @Override
        public void setEnvironment(Environment environment) {
            initConfig(environment);
        }
    
        private void initConfig(Environment env) {
            //读取配置文件获取更多数据源
            String dsNames = env.getProperty("spring.datasource.names");
            for (String dsName : dsNames.split(",")) {
                HikariConfig hikariConfig = new HikariConfig();
                hikariConfig.setPoolName(dsName);
                hikariConfig.setDriverClassName(env.getProperty("spring.datasource." + dsName.trim() + ".driver-class-name"));
                hikariConfig.setJdbcUrl(env.getProperty("spring.datasource." + dsName.trim() + ".jdbc-url"));
                hikariConfig.setUsername(env.getProperty("spring.datasource." + dsName.trim() + ".username"));
                hikariConfig.setPassword(env.getProperty("spring.datasource." + dsName.trim() + ".password"));
                hikariConfig.setConnectionTimeout(Long.parseLong(Objects.requireNonNull(env.getProperty("spring.datasource." + dsName.trim() + ".hikari.connection-timeout"))));
                hikariConfig.setMinimumIdle(Integer.parseInt(Objects.requireNonNull(env.getProperty("spring.datasource." + dsName.trim() + ".hikari.minimum-idle"))));
                hikariConfig.setMaximumPoolSize(Integer.parseInt(Objects.requireNonNull(env.getProperty("spring.datasource." + dsName.trim() + ".hikari.maximum-pool-size"))));
                hikariConfig.setConnectionInitSql("SELECT 1");
                HikariDataSource dataSource = new HikariDataSource(hikariConfig);
                if (dataSourcesMap.size() == 0) {
                    defaultDataSource = dataSource;
                }
                dataSourcesMap.put(dsName, dataSource);
            }
        }
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
            Map<ObjectObject> targetDataSources = new HashMap<ObjectObject>();
            //添加其他数据源
            targetDataSources.putAll(dataSourcesMap);
            //创建DynamicDataSource
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(DynamicDataSource.class);
            beanDefinition.setSynthetic(true);
            MutablePropertyValues mpv = beanDefinition.getPropertyValues();
            //defaultTargetDataSource 和 targetDataSources属性是 AbstractRoutingDataSource的两个属性Map
            mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
            mpv.addPropertyValue("targetDataSources", targetDataSources);
            //注册
            registry.registerBeanDefinition("dataSource", beanDefinition);
        }
    
    }
    
    @Import

    @Import模式是向容器导入Bean是一种非常重要的方式,在注解驱动的Spring项目中,@Enablexxx的设计模式中有大量的使用,我们通过ImportBeanDefinitionRegistrar完成Bean的扫描,通过@Import导入到容器中,然后将EnableDynamicDataSource放入SpringBoot的启动项之上,到这里有没有感觉到茅塞顿开的感觉。

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Import({DynamicDataSourceBeanDefinitionRegistrar.class})
    public @interface EnableDynamicDataSource {
    }
    @SpringBootApplication
    @EnableAspectJAutoProxy
    @EnableDynamicDataSource
    public class DataSourceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DataSourceApplication.class, args);
        }
    }
    
    DynamicDataSourceConfig

    该类负责将Mapper扫描以及SpringFactory定义;

    @Configuration
    @MapperScan(basePackages = "org.datasource.demo3.mapper")
    public class DynamicDataSourceConfig {
    
    
        @Autowired
        private DataSource dynamicDataSource;
    
        @Bean(name = "sqlSessionFactory")
        public SqlSessionFactory sqlSessionFactory()
                throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setTransactionFactory(new MultiDataSourceTransactionFactory());
            bean.setDataSource(dynamicDataSource);
            //设置我们的xml文件路径
            bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(
                    "classpath*:mapper/*.xml"));
            return bean.getObject();
        }
    }
    
    yaml

    关于yaml部分我们增加了names定义,方便识别出来配置了几个DataSource,剩下的部分与AOP保持一致。

    spring:
      datasource:
        names: user,soul
        user:
          jdbc-url: jdbc:mysql://127.0.0.1:3306/study_user?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
          type: com.zaxxer.hikari.HikariDataSource
          #hikari连接池配置
          hikari:
            #最小空闲连接数
            minimum-idle: 5
            #最大连接池
            maximum-pool-size: 20
            #链接超时时间  3秒
            connection-timeout: 3000
        soul:
          jdbc-url: jdbc:mysql://127.0.0.1:3306/soul?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
          type: com.zaxxer.hikari.HikariDataSource
          #hikari连接池配置
          hikari:
            #最小空闲连接数
            minimum-idle: 5
            #最大连接池
            maximum-pool-size: 20
            #链接超时时间  3秒
            connection-timeout: 3000
    

    结束

    欢迎大家点点关注,点点赞! 今年前半年文章会偏Spring、SpringCloud相关的实战,后半年文章会多一些理论。

  • 相关阅读:
    2016年上半年上午易错题(软件设计师考试)
    【0到1学习Unity脚本编程】第一人称视角的角色控制器
    易点易动固定资产管理系统:精准管理与科学采购,降本增效的利器
    《windows核心编程》第1章 错误处理
    Android Jetpack之LifeCycle
    模拟类型的题目
    某计费管理系统任意文件读取漏洞
    【day 03】初始vue的相关指令
    C# 背景与前景
    C语言实验七 二维数组程序设计
  • 原文地址:https://www.cnblogs.com/wtzbk/p/17157708.html