Spring boot 多数据源配置(分配置文件和数据库表两种情况)

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/love_yang13/article/details/78384637
由于项目有很多数据源需要配置,所以参考网上的资料研究了下。现在有2种实现方案,一种是数据源信息在项目中不会很多就2-3个那种,那就直接读取配置文件就行了,还有一种是有很多数据源的那种成百上千的可以考虑写到数据表中。
一、配置在配置文件中使用方法
1、配置文件application.properties(需要注意所有数据源共享主数据源的其它属性配置,如果需要不同的数据源配置不同的数据源配置,需要修改代码)
# 数据库访问配置
# 主数据源,默认的
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url= jdbc:mysql://127.0.0.1:3306/test0?useUnicode=true&characterEncoding=UTF-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root1234

# 更多数据源
com.pgl.edi.datasource.names=test1,test2 

com.pgl.edi.datasource.test1.type=com.alibaba.druid.pool.DruidDataSource
com.pgl.edi.datasource.test1.driver-class-name=com.mysql.jdbc.Driver
com.pgl.edi.datasource.test1.url=jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=UTF-8
com.pgl.edi.datasource.test1.username=root
com.pgl.edi.datasource.test1.password=root1234

com.pgl.edi.datasource.test2.type=com.alibaba.druid.pool.DruidDataSource
com.pgl.edi.datasource.test2.driver-class-name=com.mysql.jdbc.Driver
com.pgl.edi.datasource.test2.url= jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&characterEncoding=UTF-8
com.pgl.edi.datasource.test2.username=root
com.pgl.edi.datasource.test2.password=root1234
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM t_user
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=true
spring.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.filters=stat,wall,log4j
2、然后是注册数据源类DynamicDataSourceRegister 如下
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

/**
 * 
 * 功能描述:动态数据源注册 
 * 启动动态数据源请在启动类中(如Start)
 * 添加 @Import(DynamicDataSourceRegister.class)
 */
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

	private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);

	private ConversionService conversionService = new DefaultConversionService();
	private PropertyValues dataSourcePropertyValues;

	// 如配置文件中未指定数据源类型,使用该默认值
	private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
	 

	// 数据源
	private DataSource defaultDataSource;
	private Map<String, DataSource> customDataSources = new HashMap<>();

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
		// 将主数据源添加到更多数据源中
		targetDataSources.put("dataSource", defaultDataSource);
		DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
		// 添加更多数据源
		targetDataSources.putAll(customDataSources);
		for (String key : customDataSources.keySet()) {
			DynamicDataSourceContextHolder.dataSourceIds.add(key);
		}

		// 创建DynamicDataSource
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClass(DynamicDataSource.class);
		beanDefinition.setSynthetic(true);
		MutablePropertyValues mpv = beanDefinition.getPropertyValues();
		mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
		mpv.addPropertyValue("targetDataSources", targetDataSources);
		registry.registerBeanDefinition("dataSource", beanDefinition);

		logger.info("Dynamic DataSource Registry");
	}

	/**
	 * 创建DataSource
	 *
	 * @param type
	 * @param driverClassName
	 * @param url
	 * @param username
	 * @param password
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public DataSource buildDataSource(Map<String, Object> dsMap) {
		try {
			Object type = dsMap.get("type");
			if (type == null)
				type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource

			Class<? extends DataSource> dataSourceType;
			dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);

			String driverClassName = dsMap.get("driver-class-name").toString();
			String url = dsMap.get("url").toString();
			String username = dsMap.get("username").toString();
			String password = dsMap.get("password").toString();

			DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
					.username(username).password(password).type(dataSourceType);
			return factory.build();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 加载多数据源配置
	 */
	@Override
	public void setEnvironment(Environment env) {
		initDefaultDataSource(env);
		initCustomDataSources(env);
	}

	/**
	 * 初始化主数据源
	 *
	 */
	private void initDefaultDataSource(Environment env) {
		// 读取主数据源
		RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
		Map<String, Object> dsMap = new HashMap<>();
		dsMap.put("type", propertyResolver.getProperty("type"));
		dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name"));
		dsMap.put("url", propertyResolver.getProperty("url"));
		dsMap.put("username", propertyResolver.getProperty("username"));
		dsMap.put("password", propertyResolver.getProperty("password"));

		defaultDataSource = buildDataSource(dsMap);

		dataBinder(defaultDataSource, env);
	}

	/**
	 * 为DataSource绑定更多数据
	 *
	 * @param dataSource
	 * @param env
	 */
	private void dataBinder(DataSource dataSource, Environment env) {
		RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
		// dataBinder.setValidator(new
		// LocalValidatorFactory().run(this.applicationContext));
		dataBinder.setConversionService(conversionService);
		dataBinder.setIgnoreNestedProperties(false);// false
		dataBinder.setIgnoreInvalidFields(false);// false
		dataBinder.setIgnoreUnknownFields(true);// true
		if (dataSourcePropertyValues == null) {
			Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
			Map<String, Object> values = new HashMap<>(rpr);
			// 排除已经设置的属性
			values.remove("type");
			values.remove("driver-class-name");
			values.remove("url");
			values.remove("username");
			values.remove("password");
			dataSourcePropertyValues = new MutablePropertyValues(values);
		}
		dataBinder.bind(dataSourcePropertyValues);
	}

	/**
	 * 初始化更多数据源
	 *
	 */
	private void initCustomDataSources(Environment env) {
		// 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
		RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "com.pgl.edi.datasource.");//这里写自己的配置的数据源前缀
		String dsPrefixs = propertyResolver.getProperty("names");//根据自己定义的获取数据源key
		for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
			Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
			DataSource ds = buildDataSource(dsMap);
			customDataSources.put(dsPrefix, ds);
			dataBinder(ds, env);
		}
	}

}
上面是注册多数据源代码,下面是如何使用数据源需要以下几个类DynamicDataSourceContextHolder、DynamicDataSourceAspect、DynamicDataSource、DataSource。
import java.util.ArrayList;
import java.util.List;


public class DynamicDataSourceContextHolder {
	private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
	public static List<String> dataSourceIds = new ArrayList<>();

	public static void setDataSourceType(String dataSourceType) {
		contextHolder.set(dataSourceType);
	}

	public static String getDataSourceType() {
		return contextHolder.get();
	}

	public static void clearDataSourceType() {
		contextHolder.remove();
	}

	/**
	 * 判断指定DataSrouce当前是否存在
	 *
	 * @param dataSourceId
	 * @return	
	 */
	public static boolean containsDataSource(String dataSourceId) {
		return dataSourceIds.contains(dataSourceId);
	}
}
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Order(-1)// 保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {
	private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    @Before("@annotation(ds)")
    public void changeDataSource(JoinPoint point, DataSource ds) throws Throwable {
        String dsId = ds.name();
        if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
            logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
        } else {
            logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature());
            DynamicDataSourceContextHolder.setDataSourceType(ds.name());
        }
    }

    @After("@annotation(ds)")
    public void restoreDataSource(JoinPoint point, DataSource ds) {
        logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;


public class DynamicDataSource extends AbstractRoutingDataSource {

	@Override
	protected Object determineCurrentLookupKey() {
		  return DynamicDataSourceContextHolder.getDataSourceType();
	}

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

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
	String name();
}
以上就是这四个类的代码。使用的时候需要注意在启动类上使用@Import(DynamicDataSourceRegister.class),在要使用方法用注解的方式@DataSource(name="test1"),这样就可以使用了。如果没有指定数据源则使用默认数据源。还有一点配置文件中的names就是我们所使用数据源的key。
二、数据源信息配置在数据库中
1、配置主数据源,主数据源主要是访问存储其他数据源信息的。
# 主数据源,默认的
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url= jdbc:mysql://127.0.0.1:3306/test0?useUnicode=true&characterEncoding=UTF-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root1234
然后建立数据库dataource表如下
CREATE TABLE `datasource` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `type` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '数据源类型',
  `driver_class_name` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '数据库驱动类',
  `url` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '连接url',
  `username` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '用户名',
  `password` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '密码',
  `rmark` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '备注',
  `dsname` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '数据源名称',
  `status` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '1' COMMENT '状态',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
在项目配置文件配置datasource表所在数据库信息,在这里也可以配置数据源的其他信息,比如初始化大小、最大连接大小等信息。(需要注意所有数据源共享主数据源的其它属性配置,如果需要不同的数据源配置不同的数据源配置,需要修改代码)。需要修改的地方就一个DynamicDataSourceRegister类中initCustomDataSources方法需要修改为如下
private void initCustomDataSources(Environment env) {
		// 读取库表中datasource获取更多数据源
		Map<String, Map<String, Object>> customInfo=getCustomDataSourceInfo();
		for (String key : customInfo.keySet()) {  
		    Map<String, Object> dsMap = customInfo.get(key);  
		    DataSource ds = buildDataSource(dsMap);
			customDataSources.put(key, ds);
			dataBinder(ds, env); 
		}  
	}

	private Map<String, Map<String, Object>> getCustomDataSourceInfo() {
		Map<String, Map<String, Object>> customMap = new HashMap<>();
		String sql = "select type,`driver_class_name`,url,username,`password`,`dsname` from datasource where status=1";
		JdbcTemplate jdbcTemplate=new JdbcTemplate(defaultDataSource);
		List<DataSourceInfo> infos=jdbcTemplate.query(sql, new RowMapper<DataSourceInfo>() {
			@Override
			public DataSourceInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
				DataSourceInfo info=new DataSourceInfo();
				info.setType(rs.getString("type"));
				info.setDriverClassName(rs.getString("driver_class_name"));
				info.setUrl(rs.getString("url"));
				info.setPassword(rs.getString("password"));
				info.setUsername(rs.getString("username"));
				info.setDsName(rs.getString("dsname"));
				return info;
			}
		});
		for(DataSourceInfo info:infos) {
			Map<String, Object> dsMap = new HashMap<>();
			dsMap.put("type", info.getType());
			dsMap.put("driver-class-name", info.getDriverClassName());
			dsMap.put("url", info.getUrl());
			dsMap.put("username", info.getUsername());
			dsMap.put("password", info.getPassword());
			customMap.put(info.getDsName(), dsMap);
		}
		return customMap;
	}
使用方式和第一种一样。
在很多时候我们可能不喜欢用注解的方式指定使用的数据源,我们可以修改DynamicDataSourceAspect类可以使用如下配置
@Aspect
@Order(-10) // 保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {
	private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
        //由于我项目中使用方法名后缀是FromDB进入AOP处理且默认第一个参数为使用数据源的key
  	//这里可以自己修改想要以什么方式匹配使用数据源
	@Pointcut("execution(* com.pgl.demo.service.impl..*.*FromDB(..))")
	public void excude() {
	}

	@Before("excude()")
	public void changeDataSource(JoinPoint point) throws Throwable {
	
		Object obj = point.getArgs()[0];
		if(obj instanceof String) {
			String dsName=obj.toString();
			if (!DynamicDataSourceContextHolder.containsDataSource(dsName)) {
				logger.error("数据源[{}]不存在,使用默认数据源 > {}", obj, point.getSignature());
				throw new Throwable("数据源["+dsName+"]不存在");
			} else {
				logger.info("Use DataSource : {} > {}", dsName, point.getSignature());
				DynamicDataSourceContextHolder.setDataSourceType(dsName);
			}
			logger.info("-----------args DataSource : {} > {}", dsName, point.getSignature());
		}else {
			logger.error("数据源[{}]不存在", obj);
			 throw new Throwable("数据源["+obj+"]不存在");
		}
		
	}

	
	@After("excude()")
	public void restoreDataSource(JoinPoint point) {
		Object obj = point.getArgs()[0];
		 logger.info("Revert DataSource : {} > {}", obj.toString(), point.getSignature());
		 DynamicDataSourceContextHolder.clearDataSourceType();
	}
}
使用这种方式的话要拦截的方法必须不能private的。













展开阅读全文

没有更多推荐了,返回首页