博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringMVC+Mybatis 多数据源配置
阅读量:4091 次
发布时间:2019-05-25

本文共 6033 字,大约阅读时间需要 20 分钟。

(Notice:以下所有经验也是我根据网上的经验整理的,如有侵权可以联系我删除,Wx:IT_Ezra,QQ 654303408。 有问题讨论也可联系我,QQ同上。)

总所周知,随着业务的发展,数据应用压力逐渐增大,特别是数据库。不论从读写分离还是分库的方法来提高应用的性能,都需要涉及到多数据源问题。本文主要介绍在Spring MVC+Mybatis下的多数据源配置。

本文多数据源配置的实现,是基于Spring通过配置多个dateSource对象,然后通过aop来实现具体使用某一个数据源,从而实现多数据源。

1.基本类:DataSourceRouter 继承AbstractRoutingDataSource

AbstractRoutingDataSource 是spring提供的一个多数据源抽象类。spring会在使用事务的地方来调用此类的determineCurrentLookupKey()方法来获取数据源的key值。我们继承此抽象类并实现此方法:
package com.ctitc.collect.manage.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/** *  * 实现spring多路由配置,由spring调用 */public class DataSourceRouter extends AbstractRoutingDataSource {  // 获取数据源名称 protected Object determineCurrentLookupKey() {  return HandleDataSource.getDataSource(); }}

2. 线程内部数据源处理类

DataSourceRouter 类中通过HandleDataSource.getDataSource()获取数据源的key值。此方法应该和线程绑定。
package com.ctitc.collect.manage.datasource;/** * 线程相关的数据源处理类 * */public class HandleDataSource { // 数据源名称线程池 private static final ThreadLocal
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(); }}

3. 自定义数据源注解类

对于spring来说,注解即简单方便且可读性也高。所以,我们也通过注解在service的方法前指定所用的数据源。我们先定义自己的注解类,其中value为数据源的key值。也可以用来mapper上。
package com.ctitc.collect.manage.datasource;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * 数据源注解类 * */@Target({ ElementType.TYPE, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)public @interface DataSource {    String value();}

4. AOP 拦截service并切换数据源

指定注解以后,我们可以通过AOP拦截所有service方法,在方法执行之前获取方法上的注解:即数据源的key值。
import java.lang.reflect.Method;import java.text.MessageFormat;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.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.context.annotation.EnableAspectJAutoProxy;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;/** * 切换数据源(不同方法调用不同数据源) */@Aspect@Component@Order(1) //请注意:这里order一定要小于tx:annotation-driven的order,即先执行DataSourceAspect切面,再执行事务切面,才能获取到最终的数据源@EnableAspectJAutoProxy(proxyTargetClass = true)public class DataSourceAspect {    static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);    /**     * 切入点 service包及子孙包下的所有类     */    @Pointcut("execution(* com.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()) { dataSource = getDataSource(clazz, method); if(dataSource != null){ break ;//从某个接口中一旦发现注解,不再循环 } } } if(dataSource != null && !StringUtils.isEmpty(dataSource.value()) ){ HandleDataSource.setDataSource(dataSource.value()); } } @After("aspect()") public void after(JoinPoint point) { //使用完记得清空 HandleDataSource.setDataSource(null); } /** * 获取方法或类的注解对象DataSource * @param target 类class * @param method 方法 * @return DataSource */ public 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}]:" , target.getName(), method.getName()),e) ; } return null ; }}

5.spring数据源配置

在spring中配置不同的数据源
数据源A :DataSourceA .
业务数据源
数据源B :DataSourceB .
订单数据源

dataSource 则是刚刚实现的DataSourceRouter,且需要指定此类的 targetDataSources属性和 defaultTargetDataSource属性。

targetDataSources :数据源列表,key-value形式,即上面配置的两个数据源

defaultTargetDataSource:默认数据源,如果未指定数据源 或者指定的数据源不存在的话 默认使用这个数据源

多数据源路由

6. AOP的顺序问题

由于我使用的注解式事务,和我们的AOP数据源切面有一个顺序的关系。数据源切换必须先执行,数据库事务才能获取到正确的数据源。所以要明确指定 注解式事务和 我们AOP数据源切面的先后顺序。
  • 我们数据源切换的AOP是通过注解来实现的,只需要在AOP类上加上一个order(1)注解即可,其中1代表顺序号。
  • 注解式事务的是通过xml配置启动

实现上述基本配置之后,就可以在service方法或者mapper方法上添加注解了

@DataSource("order") public  String funtion(){ 	return "hello world"; 	}

本文参考 https://www.jianshu.com/p/fddcc1a6b2d8 。

转载地址:http://nvcii.baihongyu.com/

你可能感兴趣的文章
DirectX11 点光
查看>>
DirectX11 聚光灯
查看>>
DirectX11 HLSL打包(packing)格式和“pad”变量的必要性
查看>>
DirectX11 光照演示示例Demo
查看>>
漫谈一下前端的可视化技术
查看>>
VUe+webpack构建单页router应用(一)
查看>>
Vue+webpack构建单页router应用(二)
查看>>
从头开始讲Node.js——异步与事件驱动
查看>>
Node.js-模块和包
查看>>
Node.js核心模块
查看>>
express的应用
查看>>
NodeJS开发指南——mongoDB、Session
查看>>
Express: Can’t set headers after they are sent.
查看>>
2017年,这一次我们不聊技术
查看>>
实现接口创建线程
查看>>
Java对象序列化与反序列化(1)
查看>>
HTML5的表单验证实例
查看>>
JavaScript入门笔记:全选功能的实现
查看>>
程序设计方法概述:从面相对象到面向功能到面向对象
查看>>
数据库事务
查看>>