Spring+Mybatis多数据源配置

同一个项目有时会涉及到多个数据库,也就是多数据源。多数据源又可以分为两种情况:

1、两个或多个数据库没有相关性,各自独立,这种可以作为两个项目来开发。

2、两个或多个数据库时masert-slave的关系,比如mysql搭建一个master-master,其后又带有多个slave。

Spring提供两种多数据源配置方式。

1、基于AbstractRoutingDataSource和AOP的多数据源的配置

        基本原理是,我们自己定义一个DataSource类DataSourceRouter,来继承AbstractRoutingDataSource,然后在配置文件中向DataSourceRouter注入多个数据源,然后通过 AOP 来灵活配置,在哪些地方选择数据源1,在哪些地方需要选择数据源2。具体代码如下:

1、数据源选择注解:

/**
 * Created by bo on 2019/1/3.
 * 数据源注解
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    String value();
}

2、定义路由:

/**
 * Created by bo on 2019/1/3.
 * AbstractRoutingDataSource是Spring提供的一个多数据源抽象类。
 * Spring会在使用事务的地方来调用此类的determineCurrentLookupKey()方法来获取数据源的key值。
 * 实现Spring多路由配置,由Spring调用
 */
public class DataSourceRouter extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

3、通过 TheadLocal 来保存每个线程选择哪个数据源的标志(key)

/**
 * Created by bo on 2019/1/3.
 * 线程相关的数据源处理类
 */
public class DataSourceContextHolder {
    //数据源名称线程池
    private static final ThreadLocal<String> holder = new ThreadLocal<>();

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

    /**
     * 获取数据源
     * @return 数据源名称
     */
    public static String getDataSource() {
        return holder.get();
    }

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

}

4、定义切面,处理数据源选择注解:

/**
 * Created by bo on 2019/1/3.
 * 切换数据源
 */
@Aspect
@Component
@Order(1) //请注意:这里order一定要小于tx:annotation-driven的order,即先执行DataSourceAspect切面,再执行事务切面,才能获取到最终的数据源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {
    private static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

    /**
     * 切入点service包及子孙包下的所有类
     */
    @Pointcut("execution(* com.iie..service..*(..))")
    public void aspect() {
    }

    /**
     * 配置前置通知,使用在方法aspect()上注册的切入点
     */
    @Before("aspect()")
    public void before(JoinPoint point) {
        Class<?> target = point.getTarget().getClass();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource dataSource = null;

        //从类初始化
        dataSource = this.getDataSource(target, method);
        //从接口初始化
        if (dataSource == null) {
            for (Class<?> clazz : target.getInterfaces()) { //遍历target实现的所有接口
                dataSource = getDataSource(clazz, method);
                if (dataSource != null) {
                    break;//从某个接口中一旦发现注解,不再循环
                }
            }
        }

        if (dataSource != null && !StringUtils.isEmpty(dataSource.value())) {
            DataSourceContextHolder.setDataSource(dataSource.value());
        }
    }

    @After("aspect()")
    public void after(JoinPoint point) {
        //使用完记得清空
        DataSourceContextHolder.setDataSource(null);
    }

    /**
     * 获取方法或类的注解对象DataSource
     * @param target 类class
     * @param method 方法
     * @return DataSource
     */
    private DataSource getDataSource(Class<?> target, Method method) {
        try {
            //1.优先方法注解
            Class<?>[] types = method.getParameterTypes();
            Method m = target.getMethod(method.getName(), types);
            if (m != null && m.isAnnotationPresent(DataSource.class)) {
                return m.getAnnotation(DataSource.class);
            }
            //2.其次类注解
            if (target.isAnnotationPresent(DataSource.class)) {
                return target.getAnnotation(DataSource.class);
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(MessageFormat.format("通过注解切换数据源时发生异常[class={0},method={1},exception={2}]:", target.getName(), method.getName()), e.getMessage());
        }
        return null;
    }

}

5、在配置文件中向DataSourceRouter注入不同数据源:

<!-- 小组工作计划数据源 -->
    <bean id="teamDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 基本属性 url、mapper、password -->
        <property name="driverClassName" value="${team.driverClassName}"/>
        <property name="url" value="${team.url}"/>
        <property name="username" value="${team.username}"/>
        <property name="password" value="${team.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="1"/>
        <property name="minIdle" value="1"/>
        <property name="maxActive" value="50"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <!--防止mysql 8小时连接失效异常-->
        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="stat"/>
    </bean>
    <!-- 数据资产管理数据源 -->
    <bean id="dataDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 基本属性 url、mapper、password -->
        <property name="driverClassName" value="${data.driverClassName}"/>
        <property name="url" value="${data.url}"/>
        <property name="username" value="${data.username}"/>
        <property name="password" value="${data.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="1"/>
        <property name="minIdle" value="1"/>
        <property name="maxActive" value="50"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <!--防止mysql 8小时连接失效异常-->
        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="stat"/>
    </bean>
    <!-- 多数据源路由 -->
    <bean id="dataSource" class="com.iie.datasource.DataSourceRouter" lazy-init="true">
        <property name="targetDataSources">
            <map key-type="java.lang.String" value-type="javax.sql.DataSource">
                <!-- write -->
                <entry key="team" value-ref="teamDataSource" />
                <entry key="data" value-ref="dataDataSource" />
            </map>
        </property>
        <!-- 默认数据源,如果未指定数据源 或者指定的数据源不存在的话 默认使用这个数据源 -->
        <!--<property name="defaultTargetDataSource" ref="teamDataSource" />-->
    </bean>

6、在程序中使用,直接在Service类或者Server方法中通过@DataSource注解指定数据源:

/**
 * Created by bo on 2018/9/27.
 */
@Service
@DataSource(value = "team")
public class GroupService {
    @Autowired
    private GroupMapper groupMapper;
    @Autowired
    private CustGroupMapper custGroupMapper;
}

2、采用Spring配置文件直接配置多个数据源

其基本原理:创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:每个数据库对应一个 SqlSessionFactory 实例

<context:component-scan base-package="net.aazj.service,net.aazj.aop" />
    <context:component-scan base-package="net.aazj.aop" />
    <!-- 引入属性文件 -->
    <context:property-placeholder location="classpath:config/db.properties" />

    <!-- 配置数据源 -->
    <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc_url}" />
        <property name="username" value="${jdbc_username}" />
        <property name="password" value="${jdbc_password}" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="0" />
        <!-- 连接池最大使用连接数量 -->
        <property name="maxActive" value="20" />
        <!-- 连接池最大空闲 -->
        <property name="maxIdle" value="20" />
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="0" />
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="60000" />
    </bean>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource" />
      <property name="configLocation" value="classpath:config/mybatis-config.xml" />
      <property name="mapperLocations" value="classpath*:config/mappers/**/*.xml" />
    </bean>
    
    <!-- Transaction manager for a single JDBC DataSource -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    
    <!-- 使用annotation定义事务 -->
    <tx:annotation-driven transaction-manager="transactionManager" /> 
    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <property name="basePackage" value="net.aazj.mapper" />
      <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <!-- Enables the use of the @AspectJ style of Spring AOP -->
    <aop:aspectj-autoproxy/>
    
    <!-- ===============第二个数据源的配置=============== -->
    <bean name="dataSource_2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc_url_2}" />
        <property name="username" value="${jdbc_username_2}" />
        <property name="password" value="${jdbc_password_2}" />
        <!-- 初始化连接大小 -->
        <property name="initialSize" value="0" />
        <!-- 连接池最大使用连接数量 -->
        <property name="maxActive" value="20" />
        <!-- 连接池最大空闲 -->
        <property name="maxIdle" value="20" />
        <!-- 连接池最小空闲 -->
        <property name="minIdle" value="0" />
        <!-- 获取连接最大等待时间 -->
        <property name="maxWait" value="60000" />
    </bean>
    
    <bean id="sqlSessionFactory_slave" class="org.mybatis.spring.SqlSessionFactoryBean">
      <property name="dataSource" ref="dataSource_2" />
      <property name="configLocation" value="classpath:config/mybatis-config-2.xml" />
      <property name="mapperLocations" value="classpath*:config/mappers2/**/*.xml" />
    </bean>
    
    <!-- Transaction manager for a single JDBC DataSource -->
    <bean id="transactionManager_2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource_2" />
    </bean>
    
    <!-- 使用annotation定义事务 -->
    <tx:annotation-driven transaction-manager="transactionManager_2" /> 
    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <property name="basePackage" value="net.aazj.mapper2" />
      <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory_2"/>
    </bean>

 

参考:

https://www.cnblogs.com/digdeep/p/4512368.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值