【教程】Spring+Mybatis环境配置多数据源

一、简要概述

在做项目的时候遇到需要从两个数据源获取数据,项目使用的Spring + Mybatis环境,看到网上有一些关于多数据源的配置,自己也整理学习一下,然后自动切换实现从不同的数据源获取数据功能。

二、代码详解

2.1 DataSourceConstants 数据源常量类

/**
 * 数据源名称常量类
 * 对应 application.xml 中 bean multipleDataSource
 * @author:dufy
 * @version:1.0.0
 * @date 2018/12/17
 */
public class DataSourceConstants {
    /**
     * 数据源1,默认数据源配置
     */
    public static final String DATASOURCE_1 = "dataSource1";
    /**
     * 数据源2
     */
    public static final String DATASOURCE_2 = "dataSource2";


}

2.2 DataSourceType 自定义数据源注解

/**
 * 自定义数据源类型注解
 *
 * @author:dufy
 * @version:1.0.0
 * @date 2018/12/17
 */

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceType {

    String value() default DataSourceConstants.DATASOURCE_1;

}

2.3 MultipleDataSource 多数据源配置类

MultipleDataSource 继承 AbstractRoutingDataSource 类,为什么继承这个类就可以了?请看 第五章 :实现原理。

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 自定义多数据源配置类
 *
 * @author:dufy
 * @version:1.0.0
 * @date 2018/12/17
 */
public class MultipleDataSource extends AbstractRoutingDataSource {

    private static final ThreadLocal dataSourceHolder = new ThreadLocal();

    /**
     * 设置数据源
     * @param dataSource 数据源名称
     */
    public static void setDataSource(String dataSource){
        dataSourceHolder.set(dataSource);
    }

    /**
     * 获取数据源
     * @return
     */
    public static String getDatasource() {
        return dataSourceHolder.get();
    }

    /**
     * 清除数据源
     */
    public static void clearDataSource(){
        dataSourceHolder.remove();
    }


    @Override
    protected Object determineCurrentLookupKey() {
        return dataSourceHolder.get();
    }
}

2.4 MultipleDataSourceAop 多数据源自动切换通知类

注意:请设置 @Order(0)。否则可能出现 数据源切换失败问题! 因为要在事务开启之前就进行判断,并进行切换数据源!

/**
 * 多数据源自动切换通知类
*

* 首先判断当前类是否被该DataSourceType注解进行注释,如果没有指定注解,则采用默认的数据源配置;
* 如果有,则读取注解中的value值,将数据源切到value指定的数据源 * * @author:dufy * @version:1.0.0 * @date 2018/12/17 */ @Aspect // for aop @Component // for auto scan @Order(0) // execute before @Transactional public class MultipleDataSourceAop { private final Logger logger = Logger.getLogger(MultipleDataSourceAop.class); /** * 拦截 com.**.servicee中所有的方法,根据配置情况进行数据源切换 * com.jiuling.tz.service * com.jiuling.web.service * @param joinPoint * @throws Throwable */ @Before("execution(* com.dufy.*.service.*.*(..))") public void changeDataSource(JoinPoint joinPoint) throws Throwable { try { // 拦截的实体类,就是当前正在执行的service Class clazz = joinPoint.getTarget().getClass(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 提取目标对象方法注解和类型注解中的数据源标识 Class[] types = method.getParameterTypes(); if (clazz.isAnnotationPresent(DataSourceType.class)) { DataSourceType source = clazz.getAnnotation(DataSourceType.class); MultipleDataSource.setDataSource(source.value()); logger.info("Service Class 数据源切换至--->" + source.value()); } Method m = clazz.getMethod(method.getName(), types); if (m != null && m.isAnnotationPresent(DataSourceType.class)) { DataSourceType source = m.getAnnotation(DataSourceType.class); MultipleDataSource.setDataSource(source.value()); logger.info("Service Method 数据源切换至--->" + source.value()); } } catch (Exception e) { e.printStackTrace(); } } /** * 方法结束后 */ @After("execution(* com.dufy.*.service.*.*(..))") public void afterReturning() throws Throwable { try { MultipleDataSource.clearDataSource(); logger.debug("数据源已移除!"); } catch (Exception e) { e.printStackTrace(); logger.debug("数据源移除报错!"); } } }

三、配置详情

applicationContext.xml 中配置详情




    
    

    
    
        
            
                classpath*:jdbc.properties
            
        
    

    
    
        
        
        
        

        
        
        
        

        
        

        
        

        
        

        
        
        
        

        
        
        

        
        
    

    
    
        
        
        
        
        
    


    
    
        
        
            
                
                
                
            
        
    

    
    

    
    

    
    

    
    
        
            
            
            
            
            
            
            
            
            
        
    

    
    
       
        
    

    
    

    
    
  


jdbc.properties 配置内容

##-------------mysql数据库连接配置 ---------------------###
# dataSource1
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.1.110:3306/jdsc?useUnicode=true&characterEncoding=utf-8
jdbc.username=test
jdbc.password=123456

#配置初始化大小、最小、最大
ds.initialSize=1
ds.minIdle=1
ds.maxActive=20
#配置获取连接等待超时的时间 
ds.maxWait=60000

#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
ds.timeBetweenEvictionRunsMillis=60000

#配置一个连接在池中最小生存的时间,单位是毫秒
ds.minEvictableIdleTimeMillis=300000

# dataSource2
jd.jdbc.url=jdbc:mysql://192.168.1.120:3306/jdsc?useUnicode=true&characterEncoding=utf-8
jd.jdbc.username=root
jd.jdbc.password=123456

四、测试切换

注意:测试服务类的包路径,因为只有被AOP拦截的到的指定的Service才会进行数据源的切换。

package com.dufy.web.service.impl.Data1ServiceImpl;

/**
 * 使用 dataSourc1 ,配置dataSourc1的数据源
 * @author:duf
 * @version:1.0.0
 * @date 2018/12/17
 */
@Service
@DataSourceType(value = DataSourceConstants.DATASOURCE_1)
public class Data1ServiceImpl implements Data1Service {

    @Resource
    private Data1Mapper data1Mapper;

    @Override
    public List selectCaseByUpdateTime(String name) {
        List data1 = data1Mapper.selectData1(name);
        return data1;
    }

}

package com.dufy.web.service.impl.Data2ServiceImpl;
/**
 * 使用 dataSourc2 ,配置dataSourc2的数据源
 * @author:duf
 * @version:1.0.0
 * @date 2018/12/17
 */
@Service
@DataSourceType(value = DataSourceConstants.DATASOURCE_2)
public class Data2ServiceImpl implements Data2Service {

    @Resource
    private Data2Mapper data2Mapper;

    @Override
    public List selectCaseByUpdateTime(String name) {
        List data2 = data2Mapper.selectData2(name);
        return data2;
    }

}

通过测试后发现,两个Service服务器分别调用自己的配置的数据源进行数据的获取!

五、实现原理

基于AbstractRoutingDataSource 实现 多数据源配置,通过AOP来进行数据源的灵活切换。AOP的相关原理这里不做说明,就简单说一下 AbstractRoutingDataSource ,它是如何切换数据源的!

首先我们继承 AbstractRoutingDataSource 它是一个抽象类,然后要实现它里面的一个抽象方法。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean 

实现了InitializingBean,InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。

AbstractRoutingDataSourceafterPropertiesSet方法的实现。

public void afterPropertiesSet() {
    if (this.targetDataSources == null) {
        throw new IllegalArgumentException("Property 'targetDataSources' is required");
    } else {
        this.resolvedDataSources = new HashMap(this.targetDataSources.size());
        Iterator var1 = this.targetDataSources.entrySet().iterator();

        while(var1.hasNext()) {
            Entry entry = (Entry)var1.next();
            Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());
            DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());
            this.resolvedDataSources.put(lookupKey, dataSource);
        }

        if (this.defaultTargetDataSource != null) {
            this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
        }

    }
}

afterPropertiesSet方法将配置在 applicationContext.xml中的 targetDataSources 解析并构造出一个HashMap。

然后在实际过程中当需要访问数据库的时候,会首先获取一个Connection,下面看一下获取 Connection 的方法。

public Connection getConnection() throws SQLException {
    return this.determineTargetDataSource().getConnection();
}

public Connection getConnection(String username, String password) throws SQLException {
    return this.determineTargetDataSource().getConnection(username, password);
}

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    // 这里需要注意,通过子类的 determineCurrentLookupKey 方法 获取 lookupKey
    Object lookupKey = this.determineCurrentLookupKey();
    // 转换为对应的 DataSource 数据源
    DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
        // 如果符合上述条件,则获取默认的数据源,也就是在 ApplicationContext.xml 配置的默认数据源
        dataSource = this.resolvedDefaultDataSource;
    }

    if (dataSource == null) {
        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    } else {
        return dataSource;
    }
}
// 抽象方法,用于子类实现
protected abstract Object determineCurrentLookupKey();

知道了 AbstractRoutingDataSource 的抽象方法后,通过AOP拦截,将Service上面配置不同的数据源进行装配到当前请求的ThreadLocal中, 最后 在获取Connection的时候,就能通过determineCurrentLookupKey方法获取到 设置的数据源。

@Override
protected Object determineCurrentLookupKey() {
    return dataSourceHolder.get();
}

再次强调必须保证切换数据源的Aspect必须在@Transactional这个Aspect之前执行,使用@Order(0)来保证切换数据源先于@Transactional执行)

六、参考文章

Spring, MyBatis 多数据源的配置和管理

spring配置多数据源涉及事务无法切换解决方案(绝对有效)


如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!

如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!


欢迎访问我的csdn博客和关注的个人微信公众号!

愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人。

【教程】Spring+Mybatis环境配置多数据源_第1张图片
image

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页 : http://blog.csdn.net/u010648555

© 每天都在变得更好的阿飞

你可能感兴趣的:(【教程】Spring+Mybatis环境配置多数据源)