基于spring boot多主从数据源-(完全动态)

在网上搜集了一波资料发现,关于spring boot动态数据源的文章少之甚少。大部分都是已知数据源,增一套配置,写一个bean。

经过笔者日以彻夜的研究,终于得出了方案。

1、遍历配置,获得数据源配置

2、创建数据源连接池

3、使用AbstractRoutingDataSource接管数据源

===============================================

yml配置: 



spring:
  datasource:
     master-source:
      jdbcUrl: jdbc:mysql://xxx:3306/db?autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8
      username: user
      password: password
      minimumIdle: 16
      maximumPoolSize: 1024
      connectionTestQuery: SELECT 1 FROM DUAL
      driverClassName: com.mysql.jdbc.Driver
      dataSource:
        cachePrepStmts: true
        prepStmtCacheSize: 1024
        prepStmtCacheSqlLimit: 4096
     slave-a-source:
      jdbcUrl: jdbc:mysql://xxx:3306/db?autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8
      username: user
      password: password
      minimumIdle: 16
      maximumPoolSize: 1024
      connectionTestQuery: SELECT 1 FROM DUAL
      driverClassName: com.mysql.jdbc.Driver
      dataSource:
        cachePrepStmts: true
        prepStmtCacheSize: 1024
        prepStmtCacheSqlLimit: 4096

Bean配置:

package com.jiujun.voice.common.jdbc.source;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.ConfigurablePropertyResolver;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.util.ConcurrentReferenceHashMap;

import com.jiujun.voice.common.jdbc.source.DynamicDataSource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

@Configuration
public class DataSourceConfig {

	private static final String PREFIX = "spring.datasource";

	@Bean(name = "dynamicDataSource")
	@Primary
	public DynamicDataSource dynamicDataSources(ConfigurablePropertyResolver environment)
			throws SQLException, InterruptedException {
		// 抽取配置容器
		PropertySources propertySources = getFieldValue(environment, "propertySources");
		List<PropertySource<?>> list = getFieldValue(propertySources, "propertySourceList");
		PropertySource<?> propertySource = getByCollections(list, "name", "configurationProperties");
		ConcurrentReferenceHashMap<PropertySource<?>, ConfigurationPropertySource> propertySourceMap = getFieldValue(
				propertySource.getSource(), "cache");
		// 筛选yml配置
		List<ConfigurationPropertySource> ymlPropertys = new ArrayList<ConfigurationPropertySource>();
		for (PropertySource<?> key : propertySourceMap.keySet()) {
			if (isNullOrEmpty(propertySourceMap.get(key))) {
				continue;
			}

			ConfigurationPropertySource value = propertySourceMap.get(key);
			if (key.getName().startsWith("applicationConfig:")) {
				ymlPropertys.add(value);
			}
		}
		Map<String, Object> propertyMap = new HashMap<String, Object>();
		// 合并配置
		for (ConfigurationPropertySource source : ymlPropertys) {
			MapPropertySource underlyingSource = (MapPropertySource) source.getUnderlyingSource();
			for (String propertyName : underlyingSource.getPropertyNames()) {
				propertyMap.put(propertyName, underlyingSource.getProperty(propertyName));
			}
		}
		// 整合数据源配置
		Map<String, Properties> dataSourceConfigs = new HashMap<String, Properties>();
		for (String propertyName : propertyMap.keySet()) {
			if (!propertyName.startsWith(PREFIX)) {
				continue;
			}
			String dataSourceName = getDatasourceNameByPropertyName(propertyName);
			String fieldName = getFieldNameByPropertyName(propertyName);
			Object fieldValue = propertyMap.get(propertyName);
			if (!dataSourceConfigs.containsKey(dataSourceName)) {
				Properties properties = new Properties();
				dataSourceConfigs.put(dataSourceName, properties);
			}
			dataSourceConfigs.get(dataSourceName).put(fieldName, fieldValue);
		}

		// 创建数据源
		Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
		for (String dataSourceName : dataSourceConfigs.keySet()) {
			Properties properties = dataSourceConfigs.get(dataSourceName);
			HikariConfig configuration = new HikariConfig(properties);
			HikariDataSource dataSource = new HikariDataSource(configuration);
			targetDataSources.put(dataSourceName, dataSource);
		}
		DynamicDataSource dynamicDataSource = new DynamicDataSource();
		dynamicDataSource.setTargetDataSources(targetDataSources);
		dynamicDataSource.afterPropertiesSet();
		return dynamicDataSource;
	}

	@Bean(name = "jdbcTemplate")
	public JdbcTemplate instanceJdbcTemplate(DynamicDataSource dynamicDataSource) {
		return new JdbcTemplate(dynamicDataSource);
	}

	@Bean
	public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
		return new DataSourceTransactionManager(dataSource);
	}

	private static String getFieldNameByPropertyName(String propertyName) {
		propertyName = propertyName.substring(PREFIX.length() + 1);
		String datasourceName = propertyName.substring(0, propertyName.indexOf("."));
		String fieldName = propertyName.substring(datasourceName.length() + 1);
		return fieldName;
	}

	private static String getDatasourceNameByPropertyName(String propertyName) {
		propertyName = propertyName.substring(PREFIX.length() + 1);
		String datasourceName = propertyName.substring(0, propertyName.indexOf("."));
		return datasourceName;
	}

	@SuppressWarnings("unchecked")
	private static <T> T getFieldValue(Object object, String fieldName) {
		if (isNullOrEmpty(object)) {
			return null;
		}
		Field[] fields = object.getClass().getDeclaredFields();
		for (Field field : fields) {
			if (!fieldName.equals(field.getName())) {
				continue;
			}
			try {
				field.setAccessible(true);
				return (T) field.get(object);
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}
		return null;
	}

	public static boolean isNullOrEmpty(Object obj) {
		try {
			if (obj == null) {
				return true;
			}
			if (obj instanceof CharSequence) {
				return ((CharSequence) obj).length() == 0;
			}
			if (obj instanceof Collection) {
				return ((Collection<?>) obj).isEmpty();
			}
			if (obj instanceof Map) {
				return ((Map<?, ?>) obj).isEmpty();
			}
			if (obj instanceof Object[]) {
				Object[] object = (Object[]) obj;
				if (object.length == 0) {
					return true;
				}
				boolean empty = true;
				for (int i = 0; i < object.length; i++) {
					if (!isNullOrEmpty(object[i])) {
						empty = false;
						break;
					}
				}
				return empty;
			}
			return false;
		} catch (Exception e) {
			return true;
		}

	}

	@SuppressWarnings("unchecked")
	private static <T> T getByCollections(Collection<?> collections, String fieldName, Object fieldValue) {
		if (isNullOrEmpty(collections)) {
			return null;
		}
		for (Object object : collections) {
			Object value = getFieldValue(object, fieldName);
			if (fieldValue == null && value == null) {
				return (T) object;
			}
			if (fieldValue == value || fieldValue.equals(value)) {
				return (T) object;
			}
		}
		return null;
	}
}

动态数据源对象:

package com.jiujun.voice.common.jdbc.source;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.apache.log4j.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;


/**
 * 动态数据源
 * 
 * @author Coody
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
	
	static Logger logger=Logger.getLogger(DynamicDataSource.class);

	private static final ThreadLocal<String> CURRENT_SOURCE = new ThreadLocal<String>();

	public static final String MASTER_FLAG = "master";

	public static final String SLAVE_FLAG = "slave";

	private static List<String> masters = new ArrayList<String>();

	private static List<String> slaves = new ArrayList<String>();

	@Override
	public void setTargetDataSources(Map<Object, Object> targetDataSources) {
		for (Object key : targetDataSources.keySet()) {
			String dataSourceName = key.toString();
			if (dataSourceName.startsWith(MASTER_FLAG)) {
				masters.add(dataSourceName);
				this.setDefaultTargetDataSource(targetDataSources.get(dataSourceName));
				continue;
			}
			if (dataSourceName.startsWith(SLAVE_FLAG)) {
				slaves.add(dataSourceName);
				continue;
			}
			throw new RuntimeException("未知的数据源类型,数据源命名只能以" + MASTER_FLAG + "、" + SLAVE_FLAG + "开头");
		}
		if (masters==null||masters.isEmpty()) {
			throw new RuntimeException("缺少主库数据源");
		}
		super.setTargetDataSources(targetDataSources);
	}

	@Override
	protected Object determineCurrentLookupKey() {
		String dataSourceName = getDataSourceName();
		logger.debug("使用数据源>>" + dataSourceName);
		return dataSourceName;
	}

	public static void setDataSourceFlag(String sourceFlag) {
		CURRENT_SOURCE.set(sourceFlag);
	}

	public static String getDataSourceName() {
		String flag = CURRENT_SOURCE.get();
		if (flag == null) {
			flag = MASTER_FLAG;
		}
		if (flag.equals(MASTER_FLAG)) {
			if (masters.size() == 1) {
				return masters.get(0);
			}
			return masters.get(new Random().nextInt(masters.size()));
		}
		if (flag.equals(SLAVE_FLAG)) {
			if (slaves.size() == 1) {
				return slaves.get(0);
			}
			return slaves.get(new Random().nextInt(slaves.size()));
		}
		logger.error("多数据源警告:指定数据源标记错误,只能指定master、slave相关数值");
		return masters.get(0);
	}

	public static void clearDataSourceFlag() {
		CURRENT_SOURCE.remove();
	}

}

 

数据源切面(默认使用从库,当执行碰到update则自动切换为主库):

package com.jiujun.voice.common.jdbc.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import com.jiujun.voice.common.jdbc.source.DynamicDataSource;

/**
 * 多数据源控制
 * @author Coody
 * @date 2018年9月21日
 */
@Aspect
@Component
public class DataSourceAspect {
	

	/**
	 * 为所有CMD指定从库
	 * 
	 * @param pjp
	 * @return
	 * @throws Throwable
	 */
	@Around("@annotation(com.jiujun.voice.common.cmd.anntation.CmdAction)")
	public Object buildCmdDataSource(ProceedingJoinPoint pjp) throws Throwable {
		StopWatch sw = new StopWatch(getClass().getSimpleName());
		try {
			// AOP启动监听
			sw.start(pjp.getSignature().toShortString());
			//默认指定从库
			DynamicDataSource.setDataSourceFlag(DynamicDataSource.SLAVE_FLAG);
			return pjp.proceed();
		} finally {
			//释放线程数据源
			DynamicDataSource.clearDataSourceFlag();
			sw.stop();
		}
	}
}


sql语句更新处(mybatis和hibernate可采用拦截器对sql进行拦截,并设置主库):

转载于:https://my.oschina.net/hooker/blog/3019597

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 中配置主从数据源,需要使用一个名为 DynamicDataSource动态数据源。该动态数据源可以根据不同的数据源 key 来动态切换数据源,从而实现主从数据源的切换。 下面是配置步骤: 1. 创建 DynamicDataSource 类 ``` public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceKey(); } } ``` 2. 创建 DataSourceContextHolder 类 ``` public class DataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setDataSourceKey(String dataSourceKey) { contextHolder.set(dataSourceKey); } public static String getDataSourceKey() { return contextHolder.get(); } public static void clearDataSourceKey() { contextHolder.remove(); } } ``` 3. 配置数据源 在 application.yml 中配置主从数据源: ``` spring: datasource: master: url: jdbc:mysql://localhost:3306/masterdb username: root password: root slave: url: jdbc:mysql://localhost:3306/slavedb username: root password: root ``` 4. 配置 DynamicDataSource ``` @Configuration public class DataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } @Bean public DynamicDataSource dataSource() { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", masterDataSource()); targetDataSources.put("slave", slaveDataSource()); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSources); dataSource.setDefaultTargetDataSource(masterDataSource()); return dataSource; } } ``` 5. 使用数据源 在 Service 或 Dao 中使用数据源: ``` @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override @DataSource(DataSourceType.SLAVE) public List<User> listUsers() { return userDao.listUsers(); } @Override @DataSource(DataSourceType.MASTER) public int addUser(User user) { return userDao.addUser(user); } } ``` 在上面的示例中,我们使用 @DataSource 注解来标记使用哪个数据源。@DataSource 注解需要一个 DataSourceType 参数,可以是 MASTER 或 SLAVE。其中 MASTER 是默认数据源。 这样就可以实现主从数据源的配置了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值