Spring主从数据源动态切换
- 不同类型数据源都可能存在master和slave区分;
- 数据源之间已经可以通过package区分,不同package对应的service也不同;
- aop在service层面,对应不同数据源的service之间可能存在互相调用;
- 最外层方法的名称决定了该数据源应该使用master(可写)还是slave数据源(不可写);
- 在嵌套使用其他service的过程中,根据情况分析该service方法是否使用slave数据源;
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<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"/>
<!--hibernate4必须配置为开启事务 否则 getCurrentSession()获取不到 -->
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="count*" 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="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice> <!-- 只对业务逻辑层实施事务 -->
<aop:config expose-proxy="true">
<aop:pointcut id="txPointcut" expression="(execution(* com.api.example.*.*.service..*.*(..)))
or (execution(* com.api.example.*.service..*.*(..)))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
实现方案

public class MasterSlaveDataSource extends AbstractDataSource implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(MasterSlaveDataSource.class);
private DataSource masterDataSource;
private Map<String, DataSource> slaveDataSourceMap;
@Override
public Connection getConnection() throws SQLException {
return determineDataSource().getConnection();
} @Override
public Connection getConnection(String username, String password) throws SQLException {
return determineDataSource().getConnection(username, password);
}
public DataSource determineDataSource() {
if (masterSlaveDataSourceDecision.isChoiceWrite()) {
log.debug("current determine write datasource");
return masterDataSource;
} else if (masterSlaveDataSourceDecision.isChoiceNone()) {
log.debug("no choice read/write, default determine write datasource");
return masterDataSource;
} else {
return selectReadDataSource();
}
}
public class MasterSlaveDataSourceProcessor implements BeanPostProcessor {
private Map<String, Boolean> readWriteMethodMap = new HashMap<String, Boolean>();
private String txAdviceName;
private MasterSlaveDataSourceDecision masterSlaveDataSourceDecision;
public void setTxAdviceName(String txAdviceName) {
this.txAdviceName = txAdviceName;
}
public void setMasterSlaveDataSourceDecision(MasterSlaveDataSourceDecision masterSlaveDataSourceDecision) {
this.masterSlaveDataSourceDecision = masterSlaveDataSourceDecision;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (txAdviceName.equalsIgnoreCase(beanName)) {
try {
TransactionInterceptor transactionInterceptor = (TransactionInterceptor) bean;
NameMatchTransactionAttributeSource transactionAttributeSource =
(NameMatchTransactionAttributeSource) transactionInterceptor.getTransactionAttributeSource();
Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");
nameMapField.setAccessible(true);
Map<String, TransactionAttribute> nameMap = (Map<String, TransactionAttribute>) nameMapField
.get(transactionAttributeSource);
for (Entry<String, TransactionAttribute> entry : nameMap.entrySet()) {
RuleBasedTransactionAttribute attr = (RuleBasedTransactionAttribute) entry.getValue();
// 仅对read-only的处理
String methodName = entry.getKey();
if (attr.isReadOnly()) {
if (forceChoiceReadWhenWrite) {
// 不管之前操作是写,默认强制从读库读 (设置为NOT_SUPPORTED即可)
// NOT_SUPPORTED会挂起之前的事务
attr.setPropagationBehavior(Propagation.NOT_SUPPORTED.value());
} else {
// 否则 设置为SUPPORTS(这样可以参与到写事务)
attr.setPropagationBehavior(Propagation.SUPPORTS.value());
}
}
log.info("read/write transaction process method:{} force read:{}", methodName,
forceChoiceReadWhenWrite);
readWriteMethodMap.put(methodName, attr.isReadOnly());
}
} catch (Exception e) {
throw new ReadWriteDataSourceTransactionException("process read/write transaction error", e);
}
}
return bean;
}
<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="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="count*" 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="*" propagation="SUPPORTS" read-only="true"/>
public Object selectDataSource(ProceedingJoinPoint pjp) throws Throwable {
if (isChoiceReadDB(pjp.getSignature().getName())) {
masterSlaveDataSourceDecision.markRead();
} else {
masterSlaveDataSourceDecision.markWrite();
}
try {
return pjp.proceed();
} catch (Throwable t) {
masterSlaveDataSourceDecision.reset();
throw t;
} finally {
masterSlaveDataSourceDecision.pop();
}
}
private boolean isChoiceReadDB(String methodName) {
String bestNameMatch = null;
for (String mappedName : this.readWriteMethodMap.keySet()) {
if (PatternMatchUtils.simpleMatch(mappedName, methodName)&&
(bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
bestNameMatch = mappedName;
}
}
// 默认走写库
boolean currentRead = (bestNameMatch == null ? false : readWriteMethodMap.get(bestNameMatch));
// 如果当前为读库,并且设置了强制读,则忽略当前主库写状态
if (currentRead && forceChoiceReadWhenWrite) {
return true;
}
// 如果之前选择了写库,则当前使用写库
if (masterSlaveDataSourceDecision.isChoiceWrite()) {
return false;
}
return currentRead;
}
public class MasterSlaveDataSourceDecision {
public enum DataSourceType {
write, read;
}
private final ThreadLocal<Stack<DataSourceType>> holder = new ThreadLocal<Stack<DataSourceType>>() {
@Override
protected Stack<DataSourceType> initialValue() {
return new Stack<>();
}
};
public void markWrite() {
holder.get().push(DataSourceType.write);
}
public void markRead() {
holder.get().push(DataSourceType.read);
}
public void reset() {
holder.get().clear();
}
public boolean isChoiceNone() {
return holder.get().isEmpty();
}
public boolean isChoiceWrite() {
return !isChoiceNone() && DataSourceType.write == holder.get().peek();
}
public boolean isChoiceRead() {
return !isChoiceNone() && DataSourceType.read == holder.get().peek();
}
public DataSourceType pop() {
return isChoiceNone() ? null : holder.get().pop();
}
}
配置方法
<bean id="masterSlaveDataSource" class="com.api.example.tx.MasterSlaveDataSource">
<property name="masterSlaveDataSourceDecision" ref="masterSlaveDataSourceDecision"/>
<property name="masterDataSource" ref="dataSource"/>
<property name="slaveDataSourceMap">
<map><entry key="slave1" value-ref="dataSourceSlave"/></map>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="masterSlaveDataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<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"/>
<!--hibernate4必须配置为开启事务 否则 getCurrentSession()获取不到 -->
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="count*" 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="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 只对业务逻辑层实施事务 -->
<aop:config expose-proxy="true">
<aop:pointcut id="txPointcut" expression="execution(* com.api.example.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> <aop:aspect order="-1" ref="masterSlaveDataSourceProcessor">
<aop:around method="selectDataSource" pointcut-ref="txPointcut"/>
</aop:aspect>
</aop:config>
<bean id="masterSlaveDataSourceProcessor" class="com.api.example.tx.MasterSlaveDataSourceProcessor">
<property name="txAdviceName" value="txAdvice"/>
<property name="masterSlaveDataSourceDecision" ref="masterSlaveDataSourceDecision"/>
</bean> <bean id="masterSlaveDataSourceDecision" class="com.api.example.tx.MasterSlaveDataSourceDecision"/>
Spring主从数据源动态切换的更多相关文章
- Spring多数据源动态切换
title: Spring多数据源动态切换 date: 2019-11-27 categories: Java Spring tags: 数据源 typora-root-url: ...... --- ...
- spring 多数据源动态切换
理解spring动态切换数据源,需要对spring具有一定的了解 工作中经常遇到读写分离,数据源切换的问题,那么以下是本作者实际工作中编写的代码 与大家分享一下! 1.定义注解 DataSource ...
- Spring Boot 如何动态切换数据源
本章是一个完整的 Spring Boot 动态数据源切换示例,例如主数据库使用 lionsea 从数据库 lionsea_slave1.lionsea_slave2.只需要在对应的代码上使用 Data ...
- 实战:Spring AOP实现多数据源动态切换
需求背景 去年底,公司项目有一个需求中有个接口需要用到平台.算法.大数据等三个不同数据库的数据进行计算.组装以及最后的展示,当时这个需求是另一个老同事在做,我只是负责自己的部分. 直到今年回来了,这个 ...
- Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源 方法
一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基 ...
- Springboot多数据源配置--数据源动态切换
在上一篇我们介绍了多数据源,但是我们会发现在实际中我们很少直接获取数据源对象进行操作,我们常用的是jdbcTemplate或者是jpa进行操作数据库.那么这一节我们将要介绍怎么进行多数据源动态切换.添 ...
- springboot多数据源动态切换和自定义mybatis分页插件
1.配置多数据源 增加druid依赖 完整pom文件 数据源配置文件 route.datasource.driver-class-name= com.mysql.jdbc.Driver route.d ...
- Spring3.3 整合 Hibernate3、MyBatis3.2 配置多数据源/动态切换数据源方法
一.开篇 这里整合分别采用了Hibernate和MyBatis两大持久层框架,Hibernate主要完成增删改功能和一些单一的对象查询功能,MyBatis主要负责查询功能.所以在出来数据库方言的时候基 ...
- mybatis 多数据源动态切换
笔者主要从事c#开发,近期因为项目需要,搭建了一套spring-cloud微服务框架,集成了eureka服务注册中心. gateway网关过滤.admin服务监控.auth授权体系验证,集成了redi ...
随机推荐
- eclipse中导入maven项目:org.apache.maven.archiver.MavenArchiver.getManifest(org.apache.maven.project.Maven
org.codehaus.plexus.archiver.jar.Manifest.write(java.io.PrintWriter) 解决方法为:更新eclipse中的maven插件 1.help ...
- python 随机整数
# Program to generate a random number between and # import the random module import random print(ran ...
- javascript之反柯里化(uncurrying)
在JavaScript中,当我们调用对象的某个方法时,其实不用去关心该对象原本是否被设计为拥有这个方法,这是动态类型语言的特点.可以通过反柯里化(uncurrying)函数实现,让一个对象去借用一个原 ...
- JAVA异常处理机制分析(上)
过去曾有一段时间关于java的异常处理机制曾经让我吃尽苦头,异常机制看似简单,原理,用法也仅仅如此,但是,用起来或是在使用一些框架的时候总会因为使用不当,造成灾难性后果. jdk异常处理机制 ...
- 安装pip最简单的方法
http://blog.csdn.net/lyj_viviani/article/details/70568434
- uva10944 状态压缩bfs or DP
又是一道状压搜索,题解有的是状压DP做的目前不会日后补 写好了以后一直蜜汁WA,看别人代码把判断再次回到原点的语句写在了Q.pop()之后而不是for里,对我也是一种启发吧这样写确实有好处比如起点就是 ...
- UVA-11925 Generating Permutations (逆向思维)
题目大意:给出1~n的某个排列,问由升序变到这个排列最少需要几次操作.操作1:将头两个数交换:操作2:将头一个数移动最后一个位置. 题目分析:反过来考虑,将这个排列变为升序排列,那么这个变化过程实际上 ...
- IOS-组件化架构漫谈
本文作者: 伯乐在线 - 刘小壮 .未经作者许可,禁止转载!欢迎加入伯乐在线 专栏作者. 前段时间公司项目打算重构,准确来说应该是按之前的产品逻辑重写一个项目
- 在Jsp中调用静态资源,路径配置问题,jsp获取路径的一些方法
在Jsp中调用图片.JS脚本等,针对取得的路径有两种调用方式: 1.放入Body中生成绝对路径(不建议) <%@ page language="java" import=&q ...
- 记录JAVAWEB部署到JBOSS中遇到的问题
服务器环境 jdk1.8.0_212 + jboss-eap-6.4 系统是 Service 2008 R2 数据服务器 oracle12c 系统是 Service 2008 R2 首先安装的or ...