applicationContext-dao.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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
default-lazy-init="true">
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- HikariCP -->
<context:property-placeholder location="classpath:jdbc.properties" ignore-unresolvable="true" />
<bean id="masterDataSource1" class="com.zaxxer.hikari.HikariDataSource"
destroy-method="close">
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="connectionTestQuery" value="select 1"></property>
<property name="maximumPoolSize" value="20"></property>
</bean>
<bean id="slaveDataSource1" class="com.zaxxer.hikari.HikariDataSource"
destroy-method="close">
<property name="jdbcUrl" value="${jdbc.urlb}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="connectionTestQuery" value="select 1"></property>
<property name="maximumPoolSize" value="20"></property>
</bean>
<bean id="slaveDataSource2" class="com.zaxxer.hikari.HikariDataSource"
destroy-method="close">
<property name="jdbcUrl" value="${jdbc.urlc}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="connectionTestQuery" value="select 1"></property>
<property name="maximumPoolSize" value="20"></property>
</bean>
<!-- 自定义数据源 -->
<bean id="dynamicDataSourceHolder" class="cn.com.yangdi.notify.base.DynamicDataSourceHolder">
<property name="masterDataSources">
<map key-type="java.lang.String">
<entry value-ref="masterDataSource1" key="masterDataSource1"></entry>
</map>
</property>
<property name="slavetDataSources">
<map key-type="java.lang.String">
<entry value-ref="slaveDataSource1" key="slaveDataSource1"></entry>
<entry value-ref="slaveDataSource2" key="slaveDataSource2"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="slaveDataSource2" />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" lazy-init="false">
<property name="dataSource" ref="dynamicDataSourceHolder" />
<property name="packagesToScan">
<list>
<value>cn.com.yangdi.notify.model</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<!-- 常用数据库方言 MySQL5Dialect,SQLServerDialect,OracleDialect,SybaseDialect,DB2Dialect -->
<!-- <prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop> -->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext </prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.format_sql">false</prop>
<prop key="hibernate.query.substitutions">true 1, false 0</prop>
<prop key="hibernate.default_batch_fetch_size">4</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager"
lazy-init="false">
<property name="sessionFactory" ref="sessionFactory" />
<qualifier value="transactionManager" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Activates scanning of @Repository -->
<context:component-scan base-package="cn.com.yangdi.notify.dao" />
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
<tx:method name="query*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="list*" propagation="SUPPORTS" read-only="true" />
<tx:method name="search*" propagation="SUPPORTS" read-only="true" />
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<bean id="dynamicSourceAop" class="cn.com.yangdi.notify.aop.DynamicDataSourceAop" />
<aop:config expose-proxy="true">
<!-- 只对业务逻辑层实施事务 -->
<aop:pointcut id="txPointcut" expression="execution(* cn.com.yangdi.notify.biz..*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
<!-- 通过AOP切面实现读/写库选择 -->
<aop:aspect order="-2147483648" ref="dynamicSourceAop">
<aop:around pointcut-ref="txPointcut" method="doAroundMethod" />
</aop:aspect>
</aop:config>
</beans>
*******************************************************************************************************************************************
DynamicDataSourceHolder.java 内容如下:
package cn.com.yangdi.notify.base;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.sql.DataSource;
import org.apache.commons.lang3.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSourceHolder extends AbstractRoutingDataSource{
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceHolder.class);
// 线程本地环境
private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<String>();
// 可选取slave的keys
private List<String> slaveDataSourcesKeys;
// 可选取master的keys
private List<String> masterDataSourcesKeys;
private Map<String, DataSource> slavetDataSources;
private Map<String, DataSource> masterDataSources;
@Override
protected Object determineCurrentLookupKey() {
return dataSourceHolder.get();
}
@Override
public void afterPropertiesSet() {
//System.err.println(super.determineTargetDataSource());
// 数据检验和合并
logger.info("开始向spring routing datasource 提供数据源选取");
Map<Object, Object> allDataSources = new HashMap<Object, Object>();
allDataSources.putAll(masterDataSources);
if (slavetDataSources != null) {
allDataSources.putAll(slavetDataSources);
}
super.setTargetDataSources(allDataSources);
super.afterPropertiesSet();
logger.info("已经完成向spring routing datasource 提供数据源选取");
}
/**
* 注册slave datasource
*
* @param slavetDataSources
*/
public void setSlavetDataSources(Map<String, DataSource> slavetDataSources) {
if (slavetDataSources == null || slavetDataSources.size() == 0) {
return;
}
logger.info("提供可选取slave数据源:{}", slavetDataSources.keySet());
this.slavetDataSources = slavetDataSources;
slaveDataSourcesKeys = new ArrayList<String>();
for (Entry<String, DataSource> entry : slavetDataSources.entrySet()) {
slaveDataSourcesKeys.add(entry.getKey());
}
}
/**
* 注册master datasource
*
* @param masterDataSources
*/
public void setMasterDataSources(Map<String, DataSource> masterDataSources) {
if (masterDataSources == null) {
throw new IllegalArgumentException("Property 'masterDataSources' is required");
}
logger.info("提供可选取master数据源:{}", masterDataSources.keySet());
this.masterDataSources = masterDataSources;
this.masterDataSourcesKeys = new ArrayList<String>();
for (Entry<String, DataSource> entry : masterDataSources.entrySet()) {
masterDataSourcesKeys.add(entry.getKey());
}
}
/**
* 标记选取slave数据源
*/
public void markSlave() {
logger.info("调用slave数据源");
if (dataSourceHolder.get() != null) {
// 从现在的策略来看,不允许标记两次,直接抛异常,优于早发现问题
throw new IllegalArgumentException("当前已有选取数据源,不允许覆盖,已选数据源key:" + dataSourceHolder.get());
}
String dataSourceKey = selectFromSlave();
setDataSource(dataSourceKey);
}
/**
* 标记选取master数据源
*/
public void markMaster() {
logger.info("调用master数据源");
if (dataSourceHolder.get() != null) {
// 从现在的策略来看,不允许标记两次,直接抛异常,优于早发现问题
throw new IllegalArgumentException("当前已有选取数据源,不允许覆盖,已选数据源key:" + dataSourceHolder.get());
}
String dataSourceKey = selectFromMaster();
setDataSource(dataSourceKey);
}
/**
* 删除己标记选取的数据源
*/
public void markRemove() {
dataSourceHolder.remove();
}
/**
* 是否已经绑定datasource
* 绑定:true
* 没绑定:false
* @return
*/
public boolean hasBindedDataSourse(){
boolean hasBinded= dataSourceHolder.get()!=null;
return hasBinded;
}
private String selectFromSlave() {
if (slavetDataSources == null) {
logger.info("提供可选取slave数据源:{},将自动切换从主master选取数据源", slavetDataSources);
return selectFromMaster();
} else {
System.out.println("Slave size "+slaveDataSourcesKeys.size());
System.out.println(" RandomUtils.nextInt"+RandomUtils.nextInt(0,slaveDataSourcesKeys.size()));
return slaveDataSourcesKeys.get(RandomUtils.nextInt(0,slaveDataSourcesKeys.size()));
}
}
private String selectFromMaster() {
String dataSourceKey = masterDataSourcesKeys.get(RandomUtils.nextInt(0,masterDataSourcesKeys.size()));
return dataSourceKey;
}
private void setDataSource(String dataSourceKey) {
logger.info("dataSourceHolder set datasource keys:{}", dataSourceKey);
dataSourceHolder.set(dataSourceKey);
}
}
*******************************************************************************************************************************************
DynamicDataSourceAop 内容如下:
package cn.com.yangdi.notify.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import cn.com.yangdi.notify.base.DynamicDataSourceHolder;
public class DynamicDataSourceAop {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAop.class);
@Autowired
private DynamicDataSourceHolder dataSourceHolder;
public Object doAroundMethod(ProceedingJoinPoint pjp) throws Throwable {
Object response = null;
String method = pjp.getSignature().getName();
boolean hasBinded = false;
try {
logger.debug("AOP注入datasource");
logger.error("dataSourceHolder={}",dataSourceHolder );
// 以下策略可以抽成一个类
hasBinded = dataSourceHolder.hasBindedDataSourse();
if (!hasBinded) {
if (method.startsWith("query") || method.startsWith("select") || method.startsWith("find")
|| method.startsWith("get") || method.startsWith("list") || method.startsWith("search")) {
dataSourceHolder.markSlave();
} else {
dataSourceHolder.markMaster();
}
}
response = pjp.proceed();
} finally {
if (!hasBinded) {
dataSourceHolder.markRemove();
}
}
return response;
}
}