1. 定时检查数据库连接是否可用(手动切换)
昨天生产库一台装有数据库的服务器关机了。虽然另一台服务器上还有服务,但是共用的一个数据库。所以。。。主服务器挂了另一台也没啥用。
所有为避免再次出现这种情况,决定配置双数据库。我的需求是主数据库服务挂掉之后,副数据库能随时顶上。废话不多说,直接上代码
1. applicationContext-hibernate.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd">
<context:component-scan base-package="cn.com.itsea.insurance">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="fileEncoding" value="UTF-8"/>
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<!-- 主数据库配置 -->
<bean id="primary_dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${primary.jdbc.url}" />
<property name="driverClassName" value="${primary.jdbc.driverClassName}" />
<property name="username" value="${primary.jdbc.username}" />
<property name="password" value="${primary.jdbc.password}" />
<property name="initialSize" value="20" />
<property name="minIdle" value="20" />
<property name="maxActive" value="200" />
<property name="maxWait" value="60000" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 1 FROM DUAL" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
<property name="filters" value="stat" />
</bean>
<!-- 副数据库配置 -->
<bean id="second_dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${second.jdbc.url}" />
<property name="driverClassName" value="${second.jdbc.driverClassName}" />
<property name="username" value="${second.jdbc.username}" />
<property name="password" value="${second.jdbc.password}" />
<property name="initialSize" value="20" />
<property name="minIdle" value="20" />
<property name="maxActive" value="200" />
<property name="maxWait" value="60000" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 1 FROM DUAL" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
<property name="filters" value="stat" />
</bean>
<!-- 编写spring 配置文件的配置多数源映射关系 -->
<bean id="dataSource" class="cn.com.itsea.insurance.config.DynamicDataSourceConfig">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="primary_dataSource" value-ref="primary_dataSource"></entry>
<entry key="second_dataSource" value-ref="second_dataSource"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="primary_dataSource"></property>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan">
<list>
<value>cn.com.itsea.insurance.model</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.format_sql">false</prop>
</props>
</property>
</bean>
<aop:aspectj-autoproxy expose-proxy="true"/>
<tx:annotation-driven proxy-target-class="true" transaction-manager="txManager"/>
<bean id="txManager"class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="merge*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="put*" propagation="REQUIRED" />
<tx:method name="use*" propagation="REQUIRED"/>
<!--hibernate4必须配置为开启事务 否则 getCurrentSession()获取不到-->
<tx:method name="get*" propagation="REQUIRED" read-only="true" />
<tx:method name="count*" propagation="REQUIRED" read-only="true" />
<tx:method name="find*" propagation="REQUIRED" read-only="true" />
<tx:method name="list*" propagation="REQUIRED" read-only="true" />
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config expose-proxy="true">
<!-- 只对业务逻辑层实施事务 -->
<aop:pointcut id="txPointcut" expression="execution(* cn.com.itsea.insurance..service..*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
2. jdbc.xml
# Properties file with JDBC-related settings.
# Applied by PropertyPlaceholderConfigurer from "dataAccessContext-local.xml".
# Targeted at system administrators, to avoid touching the context XML files.
#Hangzhou
primary.jdbc.driverClassName=oracle.jdbc.OracleDriver
primary.jdbc.url=jdbc:oracle:thin:@192.168.1.2:1521:RLSBDB
primary.jdbc.username=root
primary.jdbc.password=root
second.jdbc.driverClassName=oracle.jdbc.OracleDriver
second.jdbc.url=jdbc:oracle:thin:@192.168.1.3:1521:RLSBDB
second.jdbc.username=root
second.jdbc.password=root
3. DynamicDataSourceConfig.java
package cn.com.itsea.insurance.config;
import cn.com.itsea.insurance.utils.LogUtils;
import cn.hutool.core.util.StrUtil;
import com.google.common.base.Stopwatch;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class DynamicDataSourceConfig extends AbstractRoutingDataSource implements ApplicationContextAware {
private static final String PRIMARY_DATASOURCE = "primary_dataSource";
private static final String SECOND_DATASOURCE = "second_dataSource";
private static String USER_DATASOURCE = PRIMARY_DATASOURCE;
private static final long SLEEP_TIME = 1000 * 60;
private static final Lock lock = new ReentrantLock();
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
protected Object determineCurrentLookupKey() {
return USER_DATASOURCE;
}
public void initialContext(){
new Thread(()->{
while (true){
try {
Thread.sleep(SLEEP_TIME);
dynamicallySwitchDataSources();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 监听当前使用数据源是否可用
* 如果不可用切换数据源
* 默认为主数据源
*/
private void dynamicallySwitchDataSources() {
// 是否需要切换数据源
boolean isItSwitchDataSources = false;
Stopwatch stopwatch = Stopwatch.createStarted();
StringBuilder logUtil = new StringBuilder("dataSources connection check");
lock.lock();
try {
// 当前使用的数据源类型
String userDataSourceType = (String) determineCurrentLookupKey();
logUtil.append(StrUtil.format("userDataSourceType={}",userDataSourceType));
boolean primaryDatasource = dataSourceIsClosed(PRIMARY_DATASOURCE);
boolean secondDatasource = dataSourceIsClosed(SECOND_DATASOURCE);
// 主数据源未关闭且正在使用副数据源
if(!primaryDatasource && !USER_DATASOURCE.equals(PRIMARY_DATASOURCE)){
USER_DATASOURCE = PRIMARY_DATASOURCE;
logUtil.append(StrUtil.format("userDataSourceType Switch to {}",userDataSourceType));
return;
}
// 主数据源已关闭且副数据源保持连接
if(primaryDatasource && !secondDatasource && !USER_DATASOURCE.equals(SECOND_DATASOURCE)){
USER_DATASOURCE = SECOND_DATASOURCE;
logUtil.append(StrUtil.format("userDataSourceType Switch to {}",userDataSourceType));
return;
}
}catch (Exception e){
e.printStackTrace();
logUtil.append(LogUtils.getTrace(e));
}finally {
// 日志处理
logUtil.append(StrUtil.format("cost.time={}",stopwatch.elapsed(TimeUnit.MILLISECONDS)));
log.info(logUtil.toString());
lock.unlock();
}
}
/**
* 数据源是否关闭
* @param dataSourceType DataSource 类型
* @return
*/
private boolean dataSourceIsClosed(String dataSourceType){
DataSource dataSource = (DataSource) applicationContext.getBean(dataSourceType);
if(null == dataSource){
return true;
}
try {
Connection connection = dataSource.getConnection();
boolean isClosed = connection.isClosed();
if(isClosed){
return true;
}
} catch (SQLException e) {
e.printStackTrace();
log.error(StrUtil.format("dataSourceType={} connection closed",dataSourceType));
return true;
}
return false;
}
}
4. SpringContextHolder.java
package cn.com.itsea.insurance.utils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
/**
* 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候取出ApplicaitonContext.
* @author <a href="mailto:912349441@qq.com">tangzx</a>
* @version V1.0
* @ClassName: SpringContextHolder
* @date 创建时间:2019-12-2 10:37:44
* @since JDK 1.7
*/
@Service
@Lazy(false)
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
private static Logger logger = LoggerFactory.getLogger(SpringContextHolder.class);
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
assertContextInjected();
return applicationContext;
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
assertContextInjected();
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
assertContextInjected();
return applicationContext.getBean(requiredType);
}
/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
public static void clearHolder() {
if (logger.isDebugEnabled()) {
logger.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
}
applicationContext = null;
}
/**
* 实现ApplicationContextAware接口, 注入Context到静态变量中.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextHolder.applicationContext = applicationContext;
}
/**
* 实现DisposableBean接口, 在Context关闭时清理静态变量.
*/
@Override
public void destroy() throws Exception {
SpringContextHolder.clearHolder();
}
/**
* 检查ApplicationContext不为空.
*/
private static void assertContextInjected() {
Validate.validState(applicationContext != null,
"applicaitonContext属性未注入, 请在applicationContext.xml中定义SpringContextHolder.");
}
}
需要在系统启动完成之后,需要调用 initialContext 方法
SpringContextHolder.getBean(DynamicDataSourceConfig.class).initialContext();
每隔一段时间获取主数据源连接心跳,如果连接不可用切换到副数据库。等到主数据库启动之后再切换回去。
2. 动态切换
2.1 DynamicDataSourceConfig
package cn.com.itsea.insurance.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSourceConfig extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSourceType();
}
}
2.2 DynamicDataSourceHolder.java
package cn.com.itsea.insurance.config;
public class DynamicDataSourceHolder {
// 线程本地环境
private static final ThreadLocal contextHolder = new ThreadLocal();
// 设置数据源类型
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
// 获取数据源类型
public static String getDataSourceType() {
return (String) contextHolder.get();
}
// 清除数据源类型
public static void clearDataSourceType() {
contextHolder.remove();
}
}
程序会随机访问两个数据库(建议数据库之间做了主从同步)
DynamicDataSourceHolder.setDataSourceType("second_dataSource");
转载请注明:https://blog.csdn.net/CSDN_Ty/article/details/108519274