Spring+Mybatis 多套数据源实现 本文的特色 实现说明 添加依赖,先添加Spring Framework必要依赖 添加Spring Boot 依赖,以使用ConfigurationProperties注解。【可选】 依赖版本 实现AbstractRoutingDataSource,存储数据源 定义三套数据源 定义切面 开启最小化必要注解 启动入口定义

  • 使用Spring最小化配置,仅用经典的Spring Framework,并借用Spring Boot的ConfigurationProperties注解。
  • 最小化配置
  • 最小化依赖

实现说明

  • 添加依赖,先添加Spring Framework必要依赖。
  • 添加Spring Boot 依赖,以使用ConfigurationProperties注解。【可选】
  • 实现AbstractRoutingDataSource,存储数据源
  • 定义三套数据源
  • 定义切面
  • 开启最小化必要注解
  • 启动入口定义

添加依赖,先添加Spring Framework必要依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

添加其他依赖

Jackson

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>${jackson.version}</version>
        </dependency>

Mysql驱动

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

Mybatis

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis-spring.version}</version>
        </dependency>

添加Spring Boot 依赖,以使用ConfigurationProperties注解。【可选】

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <!-- ForConfigurationProperties -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>${validation-api.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.el</artifactId>
            <version>${javax.el.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.3.6.Final</version>
        </dependency>

依赖版本

注意:spring-boot的版本和springframework是匹配的

<jackson.version>2.9.4</jackson.version>
<logback.version>1.2.3</logback.version>
<logback.ext.version>0.1.4</logback.ext.version>
<org.springframework.version>4.3.25.RELEASE</org.springframework.version>
<spring-boot.version>1.5.22.RELEASE</spring-boot.version>
<mybatis.version>3.3.1</mybatis.version>
<mybatis-spring.version>1.2.3</mybatis-spring.version>
<mysql.version>5.1.38</mysql.version>
<javax.el.version>3.0.0</javax.el.version>
<validation-api.version>1.1.0.Final</validation-api.version>

实现AbstractRoutingDataSource,存储数据源

DataSourceNames 枚举,用于指定数据源名称

public enum DataSourceNames {
    ONE,
    TWO,
    SLANKKA;
}

DynamicDataSource

public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final ThreadLocal<DataSourceNames> dataSourceNameHolder = new ThreadLocal<>();

    public DynamicDataSource(
            DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }

    public static void setDataSource(DataSourceNames dataSource) {
        dataSourceNameHolder.set(dataSource);
    }

    public static DataSourceNames getDataSource() {
        return dataSourceNameHolder.get();
    }

    public static void clearDataSource() {
        dataSourceNameHolder.remove();
    }
}

定义SwitchSource注解,用于在方法上指定数据源

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SwitchSource {
    DataSourceNames value() default DataSourceNames.ONE;
}

定义三套数据源

数据源配置类 (Getter Setter省略)

@ConfigurationProperties
public class DatasourceConfig {
    private String url;
    private String driver;
    private String username;
    private String password;
}

    @Bean("firstDataSource")
    public DataSource secondDataSource(@Qualifier("firstDatasourceConfig") @Autowired DatasourceConfig firstDatasourceConfig) {
        final DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName(firstDatasourceConfig.getDriver());
        driverManagerDataSource.setUrl(firstDatasourceConfig.getUrl());
        driverManagerDataSource.setUsername(firstDatasourceConfig.getUsername());
        driverManagerDataSource.setPassword(firstDatasourceConfig.getPassword());
        return driverManagerDataSource;
    }


    @Bean("secondDataSource")
    public DataSource secondDataSource(@Qualifier("secondDatasourceConfig") @Autowired DatasourceConfig secondDatasourceConfig) {
        final DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName(secondDatasourceConfig.getDriver());
        driverManagerDataSource.setUrl(secondDatasourceConfig.getUrl());
        driverManagerDataSource.setUsername(secondDatasourceConfig.getUsername());
        driverManagerDataSource.setPassword(secondDatasourceConfig.getPassword());
        return driverManagerDataSource;
    }

    @Bean("thirdDataSource")
    public DataSource thirdDataSource(@Qualifier("slankkaDatasourceConfig") @Autowired DatasourceConfig slankkaDatasourceConfig) {
        final DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName(slankkaDatasourceConfig.getDriver());
        driverManagerDataSource.setUrl(slankkaDatasourceConfig.getUrl());
        driverManagerDataSource.setUsername(slankkaDatasourceConfig.getUsername());
        driverManagerDataSource.setPassword(slankkaDatasourceConfig.getPassword());
        return driverManagerDataSource;
    }

    @Bean
    @DependsOn(value = {"firstDataSource", "secondDataSource", "thirdDataSource"})
    public DynamicDataSource dataSource(
            DataSource firstDataSource, DataSource secondDataSource, DataSource thirdDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.ONE, firstDataSource);
        targetDataSources.put(DataSourceNames.TWO, secondDataSource);
        targetDataSources.put(DataSourceNames.SLANKKA, thirdDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }

数据源的配置

    @Bean
    @ConfigurationProperties
    public DatasourceConfig datasourceConfig() {
        return new DatasourceConfig();
    }

    @Bean
    @ConfigurationProperties("slankkatwo.datasource")
    public DatasourceConfig cloudDatasourceConfig() {
        return new DatasourceConfig();
    }

    @Bean
    @ConfigurationProperties("slankkathree.datasource")
    public DatasourceConfig platDatasourceConfig() {
        return new DatasourceConfig();
    }

定义切面

@Aspect
@Component
public class DataSourceAspect implements Ordered {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(com.slankka.blog.route.SwitchSource)")
    public void dataSourcePointCut() {}

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        SwitchSource ds = method.getAnnotation(SwitchSource.class);
        if (ds == null) {
            DynamicDataSource.setDataSource(DataSourceNames.ONE);
            logger.debug("set datasource <= " + DataSourceNames.ONE);
        } else {
            DynamicDataSource.setDataSource(ds.value());
            logger.debug("set datasource => " + ds.value());
        }
        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
            logger.debug("clean datasource");
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

Mybatis配置

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(@Autowired DataSource dataSource)
            throws IOException {
        final SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver()
                        .getResources("classpath*:mybatis/**/*.xml"));
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean
    //可选
    public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) throws IOException {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public DataSourceTransactionManager transactionManager(@Autowired DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        final MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.slankka.blog.dao");
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
        return mapperScannerConfigurer;
    }

开启最小化必要注解

@Configuration
@EnableConfigurationProperties(DatasourceConfig.class)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AnnotationDrivenAppConfig {
    //配置类
}

启动入口定义

说明:这里手动启动Spring Framework,没有用到SpringBoot的方式

public class Application {

    public static void main(String[] args) {
        post();
    }

    public static void post() {
        ConfigurableApplicationContext ctx =
                new AnnotationConfigApplicationContext("com.slankka.blog");
        ctx.start();
        try{
            //business
        }finally {
            ctx.close();
        }
    }
}