spring配置多数据源及使用AOP加注解实现数据源动态切换和事务管理

spring配置多数据源及使用AOP加注解实现数据源动态切换

这里使用的是springboot,使用上和spring没区别

多数据源配置

首先在配置文件中定义多数据源的url、uername等信息。

#db1数据源
#使用p6spy打印sql
db1.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
db1.datasource.url=jdbc:p6spy:mysql://localhost:3306/db1
db1.datasource.username=root
db1.datasource.password=root

#db2数据源
#使用p6spy打印sql
db2.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
db2.datasource.url=jdbc:p6spy:mysql://localhost:3306/db2
db2.datasource.username=root
db2.datasource.password=root

#mybatis mapper.xml路径
mybatis.mapper-locations=classpath:mybatis-mapper/*.xml

然后定义数据库的dataSource的bean

首先实现一个基础类:

/**
 * description:
 * 数据源基础信息  及获取数据源的基础方法
 * @author wkGui
 */
public class BaseDataSource {
    private String driverClassName;
    private String url;
    private String username;
    private String password;

    /**
     * 配置获取数据源
     */
    public DataSource configurationDataSource() throws Exception{
        Map propertis = new HashMap<>(8);
        propertis.put("url", getUrl());
        propertis.put("password", getPassword());
        propertis.put("username", getUsername());
        propertis.put("driverClassName", getDriverClassName());
        return DruidDataSourceFactory.createDataSource(propertis);
    }

    //省略getter、setter方法
}

然后继承基础方法实现配置两个数据库的dataSource bean

/**
 * description:
 *  db1数据源配置
 * @author wkGui
 */
@Configuration
@ConfigurationProperties(prefix = "db1.datasource")
public class Db1DataSourceConfig extends BaseDataSource {

    @Bean(name = "db1DataSource")
    @Override
    public DataSource configurationDataSource() throws Exception {
        return super.configurationDataSource();
    }
}

/**
 * description:
 *  db2数据源配置
 * @author wkGui
 */
@Configuration
@ConfigurationProperties(prefix = "db2.datasource")
public class Db1DataSourceConfig extends BaseDataSource {

    @Bean(name = "db2DataSource")
    @Override
    public DataSource configurationDataSource() throws Exception {
        return super.configurationDataSource();
    }
}

动态切换数据源配置的原理

DataSource是和线程绑定的,动态数据源的配置主要是通过继承AbstractRoutingDataSource类实现的,实现在AbstractRoutingDataSource类中的 protected Object determineCurrentLookupKey()方法来获取数据源,所以我们需要先创建一个多线程线程数据隔离的类来存放DataSource,然后在determineCurrentLookupKey()方法中通过这个类获取当前线程的DataSource,在AbstractRoutingDataSource类中,DataSource是通过Key-value的方式保存的,我们可以通过ThreadLocal来保存Key,从而实现数据源的动态切换:

/**
 * description:
 *  数据源与Enum的映射
 * @author wkGui
 */
public enum  DataSourceTypeEnum {
    //db1数据库
    db1,
    //db2数据库
    db2
}

/**
 * description:
 *  保存线程安全的数据源
 * @author wkGui
 */
public class ConcurrentDataSourceHolder {
    private static final ThreadLocal HOLDER = new ThreadLocal<>();

    public static void setDataSource(DataSourceTypeEnum dataSourceTypeEnum){
        HOLDER.set(dataSourceTypeEnum);
    }

    public static DataSourceTypeEnum getDataSource(){
        return HOLDER.get();
    }

    public static void remove(){
        HOLDER.remove();
    }
}

/**
 * description:
 *  实现AbstractRoutingDataSource接口
 * @author wkGui
 */
public class DynamicDataSource extends AbstractRoutingDataSource{
    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return ConcurrentDataSourceHolder.getDataSource();
    }
}

然后配置动态数据源类DynamicDataSource、SqlSessionFactory以及DataSourceTransactionManager的bean


/**
 * description:
 * 动态数据源配置
 * SQLSessionFactory配置
 * 事务配置
 * @author wkGui
 */
@Configuration
public class DataSourceConfig {
    @Resource
    private Environment env;

    /**
     * 动态数据源配置(AbstractRoutingDataSource接口)
     * @param emrDataSource db1数据源
     * @param hisDataSource db1数据源
     */
    @Bean(name = "dynamicDataSource")
    public DynamicDataSource dataSource(@Qualifier("db1DataSource")DataSource emrDataSource,
                                        @Qualifier("db2DataSource")DataSource hisDataSource){
        Map targetDataSources = new HashMap<>(8);
        targetDataSources.put(DataSourceTypeEnum.db1, db1DataSource);
        targetDataSources.put(DataSourceTypeEnum.db2, db2DataSource);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        //默认数据源
        dataSource.setDefaultTargetDataSource(db1DataSource);

        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource);

        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapper-locations")));

        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 配置事务管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }

}

因为这里db2是只读的,所以基础的事务配置就可以满足,如果两个数据库都有读写操作就需要其他的事务管理方式了。

在每次操作数据库之前,通过ConcurrentDataSourceHolder的setDataSource(DataSourceTypeEnum dataSourceTypeEnum)方法来切换数据源,达到动态切换的目的。

ConcurrentDataSourceHolder.setDataSource(DataSourceTypeEnum.db2);
db2TableMapper.selectByID(1);

但是这样编写代码耦合度会非常高,使用很不方便。

使用AOP配合注解实现数据源的动态切换

我们先定义一个注解,注解有个value属性,根据其值动态切换数据源:

/**
 * description:
 *
 * @author wkGui
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DynamicDataSourceSign {
    DataSourceTypeEnum value();
}

然后使用AOP的Around方式来处理方法,当方法上有我们定义的 DynamicDataSourceSign注解时,根据其value动态切换数据源:

/**
 * description:
 *  根据DynamicDataSourceSign值动态切换数据源
 * @author wkGui
 */
@Component
@Aspect
public class DynamicDataSourceHandle {
    @Pointcut("execution(* com.wk..*.*(..))")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        DynamicDataSourceSign annotation = methodSignature.getMethod().getAnnotation(DynamicDataSourceSign.class);
        if (annotation != null) {
            //如果存在DynamicDataSourceSign 根据DynamicDataSourceSign切换数据源
            DataSourceTypeEnum dataSourceTypeEnum = annotation.value();
            ConcurrentDataSourceHolder.setDataSource(dataSourceTypeEnum);
        }
        Object obj = pjp.proceed();
        //清理dataSource
        ConcurrentDataSourceHolder.remove();
        return obj;
    }
}

然后将定义的注解加在我们需要动态切换数据源的方法上,就可以通过注解实现的动态切换数据源了。

@DynamicDataSourceSign(DynamicDataSourceSign.db2)
public xxx method1(){
    ......
}

这样在执行方法时就会通过AOP动态切换到db2的数据源啦。

事务管理

事务管理在开启时,需要确定数据源,也就是说数据源切换要在事务开启之前,我们可以使用Order来配置执行顺序,在AOP实现类上加Order注解,就可以使数据源切换提前执行,order值越小,执行顺序越靠前。

你可能感兴趣的:(spring-boot)