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 ...
随机推荐
- HDU 1503 Advanced Fruits(LCS+记录路径)
http://acm.hdu.edu.cn/showproblem.php?pid=1503 题意: 给出两个串,现在要确定一个尽量短的串,使得该串的子串包含了题目所给的两个串. 思路: 这道题目就是 ...
- 【转】Scala基础知识
原文地址.续 课程内容: 关于这节课 表达式 值 函数 类 继承 特质 类型 apply方法 单例对象 函数即对象 包 模式匹配 样本类 try-catch-finally 关于这节课 最初的几个星期 ...
- uva11551矩阵快速幂
题目看了半天没看懂,,就是把一个数列更新r次,每次更新就是计算和,就是每一个数,只要出现了的表号都要加上去,具体看代码 矩阵快速幂实现加速 #include<map> #include&l ...
- wikioi 1035 火车停留 裸费用流
链接:http://wikioi.com/problem/1035/ 怎么说呢,只能说这个建图很有意思.因为只有m条道,然后能互相接在一起的连通,对每个点进行拆点,很有意思的一道裸费用留题. 代码: ...
- MarkdownPad2 下一些设置
MarkdownPad2注册码: 邮箱: Soar360@live.com 授权秘钥: GBPduHjWfJU1mZqcPM3BikjYKF6xKhlKIys3i1MU2eJHqWGImDHzWdD6 ...
- SpringBoot 使用 EhCache2.x 缓存(三十一)
SpringBoot 使用 EhCache2.x 缓存入门很简单,废话少说上干货: 1.在POM.xml中增加jar包 <!--开启 cache 缓存--> <dependency& ...
- 右键添加git bush here
由于sourcetree自动安装的git,导致右键没有git bush here,那么我们就自己添加一下. 运行regedit.exe进入注册表,在HKEY_CLASSES_ROOT\Director ...
- Centos7 安装JDK环境和Tomcat
Linux JDK 64位下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jdk9-downloads-3848520.ht ...
- jquery3.1.1报错Uncaught TypeError: a.indexOf is not a function
jquery3.1.1报错Uncaught TypeError: a.indexOf is not a function 使用1.9就没有问题,解决办法: 就是把写的代码中: $(window).lo ...
- Sql 基础问题
Ref Projection and Selection 联结查询的原理(笛卡尔积) 设计 MySQL 数据表的时候一般都有一列为自增 ID,这样设计原因是什么,有什么好处?