(Notice:欢迎交流和沟通,Wx:IT_Ezra,QQ 654303408。仅个人观点和个人理解有问题讨论也可联系我。)
(PS:写这篇博客的原因是因为刚入行不久,第一次把自己的自己所学的东西应用在实际层面,更让我意识到,基础的重要性。)
使用场景:当我们的生产数据在逐渐增多,数据库分库分表技能也变得很常见。那么假如说,我们也用户表放到DB1,把订单表放到DB2…等。那么我们查询的时候,要根据不同的数据库去查不同的数据。众所周知,通常一个Mybatis-config.xml里面是配置一个datasource,但是实际上是我们可以配置到多个datasource,然后通过spring-aop来实现。下面直接上干货。
首先我们要配置mybatis的xml配置文件,当然有不规范的写法,就是把其中的内容直接放到spring的beans.xml文件下也是能够实现的。
1.datasource的配置,配置多个datasource,根据自己的实际情况而定。
DataSource2.设置多数据源路由。实际上就是一个分发器,就是通过到时候切面的切点值来选择不同的数据库
多数据源路由3 JDBC事务管理。配置jdbc的事务。spring里面也有事务注解来实现DB的事务。
JDBC事务管理4 配置sessionFactory ,以及包扫描
配置sessionFactory5 然后两个工具类以及aop的实现。
切面接口,DataSourceAspect ,该接口是在service层。也可以实现controller层切
/**
- @ author ezra
- @ date 2019/7/5 11:27
/
@Slf4j
@Aspect
@Component
@Order(1) //请注意:这里order一定要小于tx:annotation-driven的order,即先执行DataSourceAspect切面,再执行事务切面,才能获取到最终的数据源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {
/*
* 切入点 service包及子孙包下的所有类 也可以改为controller级别下的所有
*/
@Pointcut("execution(* com.dfs.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();
log.error(MessageFormat.format("通过注解切换数据源时发生异常[class={0},method={1}]:"
, target.getName(), method.getName()),e) ;
}
return null ;
}
}
DataSource 注解接口
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value();
}
DataSourceRouter 工具类
public class DataSourceRouter extends AbstractRoutingDataSource {
// 获取数据源名称
protected Object determineCurrentLookupKey() {
return HandleDataSource.getDataSource();
}
}
HandleDataSource 工具类
public class HandleDataSource {
// 数据源名称线程池
private static final ThreadLocal<String> holder = new ThreadLocal<String>();
/**
* 设置数据源
* @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();
}
}
网友评论