基于SpringBoot的Mybatis多数据源访问
最近一段时间一直在搞多数据访问的功能,这里稍微记录一下,之前搞得都是基于Mybatis的SqlSessionFactory.
最近学习了DataSource做代理,从Mybatis介入选择不同DataSource实现多数据源,跟以前比相对简单灵活多了,缺点就是不支持分布式事务.
源码在最下面供参考
前言
分别讲一下基于DataSource的拓展的思路与实现和基于SqlSessionFactory的思路与实现最后比较一下各自优缺点.
基于DataSource的实现
思路:这里通过DataSource做拓展思路相对简单粗暴,就是在获取链接时我们根据TheadLocal进行判断看选择的是哪一个数据库,这时调用不同的数据库获得不同的链接这样就集成了多数据源
实现
首先实现一个MultiDataSource,它代理了所有其他的DataSource
MultiDataSource
然后在配置时将MultiDataSource设置为主数据源即可
配置MultiDataSource
在接入Mybatis时有两种方案,第一种是基于Mapper接口的形式,第二种是基于SqlSession的操作形式,两者没有太大区分,只不过在实现细节上稍有不同,总的来说Mapper形式更简单,SqlSession的形式更灵活我两种形式都写了一下Demo,这里稍微截图看一下,源码在最后给出.
基于Mapper的形式
/**
* @author xiezhengchao
* @since 2018/5/20 15:24
*/
@Mapper
public interface UserMapper {
@Select("select * from user where id=#{0}")
@SwitchDataSource("test1")
User selectByPrimaryKeyTest1(Long id);
@Select("select * from user where id=#{0}")
@SwitchDataSource("test2")
User selectByPrimaryKeyTest2(Long id);
}
/**
* @author xiezhengchao
* @since 2018/5/20 15:51
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SwitchDataSource {
/**
* db key choose
*/
String value();
}
/**
* @author xiezhengchao
* @since 2018/5/20 15:52
*/
public class MapperFactoryBeanExt<T> extends MapperFactoryBean<T> {
public MapperFactoryBeanExt() {
}
public MapperFactoryBeanExt(Class<T> mapperInterface) {
super(mapperInterface);
}
@Override
@SuppressWarnings("all")
public T getObject() throws Exception {
T proxy = super.getObject();
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[] { getObjectType() },
new MapperHandle(proxy));
}
private class MapperHandle implements InvocationHandler {
private T originProxy;
MapperHandle(T originProxy) {
this.originProxy = originProxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SwitchDataSource switchDataSource = method.getAnnotation(SwitchDataSource.class);
if (switchDataSource != null) {
DataSourceSelector.set(switchDataSource.value());
}
return method.invoke(originProxy, args);
}
}
}
下面看一下运行效果
运行结果
注意@MapperScan的factoryBean参数我们替换为自己的FactoryBean了,否则就切换不了啦
基于SqlSession的形式
/**
* 为容器中的Dao生成代理
*
* @author xiezhengchao
* @since 2018/5/20 18:56
*/
@Component
public class BaseDaoBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (BaseDao.class.isAssignableFrom(AopUtils.getTargetClass(bean))) {
// 采用cglib做代理,子类更灵活
return CglibProxy.newInstance(bean);
}
return bean;
}
private static class CglibProxy implements MethodInterceptor {
private Object target;
CglibProxy(Object target) {
this.target = target;
}
static Object newInstance(Object target) {
CglibProxy cglibProxy = new CglibProxy(target);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(cglibProxy);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
SwitchDataSource switchDataSource = method.getAnnotation(SwitchDataSource.class);
if (switchDataSource != null) {
DataSourceSelector.set(switchDataSource.value());
}
return method.invoke(target, args);
}
}
}
/**
* 顶层Dao接口,可以放置一些公共方法
*
* @author xiezhengchao
* @since 2018/5/20 18:57
*/
public interface BaseDao {
// just flag
}
/**
* 这里可以实现一些公共方法
*
* @author xiezhengchao
* @since 2018/5/20 15:24
*/
public abstract class BaseDaoImpl implements BaseDao {
}
/**
* @author xiezhengchao
* @since 2018/5/20 17:19
*/
@Component
public class UserDao extends BaseDaoImpl {
@Resource
private SqlSession sqlSession;
@SwitchDataSource("test1")
public User selectByPrimaryKeyTest1(Long id) {
return sqlSession.selectOne("selectByPrimaryKey", id);
}
@SwitchDataSource("test2")
public User selectByPrimaryKeyTest2(Long id) {
return sqlSession.selectOne("selectByPrimaryKey", id);
}
}
查看运行结果
运行结果
这种方式因为直接操作了SqlSession对象所以更灵活,有更大的拓展空间.能够拓展出自己的继承体系,数据库访问层次体系等.
基于SqlSessionFactory的实现
思路:这个还是比较简单的,我们想要JTA的DataSource来管理数据源,那么我们的拓展层面就要放到Mybatis而不能去动DataSource了,所以就是将不同的Dao类进行分包,生成不同的SqlSessionFactory,然后生成多个SqlSession最后通过代理进行路由选择.
实现就不贴了,比较简单,有兴趣可以直接去看源码,这里给出以下SqlSession的代理实现部分,相对简单.
多个SqlSession的代理选择
总结
就目前使用过的几个多数据源访问来说核心的切入点有DataSource或者SqlSession.主要思路都是前置进行注解设置数据源,后面再某个面做动态代理设置到ThreadLocal中然后在下层根据ThreadLocal中不同的值进行切换数据源.
注意这里的数据源可以是DataSource也可以是SqlSession.越往上相对越复杂.但是能兼容的功能就越多.
源码
基于DataSource的多数据源实现:https://github.com/znyh113too/mybatis-multi-datasource-support
基于SqlSessionFactory的多数据源实现:https://github.com/znyh113too/bubi-mybatis-spring-boot
over