前言
在使用spring开发业务系统时,经常会遇到一个项目需要连接多个数据库配置多个数据源的问题。比如数据库做了读写分离,读写操作访问不同数据库。本文主要介绍借助spring提供的AbstractRoutingDataSource
实现多数据源配置,用AOP和注解灵活切换数据源,实现读写分离。
思路
读写分离的基本出发点是,为了减轻数据库压力,让主数据库尽量只处理增删改操作,从数据库只处理查询操作。其中主从数据库使用mysql自带的主从同步机制保证数据一致。在数据库配置好主从同步后,核心问题就是业务系统如何在使用的过程中根据需要切换使用的数据源。本文实现多数据源切换的主要思路是,我们借助spring提供的AbstractRoutingDataSource
类实现可配置多数据源的DataSource
。然后使用注解和AOP,当发现Service层的方法使用了只读的注解,则选择从库读取数据,如果没有注解则使用主库。
AbstractRoutingDataSource
根据上述思路,多数据源的切换主要是借助spring的AbstractRoutingDataSource
,下面主要介绍下这个类。源码200多行,下面我们先从类的说明入手:
1 | /** |
首先,该抽象类继承了AbstractDataSource
,也就是是他本质是DataSource
接口的抽象实现。其次,在getConnection()
时会基于一个key,选择出实际的DataSource
。最后官方建议这个key可是通过线程上线文(ThreadLocal
)传递。接着看getConnection()
方法。
1 |
|
从源码中能看出来,实际上该类通过determineTargetDataSource()
方法选择实际执行的DataSource
。在determineTargetDataSource()
方法中通过determineCurrentLookupKey()
方法获取到key,根据这个key从resolvedDataSources
这个Map中取出对应的DataSource
。如果没有取到,则使用默认的DataSource。其中取key的determineCurrentLookupKey()
方法是需要用户自己实现的。
看到这里其实思路已经很明显了,我们需要继承实现AbstractRoutingDataSource
,主要要干的事有:
- 实现
determineCurrentLookupKey
方法,该方法需要返回1个key,这个key可以从resolvedDataSources
中获取一个datasource
- 构建
resolvedDataSources
这个map
针对第一个问题determineCurrentLookupKey
方法的实现,官方已经给出了明确的提示从线程上线文中获取key
,因此考虑调用方法时从ThreadLocal
中拿指定ken值。下面是第二个问题,如果优雅的构造resolvedDataSources
这个Map。相关代码如下:
1 |
|
通过观察可以发现该抽象类还实现了InitializingBean
接口,在afterPropertiesSet()
方法中,使用targetDataSources
的内容构建的resolvedDataSources
。targetDataSources
有公有的set方法。这样看针对第二个问题的思路也很明显了。
我们在创建AbstractRoutingDataSource
的 Bean时需要对targetDataSources
进行赋值,在交个spring容器管理时,targetDataSources
的内容会被付给resolvedDataSources
。在赋值是还能通过resolveSpecifiedLookupKey
和resolveSpecifiedDataSource
方法对key和DataSource进行统一的转换。相关实现代码如下:
1 | 4j |
1 | // ThreadLocal |
1 |
|
业务实现
在上一部分我们清楚了AbstractRoutingDataSource
本质是DataSource的抽象类,我们主要工作是实现这个抽象类并交给spring使用。那么在业务进行中我们需要做的主要就是在合适的时机设置ThreadLocal
。本文提供的思路是借助注解和切面,注解合适的Service
方法,在调用该方法时根据注解往ThreadLocal
中保存合适的Key。这里逻辑比较清晰,直接展示相关代码
1 |
|
1 |
|
使用方法就是在只有读业务的Service
方法上添加@readOnly
注解即可。
总结
业务中实现读写分离是比较常见的场景,他可以有效的减轻数据库的压力,横向扩展数据库能力。本文介绍了在业务端手动实现切换数据源。其实,有一些插件已经封装了该功能,比如Apache的开源项目——ShardingSphere
,它提供了两种实现方式。有侵入代码的ShardingSphere-jdbc
,他会接管你的数据库连接,在此处做读写分类。非侵入代码的ShardingSphere-proxy
,他会接管你的数据库,你像连接普通数据库一样连接ShardingSphere-proxy
就可享受读写分离、数据分片、分布式事务等功能(ShardingSphere-jdbc也可实现)。有兴趣的小伙伴可以自行了解。