Spring多数据源配置和动态切换

平常我们工作有时会有读写分离,或者业务数据在不同的数据库的情况,如果在一个项目里操作,就需要配置多个数据源,并进行动态的切换了。

服务框架:spring + mybatis + druid

多数据源的配置有两种方式:

第一种是基于配置来实现:将数据源的配置一摸一样的配置多个,这种情况下,就需要将不同的业务拆分到不同的包下了,数据源配置中指定扫描不同的包。

<!-- ========= A数据源配置 ======== -->
<bean id="dataSourceA" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- MySQL数据库配置 -->
    <property name="url" value="${db.a.url}" />
    <property name="username" value="${db.a.username}" />
    <property name="password" value="${db.a.password}" />
    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="${db.a.initial-size}" />
    <property name="minIdle" value="${db.a.min-idle}" />
    <property name="maxActive" value="${db.a.max-active}" />
    <!-- 配置获取连接等待超时的时间 -->
    <property name="maxWait" value="${db.a.max-wait}" />
    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="${db.a.time-between-eviction-runs-millis}" />
    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="${db.a.min-evictable-idle-time-millis}" />
    <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
    <property name="poolPreparedStatements" value="${db.a.pool-prepared-statements}" />
    <property name="maxPoolPreparedStatementPerConnectionSize" value="${db.a.max-pool-prepared-statement-per-connection-size}" />
</bean>


<bean id="sqlSessionFactoryA" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSourceA"/>
    <!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
    <property name="typeAliasesPackage" value="dbsource.manager.a.entity"/>
    <!-- 显式指定Mapper文件位置 -->
    <property name="mapperLocations" value="classpath:/asqlmap/*Mapper.xml"/>
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryA"/>
    <property name="basePackage" value="dbsource.manager.a.dao"/>
</bean>

<bean id="transactionManagerA" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSourceA"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManagerA"  proxy-target-class="true"/>


<!-- ========= B数据源配置 ======== -->
<bean id="datasourceB" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- MySQL数据库配置 -->
    <property name="url" value="${db.b.url}" />
    <property name="username" value="${db.b.username}" />
    <property name="password" value="${db.b.password}" />
    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="${db.b.initial-size}" />
    <property name="minIdle" value="${db.b.min-idle}" />
    <property name="maxActive" value="${db.b.max-active}" />
    <!-- 配置获取连接等待超时的时间 -->
    <property name="maxWait" value="${db.b.max-wait}" />
    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="${db.b.time-between-eviction-runs-millis}" />
    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="${db.b.min-evictable-idle-time-millis}" />
    <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
    <property name="poolPreparedStatements" value="${db.b.pool-prepared-statements}" />
    <property name="maxPoolPreparedStatementPerConnectionSize" value="${db.b.max-pool-prepared-statement-per-connection-size}" />
</bean>

<bean id="sqlSessionFactoryB" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="datasourceB"/>
    <!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
    <property name="typeAliasesPackage" value="dbsource.manager.b.entity"/>
    <!-- 显式指定Mapper文件位置 -->
    <property name="mapperLocations" value="classpath:/bsqlmap/*Mapper.xml"/>
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryB"  />
    <property name="basePackage" value="dbsource.manager.b.dao"/>
</bean>

<bean id="tmsgTransactionManagerB" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasourceB"/>
</bean>
<tx:annotation-driven transaction-manager="tmsgTransactionManagerB"  proxy-target-class="true"/>

划重点:po、dao、mapper三处的文件需要放在不同的包里,不同的数据源配置扫描不同的包

这种多数据源配置改动起来很简单,缺点就是不够灵活,无法对具体的场景进行数据源的切换。

第二种是基于切面来实现:自定义注解,在需要切换数据源的地方加上注解,切换到指定的数据源。

定义数据源类型枚举类

public enum DBTypeEnum {

    /**
     * A数据库
     */
    ADB,

    /**
     * B数据库
     */
    DBD;

}

定义数据源类型管理类

public class DatasourceTypeManager {

    private final static ThreadLocal<DBTypeEnum> DB_HOLDER = new ThreadLocal<>();

    public static DBTypeEnum getDatasource() {
        return DB_HOLDER.get();
    }

    public static void setDatasource(DBTypeEnum dbType) {
        DB_HOLDER.set(dbType);
    }

    public static void resetDatasource() {
        DB_HOLDER.set(DBTypeEnum.MASTER);
    }

}

Threadlocal顾名思义保存的是每个线程自己的变量,线程间是彼此隔离的,所以不会有线程安全问题。Threadlocal会为每个线程创建一个副本,存储变量,线程内任何地方都可以直接使用,所以对性能不会有严重影响。

实现抽象类AbstractRoutingDataSource,实现数据源的动态路由

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DatasourceTypeManager.getDatasource();
    }

}

自定义注解,这里定义注解可加在类上和方法上

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Datasource {

    DBTypeEnum type() default DBTypeEnum.ADB;

}

切面实现数据源动态切换

@Aspect
@Order(0)
@Component
public class DatasourceAspect {

    @Pointcut("@annotation(dbsource.manager.multipledb.Datasource) " +
        "|| @within(dbsource.manager.multipledb.Datasource)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 数据源切换
        this.changeDbType(joinPoint);

        try {
            return joinPoint.proceed();
        } finally {
            DatasourceTypeManager.resetDatasource();
        }
    }

    /**
     * 数据源切换,优先方法上的配置,方法上没有配置,则取类上的配置
     *
     * @param joinPoint
     */
    private void changeDbType(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Datasource datasource = method.getAnnotation(Datasource.class);
        if (datasource != null) {
            DatasourceTypeManager.setDatasource(datasource.type());
            return;
        }
        // 获取类上的注解
        datasource = joinPoint.getTarget().getClass().getAnnotation(Datasource.class);
        if (datasource == null) {
            DatasourceTypeManager.setDatasource(DBTypeEnum.ADB);
        } else {
            DatasourceTypeManager.setDatasource(datasource.type());
        }
    }
}

说明:切点中,@annotation匹配方法上的注解,@within匹配类上的注解

数据源的XML配置,多个数据源的基本信息配置是省不掉的,还有就是在配置中向DynamicDataSource注入多个数据源

<!-- ========= A数据源配置 ======== -->
<bean id="dataSourceA" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- MySQL数据库配置 -->
    <property name="url" value="${db.a.url}" />
    <property name="username" value="${db.a.username}" />
    <property name="password" value="${db.a.password}" />
    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="${db.a.initial-size}" />
    <property name="minIdle" value="${db.a.min-idle}" />
    <property name="maxActive" value="${db.a.max-active}" />
    <!-- 配置获取连接等待超时的时间 -->
    <property name="maxWait" value="${db.a.max-wait}" />
    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="${db.a.time-between-eviction-runs-millis}" />
    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="${db.a.min-evictable-idle-time-millis}" />
    <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
    <property name="poolPreparedStatements" value="${db.a.pool-prepared-statements}" />
    <property name="maxPoolPreparedStatementPerConnectionSize" value="${db.a.max-pool-prepared-statement-per-connection-size}" />
</bean>

<!-- ========= B数据源配置 ======== -->
<bean id="datasourceB" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- MySQL数据库配置 -->
    <property name="url" value="${db.b.url}" />
    <property name="username" value="${db.b.username}" />
    <property name="password" value="${db.b.password}" />
    <!-- 配置初始化大小、最小、最大 -->
    <property name="initialSize" value="${db.b.initial-size}" />
    <property name="minIdle" value="${db.b.min-idle}" />
    <property name="maxActive" value="${db.b.max-active}" />
    <!-- 配置获取连接等待超时的时间 -->
    <property name="maxWait" value="${db.b.max-wait}" />
    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="${db.b.time-between-eviction-runs-millis}" />
    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="${db.b.min-evictable-idle-time-millis}" />
    <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
    <property name="poolPreparedStatements" value="${db.b.pool-prepared-statements}" />
    <property name="maxPoolPreparedStatementPerConnectionSize" value="${db.b.max-pool-prepared-statement-per-connection-size}" />
</bean>

<!-- 多个数据源注入 -->
<bean id="dataSource" class="dbsource.manager.multipledb.DynamicDataSource">
    <!-- 设置默认数据源 -->
    <property name="defaultTargetDataSource" ref="datasourceA"/>
    <property name="targetDataSources">
      <map key-type="dbsource.manager.multipledb.DBTypeEnum">
        <entry key="ADB" value-ref="datasourceA"/>
        <entry key="BDB" value-ref="datasourceB"/>
      </map>
    </property>
  </bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!-- 自动扫描entity目录, 省掉Configuration.xml里的手工配置 -->
    <property name="typeAliasesPackage" value="dbsource.manager.entity"/>
    <!-- 显式指定Mapper文件位置 -->
    <property name="mapperLocations" value="classpath:/sqlmap/*Mapper.xml"/>
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    <property name="basePackage" value="dbsource.manager.dao"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"  proxy-target-class="true"/>

然后我们就可以在需要切换数据源的方法和类上加上注解了 @Datasource(type = DBTypeEnum.ADB)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值