springboot动态数据源

基于spring-boot2.0.0版本实现多数据源注册

功能点:一,可以通过配置控制数据源注册个数,实现事先不知道数据源个数和别名,在不修改任何有关数据库相关代码条件下,仅仅在使用时按照规则添加配置来注册多数据源.可以将其作为基础jar提供公共服务.

二,实现了多数据源中,调用单个数据源的事务控制(同一个方法中只调用一个数据库的事务),同一个方法中调用不同数据源就是分布式事务,以下代码没有实现.

实现代码:

1,继承AbstractRoutingDataSource重写determineCurrentLookupKey方法,得到通过AOP切换之后的数据源名称

import lombok.extern.slf4j.Slf4j;

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

/**
 * 功能说明: 通过重写determineCurrentLookupKey得到切换之后的数据源名称
* 系统版本: v1.0
* 开发人员: @author liansh
* 开发时间: 2019年6月25日
*/ @Slf4j public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal contextHolder = new ThreadLocal(); @Override protected Object determineCurrentLookupKey() { log.debug("数据源为:{}", DynamicDataSource.getDataSource()); return DynamicDataSource.getDataSource(); } public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } }

2,添加注解,在方法上声明该注解,指定数据源,不添加注解就会使用默认数据源

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 功能说明: 数据源
* 系统版本: v1.0
* 开发人员: @author liansh
* 开发时间: 2019年6月25日
*/ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DS { String defaultStr = "default"; String value() default defaultStr; }

3,读取数据源相关配置,通过数据库别名,动态读取具有相同特征(spring.datasource.xxx.username,xxx作为别名)的数据库相关配置并注册进spring容器,旨在将此类作为jar,不管扩展多少个未知数据源,都可以不用修改数据源相关代码

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

import javax.sql.DataSource;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import com.alibaba.druid.pool.DruidDataSource;

/**
 * 功能说明: 动态数据源配置,配合注解可完成数据库自动切换,但切换必须在service以上,不能在mapper层
* 系统版本: v1.0
* 开发人员: @author liansh
* 开发时间: 2019年6月25日
*/ @Slf4j @Configuration public class DataSourceConfig { @Value("${jdbc.mapper-locations:classpath*:**/*Mapper.xml}") private String mappingPath; /** 需要注册的数据库别名列表 */ @Value("${jdbc.target-sources:default,order}") private String targetSources = ""; @Autowired private Environment env; private Map getTargetSource() { Map target = new HashMap(); // 遍历数据源名称,读取相关配置,配置格式为spring.datasource.xxx.username for (String key : targetSources.split(",")) { String point = key.equals(DS.defaultStr) ? "" : "."; String pkg = key.equals(DS.defaultStr) ? "" : key; DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(env.getProperty("spring.datasource." + pkg + point + "driverClassName")); dataSource.setUrl(env.getProperty("spring.datasource." + pkg + point + "jdbc-url")); dataSource.setUsername(env.getProperty("spring.datasource." + pkg + point + "username")); dataSource.setPassword(env.getProperty("spring.datasource." + pkg + point + "password")); dataSource.setTestWhileIdle(true); String maxActive = env.getProperty("spring.datasource." + pkg + point + "maxActive", "50"); String premoveAbandoned = env.getProperty("spring.datasource." + pkg + point + "removeAbandoned", "true"); String premoveAbandonedTimeout = env.getProperty("spring.datasource." + pkg + point + "removeAbandonedTimeout", "180"); int poolMaximumActiveConnections = 10, removeAbandonedTimeout = 10; boolean removeAbandoned = "true".equalsIgnoreCase(env.getProperty("db.removeAbandoned")); if (NumberUtils.isCreatable(maxActive)) { poolMaximumActiveConnections = NumberUtils.toInt(maxActive); } if (StringUtils.isNotBlank(premoveAbandoned)) { removeAbandoned = "true".equalsIgnoreCase(premoveAbandoned); } if (NumberUtils.isCreatable(premoveAbandonedTimeout)) { removeAbandonedTimeout = NumberUtils.toInt(premoveAbandonedTimeout); } dataSource.setRemoveAbandoned(removeAbandoned); dataSource.setRemoveAbandonedTimeout(removeAbandonedTimeout); dataSource.setMaxActive(poolMaximumActiveConnections); target.put(pkg.equals("") ? DS.defaultStr : pkg, dataSource); log.info("jdbc.properties加载" + pkg + "数据库配置成功" + "(" + key + ")"); } return target; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); // 需要动态指定 factoryBean.setDataSource(dataSource()); // 手动实现数据连接无法自动读取xml配置的路径,需要手动指定,若用注解生成的java类sql,无需手动切换 factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mappingPath)); return factoryBean.getObject(); } /** 事务:此处虽然Bean只有一个,但不管<单独>使用哪一个数据源,事务都可以接管成功 */ @Bean public DataSourceTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } /** 动态数据源相关配置 */ @Bean public DataSource dataSource() { Map targetDataSources = getTargetSource(); DynamicDataSource dynamicDataSource = new DynamicDataSource(); // 默认数据源 dynamicDataSource.setDefaultTargetDataSource(targetDataSources.get(DS.defaultStr)); dynamicDataSource.setTargetDataSources(targetDataSources); return dynamicDataSource; } }

4,注册AOP拦截,前置切面完成切换数据源,后置切面清空数据源,必须添加@Order注解,使该切面先于事务切面执行,否则会造成开启事务情况下切换数据源失败

package com.lsh.jdbc.dataSource;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 功能说明: 数据源切换Aop切面,作用:在方法之前完成数据源的切换
* 如果该切面不添加@Order注解,那么事务切面将在切换数据源之前执行,导致切换数据源失败
* 系统版本: v1.0
* 开发人员: @author liansh
* 开发时间: 2019年6月25日
*/ @Order(1) @Aspect @Component public class DynamicDataSourceAspect { @Before("@annotation(DS)") public void beforeSwitchDS(JoinPoint point) { // 获得当前访问的class Class className = point.getTarget().getClass(); // 获得访问的方法名 String methodName = point.getSignature().getName(); // 得到方法的参数的类型 Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes(); String dataSource = DS.defaultStr; try { // 得到访问的方法对 Method method = className.getMethod(methodName, argClass); DS interceptor = className.getAnnotation(DS.class); if (interceptor != null) {// 判断类上是否存在@DS注解 // 取出注解中的数据源名 dataSource = interceptor.value(); } else if (method.isAnnotationPresent(DS.class)) {// 判断方法上是否存在@DS注解 DS annotation = method.getAnnotation(DS.class); // 取出注解中的数据源名 dataSource = annotation.value(); } } catch (Exception e) { e.printStackTrace(); } DynamicDataSource.setDataSource(dataSource); } @After("@annotation(DS)") public void afterSwitchDS(JoinPoint point) { DynamicDataSource.clearDataSource(); } }

完整代码示例

你可能感兴趣的:(spring-boot,code系列)