娄底市京豪网络科技有限公司
首页 | 联系方式 | 加入收藏 | 设为首页 | 手机站

产品目录

联系方式

联系人:业务部
电话: 00136-290669
邮箱:service@xinyujlm.com

当前位置:首页 >> 产品展示 >> 默认分类 >> 正文

springMvc-Mybatis 实现主从数据库/多数据源切换配置

详细信息:

一、配置文件

1、spring-mybatis.xml

master_driverUrl=jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
master_username=root
master_password=12345
slave_driverUrl=jdbc:mysql://localhost:3306/wechat?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
slave_username=root
slave_password=12345

2、spring-mybatis.xml
<!-- 多数据源aop datasource -->
 <context:component-scan base-package="com.wangzhixuan.commons.datasource" />
 <!-- base dataSource -->
 <bean name="baseDataSource" class="com.alibaba.druid.pool.DruidDataSource"
 destroy-method="close">
 <property name="initialSize" value="5" />
 <property name="maxActive" value="100" />
 <property name="minIdle" value="10" />
 <property name="maxWait" value="60000" />
 <property name="validationQuery" value="SELECT 'x'" />
 <property name="testOnBorrow" value="true" />
 <property name="testOnReturn" value="true" />
 <property name="testWhileIdle" value="true" />
 <property name="timeBetweenEvictionRunsMillis" value="60000" />
 <property name="minEvictableIdleTimeMillis" value="300000" />
 <property name="removeAbandoned" value="true" />
 <property name="removeAbandonedTimeout" value="1800" />
 <property name="logAbandoned" value="true" />
 <property name="filters" value="mergeStat" />
 </bean>
 <!-- 主库 -->
 <bean name="master-dataSource" parent="baseDataSource"
 init-method="init">
 <property name="url" value="${master_driverUrl}" />
 <property name="username" value="${master_username}" />
 <property name="password" value="${master_password}" />
 </bean>
 <!-- 从库 -->
 <bean name="slave-dataSource" parent="baseDataSource" init-method="init">
 <property name="url" value="${slave_driverUrl}" />
 <property name="username" value="${slave_username}" />
 <property name="password" value="${slave_password}" />
 </bean>
 
 <!--主从库选择 -->
 <bean id="dynamicDataSource" class="com.wangzhixuan.commons.datasource.DynamicDataSource">
 <property name="master" ref="master-dataSource" />
 <property name="slaves">
 <list>
 <ref bean="slave-dataSource" />
 </list>
 </property>
 </bean>

二、通用类
package com.wangzhixuan.commons.datasource;
import com.wangzhixuan.commons.annotation.DataSourceChange;
import com.wangzhixuan.commons.exception.DataSourceAspectException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
 * 有{@link com.wangzhixuan.commons.annotation.DataSourceChange}注解的方法,调用时会切换到指定的数据源
 *
 * @author tanghd
 */
@Aspect
@Component
public class DataSourceAspect {
 private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceAspect.class);
 @Around("@annotation(dataSourceChange)")
 public Object doAround(ProceedingJoinPoint pjp, DataSourceChange dataSourceChange) {
 Object retVal = null;
 boolean selectedDataSource = false;
 try {
 if (null != dataSourceChange) {
 selectedDataSource = true;
 if (dataSourceChange.slave()) {
 DynamicDataSource.useSlave();
 } else {
 DynamicDataSource.useMaster();
 }
 }
 retVal = pjp.proceed();
 } catch (Throwable e) {
 LOGGER.warn("数据源切换错误", e);
 throw new DataSourceAspectException("数据源切换错误", e);
 } finally {
 if (selectedDataSource) {
 DynamicDataSource.reset();
 }
 }
 return retVal;
 }
}

package com.wangzhixuan.commons.datasource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
/**
 * 继承{@link org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource}
 * 配置主从数据源后,根据选择,返回对应的数据源。多个从库的情况下,会平均的分配从库,用于负载均? 。
 *
 * @author tanghd
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
 private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);
 private DataSource master; // 主库,只允许有一个
 private List<DataSource> slaves; // 从库,允许有多个
 private AtomicLong slaveCount = new AtomicLong();
 private int slaveSize = 0;
 private Map<Object, Object> dataSources = new HashMap<Object, Object>();
 private static final String DEFAULT = "master";
 private static final String SLAVE = "slave";
 private static final ThreadLocal<LinkedList<String>> datasourceHolder = new ThreadLocal<LinkedList<String>>() {
 @Override
 protected LinkedList<String> initialValue() {
 return new LinkedList<String>();
 }
 };
 /**
 * 初始化
 */
 @Override
 public void afterPropertiesSet() {
 if (null == master) {
 throw new IllegalArgumentException("Property 'master' is required");
 }
 dataSources.put(DEFAULT, master);
 if (null != slaves && slaves.size() > 0) {
 for (int i = 0; i < slaves.size(); i++) {
 dataSources.put(SLAVE + (i + 1), slaves.get(i));
 }
 slaveSize = slaves.size();
 }
 this.setDefaultTargetDataSource(master);
 this.setTargetDataSources(dataSources);
 super.afterPropertiesSet();
 }
 /**
 * 选择使用主库,并把选择放到当前ThreadLocal的栈顶
 */
 public static void useMaster() {
 if (LOGGER.isDebugEnabled()) {
 LOGGER.debug("use datasource :" + datasourceHolder.get());
 }
 LinkedList<String> m = datasourceHolder.get();
 m.offerFirst(DEFAULT);
 }
 /**
 * 选择使用从库,并把选择放到当前ThreadLocal的栈顶
 */
 public static void useSlave() {
 if (LOGGER.isDebugEnabled()) {
 LOGGER.debug("use datasource :" + datasourceHolder.get());
 }
 LinkedList<String> m = datasourceHolder.get();
 m.offerFirst(SLAVE);
 }
 /**
 * 重置当前栈
 */
 public static void reset() {
 LinkedList<String> m = datasourceHolder.get();
 if (LOGGER.isDebugEnabled()) {
 LOGGER.debug("reset datasource {}", m);
 }
 if (m.size() > 0) {
 m.poll();
 }
 }
 /**
 * 如果是选择使用从库,且从库的数量大于1,则通过取模来控制从库的负载,
 * 计算结果返回AbstractRoutingDataSource
 */
 @Override
 protected Object determineCurrentLookupKey() {
 LinkedList<String> m = datasourceHolder.get();
 String key = m.peekFirst() == null ? DEFAULT : m.peekFirst();
 if (LOGGER.isDebugEnabled()) {
 LOGGER.debug("currenty datasource :" + key);
 }
 if (null != key) {
 if (DEFAULT.equals(key)) {
 return key;
 } else if (SLAVE.equals(key)) {
 if (slaveSize > 1) {// Slave loadBalance
 long c = slaveCount.incrementAndGet();
 c = c % slaveSize;
 return SLAVE + (c + 1);
 } else {
 return SLAVE + "1";
 }
 }
 return null;
 } else {
 return null;
 }
 }
 public DataSource getMaster() {
 return master;
 }
 public List<DataSource> getSlaves() {
 return slaves;
 }
 public void setMaster(DataSource master) {
 this.master = master;
 }
 public void setSlaves(List<DataSource> slaves) {
 this.slaves = slaves;
 }
}

package com.wangzhixuan.commons.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceChange {
 boolean slave() default false;
}

三、在服务层调用时加入数据源标识,DataSourceChange注解,设置数据源为从库(这里只写出了接口的实现,dao和mapper,请自行添加)
package co