一键配置Spring+Mybatis多数据源,效率提升80%!

本文介绍了如何在Spring项目中配置和管理多个数据源,包括通过配置文件设置两个数据源和基于AOP的动态数据源切换。通过自定义DataSource类和使用AbstractRoutingDataSource,实现了在业务层无感知的情况下灵活切换数据源。这种方法对于处理多个独立数据库的情况非常有效,同时也便于事务管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前段时间接手一个新的项目,需要同时涉及到两个数据库。两个数据库相关性较小,各自独立,其中一个数据库只需要获取相关业务注册信息。通过查找资料,找到了两种解决问题的办法。

 

最近整理的Java架构学习视频和大厂项目底层知识点,需要的同学欢迎私信我【资料】发给你~一起学习进步!

1. spring配置文件配置2个数据源

配置文件如下所示:

<-- 使用alibaba的Druid数据库连接池 -->
<-- 配置数据源One -->

<bean id="sqlSessionFactoryOne" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSourceOne" />
    <property name="configLocation" value="classpath:conf/mybatis-config-one.xml" />
</bean>

<-- mapper扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <-- 扫描包路径,如果需要扫描多个包,中间使用半角逗号隔开 -->
    <property name="basePackage" value="com.test.storage.dao.mapper.one" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryOne"/>
</bean>


<-- 配置数据源Two -->
<bean id="dataSourceTwo" class="com.alibaba.druid.pool.DruidDataSource"
      init-method="init" destroy-method="close">
    <property name="url" value="${url_two}"/>
    <property name="username" value="${username_two}"/>
    <property name="password" value="${password_two}"/>
    <property name="removeAbandoned" value="true"/>
    <property name="removeAbandonedTimeout" value="180"/>
    <property name="timeBetweenEvictionRunsMillis" value="20000"/>
    <property name="minEvictableIdleTimeMillis" value="30000"/>
    <property name="maxWait" value="5000"/>
    <property name="testWhileIdle" value="true"/>
    <property name="maxActive" value="200"/>
    <property name="minIdle" value="25"/>
    <property name="initialSize" value="25"/>
    <property name="validationQueryTimeout" value="50"/>
    <property name="validationQuery" value="SELECT 'x'" />
</bean>

<bean id="sqlSessionFactoryTwo" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSourceTwo" />
    <property name="configLocation" value="classpath:conf/mybatis-config-two.xml" />
</bean>

<-- mapper扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <-- 扫描包路径,如果需要扫描多个包,中间使用半角逗号隔开 -->
    <property name="basePackage" value="com.test.storage.dao.mapper.two" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryTwo"/>
</bean>

此种方法中除了 dataSource,sqlSessionFactory 和 MapperScannerConfigurer 都是配置了2份,mybatis-config.xml也是配置了2份,需要将不同数据源中使用到的别名和mapper.xml文件分开配置的。

此种方法的好处是mapper.xml 与 数据源绑定在了一起,业务层对底层是无感知的,缺点是当涉及到多个数据源的操作时是无法做到事务的。

2. 基于 AOP 的多数据源的配置

此方法涉及到了AbstractRoutingDataSource 抽象类。
需要自定义一个继承AbstractRoutingDataSource的DataSource类MultipleDataSource。
然后通过 AOP 动态的选择数据源的切换。

首先我们定义一个多数据源的枚举,如下所示:

public enum MultiDataSourceTypeEnum {

    One("dataSourceOne"),
    Two("dataSourceTwo") ;

    private String name;

    MultiDataSourceTypeEnum(String name) {
        this.name = name;
}

然后定义一个多数据源的管理类,通过 ThreadLocal 来标识每次使用的数据源。

public class MultiDataSourceTypeManager {

    private static final ThreadLocal<MultiDataSourceTypeEnum> dataSourceTypeEnum = new ThreadLocal<MultiDataSourceTypeEnum>(){
        @Override
        protected MultiDataSourceTypeEnum initialValue(){
            return MultiDataSourceTypeEnum.One;
        }
    };
    
    public static MultiDataSourceTypeEnum get(){
        return dataSourceTypeEnum .get();
    }
    
    public static void set(MultiDataSourceTypeEnum dataSourceType){
        dataSourceTypeEnum .set(dataSourceType);
    }    

}

接下来就是我们继承 AbstractRoutingDataSource 的自定义 DataSource类了。通过determineCurrentLookupKey 方法返回每次需要使用的数据源。

public class MultiDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return MultiDataSourceTypeManager.get();
    }
}

在配置文件中,除了如第一种方法一样定义两个数据源,还需要将我们自定义的 MultiDataSource 配置如下:

<bean id="dataSource" class="com.test.multiDatasource.MultiDataSource">
    <property name="defaultTargetDataSource" ref="dataSourceOne" />
    <property name="targetDataSources">
        <map key-type="com.test.multiDatasource.MultiDataSourceTypeEnum">
            <entry key="One" value-ref="dataSourceOne"/>
            <entry key="Two" value-ref="dataSourceTwo"/>
        </map>
    </property>
</bean>

下面就是我们具体控制数据源切换的地方。定义了两个切点,实现在service层调用mapper的时候将数据源进行切换。我是默认使用数据源one作为主数据源。

@Aspect
@Component
public class MultiDataSourceInterceptor {

    public static final Logger logger = Logger.getLogger(MultiDataSourceInterceptor.class);

    @Pointcut("execution(* com.test.service.two..*.*(..))")
    public void dataSourceTwo(){};

    @Pointcut("execution(* com.test.service.one..*.*(..))")
    public void dataSourceOne(){};

    @Before("dataSourceTwo()")
    public void beforeTwo(JoinPoint jp) {
        MultiDataSourceTypeManager.set(MultiDataSourceTypeEnum.Two);
        logger.info("MultiDataSourceInterceptor:" + MultiDataSourceTypeEnum.Two.toString());
    }

    @After("dataSourceTwo()")
    public void afterTwo(JoinPoint jp) {
        MultiDataSourceTypeManager.set(MultiDataSourceTypeEnum.One);
        logger.info("MultiDataSourceInterceptor:" + MultiDataSourceTypeEnum.One.toString());
    }

    @Before("dataSourceOne()")
    public void beforeOne(JoinPoint jp) {
        MultiDataSourceTypeManager.set(MultiDataSourceTypeEnum.One);
        logger.info("MultiDataSourceInterceptor:" + MultiDataSourceTypeEnum.One.toString());
    }

}

此方法扩展十分容易,切换的粒度通过AOP可以很好的控制。

原理分析:

我们自定义的 MultiDataSource 是继承了 AbstractRoutingDataSource的。在实例化bean的时候会调用 afterPropertiesSet()函数,该函数如下:

public void afterPropertiesSet() {
    if(this.targetDataSources == null) {
        throw new IllegalArgumentException("Property 'targetDataSources' is required");
    } else {
        this.resolvedDataSources = new HashMap(this.targetDataSources.size());
        Iterator var2 = this.targetDataSources.entrySet().iterator();

        while(var2.hasNext()) {
            Entry entry = (Entry)var2.next();
            Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());
            DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());
            this.resolvedDataSources.put(lookupKey, dataSource);
        }

        if(this.defaultTargetDataSource != null) {
            this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
        }

    }
}

注意这里的this.targetDataSources就是我们在配置文件中配置的

	<property name="targetDataSources">
        <map key-type="com.test.multiDatasource.MultiDataSourceTypeEnum">
            <entry key="One" value-ref="dataSourceOne"/>
            <entry key="Two" value-ref="dataSourceTwo"/>
        </map>
    </property>

通过此处,就将我们自定义的两个数据源定义进来了。在选择具体的数据源时会调用determineTargetDataSource()函数,该函数如下:

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = this.determineCurrentLookupKey();
    DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
    if(dataSource == null && (this.lenientFallback || lookupKey == null)) {
        dataSource = this.resolvedDefaultDataSource;
    }

    if(dataSource == null) {
        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    } else {
        return dataSource;
    }
}

注意此处的 this.determineCurrentLookupKey()函数,我们在 MultiDataSource 中重写了

protected Object determineCurrentLookupKey() {
    return MultiDataSourceTypeManager.get();
}

 

来源:网易工程师-杜满意

有任何问题欢迎留言交流~


整理总结不易,如果觉得这篇文章有意思的话,欢迎转发、收藏,给我一些鼓励~

有想看的内容或者建议,敬请留言!

最近利用空余时间整理了一些精选Java架构学习视频和大厂项目底层知识点,需要的同学欢迎私信我发给你~一起学习进步!有任何问题也欢迎交流~

Java日记本,每日存档超实用的技术干货学习笔记,每天陪你前进一点点~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值