1。因项目中采用了多数据源的业务操作,所以借此 使用springboot搭建了多数据源的管理模式。
在对应的配置文件中,新建一个主数据源,在自定义不同的其他数据源。并配置别名
@EnableAutoConfiguration(exclude={
JpaRepositoriesAutoConfiguration.class
})
@SpringBootApplication
@MapperScan(basePackages = { "com.dao" })
@EnableScheduling
@EnableTransactionManagement
@Import({DynamicDataSourceRegister.class})
public class SpringbootApplication {
public static void main(String[] args){
SpringApplication.run(SpringbootApplication.class, args);
}
}
这里的启动类一定要添加@EnableAutoConfiguration(exclude={ JpaRepositoriesAutoConfiguration.class })。指的是取消spring默认的数据源管理模式。
1.自定义一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD, ElementType.TYPE
})
public @interface DS {
String value() ;
}
2.这一步为加载数据源操作
package com.ds;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.log4j.Logger;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.annotation.Value;
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.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
@EnableConfigurationProperties(DataSourceProperties.class)
@MapperScan(value = "com.dao")
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
public final static Logger _logger = Logger.getLogger(DynamicDataSourceRegister.class);
//如配置文件中未指定数据源类型,使用该默认值
private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
// 默认数据源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
//对接数据库的实体层
@Value("${mybatis.type-aliases-package}")
private String ALIASES_PACKAGE;
@Value("${mybatis.mapper-locations}")
private String MAPPER_LOCATION;
@Override
public void setEnvironment(Environment environment) {
//_logger.error("DynamicDataSourceRegister.setEnvironment()");
initDefaultDataSource(environment);
initCustomDataSources(environment);
}
/**
* 加载主数据源配置.
* @param 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("driverClassName", propertyResolver.getProperty("driverClassName"));
dsMap.put("url", propertyResolver.getProperty("url"));
dsMap.put("username", propertyResolver.getProperty("username"));
dsMap.put("password", propertyResolver.getProperty("password"));
dsMap.put("resourceName", propertyResolver.getProperty("resourceName"));
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource, env);
}
/**
* 初始化更多数据源
* @param env
*/
private void initCustomDataSources(Environment env) {
// 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
String dsPrefixs = propertyResolver.getProperty("names");
for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
dataBinder(ds, env);
}
}
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map<String, Object> dsMap) {
Object type = dsMap.get("type");
if (type == null){
type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
}
Class<? extends DataSource> dataSourceType;
try {
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
String driverClassName = dsMap.get("driverClassName").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString();
String resourceName = dsMap.get("resourceName").toString();
/*DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password).type(dataSourceType);
return factory.build();*/
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(password);
mysqlXaDataSource.setUser(username);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName(resourceName);
xaDataSource.setMinPoolSize(20);
xaDataSource.setMaxPoolSize(100);
xaDataSource.setMaxLifetime(20000);
xaDataSource.setBorrowConnectionTimeout(30);
xaDataSource.setLoginTimeout(30);
xaDataSource.setMaintenanceInterval(60);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setTestQuery("SELECT 1");
return xaDataSource;
} catch (ClassNotFoundException e) {
_logger.error("error"+e);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* 为DataSource绑定更多数据
* @param dataSource
* @param env
*/
private void dataBinder(DataSource dataSource, Environment env){
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
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("driverClassName");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
}
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
//_logger.error("DynamicDataSourceRegister.registerBeanDefinitions()");
Map<Object, Object> targetDataSources = new HashMap<>();
// 将主数据源添加到更多数据源中
targetDataSources.put("dataSource", defaultDataSource);
DataSourceContextHolder.dataSourceIds.add("dataSource");
// 添加更多数据源
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
// _logger.error("----从数据源---" + key);
DataSourceContextHolder.dataSourceIds.add(key);
}
// 创建DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
//添加属性:AbstractRoutingDataSource.defaultTargetDataSource
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);
_logger.error("动态数据源注册成功,从数据源个数 == " + customDataSources.size());
}
}
3.封装一个切面,来转换对应的数据源操作
package com.ds;
import com.utils.ReflectUtil;
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.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Order(-100)
@Component
public class DynamicDataSourceAspect {
public final static Logger _logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Pointcut("execution(* com.dao.demo.*.*(..))")
private void pointCutMethod() {
}
@Before("pointCutMethod()")
public void changeDataSource1(JoinPoint point) throws Exception {
//拿到anotation中配置的数据源
String resultDS = determineDatasource(point);
System.out.print("----------"+resultDS);
//没有配置实用默认数据源
if (resultDS == null) {
DataSourceContextHolder.setDataSourceType(null);
return;
}
//将数据源设置到数据源持有者
DataSourceContextHolder.setDataSourceType(resultDS);
}
/*
/*
*/
/**
* <p>创建时间: 2013-8-20 上午9:48:44</p>
* 如果需要修改获取数据源的逻辑,请重写此方法
*
* @param jp
* @return
*/
@SuppressWarnings("rawtypes")
protected String determineDatasource(JoinPoint jp) {
String methodName = jp.getSignature().getName();
Class targetClass = jp.getSignature().getDeclaringType();
String dataSourceForTargetClass = resolveDataSourceFromClass(targetClass);
String dataSourceForTargetMethod = resolveDataSourceFromMethod(
targetClass, methodName);
String resultDS = determinateDataSource(dataSourceForTargetClass,
dataSourceForTargetMethod);
return resultDS;
}
/**
* 方法执行完毕以后,数据源切换回之前的数据源。
* 比如foo()方法里面调用bar(),但是bar()另外一个数据源,
* bar()执行时,切换到自己数据源,执行完以后,要切换到foo()所需要的数据源,以供
* foo()继续执行。
* <p>创建时间: 2013-8-16 下午4:27:06</p>
*/
@After("pointCutMethod()")
public void restoreDataSourceAfterMethodExecution() {
DataSourceContextHolder.clearDataSourceType();
}
/**
* <li>创建时间: 2013-6-17 下午5:34:13</li> <li>创建人:amos.zhou</li> <li>方法描述 :</li>
*
* @param targetClass
* @param methodName
* @return
*/
@SuppressWarnings("rawtypes")
private String resolveDataSourceFromMethod(Class targetClass,
String methodName) {
Method m = ReflectUtil.findUniqueMethod(targetClass, methodName);
if (m != null) {
DS choDs = m.getAnnotation(DS.class);
return resolveDataSourcename(choDs);
}
return null;
}
/**
* <li>创建时间: 2013-6-17 下午5:06:02</li>
* <li>创建人:amos.zhou</li>
* <li>方法描述 : 确定
* 最终数据源,如果方法上设置有数据源,则以方法上的为准,如果方法上没有设置,则以类上的为准,如果类上没有设置,则使用默认数据源</li>
*
* @param classDS
* @param methodDS
* @return
*/
private String determinateDataSource(String classDS, String methodDS) {
// if (null == classDS && null == methodDS) {
// return null;
// }
// 两者必有一个不为null,如果两者都为Null,也会返回Null
return methodDS == null ? classDS : methodDS;
}
/**
* <li>创建时间: 2013-6-17 下午4:33:03</li> <li>创建人:amos.zhou</li> <li>方法描述 : 类级别的 @ChooseDataSource
* 的解析</li>
*
* @param targetClass
* @return
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private String resolveDataSourceFromClass(Class targetClass) {
DS classAnnotation = (DS) targetClass
.getAnnotation(DS.class);
// 直接为整个类进行设置
return null != classAnnotation ? resolveDataSourcename(classAnnotation)
: null;
}
/**
* <li>创建时间: 2013-6-17 下午4:31:42</li> <li>创建人:amos.zhou</li> <li>方法描述 :
* 组装DataSource的名字</li>
*
* @param ds
* @return
*/
private String resolveDataSourcename(DS ds) {
return ds == null ? null : ds.value();
}
}
4.
package com.ds;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
5.
package com.ds;
import java.util.ArrayList;
import java.util.List;
public class DataSourceContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal(); // 线程本地环境
protected static final List<String> dataSourceIds = new ArrayList<String>();
// 设置数据源类型
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
// 获取数据源类型
public static String getDataSourceType() {
return (String) contextHolder.get();
}
// 清除数据源类型
public static void clearDataSourceType() {
contextHolder.remove();
}
public static boolean containsDataSource(String dataSourceId){
return dataSourceIds.contains(dataSourceId);
}
}
6.
package com.dao.demo;
import com.ds.DS;
import com.entity.UserEntity;
//注入对应的数据源
@DS("xyx_carsharing2")
public interface UserEntityMapper {
UserEntity selectByPrimaryKey(String id);
int updateByPrimaryKeySelective(UserEntity record);
}
以上就是springboot集成多数据源的配置流程。
具体代码可以参考我的Github:
https://github.com/gagaqiang/warn_code.git