1、读写分离
采用继承DataSourceTransactionManager控制事务读写分离,以及mybatis拦截器控制方法读写分离,通过继承AbstractRoutingDataSource获取动态数据源。
1.1、代码说明
1.1.1、本地数据源管理
创建本地数据源类型管理类,使用ThreadLocal保存本地数据源类型
bodsite-common - com.bodsite.common.datasource. DataSourceHandler
package com.bodsite.common.datasource; /** * @Description:本地线程数据源 * @author bod * @date * */ public class DataSourceHandler { public enum DYNAMIC_DATA_SOURCE{ MASTER,//主库(写) SLAVE;//从库(读) }
private static final ThreadLocal
protected static void set(DYNAMIC_DATA_SOURCE dynamic_data_source){ dataSourceThreadLocal.set(dynamic_data_source); }
/** * 设置为主库 * @author bod */ protected static void setMaster(){ dataSourceThreadLocal.set(DYNAMIC_DATA_SOURCE.MASTER); }
/** * 设置为读库 * @author bod */ protected static void setSlave(){ dataSourceThreadLocal.set(DYNAMIC_DATA_SOURCE.SLAVE); }
/** * 判断是否为主库 * @author bod */ protected static boolean isMaster(){ return isThis(DYNAMIC_DATA_SOURCE.MASTER); }
/** * 判断是否为从 * @author bod */ protected static boolean isSlave(){ return isThis(DYNAMIC_DATA_SOURCE.SLAVE); }
protected static boolean isThis(DYNAMIC_DATA_SOURCE dynamic_data_source){ if(dataSourceThreadLocal.get()==null){ return false; } return dynamic_data_source == dataSourceThreadLocal.get(); }
protected static void DataSoruceClean(){ dataSourceThreadLocal.remove(); } } |
1.1.2、mybatis拦截,设置数据源
创建mybatis拦截器,拦截update、query方法,设置数据源,如果有事务,不做拦截,有事务的情况,在事务管理器中进行设置。
bodsite-common - com.bodsite.common.datasource. DynamicDataSourceInterceptor
package com.bodsite.common.datasource;
import java.util.Properties;
import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.keygen.SelectKeyGenerator; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Plugin; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.springframework.transaction.support.TransactionSynchronizationManager;
/** * @Description: mybatis plugin 拦截器-设置数据源 * @author bod * @date * */ @Intercepts({ @Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }), // method:方法名,type:类,args:方法参数 @Signature(method = "update", type = Executor.class, args = { MappedStatement.class, Object.class }) }) public class DynamicDataSourceInterceptor implements Interceptor {
@Override public Object intercept(Invocation invocation) throws Throwable { // 是否有事务 boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive(); if (!synchronizationActive) { Object[] args = invocation.getArgs(); MappedStatement ms = (MappedStatement) args[0]; if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)){//查询 //!selectKey 为自增id查询主键(SELECT LAST_INSERT_ID() )方法,使用主库 if(ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) { DataSourceHandler.setMaster(); }else{ DataSourceHandler.setSlave(); } }else{//其他 DataSourceHandler.setMaster(); } } Object result = invocation.proceed(); DataSourceHandler.DataSoruceClean(); return result; }
@Override public Object plugin(Object target) { if (target instanceof Executor) { return Plugin.wrap(target, this); } else { return target; } }
@Override public void setProperties(Properties properties) {
}
}
|
1.1.3、spring事务管理,设置数据源
创建动态事务管理类,继承DataSourceTransactionManager,根据读写判断,设置数据源。
bodsite-common - com.bodsite.common.datasource. DataSourceTransactionManager
package com.bodsite.common.datasource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.TransactionDefinition;
/** * * @Description:根据事务这是数据源(必须有事务才能进入) * @author bod * @date * */ public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager{
/** * */ private static final long serialVersionUID = 1L;
@Override protected void doBegin(Object transaction, TransactionDefinition definition) { boolean readOnly = definition.isReadOnly(); if(readOnly){//只读 DataSourceHandler.setSlave(); }else{//读写 DataSourceHandler.setMaster(); } super.doBegin(transaction, definition); }
@Override protected void doCleanupAfterCompletion(Object transaction) { super.doCleanupAfterCompletion(transaction); DataSourceHandler.DataSoruceClean(); }
}
|
1.1.4、动态获取数据源
创建动态获取数据源类,继承AbstractRoutingDataSource,根据读写判断,设置数据源。
提供两种获取数据源方式:
1、通过重写determineCurrentLookupKey方法,返回数据源名称,走AbstractRoutingDataSource的determineTargetDataSource(根据determineCurrentLookupKey返回的名称)方法获取数据源。
2、通过重写determineTargetDataSource。直接返回数据源。
bodsite-common - com.bodsite.common.datasource. DynamicDataSource
package com.bodsite.common.datasource;
import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import com.bodsite.common.logger.Logger; import com.bodsite.common.logger.LoggerFactory;
/** * @Description:动态数据源 * @author bod * @date * */ public class DynamicDataSource extends AbstractRoutingDataSource { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class); private DataSource master; private List private int slaveSize; //读数据源个数 private Integer strategy;// 默认,0:轮询,1,随机 private AtomicInteger counter = new AtomicInteger(); private Map
/******************* 1、加载数据源关系, 设置数据源名,会根据数据源名返回数据源 ************/ /** * 设置数据源名 */ @Override protected Object determineCurrentLookupKey() { return getDataSourceName(); }
/** * 设置数据源映射关系 */ @Override public void afterPropertiesSet() { if (this.master == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } setDefaultTargetDataSource(master); targetDataSources.put(DataSourceHandler.DYNAMIC_DATA_SOURCE.MASTER.name(), master); if (slaves != null && !slaves.isEmpty()) { this.slaveSize = slaves.size(); for (int i = 0; i < slaveSize; i++) { targetDataSources.put(DataSourceHandler.DYNAMIC_DATA_SOURCE.SLAVE.name() + i, slaves.get(i)); } } setTargetDataSources(targetDataSources); super.afterPropertiesSet(); }
/******************* 2、直接返回数据源 ***********************/ /** * 原方法:根据数据源名称返回数据源,自定义直接返回数据源 */ /*@Override protected DataSource determineTargetDataSource() { return (DataSource) targetDataSources.get(getDataSourceName()); }*/
/** * 获取数据源名称 * @author bod */ public String getDataSourceName() { String dataSourceName = null; if (DataSourceHandler.isMaster()) { dataSourceName = DataSourceHandler.DYNAMIC_DATA_SOURCE.MASTER.name(); } else if (DataSourceHandler.isSlave() && slaves != null && !slaves.isEmpty()) { int index = 0; if (strategy == null || strategy == 0) { int count = counter.incrementAndGet(); index = count%slaveSize; }else if(strategy == 1){ index = ThreadLocalRandom.current().nextInt(0, slaveSize); } dataSourceName = DataSourceHandler.DYNAMIC_DATA_SOURCE.SLAVE.name()+index; }else{ dataSourceName = DataSourceHandler.DYNAMIC_DATA_SOURCE.MASTER.name(); } logger.info("This data source name is "+dataSourceName); return dataSourceName; } public DataSource getMaster() { return master; }
public void setMaster(DataSource master) { this.master = master; }
public List return slaves; }
public void setSlaves(List this.slaves = slaves; }
public Integer getStrategy() { return strategy; }
public void setStrategy(Integer strategy) { this.strategy = strategy; }
}
|
1.1.5、application-mybatis.xml配置
bodsite-site-service - application-mybatis.xml
"1.0" encoding="UTF-8"?> xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
init-method="init" destroy-method="close">
init-method="init" destroy-method="close">
class="com.bodsite.common.datasource.DynamicDataSourceTransactionManager">
proxy-target-class="true" />
|
1.1.6、mybatis-generator.xml 配置-添加拦截器
bodsite-site-service - mybatis- generator.xml
"1.0" encoding="UTF-8"?>
|
1.2、测试
1、启动bodsite-site-service,在bodsite-site中的DemoConsumer类,执行查询
2、在bodsite-site中的DemoConsumer类,执行插入
项目地址:https://git.oschina.net/bodsite/bodsite