AOP 动态数据源、主从分离

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;
}
}



*******************************************************************************************************************************************
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值