Mybatis主从读写分离
参考自这篇文章:https://www.cnblogs.com/cjsblog/p/9712457.html
具体的代码,文章里面都有,这里就不贴出来了。因此本文主要记录下遇到的几个问题,以及一些关于代码的理解。
主从读写分离的实现思路
将主从设置为多个数据源
读写分离,实际上就是在需要读的时候,将数据库连接切换到读数据源;在需要写的时候,将数据库连接切换到写数据源,因此我们肯定要提前先设置多个数据源才行。
文章中设置了4个数据源,包括一个主库、2个从库以及一个用于切换主从的数据源。
通过ThreadLocal保证线程安全
普通的数据库读写在多线程、高并发下,有很大的安全隐患。
ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
参考文章:http://www.cnblogs.com/dolphin0520/p/3920407.html
通过AOP+注解,智能切换主从
数据库的操作调用,一般都是在Service层。我们可以通过定义一个针对Service层的AOP切面,然后通过Service的方法名来确定到底应该连主库还是从库。
这是切换的核心部分,因此这里贴下代码(注意该文章的AOP切点的设置有问题,会将读写操作都切换到主库,这个需要自行修改下;下面的示例代码是已经修正过的):
1 | |
针对一些特殊场景,比如用户的浏览量、点赞等,需要写入后马上查询的,为了避免主从同步延时导致数据读取有误,一般都要求写入主库后,读取也在主库进行。这种情况就需要我们能够手动指定切换到主or从。
这可以通过注解来搭配AOP切点实现,上面的代码已经体现了,就不再赘述:
1 | |
注意事项
AOP切面必须涵盖所有的service或者mapper方法
在之前的项目中出现过这个问题:有个方法没有被AOP涵盖到,这是一个update数据库的方法(方法名没有包含update等AOP中定义好的关键词,而是叫做doPriority()),结果引用了从库的数据源;而从库是read-only模式,就导致了写入失败。
遇到的问题
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.test.poi.api.mapper.PoiFormulaMapper.getNamesByIds
我的程序里面有两个包,poi和pos,然后发现删除pos就正常了,因此确定问题出在pos中。
经过排查,最终发现是因为项目分为了多个包,我在poi里面设置了数据源的配置;而之前另外一个同事在pos这个包里面,也设置了数据源,导致二者冲突了。这个错误提示信息不够友好,很容易造成误导。
解决方案:注释掉之前同事在pos中设置的数据源类的@Configuration注解即可
java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
这个报错造成的现象是:两个从数据源,一个可以正常使用(slave1),一个不行(slave2)。
通过排查,最终确定应该是配置文件有特殊字符,导致slave1的配置没有被正常读取到。
但是我用vim的set invlist对比了下,也没看出什么特殊字符,很奇怪;不过用正确的slave2的配置覆盖上去就正常了;说明肯定还是有不一样的字符的。
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ‘com.test.pos.mapper.ClassifyListMapper’ available
只加载这个路径就会报错:
@MapperScan(“com.test.poi.api.mapper”)
写成这个则可以成功加载:
@MapperScan({“com.test.poi.api.mapper”, “com.test.pos.mapper”})
原因是这个ClassifyListMapper是我们自己定义的,位于pos下,我不扫描pos的mapper,当然报错了。