【Java编程系列】Spring中使用代码实现动态切换主从库(多数据源)

热门系列:


1.前言

今天主要和大家分享一下,我在项目中使用到的通过java代码控制主从库切换的使用情况,也作为自己的一个笔记。


2.正题

①在spring-mybatis.xml中配置多数据源,如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 自动扫描 -->
<context:component-scan base-package="com.cams" />

<!-- 事务控制 -->
<tx:annotation-driven transaction-manager="transactionManager" />

<!-- 引入配置文件 -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties" />
</bean>

<!-- 主库 -->
<bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url1}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${jdbc.initialSize}"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${jdbc.maxActive}"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${jdbc.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${jdbc.minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${jdbc.maxWait}"></property>
<property name="testOnBorrow" value="true" />
<!-- 是否自我中断 -->
<!-- <property name="removeAbandoned" value="true"/> -->
<!-- 中断时限 -->
<!-- <property name="removeAbandonedTimeout" value="180" /> -->
<!-- 是否记录中断事件 -->
<!-- <property name="logAbandoned" value="true"></property> -->

<property name="timeBetweenEvictionRunsMillis" value="20000" />
<property name="minEvictableIdleTimeMillis" value="28000" />
<property name="validationQuery" value="select 1" />
</bean>

<!-- 从库 -->
<bean id="slaveDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url2}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${jdbc.initialSize}"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${jdbc.maxActive}"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${jdbc.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${jdbc.minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${jdbc.maxWait}"></property>
<property name="testOnBorrow" value="true" />
<!-- 是否自我中断 -->
<!-- <property name="removeAbandoned" value="true"/> -->
<!-- 中断时限 -->
<!-- <property name="removeAbandonedTimeout" value="180" /> -->
<!-- 是否记录中断事件 -->
<!-- <property name="logAbandoned" value="true"></property> -->

<property name="timeBetweenEvictionRunsMillis" value="20000" />
<property name="minEvictableIdleTimeMillis" value="28000" />
<property name="validationQuery" value="select 1" />
</bean>

<!-- 动态配置数据源(此处多数据源的key值必须与DynamicDataSource类中你设置调用数据库的名称一致,即目前DataSourceName类中的value值) -->
<bean id="dataSource" class="com.cams.DBOperation.DynamicDataSource">
<property name ="targetDataSources">
<map>
<entry value-ref ="masterDataSource" key="masterDataSource"></entry >
<entry value-ref ="slaveDataSource" key="slaveDataSource"></entry >
</map>
</property >
<property name ="defaultTargetDataSource" ref= "masterDataSource"></property >
</bean>

<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/cams/mapping/*.xml"></property>
</bean>

<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.cams.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>

<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<bean id="changeDBInterceptor" class="com.cams.DBOperation.ChangDBInterceptor"></bean>

<!-- 配置AOP切面,所有service作为切点,执行changeDBInterceptor类中的changeDB方法 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="txPointcut" expression="execution(* com.cams.service..*Service*..*(..))" />
<aop:aspect ref="changeDBInterceptor" order="1">
<aop:around pointcut-ref="txPointcut" method="changeDB"/>
</aop:aspect>
</aop:config>

</beans>

需注意:

此处配置多数据源后,数据源必须配置在名为dataSource的bean中,且该bean集成了spring框架的AbstractRoutingDataSource类(该类可设置决定调用哪一种数据源);而且sqlSessionFactory和transactionManager属性中所依赖也必须是此dataSource。


②在工程中添加4个类,分别是DataSourceContextHolder , DataSourceName , DynamicDataSource , ChangDBInterceptor

四个类代码分别如下:
public class DataSourceContextHolder {

        private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

        //设置当前Thread的数据库类型
        public static void setType(String type)
        {
            contextHolder.set(type);
        }

        //获取当前Thread的数据库类型
        public static String getType()
        {
            return (String)contextHolder.get();
        }

        //清除
        public static void clearType()
        {
            contextHolder.remove();
        }

}
public class DataSourceName {
        //设置数据库连接名称
        public static final String DBConnect_Master = "masterDataSource";
        public static final String DBConnect_slave = "slaveDataSource";
}
public class DynamicDataSource extends AbstractRoutingDataSource {

        /**
         * spring框架设置使用哪个数据源
         */
        @Override
        protected Object determineCurrentLookupKey() {
            // TODO Auto-generated method stub
            //如果当返回结果为null时则默认使用主库
            if(DataSourceContextHolder.getType() == null || "".equals(DataSourceContextHolder.getType()))
            {
                DataSourceContextHolder.setType(DataSourceName.DBConnect_Master);
            }

            return DataSourceContextHolder.getType();
        }

}
public class ChangDBInterceptor {

        private static Logger logger = Logger.getLogger(ChangDBInterceptor.class);
        private static ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-mybatis.xml");

        public Object changeDB(ProceedingJoinPoint pjp) throws Throwable
        {
            //获取主数据库
            DataSource dataSource = (DataSource)applicationContext.getBean("masterDataSource");

            Connection connection = null;
            try {
                //通过捕获异常判断主库是否可以连接
                connection = dataSource.getConnection();
                //如果主库连接关闭
                if(connection.isClosed())
                {
                    //获取从库判断是否可以连接
                    dataSource = (DataSource)applicationContext.getBean("slaveDataSource");
                    try {
                        connection = dataSource.getConnection();

                        if(connection.isClosed())
                        {
                            logger.warn("===================当前slaveDataSource从库已关闭连接,目前主从库都不可用===================");
                            return null;
                        }
                        else
                        {
                            DataSourceContextHolder.setType(DataSourceName.DBConnect_slave);
                            return pjp.proceed();
                        }

                    } catch (SQLException e1) {
                        // TODO Auto-generated catch block
                        logger.warn("===================当前slaveDataSource从库连接异常,且主库连接关闭,目前主从库都不可用===================\n");
                        e1.printStackTrace();
                        return null;
                    }
                }
                else
                {
                    //主库连接未有异常且未关闭,则用主库
                    DataSourceContextHolder.setType(DataSourceName.DBConnect_Master);
                    return pjp.proceed();
                }

            } catch (SQLException e) {
                // TODO Auto-generated catch block
                logger.warn("========================当前masterDataSource主库连接异常,准备连接slaveDataSource从库 ========================\n");
                e.printStackTrace();
                //获取从库判断是否可以连接
                dataSource = (DataSource)applicationContext.getBean("slaveDataSource");
                try {
                    connection = dataSource.getConnection();
                    if(connection.isClosed())
                    {
                        logger.warn("===================当前slaveDataSource从库已关闭连接,且主库连接异常,目前主从库都不可用===================");
                        return null;
                    }
                    else
                    {
                        DataSourceContextHolder.setType(DataSourceName.DBConnect_slave);
                        return pjp.proceed();
                    }
                } catch (SQLException e1) {
                    // TODO Auto-generated catch block
                    logger.warn("===================当前slaveDataSource从库连接异常,且主库连接异常,目前主从库都不可用===================\n");
                    e1.printStackTrace();
                    return null;
                }
            }

        }

}

注释:

DataSourceName :类为数据源bean名称设置类;

DataSourceContextHolder: 类为设置、获取、清除当前ThreadLocal中连接名称

DynamicDataSource :类为切换设置spring框架调用哪个数据源(关键类)

ChangDBInterceptor: 类为框架AOP切面拦截时所执行的类,其中的方法即是在每次AOP拦截service调用时先执行的方法


③设置spring-mybatis.xml中的AOP(在上面配置中已经出现,再贴一遍)

<!-- 该设置就是框架会自行调用设置中该类的方法 -->
<!-- 配置AOP切面,所有service作为切点,执行changeDBInterceptor类中的changeDB方法 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="txPointcut" expression="execution(* com.cams.service..*Service*..*(..))" />
<aop:aspect ref="changeDBInterceptor" order="1">
<aop:around pointcut-ref="txPointcut" method="changeDB"/>
</aop:aspect>
</aop:config>

 

 

本博客皆为学习、分享、探讨为本,欢迎各位朋友评论、点赞、收藏、关注,一起加油!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

善良勤劳勇敢而又聪明的老杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值