热门系列:
-
【Java编程系列】WebService的使用
-
【Java编程系列】在Spring MVC中使用工具类调用Service层时,Service类为null如何解决
-
【Java编程系列】Java与Mysql数据类型对应表
-
【Java编程系列】log4j配置日志按级别分别生成日志文件
-
【Java编程系列】使用Java进行串口SerialPort通讯
-
【Java编程系列】comet4j服务器推送实现
-
【Java编程系列】使用JavaMail通过SMTP协议发送局域网(内网)邮件
-
【Java编程系列】解决Java获取前端URL中加号(+)被转换成空格的问题
-
【Java编程系列】使用List集合对百万数据量高效快速过滤去重筛选
-
【Java编程系列】Java自定义标签-Tag
-
【Java编程系列】二进制如何表示小数?0.3+0.6为什么不等于0.9?纳尼!!!
-
程序人生,精彩抢先看
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>
本博客皆为学习、分享、探讨为本,欢迎各位朋友评论、点赞、收藏、关注,一起加油!