有时候在项目中会遇到需要连接两个数据库的情况。本文就结合pring和Mybati来讲下怎么使用双数据源(或者是多数据源)。
背景知识介绍#
本文中实现多数据源的关键是pring提供的AbtractRoutingDataource。这个类可以根据lookup key来实现底层数据源的动态转换。
Copypublic abtract cla AbtractRoutingDataource extend AbtractDataource implement InitializingBean { @Nullable
private Map targetDataource; @Nullable
private Object defaultTargetDataource; private boolean lenientFallback = true; private DataourceLookup dataourceLookup = new JndiDataourceLookup(); @Nullable
private Map reolvedDataource; @Nullable
private Dataource reolvedDefaultDataource; public void etTargetDataource(Map targetDataource) { thi.targetDataource = targetDataource;
} public void etDefaultTargetDataource(Object defaultTargetDataource) { thi.defaultTargetDataource = defaultTargetDataource;
} public void etLenientFallback(boolean lenientFallback) { thi.lenientFallback = lenientFallback;
} public void etDataourceLookup(@Nullable DataourceLookup dataourceLookup) { thi.dataourceLookup = (dataourceLookup != null ? dataourceLookup : new JndiDataourceLookup());
} @Override
public void afterPropertieet() { if (thi.targetDataource == null) { throw new IllegalArgumentException("Property 'targetDataource' i required");
} thi.reolvedDataource = new HahMap<>(thi.targetDataource.ize()); thi.targetDataource.forEach((key, value) -> {
Object lookupKey = reolvepecifiedLookupKey(key);
Dataource dataource = reolvepecifiedDataource(value); thi.reolvedDataource.put(lookupKey, dataource);
}); if (thi.defaultTargetDataource != null) { thi.reolvedDefaultDataource = reolvepecifiedDataource(thi.defaultTargetDataource);
}
} protected Object reolvepecifiedLookupKey(Object lookupKey) { return lookupKey;
} protected Dataource reolvepecifiedDataource(Object dataource) throw IllegalArgumentException { if (dataource intanceof Dataource) { return (Dataource) dataource;
} ele if (dataource intanceof tring) { return thi.dataourceLookup.getDataource((tring) dataource);
} ele { throw new IllegalArgumentException( "Illegal data ource value - only [javax.ql.Dataource] and tring upported: " + dataource);
}
} @Override
public Connection getConnection() throw QLException { return determineTargetDataource().getConnection();
} @Override
public Connection getConnection(tring uername, tring paword) throw QLException { return determineTargetDataource().getConnection(uername, paword);
} @Override
@uppreWarning("unchecked") public T unwrap(Cla iface) throw QLException { if (iface.iIntance(thi)) { return (T) thi;
} return determineTargetDataource().unwrap(iface);
} @Override
public boolean iWrapperFor(Cla iface) throw QLException { return (iface.iIntance(thi) || determineTargetDataource().iWrapperFor(iface));
}
protected Dataource determineTargetDataource() {
Aert.notNull(thi.reolvedDataource, "Dataource router not initialized");
Object lookupKey = determineCurrentLookupKey();
Dataource dataource = thi.reolvedDataource.get(lookupKey); if (dataource == null && (thi.lenientFallback || lookupKey == null)) {
dataource = thi.reolvedDefaultDataource;
} if (dataource == null) { throw new IllegaltateException("Cannot determine target Dataource for lookup key [" + lookupKey + "]");
} return dataource;
} @Nullable
//一般只需要用户实现这个方法。
protected abtract Object determineCurrentLookupKey();
}郑州哪里割包皮好:http://www.zztongji120.com/郑州割包皮哪个医院好:http://www.zztongji120.com/郑州哪家医院包皮手术好:http://www.zztongji120.com/
实现流程#
tep1:实现一个自定义的AbtractRoutingDataource
Copypublic cla DynamicDataource extend AbtractRoutingDataource { //这边定义了一个和线程绑定的ThreadLocal变量,用于存放需要使用的数据源的名称
private tatic final ThreadLocal dataourceNameHolder = new ThreadLocal<>(); public DynamicDataource(Dataource defaultTargetDataource, Map targetDataource) { uper.etDefaultTargetDataource(defaultTargetDataource); uper.etTargetDataource(targetDataource); uper.afterPropertieet();
} @Override
//重写了AbtractRoutingDataource的determineCurrentLookupKey方法
protected Object determineCurrentLookupKey() { return getDataource();
} public tatic void etDataource(tring dataource) {
dataourceNameHolder.et(dataource);
} public tatic tring getDataource() { return dataourceNameHolder.get();
} public tatic void clearDataource() {
dataourceNameHolder.remove();
}
}
tep2:实现一个AOP对ervice层方法进行AOP拦截,调用DynamicDataource中的ThreadLocal变量,将当前请求需要使用的数据源名称设置进去。
Copy//定义一个Dataource注解@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Dataource { tring name() default "";
}//这边再定义一个常量public interface DataourceName {
tring FIRT = "firt";
tring ECOND = "econd";
}
定义AOP处理Dataource注解
Copy@Apect@Componentpublic cla DataourceApect implement Ordered { protected Logger logger = LoggerFactory.getLogger(getCla()); @Pointcut("@annotation(com.xx.yy.annotation.Dataource)") public void dataourcePointCut() {
} @Around("dataourcePointCut()") public Object around(PeedingJoinPoint point) throw Throwable {
Methodignature ignature = (Methodignature) point.getignature();
Method method = ignature.getMethod();
Dataource d = method.getAnnotation(Dataource.cla); //如果未指定数据源就使用第一个数据源
if(d == null){
DynamicDataource.etDataource(DataourceName.FIRT);
logger.debug("et dataource i " + DataourceName.FIRT);
}ele {
DynamicDataource.etDataource(d.name());
logger.debug("et dataource i " + d.name());
} try { return point.peed();
} finally {
DynamicDataource.clearDataource();
logger.debug("clean dataource");
}
} @Override
public int getOrder() { return 1;
}
}
tep3:对数据源进行配置
Copy@Configurationpublic cla DynamicDataourceConfig { @Bean
@ConfigurationPropertie("pring.dataource.druid.firt") public Dataource firtDataource(){ return DruidDataourceBuilder.create().build();
} @Bean
@ConfigurationPropertie("pring.dataource.druid.econd") public Dataource econdDataource(){
Dataource dataource = DruidDataourceBuilder.create().build(); return dataource;
} @Bean
@Primary
@DependOn(value = {"firtDataource","econdDataource"}) public DynamicDataource dataource(Dataource firtDataource,Dataource econdDataource) {
Map targetDataource = new HahMap<>();
targetDataource.put(DataourceName.FIRT, firtDataource);
targetDataource.put(DataourceName.ECOND, econdDataource); return new DynamicDataource(firtDataource, targetDataource);
}
}
使用#
使用的时候非常简单,只需要在ervice层的方法上加上@Dataource注解就可以了。
Copy@Dataource(name = DataourceName.ECOND)public tring electByInfoName(tring name){ //...}
一些注意点#
如果你使用了pageHelper等分页插件,请将方言设置成自动模式, autoRuntimeDialect: true
Copypagehelper: reaonable: fale upportMethodArgument: true param: count=countql autoRuntimeDialect: true
如果你使用了Druid数据源,并通过下面的形式创建数据源,要保障数据源的用户名和密码字段不为null。不然DruidDataourceWrapper这个Bean会检测这个字段的值,导致启动失败。
Copy@Bean
@ConfigurationPropertie("pring.dataource.druid.firt") public Dataource firtDataource(){ return DruidDataourceBuilder.create().build();
} @Bean
@ConfigurationPropertie("pring.dataource.druid.econd") public Dataource econdDataource(){
Dataource dataource = DruidDataourceBuilder.create().build(); return dataource;