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

来源:互联网 发布:爬山虎软件 编辑:程序博客网 时间:2024/06/01 09:10
由于项目有很多数据源需要配置,所以参考网上的资料研究了下。现在有2种实现方案,一种是数据源信息在项目中不会很多就2-3个那种,那就直接读取配置文件就行了,还有一种是有很多数据源的那种成百上千的可以考虑写到数据表中。
一、配置在配置文件中使用方法
1、配置文件application.properties(需要注意所有数据源共享主数据源的其它属性配置,如果需要不同的数据源配置不同的数据源配置,需要修改代码)
# 数据库访问配置# 主数据源,默认的spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.url= jdbc:mysql://127.0.0.1:3306/test0?useUnicode=true&characterEncoding=UTF-8spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.username=rootspring.datasource.password=root1234# 更多数据源com.pgl.edi.datasource.names=test1,test2 com.pgl.edi.datasource.test1.type=com.alibaba.druid.pool.DruidDataSourcecom.pgl.edi.datasource.test1.driver-class-name=com.mysql.jdbc.Drivercom.pgl.edi.datasource.test1.url=jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=UTF-8com.pgl.edi.datasource.test1.username=rootcom.pgl.edi.datasource.test1.password=root1234com.pgl.edi.datasource.test2.type=com.alibaba.druid.pool.DruidDataSourcecom.pgl.edi.datasource.test2.driver-class-name=com.mysql.jdbc.Drivercom.pgl.edi.datasource.test2.url= jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&characterEncoding=UTF-8com.pgl.edi.datasource.test2.username=rootcom.pgl.edi.datasource.test2.password=root1234# 下面为连接池的补充设置,应用到上面所有数据源中# 初始化大小,最小,最大spring.datasource.initialSize=5spring.datasource.minIdle=5spring.datasource.maxActive=20# 配置获取连接等待超时的时间spring.datasource.maxWait=60000# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒spring.datasource.timeBetweenEvictionRunsMillis=60000# 配置一个连接在池中最小生存的时间,单位是毫秒spring.datasource.minEvictableIdleTimeMillis=300000spring.datasource.validationQuery=SELECT 1 FROM t_userspring.datasource.testWhileIdle=truespring.datasource.testOnBorrow=truespring.datasource.testOnReturn=false# 打开PSCache,并且指定每个连接上PSCache的大小spring.datasource.poolPreparedStatements=truespring.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<>();@Overridepublic 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);}// 创建DynamicDataSourceGenericBeanDefinition 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;// 默认DataSourceClass<? 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;}/** * 加载多数据源配置 */@Overridepublic 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);// falsedataBinder.setIgnoreInvalidFields(false);// falsedataBinder.setIgnoreUnknownFields(true);// trueif (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");//根据自己定义的获取数据源keyfor (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之前执行@Componentpublic 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 {@Overrideprotected 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)@Documentedpublic @interface DataSource {String name();}
以上就是这四个类的代码。使用的时候需要注意在启动类上使用@Import(DynamicDataSourceRegister.class),在要使用方法用注解的方式@DataSource(name="test1"),这样就可以使用了。如果没有指定数据源则使用默认数据源。还有一点配置文件中的names就是我们所使用数据源的key。
二、数据源信息配置在数据库中
1、配置主数据源,主数据源主要是访问存储其他数据源信息的。
# 主数据源,默认的spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.url= jdbc:mysql://127.0.0.1:3306/test0?useUnicode=true&characterEncoding=UTF-8spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.username=rootspring.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>() {@Overridepublic 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之前执行@Componentpublic 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的。













阅读全文
0 0