背景

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

如何实现

多数据源实现思路有两种,一种是通过配置多个SqlSessionFactory实现多数据源;

另外一种是通过Spring提供的AbstractRoutingDataSource抽象了一个DynamicDataSource实现动态切换数据源;

实现方案

准备

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

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <spring-boot.version>2.7.8</spring-boot.version>
        <mysql-connector-java.version>5.1.46</mysql-connector-java.version>
        <mybatis-spring-boot-starter.version>2.0.0</mybatis-spring-boot-starter.version>
        <mybatis.version>3.5.1</mybatis.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql-connector-java.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>${mybatis.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis-spring-boot-starter.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

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

Maven依赖如下:
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>4.0.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </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中获取此数据源进行处理。

AbstractRoutingDataSource

在Spring中提供了AbstractRoutingDataSource来实现此功能,继承AbstractRoutingDataSource类并覆写其determineCurrentLookupKey()方法就可以完成数据源切换,该方法只需要返回数据源key即可,也就是存放数据源的Map的key,接下来我们来看一下AbstractRoutingDataSource整体的继承结构,看他是如何做到的。

在整体的继承结构上我们会发现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<Object, DataSource>,里面应该保存当前所有可切换的数据源,接下来我们来聊聊实现,我们首先来看下目录,与分包的不同的是将所有的Mapper文件都放到一起,其他Maven依赖以及配置文件都保持一致。

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<Object, Object> 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<String> LOCAL_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使用事务的方式有两种,一种是声明式事务,一种是编程式事务,我们讨论的都是关于声明式事务,这种方式很方便,也是大家常用的,这里为什么讨论这个问题,当我们想将不同库的表放在同一个事务使用的时候,这个是时候我们会报错,如下图:

这部分也就是其他技术贴没讲解的部分,因此这里我们来补充一下这个话题,背过八股们的小伙伴都知道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事务实现模板类,整体的继承结构如下图,

在方案一中,我们针对每个DataSourece都创建对应的DataSourceTransactionManager实现,也可以看出DataSourceTransactionManager就是管理我们整体的事务的,当我们配置了事物管理器以及拦截Service中的方法后,每次执行Service中方法前会开启一个事务,并且同时会缓存DataSource、SqlSessionFactory、Connection,因为DataSource、Conneciton都是从缓存中拿的,因此我们怎么切换数据源也没用,因此就出现表不存在的报错,具体源码可参考下面截图部分:


看到这里我们大致明白了为什么会报错,那么我们该如何做才能实现这种情况呢?其实我们要做的事就是动态的根据DataSourceType获取不同的Connection,不从缓存中获取Connection。

解决方案

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

public class MultiDataSourceTransaction implements Transaction {

    private final DataSource dataSource;

    private ConcurrentMap<String, Connection> 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的桥梁。


在MapperScan自动扫描Mapper过程中,会通过ClassPathMapperScanner扫描器找到Mapper接口,封装成各自的BeanDefinition,然后循环遍历对Mapper的BeanDefinition修改beanClass为MapperFactoryBean。

由于MapperFactoryBean实现了FactoryBean,在Bean生命周期管理时会调用getObject方法,通过JDK动态代理生成代理对象MapperProxy,Mapper接口请求的时候,执行MapperProxy代理类的invoke方法,执行的过程中通过SqlSessionFactory创建的SqlSession去调用Executor执行器,进行数据库操作。下图是SqlSession创建的整个过程:

openSession方法是将Spring事务管理关联起来的核心代码,首先这里将通过 getTransactionFactoryFromEnvironment()方法获取TransactionFactory。这个操作会得到初始化时候注入的 SpringManagedTransactionFactory对象。然后将执行TransactionFactory#newTransaction() 方法,初始化 MyBatis的Transaction。
这里通过Configuration.newExecutor()创建一个Executor,Configuration指定在Executor默认为Simple,因此这里会创建一个SimpleExecutor,并初始化Transaction属性。接下来我们来看下SimpleExecutor执行执行update方法时候执行prepareStatement方法,在prepareStatement方法中执行了getConnection方法,


这里我们可以看到Connection获取过程,是通过Transaction获取的getConnection(),也就是通过之前注入的Transaction来获取Connection,这个Transaction就是SpringManagedTransaction,整体的时序图如下:


在整个调用链过程中,我们看到在DataSourceUtils有我们熟悉的TransactionSynchronizationManager,在上面Spring事务的时候我们也提到这个类,在开始Spring事务以后就会把Connetion绑定到当前线程,在DataSourceUtils获取到的Connection对象就是Srping开启事务时候创建的对象,这样就保证了Spring Transaction中的Connection跟MyBatis中执行SQL语句用的Connection为同一个 Connection,也就可以通过Spring事务管理机制进行事务管理了。

明白了整个流程,我们要做的事也就很简单,也就是每次切换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 ImportBeanDefinitionRegistrar, EnvironmentAware {

    /**
     * 默认dataSource
     */
    private DataSource defaultDataSource;

    /**
     * 数据源map
     */
    private Map<String, DataSource> 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<Object, Object> targetDataSources = new HashMap<Object, Object>();
        //添加其他数据源
        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相关的实战,后半年文章会多一些理论。

SpringBoot多数据源以及事务处理的更多相关文章

  1. Spring-Boot配置文件数据源配置项

    Spring-Boot配置文件数据源配置项(常用配置项为红色) 参数 介绍 spring.datasource.continue-on-error = false 初始化数据库时发生错误时,请勿停止 ...

  2. SpringBoot多数据源动态切换数据源

    1.配置多数据源 spring: datasource: master: password: erp_test@abc url: jdbc:mysql://127.0.0.1:3306/M201911 ...

  3. SpringBoot学习笔记(三):SpringBoot集成Mybatis、SpringBoot事务管理、SpringBoot多数据源

    SpringBoot集成Mybatis 第一步我们需要在pom.xml里面引入mybatis相关的jar包 <dependency> <groupId>org.mybatis. ...

  4. 搞定SpringBoot多数据源(1):多套源策略

    目录 1. 引言 2. 运行环境 3. 多套数据源 3.1 搭建 Spring Boot 工程 3.1.1 初始化 Spring Boot 工程 3.1.2 添加 MyBatis Plus 依赖 3. ...

  5. 搞定SpringBoot多数据源(2):动态数据源

    目录 1. 引言 2. 动态数据源流程说明 3. 实现动态数据源 3.1 说明及数据源配置 3.1.1 包结构说明 3.1.2 数据库连接信息配置 3.1.3 数据源配置 3.2 动态数据源设置 3. ...

  6. 搞定SpringBoot多数据源(3):参数化变更源

    目录 1. 引言 2. 参数化变更源说明 2.1 解决思路 2.2 流程说明 3. 实现参数化变更源 3.1 改造动态数据源 3.1.1 动态数据源添加功能 3.1.2 动态数据源配置 3.2 添加数 ...

  7. SpringBoot多数据源:动态数据源

    目录 1. 引言 2. 动态数据源流程说明 3. 实现动态数据源 3.1 说明及数据源配置 3.1.1 包结构说明 3.1.2 数据库连接信息配置 3.1.3 数据源配置 3.2 动态数据源设置 3. ...

  8. Springboot 多数据源配置,结合tk-mybatis

    一.前言 作为一个资深的CRUD工程师,我们在实际使用springboot开发项目的时候,难免会遇到同时使用多个数据库的情况,比如前脚刚查询mysql,后脚就要查询sqlserver. 这时,我们很直 ...

  9. SpringBoot多数据源中的分布式事务

    虽然现在微服务越来越流行,我们的系统随之也拆分出来好多的模块功能.这样做的目的其实就是为了弥补单体架构中存在的不足.随着微服务的拆分,肯定设计到分库分表,但这之中肯定设计到分布式事务.最典型的例子就是 ...

  10. 数据库分库分表(sharding)系列(四) 多数据源的事务处理

    系统经sharding改造之后,原来单一的数据库会演变成多个数据库,如何确保多数据源同时操作的原子性和一致性是不得不考虑的一个问题.总体上看,目前对于一个分布式系统的事务处理有三种方式:分布式事务.基 ...

随机推荐

  1. 如何基于 Redis 实现分布式锁

    什么是分布式锁 分布式锁:不同进程必须以互斥方式使用共享资源的一种锁方法实现. 实现分布式锁的基础 互斥.任何时刻,只有一个客户端持有锁. 无死锁.最终总是有可能获得锁,即使持有锁的客户端已经崩溃. ...

  2. Jmeter 之跨线程传参

    其他线程使用某个线程中提取的值,比如场景:客户端一直与服务端保持连接的同时进行其他业务操作 1.建立以下两个线程组,并添加相应业务接口 2.发送心跳时,需要token,在用户登录接口下添加提取器提取t ...

  3. Redis RDB 与AOF

    参考书籍<Redis设计与实现> 一丶为什么redis需要持久化 redis 作为一个内存数据库,如果不想办法将存储在内存中的数据,保存到磁盘中,那么一旦服务器进程退出,那么redis数据 ...

  4. [深度学习] ncnn安装和调用基础教程

    目录 1 介绍 2 Ubuntu 18下ncnn安装和使用 2.1 Ubuntu 18下ncnn编译安装 2.2 Ubuntu 18下ncnn使用 3 Windows 10下ncnn安装和使用 3.1 ...

  5. [编程基础] Python lambda函数总结

    Python lambda函数教程展示了如何在Python中创建匿名函数.Python中的匿名函数是使用lambda关键字创建的. 文章目录 1 介绍 1.1 简单使用 1.2 Python lamb ...

  6. GitHub + Hexo 搭建个人博客网站

    一.准备工作 1. GitHub + Hexo 的优势 Hexo 提供现成的模板和模块:github 的 pages 功能提供免费的服务器,零成本搭建属于自己的博客. 2. 需要了解的网站 githu ...

  7. Spring MVC的运行流程

    Spring MVC的运行流程 摘要:本文档主要结合官方给出的Spring MVC流程图结合一个简单的Spring MVC实例,分析并介绍了Spring MVC的运行流程. 目录 Spring MVC ...

  8. python 第一二次教学笔记之数据操作

    对Python 有一个认知 记住这是一个动态类型的,弱类型语言 ds =111.0 #弱类型 前面不用写明是具体什么类型 haobo=10 haobo = ds #类型转换不再有高低之分 hoabo ...

  9. 今天遇到的报错Babel noteThe code generator has deoptimised the styling of ...as it exceeds the max of 500KB.

    解决办法如下: { test: /.js$/, exclude: /node_modules/, use: 'babel-loader' } 然并卵,我已经设置了这个东西了,突然发现我的文件并不在no ...

  10. Win10环境下yolov8(ultralytics) 快速配置与测试

    win10下亲测有效!(如果想在tensorrt+cuda下部署,直接看第五5章) 一.win10下创建yolov8环境 # 注:python其他版本在win10下,可能有坑,我已经替你踩坑了,这里p ...