玩转Spring Boot 多数据源
在项目中有的时候需要用到多个数据源,有个问题就是单数据源的事务是没有问题的,多数据源是会存在事务问题的。这里不做事务讲解,事务可以用JTA分布式事务,也可以用MQ。具体不做叙述,接下来说如何实现多数据源并且使用AOP来切换。
本例代码使用Mybatis具体请看: 10.玩转Spring Boot 集成Mybatis, 11.玩转Spring Boot 集成Druid
1.在pom中加入以下依赖:
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-configuration-processor
true
org.springframework.boot
spring-boot-starter-aop
2.application.properties内容如下:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name1=com.mysql.jdbc.Driver
spring.datasource.url1=jdbc:mysql://localhost:3306/springboot2?useUnicode=true&characterEncoding=utf8
spring.datasource.username1=root
spring.datasource.password1=root
#最小连接数量
spring.datasource.minIdle=2
#最大连接数量
spring.datasource.maxActive=5
#获取连接等待超时的时间
spring.datasource.maxWait=60000
#间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
#连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
#验证SQL
spring.datasource.validationQuery=SELECT 'x' FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
#打开PSCache,并且指定每个连接上PSCache的大小如果用Oracle,
#则把poolPreparedStatements配置为true,mysql可以配置为false。分库分表较多的数据库,建议配置为false。
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
#配置监控统计拦截的filters
spring.datasource.filters=stat
3.MybatisConfig代码如下:
package com.chengli.springboot.dynamicds.config;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.alibaba.druid.pool.DruidDataSource;
import com.chengli.springboot.dynamicds.dynmic.DynmicDataSource;
@Configuration
@MapperScan(basePackages = { "com.chengli.springboot.dynamicds" }, annotationClass = Mapper.class) // 定义扫描的ROOT包,以及注解
@EnableTransactionManagement // 开启注解事务
public class MybatisConfig {
@Autowired
private DruidConfigProperties druidConfigProperties;
@Primary//设置为主要的,当同一个类型存在多个Bean的时候,spring 会默认注入以@Primary注解的bean
@Bean(initMethod = "init", destroyMethod = "close")
public DataSource springbootDataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(druidConfigProperties.getDriverClassName());
druidDataSource.setUrl(druidConfigProperties.getUrl());
druidDataSource.setUsername(druidConfigProperties.getUsername());
druidDataSource.setPassword(druidConfigProperties.getPassword());
druidDataSource.setInitialSize(druidConfigProperties.getMinIdle());
druidDataSource.setMinIdle(druidConfigProperties.getMinIdle());
druidDataSource.setMaxActive(druidConfigProperties.getMaxActive());
druidDataSource.setMaxWait(druidConfigProperties.getMaxWait());
druidDataSource.setTimeBetweenEvictionRunsMillis(druidConfigProperties.getTimeBetweenEvictionRunsMillis());
druidDataSource.setMinEvictableIdleTimeMillis(druidConfigProperties.getMinEvictableIdleTimeMillis());
druidDataSource.setValidationQuery(druidConfigProperties.getValidationQuery());
druidDataSource.setTestWhileIdle(druidConfigProperties.getTestWhileIdle());
druidDataSource.setTestOnBorrow(druidConfigProperties.getTestOnBorrow());
druidDataSource.setTestOnReturn(druidConfigProperties.getTestOnReturn());
druidDataSource.setPoolPreparedStatements(druidConfigProperties.getPoolPreparedStatements());
druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(druidConfigProperties.getMaxPoolPreparedStatementPerConnectionSize());
druidDataSource.setFilters(druidConfigProperties.getFilters());
return druidDataSource;
}
@Bean(initMethod = "init", destroyMethod = "close")
public DataSource eziliaoDataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(druidConfigProperties.getDriverClassName1());
druidDataSource.setUrl(druidConfigProperties.getUrl1());
druidDataSource.setUsername(druidConfigProperties.getUsername1());
druidDataSource.setPassword(druidConfigProperties.getPassword1());
druidDataSource.setInitialSize(druidConfigProperties.getMinIdle());
druidDataSource.setMinIdle(druidConfigProperties.getMinIdle());
druidDataSource.setMaxActive(druidConfigProperties.getMaxActive());
druidDataSource.setMaxWait(druidConfigProperties.getMaxWait());
druidDataSource.setTimeBetweenEvictionRunsMillis(druidConfigProperties.getTimeBetweenEvictionRunsMillis());
druidDataSource.setMinEvictableIdleTimeMillis(druidConfigProperties.getMinEvictableIdleTimeMillis());
druidDataSource.setValidationQuery(druidConfigProperties.getValidationQuery());
druidDataSource.setTestWhileIdle(druidConfigProperties.getTestWhileIdle());
druidDataSource.setTestOnBorrow(druidConfigProperties.getTestOnBorrow());
druidDataSource.setTestOnReturn(druidConfigProperties.getTestOnReturn());
druidDataSource.setPoolPreparedStatements(druidConfigProperties.getPoolPreparedStatements());
druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(druidConfigProperties.getMaxPoolPreparedStatementPerConnectionSize());
druidDataSource.setFilters(druidConfigProperties.getFilters());
return druidDataSource;
}
@Bean
public DataSource dynmicDataSource() throws SQLException {
DynmicDataSource dynmicDataSource = new DynmicDataSource();
Map targetDataSources = new HashMap<>();
targetDataSources.put("springbootDataSource", springbootDataSource());
targetDataSources.put("eziliaoDataSource", eziliaoDataSource());
dynmicDataSource.setTargetDataSources(targetDataSources);
dynmicDataSource.setDefaultTargetDataSource(springbootDataSource());
return dynmicDataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynmicDataSource")DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
// 添加Mybatis插件,例如分页,在之类创建你插件添加进去即可,这里我就不做叙述了。
// sqlSessionFactoryBean.setPlugins(new Interceptor[]{你的插件});
return sqlSessionFactoryBean.getObject();
}
@Bean
public PlatformTransactionManager transactionManager(@Qualifier("dynmicDataSource")DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
4.DruidConfigProperties代码修改,代码如下:
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidConfigProperties {
private String driverClassName;
private String url;
private String username;
private String password;
private String driverClassName1;
private String url1;
private String username1;
private String password1;
private Integer minIdle;
private Integer maxActive;
private Integer maxWait;
private Long timeBetweenEvictionRunsMillis;
private Long minEvictableIdleTimeMillis;
private String validationQuery;
private Boolean testWhileIdle;
private Boolean testOnBorrow;
private Boolean testOnReturn;
private Boolean poolPreparedStatements;
private Integer maxPoolPreparedStatementPerConnectionSize;
private String filters;
.........get set 方法省略
}
5.定义类,实现AbstractRoutingDataSource,代码如下:
package com.chengli.springboot.dynamicds.dynmic;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynmicDataSource extends AbstractRoutingDataSource {
/**
* 返回的内容是targetDataSources 的Key
*/
@Override
protected Object determineCurrentLookupKey() {
return DynmicDataSourceContextHolder.getDataSourceKey();
}
}
6.自定义注解UseDataSource
package com.chengli.springboot.dynamicds.dynmic;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UseDataSource {
String value();
}
7.创建DynmicDataSourceContextHolder,用于设置当前使用的数据源Key
package com.chengli.springboot.dynamicds.dynmic;
public class DynmicDataSourceContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal();
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void setDataSourceKey(String dataSourcekey) {
contextHolder.set(dataSourcekey);
}
public static void clear() {
contextHolder.remove();
}
}
8.自定义AOP,设置数据源DynamicDataSourceAspect
package com.chengli.springboot.dynamicds.dynmic;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Order(-1) //spring order排序后执行顺序是从小到大,目的是确保在事务管理器执行前先执行
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(useDataSource)")//拦截注解 UseDataSource
public void setDataSourceType(JoinPoint point, UseDataSource useDataSource) throws Throwable {
DynmicDataSourceContextHolder.setDataSourceKey(useDataSource.value());
}
@After("@annotation(useDataSource)")
public void clearDataSourceType(JoinPoint point, UseDataSource useDataSource) {
DynmicDataSourceContextHolder.clear();
}
}
到这里就完成啦,主要代码就上面这些,测试以及完整示例代码在QQ交流群中:springboot-dynamic-ds.zip
有兴趣的朋友可以加群探讨相互学习:
Spring Boot QQ交流群:599546061