基于Mybatis拦截器实现数据库切换

一、背景

工作中曾经遇到一个这样的场景:一个项目下面配置了多个数据库,一个接口的业务要查询的数据可能来源于多个表,而这些表却又分布在不同数据库中,这个时候,就可以通过Mybatis的拦截器,在sql执行前切换数据库即可。
注意:项目是springboot项目,数据使用的是mysql(没测试过oracle)。

二、实现

2.1 主启动

@SpringBootApplication 
public class Application {   
    public static ConfigurableApplicationContext applicationContext = null;  
    public static void main(String[] args){  
        applicationContext = SpringApplication.run(Application.class, args); 
    }
}

在主启动类中,获取应用上下文,目的是要用来动态的创建Bean。

2.2 mybatis拦截器

import java.sql.Connection;

@Intercepts({       
        @Signature(      
                type = Executor.class,    
                method = "query",        
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}  
        ),      
        @Signature(     
                type = Executor.class,    
                method = "query",          
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class} 
        ),     
        @Signature(         
                type = Executor.class,                
                method = "update",         
                args = {MappedStatement.class, Object.class}     
        )
})
@Component
public class MybatisInterceptor implements Interceptor {   
    private static AppLogger appLogger = AppLoggerFactory.getLogger(MybatisInterceptor.class);  
    @Autowired    
    PmgDbUtil pmgDbUtil;    
    @Override    
    public Object intercept(Invocation invocation) throws Throwable {     
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];     
        //id为执行的mapper方法的全路径名,com.cmb.pammng.pmg.manager.PmgManager.addPam    pmg为该mapper要执行的数据库名
         String id = mappedStatement.getId();
         Connection connection = Application.applicationContext.getBean("sqlSessionFactory").openSession().getConnection();
        if (id.startsWith("com.cmb.pammng.")) {      
            String[] result = id.split("\\.");       
            //切换数据源            
            connection.setCatalog(result[3]);           
            appLogger.info("数据库切换为:" + result[3]);  
        } else {
            connection.setCatalog(DB_PMG); //DB_PMG为默认数据名     
            appLogger.info("数据库切换为:" + DB_PMG);    
        }        return invocation.proceed();   
    }    
    @Override  
    public Object plugin(Object target) {      
        if (target instanceof Executor) {       
            return Plugin.wrap(target, this);     
        } else {          
            return target;    
        }    
    }   
    @Override   
    public void setProperties(Properties properties) {   
        
    }
}

    

2.3 业务方法

  @Override
    public Payment getPaymentById(Long id) {
        return paymentDao.getPaymentById(id);
    }

Mybatis的拦截器技术,可以查查资料了解下。大概原理,就是在程序执行到某个mapper方法是,被拦截器拦截,拦截器对其数据库连接做了修改,使用mapper方法真正执行时,能正确的在目标库执行。

2.4 事务问题

我们思考一个问题,在同是一个事务里含对多个数据库的crud操作,是否能将每个数据的操作统一由一个事务管理?
事实上,采用了上述的切库操作,也是可以的通过@Transactional注解实现统一事务管理的,为什么呢?
这里推荐两个链接,让大家了解一下mybatis事务和spring-mybatis事务:
mybatis:https://mybatis.org/mybatis-3/zh/getting-started.html
mybatis-spring:http://mybatis.org/spring/zh/transactions.html
我们从上述链接中可以了解mybatis的事务管理重要组件,我们也从mybatis-spring文档看到一句话:
在这里插入图片描述
再看我们拦截器里的一行代码

 Connection connection = Application.applicationContext.getBean("sqlSessionFactory").openSession().getConnection();

可以简单这样理解:一个请求进来,spring为我们提供一个线程安全的session,该线程的所有操作都在此session下完成,如此就能保证业务方法对多个库操作时实现统一事务管理。
示例:

   @Transactional
    public int create(Payment payment,Order order) {
        orderDao.create(order);//订单库
        return paymentDao.create(payment);//支付库
    }

总之,想要保证所有操作能实现事务管理,就必须保证这些操作都是在同一session下完成的。

三、拓展

我们已经能发现,上述的拦截器,只能针对于只做单表操作的mapper方法,对于多表操作的mapper方法会可能会出现问题。

问:若是某个mapper方法中的sql语句过于复杂,其关联了多个表进行查询,且这些表来自于不同的库,这如何解决?

解决方案一:在该sql语句上不属于本库的表,全部都给它加上前缀(数据库名.表名)。
解决方案二:将这些表,在本库也创建一份(但可能会出现数据分布错乱的问题)

这两个方案,可以视情况而定。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值