springboot 集成多数据源解决方案

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

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值