springboot+mybatis多数据源配置实现

简单实现了根据注解动态切换数据源,支持同一个数据库的声明式事务,但不支持JTA事务。处理流程:

  • 根据配置的数据源信息,创建动态数据源bean
  • 利用DataSourceAspect处理@DataSource注解,设置当前要使用的具体数据源

pom.xml(注意这里的spring-aop要使用spring5的,spring4不支持在mybatis的mapper接口添加注解)

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.13.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>5.0.9.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.11</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.8</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
	</dependencies>

动态数据源配置类

import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.alibaba.druid.pool.DruidDataSource;
import com.zl.datasource.config.properties.DynamicDataSourceProperties;
import com.zl.datasource.config.properties.DynamicDataSourceProperties.DataSouceProperties;
import com.zl.datasource.config.properties.GlobalProperties;
import com.zl.datasource.config.stat.DruidFilterConfiguration;
import com.zl.datasource.config.stat.DruidSpringAopConfiguration;
import com.zl.datasource.config.stat.DruidStatViewServletConfiguration;
import com.zl.datasource.config.stat.DruidWebStatFilterConfiguration;

/**
 * Druid动态数据源配置类
 * 
 * @author Zhouych
 * @Date: 2018年6月21日 下午5:27:28
 * @since JDK 1.8
 */
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@Import({ DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class,
		DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class })
@Configuration
@EnableTransactionManagement
public class DruidDynamicDataSourceConfig {

	private static String defaultDatasourceName;

	/**
	 * 装载动态数据源bean
	 * 
	 * @param globalProperties
	 * @return
	 */
	@Bean
	@Primary
	public DataSource dataSource(GlobalProperties globalProperties) {
		// 获取数据源配置
		DynamicDataSourceProperties.Multi multi = globalProperties.getDataSource().getMulti();
		List<DynamicDataSourceProperties.DataSouceProperties> allDataSources = multi.getDataSources();
		DataSource defaultDataSource = null;
		Map<String, DataSource> dataSourceMap = new HashMap<>(2);
		for (int i = 0; i < allDataSources.size(); i++) {
			DataSouceProperties innerDataSouceProperties = allDataSources.get(i);
			if (null != innerDataSouceProperties) {
				// 根据配置信息创建数据源
				DruidDataSource dataSource = createDruidDataSource(innerDataSouceProperties);
				String name = DataSourceEnum.fromValue(i + 1).name().toLowerCase();
				dataSource.setName(name);
				dataSourceMap.put(name, dataSource);
				// 筛选出默认的数据源:这里如果没有配置默认数据源,则把第一个数据源作为默认。见com.zl.datasource.config.properties.DynamicDataSourceProperties.DataSouceProperties.isDefualt
				if (defaultDataSource == null || innerDataSouceProperties.isDefualt()) {
					defaultDataSource = dataSource;
					defaultDatasourceName = name;
				}
			}
		}
		if (dataSourceMap.size() < 1) {
			throw new IllegalArgumentException("至少需要一个可用的数据源配置");
		}
		// 生成动态数据源
		DynamicDataSource dynamicDataSource = new DynamicDataSource(defaultDataSource, dataSourceMap);
		return dynamicDataSource;
	}

	/**
	 * 生成druid数据源
	 *
	 * @param dataSourceProperties
	 *            数据源配置
	 * @return 数据源
	 */
	private DruidDataSource createDruidDataSource(DataSouceProperties dataSourceProperties) {
		DruidDataSource datasource = new DruidDataSource();
		// druid配置
		datasource.setDriverClassName(dataSourceProperties.getDriverClassName());
		datasource.setUsername(dataSourceProperties.getUsername());
		datasource.setPassword(dataSourceProperties.getPassword());
		datasource.setUrl(dataSourceProperties.getUrl());
		datasource.setInitialSize(dataSourceProperties.getInitialSize());
		datasource.setMinIdle(dataSourceProperties.getMinIdle());
		datasource.setMaxActive(dataSourceProperties.getMaxActive());
		datasource.setMaxWait(dataSourceProperties.getMaxWait());
		datasource.setTimeBetweenEvictionRunsMillis(dataSourceProperties.getTimeBetweenEvictionRunsMillis());
		datasource.setMinEvictableIdleTimeMillis(dataSourceProperties.getMinEvictableIdleTimeMillis());
		datasource.setValidationQuery(dataSourceProperties.getValidationQuery());
		datasource.setValidationQueryTimeout(dataSourceProperties.getValidationQueryTimeout());
		datasource.setTestWhileIdle(dataSourceProperties.isTestWhileIdle());
		datasource.setTestOnBorrow(dataSourceProperties.isTestOnBorrow());
		datasource.setTestOnReturn(dataSourceProperties.isTestOnReturn());
		datasource.setPoolPreparedStatements(dataSourceProperties.isPoolPreparedStatements());
		datasource.setMaxPoolPreparedStatementPerConnectionSize(
				dataSourceProperties.getMaxPoolPreparedStatementPerConnectionSize());
		datasource.setConnectionProperties(dataSourceProperties.getConnectionProperties());
		datasource.setUseGlobalDataSourceStat(dataSourceProperties.isUseGlobalDataSourceStat());
		try {
			datasource.setFilters(dataSourceProperties.getFilters());
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return datasource;
	}

	public static String getDefaultDatasourceName() {
		return defaultDatasourceName;
	}

	/**
	 * 事务管理器
	 * 
	 * @param dataSource
	 * @return
	 */
	@Bean
	@Primary
	@Order(2)
	public DataSourceTransactionManager transactionManager(DataSource dataSource) {
		DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
		transactionManager.setDataSource(dataSource);
		return transactionManager;
	}

}

动态数据源实现类,继承spring的AbstractRoutingDataSource

import java.util.HashMap;
import java.util.Map;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态数据源
 * 
 * @author Zhouych
 * @Date: 2018年6月21日 下午5:25:34
 * @since JDK 1.8
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

	private static final ThreadLocal<DynamicDataSourceKey> contextHolder = new ThreadLocal<>();

	/**
	 * 构建动态数据源对象,设置默认数据源和配置的多个数据源
	 * 
	 * @param defaultTargetDataSource
	 * @param targetDataSources
	 */
	public DynamicDataSource(javax.sql.DataSource defaultTargetDataSource,
			Map<String, javax.sql.DataSource> targetDataSources) {
		super.setDefaultTargetDataSource(defaultTargetDataSource);
		super.setTargetDataSources(new HashMap<>(targetDataSources));
		super.afterPropertiesSet();
	}

	/**
	 * 根据当前线程设置的最近的key,来获取相应的数据源
	 */
	@Override
	protected Object determineCurrentLookupKey() {
		return getDataSource();
	}

	/**
	 * 设置当前线程的数据源key
	 * 
	 * @param dataSource
	 */
	public static void setDataSource(String dataSource) {
		DynamicDataSourceKey latestKey = getLatestKey();
		if (null == latestKey) {
			latestKey = DynamicDataSourceKey.builder().key(dataSource).build();
			contextHolder.set(latestKey);
		} else {
			latestKey.setChild(DynamicDataSourceKey.builder().key(dataSource).build());
		}
	}

	/**
	 * 获取最近的数据源key
	 * 
	 * @return
	 */
	public static String getDataSource() {
		DynamicDataSourceKey latestKey = getLatestKey();
		if (null == latestKey) {
			return null;
		} else {
			return latestKey.getKey();
		}
	}

	/**
	 * 获取最近的数据源key对象
	 * 
	 * @return
	 */
	private static DynamicDataSourceKey getLatestKey() {
		DynamicDataSourceKey dynamicDataSourceKey = contextHolder.get();
		if (null == dynamicDataSourceKey) {
			return null;
		}
		while (null != dynamicDataSourceKey.getChild()) {
			dynamicDataSourceKey = dynamicDataSourceKey.getChild();
		}
		return dynamicDataSourceKey;
	}

	/**
	 * 判断是否嵌套多层数据源设置
	 * 
	 * @return
	 */
	private static boolean hasChild() {
		DynamicDataSourceKey dynamicDataSourceKey = contextHolder.get();
		return (null != dynamicDataSourceKey) && (null != dynamicDataSourceKey.getChild());
	}

	/**
	 * 把最近的数据源设置清楚
	 */
	private static void setLatestKeyNull() {
		DynamicDataSourceKey dynamicDataSourceKey = contextHolder.get();
		DynamicDataSourceKey tmp = null;
		while (true) {
			tmp = dynamicDataSourceKey.getChild();
			if (null == tmp.getChild()) {
				dynamicDataSourceKey.setChild(null);
				break;
			}
			dynamicDataSourceKey = dynamicDataSourceKey.getChild();
		}

	}

	/**
	 * 清楚数据源设置
	 */
	public static void clearDataSource() {
		if (hasChild()) {
			setLatestKeyNull();
		} else {
			contextHolder.remove();
		}
	}

}

绑定到ThreadLocal的数据源key树(为了实现@DataSource的嵌套)

/**
 * 动态数据源当前线程key信息模型
 * 
 * @author Zhouych
 * @date 2018年9月21日 下午2:49:09
 * @since JDK 1.8
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DynamicDataSourceKey {

	private String key;

	/**
	 * 当同一个线程出现多次{@link @DataSource}注解时,key层层递进
	 */
	private DynamicDataSourceKey child;

}

标志使用的数据源注解

import com.zl.datasource.config.DataSourceEnum;

/**
 * 多数据源注解
 * 
 * @author Zhouych
 * @Date: 2018年6月21日 下午5:21:44
 * @since JDK 1.8
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

	DataSourceEnum value() default DataSourceEnum.ONE;

}

@DataSource注解处理切面


import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import com.zl.datasource.config.DynamicDataSource;
import com.zl.datasource.config.annotation.DataSource;
import com.zl.datasource.util.ReflectUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * 数据源切面
 * 
 * @author Zhouych
 * @Date: 2018年6月21日 下午5:16:49
 * @since JDK 1.8
 */
@Aspect
@Slf4j
@Component
public class DataSourceAspect implements Ordered {

	/**
	 * 切面定义
	 * <ul>
	 * <li>@within 切类级别的@DataSouce注解</li>
	 * <li>@annotation 切方法级别的@DataSouce注解</li>
	 * </ul>
	 */
	@Pointcut("(@within(com.zl.datasource.config.annotation.DataSource)) || (@annotation(com.zl.datasource.config.annotation.DataSource))")
	public void dataSourcePointCut() {

	}

	/**
	 * 解析{@link DataSource }注解,并设置当前线程使用的数据源,最后清楚设置的数据源。 首先从方法上{@link DataSource
	 * }注解,如果没有,再从类上获取
	 * 
	 * @param point
	 * @return
	 * @throws Throwable
	 */
	@Around("dataSourcePointCut()")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		MethodSignature signature = (MethodSignature) point.getSignature();
		Method method = signature.getMethod();
		// 先获取方法上的@DataSource注解
		// 基于cglib代理实现的代理类,在这里拿到@DataSource注解的信息
		DataSource ds = method.getAnnotation(DataSource.class);
		if (null == ds) {
			// 拿到基于JDK代理实现的代理类的@DataSource注解的信息
			Object target = point.getTarget();
			Class<?>[] parameterTypes = signature.getParameterTypes();
			ds = ReflectUtil.getMethodAnnotation(target, method.getName(), DataSource.class, parameterTypes);
			// 获取类级别的@DataSource注解
			if (null == ds) {
				ds = target.getClass().getAnnotation(DataSource.class);
			}
			if (null == ds) {
				ds = method.getDeclaringClass().getAnnotation(DataSource.class);
			}
		}
		// 如果能拿到@DataSource信息,则设置当前线程的dataSource的key为@DataSource的value
		if (null != ds) {
			DynamicDataSource.setDataSource(ds.value().name().toLowerCase());
			log.debug("set datasource to be " + ds.value().name().toLowerCase());
		}
		try {
			// 执行业务
			return point.proceed();
		} finally {
			// 清理最近的dataSource的key
			DynamicDataSource.clearDataSource();
			log.debug("clean latest datasource");
		}
	}

	@Override
	public int getOrder() {
		return 1;
	}

}

代码码云地址(说明一下,项目中有部分代码是直接拷贝druid springboot starter的源码:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

猜你喜欢

转载自blog.csdn.net/zhouyecheng/article/details/82803313