spring配置双数据源

系列文章目录

第一章 spring框架构成
第二章 spring容器
第三章 spring配置bean
第四章 bean的继承和依赖
第五章 bean的生命周期
第六章 spring依赖注入
第七章 Spring AOP
第八章 spring事务
第九章 spring事件监听
第十章 web应用使用spring
第十一章 spring注解整理
第十二章 spring事务失效场景
第十三章 spring bean的作用域
第十四章 spring的扩展接口
第十五章 spring引入外部属性文件
第十六章 spring自定义属性编辑器
第十七章 spring配置双数据源



原文链接 https://zhhll.icu/2021/框架/spring/基础/18.spring配置双数据源/

spring配置双数据源

前段时间有个需求,需要将将数据存到两个数据库中,一个库中存放主信息,一个库中存放特殊信息,看来是要使用双数据源了,来搞起来吧

既然是双数据源,先不管怎么切换,配置得先搞起来

数据源配置

<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>

    <!-- 初始化连接大小 -->
    <property name="initialSize" value="5"/>
    <!-- 连接池最大使用连接数量 -->
    <property name="maxActive" value="10"/>
    <!-- 连接池最小空闲 -->
    <property name="minIdle" value="1"/>
    <!-- 获取连接最大等待时间 -->
    <property name="maxWait" value="3000"/>

    <property name="poolPreparedStatements" value="true"/>
    <property name="maxPoolPreparedStatementPerConnectionSize" value="33"/>

    <!-- 检测连接是否有效的sql -->
    <property name="validationQuery" value="SELECT 1"/>
    <!-- 申请连接时执行validationQuery检测连接是否有效 -->
    <property name="testOnBorrow" value="false"/>
    <!-- 归还连接时执行validationQuery检测连接是否有效 -->
    <property name="testOnReturn" value="false"/>
    <!-- 申请连接时检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 -->
    <property name="testWhileIdle" value="true"/>

    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="60000"/>
    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="300000"/>

    <!-- 打开removeAbandoned功能 -->
    <property name="removeAbandoned" value="false"/>
    
</bean>

<!-- 数据源 -->
<bean name="adDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="url" value="${adurl}"/>
    <property name="username" value="${adusername}"/>
    <property name="password" value="${adpassword}"/>

    <!-- 初始化连接大小 -->
    <property name="initialSize" value="5"/>
    <!-- 连接池最大使用连接数量 -->
    <property name="maxActive" value="10"/>
    <!-- 连接池最小空闲 -->
    <property name="minIdle" value="1"/>
    <!-- 获取连接最大等待时间 -->
    <property name="maxWait" value="3000"/>

    <property name="poolPreparedStatements" value="true"/>
    <property name="maxPoolPreparedStatementPerConnectionSize" value="33"/>

    <!-- 检测连接是否有效的sql -->
    <property name="validationQuery" value="SELECT 1"/>
    <!-- 申请连接时执行validationQuery检测连接是否有效 -->
    <property name="testOnBorrow" value="false"/>
    <!-- 归还连接时执行validationQuery检测连接是否有效 -->
    <property name="testOnReturn" value="false"/>
    <!-- 申请连接时检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效 -->
    <property name="testWhileIdle" value="true"/>

    <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
    <property name="timeBetweenEvictionRunsMillis" value="60000"/>
    <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
    <property name="minEvictableIdleTimeMillis" value="300000"/>

    <!-- 打开removeAbandoned功能 -->
    <property name="removeAbandoned" value="false"/>
    
</bean>

<!-- 动态数据源 -->
    <bean id="dynamicDataSource" class="com.zhanghe.webconfig.datasource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="video" value-ref="dataSource"/>
                <entry key="ad" value-ref="adDataSource"/>
            </map>
        </property>
        <!-- 默认数据源 -->
        <property name="defaultTargetDataSource" ref="dataSource"/>
    </bean>

动态数据源

在这里插入图片描述

配置好了数据源之后,需要进行定义动态数据源,继承AbstractRoutingDataSource,AbstractRoutingDataSource是基于特定的查找key路由到特定的数据源。它内部维护了一组目标数据源,并且做了路由key与目标数据源之间的映射,提供基于key查找数据源的方法。

public class DynamicDataSource extends AbstractRoutingDataSource {
    private final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);

    @Override
    protected Object determineCurrentLookupKey() {
      	// 获取当前数据源key
        String key = DataSourceHolder.getCurDataSource();
        LOGGER.info("{}线程 获取到的数据源key--->{}",Thread.currentThread().getName(),key);
				// 如果没有的话,则使用默认的key
        if(StringUtils.isBlank(key)){
            key = DataSourceHolder.getDefaultDataSource();
        }
        LOGGER.info("{}线程 数据源选择--->{}",Thread.currentThread().getName(),key);
        return key;
    }
}

// 存储当前数据源的key
public class DataSourceHolder {
    private static final ThreadLocal<String> CUR_DATA_SOURCE = new ThreadLocal<>();

    private static final String DEFAULT_DATA_SOURCE = "video";

    public static String getCurDataSource(){
        return CUR_DATA_SOURCE.get();
    }

    public static String getDefaultDataSource(){
        return DEFAULT_DATA_SOURCE;
    }

    public static void setCurDataSource(String dataSource){
        if(StringUtils.isNotBlank(dataSource)){
            CUR_DATA_SOURCE.set(dataSource);
        }
    }

    public static void clearDataSource(){
        CUR_DATA_SOURCE.remove();
    }
}

配置是都搞定了,那么怎么切换呢,可以看到在动态数据源中其实是根据key来进行路由获取数据源的,那么其实就是怎么改变这个key了,而且是动态改变,那么就用spring aop来进行解决吧

数据源切换

首先定义一个注解@DataSource,来标识当前方法要使用的数据源

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {

    String name() default "video";
}

然后来进行aop的逻辑来根据注解的name属性来存储key

@Order(1)  // 这里要注意一下,由于spring中的@Transactional也是使用的aop来开启事务的,而切换数据源要在开启事务之前,所以我将@Order设置为了1
@Aspect
@Component
public class DataSourceAspect {
    private final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);

    @Pointcut(value="@annotation(com.zhanghe.webconfig.datasource.DataSource)")
    public void pointcut(){

    }

    @Before(value = "pointcut()")
    public void before(JoinPoint joinPoint){
        String name = getDataSourceName(joinPoint);
        LOGGER.info("{}线程拦截切换数据源{}",Thread.currentThread().getName(),name);
        DataSourceHolder.setCurDataSource(name);
    }

    @After(value = "pointcut()")
    public void after(){
        DataSourceHolder.clearDataSource();
    }

    /**
     * 获取数据源lookupkey
     * @param joinPoint
     * @return
     */
    public String getDataSourceName(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        if (method != null) {
            DataSource dataSource = method.getAnnotation(DataSource.class);

            return dataSource.name();
        } else {
            return null;
        }
    }
}

这就大功告成了,双数据源的配置就搞定了

posted @   拾光师  阅读(25)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示