MyBatis之配置多数据源

项目4的高级实现思路大解密。

在做项目的过程中,有时候一个数据源是不够,那么就需要配置多个数据源。本例介绍mybatis多数据源配置

前言

  一般项目单数据源,使用流程如下:

  单个数据源绑定给sessionFactory,再在Dao层操作,若多个数据源的话,那不是就成了下图

  可见,sessionFactory都写死在了Dao层,若我再添加个数据源的话,则又得添加一个sessionFactory。所以比较好的做法应该是下图

实现原理

1、扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)

从AbstractRoutingDataSource的源码中:

1 public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

2、我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:

public Connection getConnection() throws SQLException {

    return determineTargetDataSource().getConnection();

}

public Connection getConnection(String username, String password) throws SQLException {

    return determineTargetDataSource().getConnection(username, password);

}

3、 获取连接的方法中,重点是determineTargetDataSource()方法,看源码:

/**

    * Retrieve the current target DataSource. Determines the

    * {@link #determineCurrentLookupKey() current lookup key}, performs

    * a lookup in the {@link #setTargetDataSources targetDataSources} map,

    * falls back to the specified

    * {@link #setDefaultTargetDataSource default target DataSource} if necessary.

    * @see #determineCurrentLookupKey()

    */

    protected DataSource determineTargetDataSource() {

        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");

        Object lookupKey = determineCurrentLookupKey();

        DataSource dataSource = this.resolvedDataSources.get(lookupKey);

        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {

            dataSource = this.resolvedDefaultDataSource;

        }

        if (dataSource == null) {

            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");

        }

        return dataSource;

    }

上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。

  看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换

案例

  1、搭建一个Springmvc + Spring + Mybatis  maven项目,POM文件中引入AOP相关依赖,2、编辑一个扩展AbstractRoutingDataSource类,DynamicDataSource.java

package com.test.datasource;

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

/**

*    动态数据源(依赖于spring)

* @author peter huang

* @date 2019-08-03 17:27:35

*

*/

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override

    protected Object determineCurrentLookupKey() {

        return DataSourceHolder.getDataSource();

    }

}

3、 封装一个的对数据源进行操作的类,DataSourceHolder.java

package com.test.datasource;

public class DataSourceHolder {

    // 线程本地环境

    private static final ThreadLocal dataSources = new ThreadLocal();

    // 设置数据源

    public static void setDataSource(String customerType) {

        dataSources.set(customerType);

    }

    // 获取数据源

    public static String getDataSource() {

        return (String) dataSources.get();

    }

    // 清除数据源

    public static void clearDataSource() {

        dataSources.remove();

    }

}

4、当需要切换数据源的时候执行啦。手动在代码中调用写死吗?调用setDataSource方法

但是这种方法比较死板,所以我们可以应用spring aop来设置,把配置的数据源类型都设置成为注解标签,在service层中需要切换数据源的方法上,写上注解标签,调用相应方法切换数据源咯(就跟你设置事务一样)

1 @TargetDataSource(name=TargetDataSource.SLAVE)

2 publicList getEmpsFromSalve()

编辑注解标签TargetDataSource.java

package com.test.annotation;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface TargetDataSource {

    String name() default TargetDataSource.MASTER;

    public static String MASTER = "dataSource1";

    public static String SLAVE = "dataSource2";

}

5、编辑切面的Bean,DataSourceExchange.java

package com.test.datasource;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

import org.springframework.aop.MethodBeforeAdvice;

import com.test.annotation.TargetDataSource;

public class DataSourceExchange implements MethodBeforeAdvice, AfterReturningAdvice {

    @Override

    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {

        DataSourceHolder.clearDataSource();

    }

    @Override

    public void before(Method method, Object[] args, Object target) throws Throwable {

        // 这里TargetDataSource是自定义的注解

        if (method.isAnnotationPresent(TargetDataSource.class)) {

            TargetDataSource datasource = method.getAnnotation(TargetDataSource.class);

            DataSourceHolder.setDataSource(datasource.name());

        } else {

            if(target.getClass().isAnnotationPresent(TargetDataSource.class))

            {

                TargetDataSource datasource = target.getClass().getAnnotation(TargetDataSource.class);

                DataSourceHolder.setDataSource(datasource.name());

            }

        }

    }

}

6、配置文件

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd

        http://mybatis.org/schema/mybatis-spring

        http://mybatis.org/schema/mybatis-spring.xsd

        http://www.springframework.org/schema/aop

        http://www.springframework.org/schema/aop/spring-aop.xsd

        http://www.springframework.org/schema/tx

        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd

        http://www.springframework.org/schema/context

        http://www.springframework.org/schema/context/spring-context-4.0.xsd">

   

   

   

       

       

       

       

   

   

       

       

       

       

   

   

   

       

           

               

               

           

       

       

       

   

   

   

       

   

   

   

   

   

   

       

       

       

       

       

   

   

   

       

       

   

   

   

   

   

   

   

       

       

       

       

   

注意:Spring中的事务是通过aop来实现的,当我们自己写aop拦截的时候,会遇到跟spring的事务aop执行的先后顺序问题,比如说动态切换数据源的问题,如果事务在前,数据源切换在后,会导致数据源切换失效,所以就用到了Order(排序)这个关键字

1

12

7、在service上加上注解即可使用

1@Transactional

2@TargetDataSource(name=TargetDataSource.SLAVE)

3publicint addEmployeeFromSalve(Employee employee) 

{4

5return employeeMapper.insert(employee);

6}

数据流转顺序:

   1.xml拦截到数据源名称

   2.执行切面DataSourceExchange中的before方法,将数据源名称放入 DataSourceHolder中

   3.Spring 调用determineCurrentLookupKey()方法 ,从DataSourceHolder取出当前的数据库名称,并返回

  4.AbstractRoutingDataSource类中determineTargetDataSource()方法调用determineCurrentLookupKey()匹配到指定的数据库,并建立链接,即为切换到相应的数据库;

  5.在指定的数据库中执行相应的sql

你可能感兴趣的:(MyBatis之配置多数据源)