项目源码

spring-boot-dynamic-datasource-started-master.rar

多数据源配置

AbstractRoutingDataSource

spring jdbc包中,本身对多数据源的支持。
AbstractRoutingDataSource 是 DateSource 的子子类。
其中有targetDataSources 以map的形式包含所有注册的数据源。
实现该DataSource,并实现获取key的方法

  1. public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
  2. private static final Logger LOG = Logger.getLogger(DynamicRoutingDataSource.class);
  3. @Override
  4. //SqlSessionFactory 拿用户指定的数据源的key
  5. protected Object determineCurrentLookupKey() {
  6. return DynamicDataSourceContextHolder.get();
  7. }
  8. }

配置文件把数据源的连接信息都写上

###spring boot自动配置单数据源###
spring.datasource.url=jdbc:mysql://localhost:3306/db_master?useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=123456
###手动配置多数据源###
#mydb1
multiple.datasource.mydb1.driver-class-name=com.mysql.jdbc.Driver
multiple.datasource.mydb1.url=jdbc:mysql://localhost:3306/mydb1?useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&allowMultiQueries=true
multiple.datasource.mydb1.username=root
multiple.datasource.mydb1.password=123456
#mydb2
multiple.datasource.mydb2.driver-class-name=com.mysql.jdbc.Driver
multiple.datasource.mydb2.url=jdbc:mysql://localhost:3306/mydb2?useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&allowMultiQueries=true
multiple.datasource.mydb2.username=root
multiple.datasource.mydb2.password=123456

spring的配置类声明数据源
所有的数据源都放在了 DynamicRoutingDataSource的map中
这里dynamicDataSource 自己实现的DataSource 将给到需要数据源的地方使用


    //使用 Druid 连接池管理, 手动配置前缀
    @Bean
    @ConfigurationProperties(prefix = "multiple.datasource.mydb1")
    public DataSource mydb1() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "multiple.datasource.mydb2")
    public DataSource mydb2() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 核心动态数据源
     * @return 数据源实例
     */
    @Bean
    public DataSource dynamicDataSource() {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        //默认 数据库
        dataSource.setDefaultTargetDataSource(mydb1());
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put(DataSourceKey.mydb1, mydb1());
        dataSourceMap.put(DataSourceKey.mydb2, mydb2());
        dataSource.setTargetDataSources(dataSourceMap);
        return dataSource;
    }

给SqlSessionFactory 、 TransactionManager 用

@Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        //此处设置为了解决找不到mapper文件的问题
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    /**
     * 事务管理
     *
     * @return 事务管理实例
     */
    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }

多数据源使用

思路:
配置已经完成,
现在需要在 sqlSessionFactroy 在生成 sqlSession之前,设置好我们指定的数据源。

生成 sqlSession 在 工厂内部会调用该方法,通过
AbstractRoutingDataSource determineCurrentLookupKey 拿到key, 再使用key去拿我们预先设置好的数据源。

使用注解标识事务方法,需要匹配的数据源

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    DataSourceKey dataSourceKey() default DataSourceKey.mydb1;
}
@Service("userInfoService")
public class UserInfoServiceImpl implements UserInfoService {
    private static final Logger LOG = Logger.getLogger(UserInfoServiceImpl.class);
    @Resource
    private UserInfoMapper userInfoMapper;

    @TargetDataSource(dataSourceKey = DataSourceKey.mydb1)
    @Override
    public List<UserInfo> listAll() {
        return userInfoMapper.listAll();
    }

    @TargetDataSource(dataSourceKey = DataSourceKey.mydb1)
    @Override
    public int insert(UserInfo userInfo) {
        return userInfoMapper.insert(userInfo);
    }

    @TargetDataSource(dataSourceKey = DataSourceKey.mydb2)
    @Override
    public List<UserInfo> listAlldb2() {
        return userInfoMapper.listAll();
    }

    @TargetDataSource(dataSourceKey = DataSourceKey.mydb2)
    @Override
    public int insertdb2(UserInfo userInfo) {
        return userInfoMapper.insert(userInfo);
    }
}

使用aop在方法执行前,做数据源的设置。 数据源的设置放在ThreadLocal中

aop,切点为我们定义的注解。 往线程

    @Before("@annotation(targetDataSource)")
    public void doBefore(JoinPoint joinPoint, TargetDataSource targetDataSource) {
        DataSourceKey dataSourceKey = targetDataSource.dataSourceKey();
        if (dataSourceKey == DataSourceKey.mydb1) {
            DynamicDataSourceContextHolder.set(DataSourceKey.mydb1);
        } if (dataSourceKey == DataSourceKey.mydb2) {
            DynamicDataSourceContextHolder.set(DataSourceKey.mydb2);
        }else{ //如果未匹配默认 mydb1
            DynamicDataSourceContextHolder.set(DataSourceKey.mydb1);
        }
    }

使用ThreadLocal 存储设置(使用ThreadLocal好处:线程隔离,且配置暂存后本线程内可随处取出)

public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<DataSourceKey> currentDatesource = new ThreadLocal<>();

    public static void clear() {
        currentDatesource.remove();
    }

    public static DataSourceKey get() {
        return currentDatesource.get();
    }

    public static void set(DataSourceKey value) {
        currentDatesource.set(value);
    }
}

再回到自定义的数据源中看下。 从ThreadLocal中拿key,匹配上注解定义的数据源。
return DynamicDataSourceContextHolder.get();

实现只需在事务方法上定义注解,其余流程自动匹配数据源并执行返回