博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
主从切换_SpringBoot 多数据源配置+动态数据源切换+多数据源事物配置实现主从数据库存储分离...
阅读量:6269 次
发布时间:2019-06-22

本文共 9734 字,大约阅读时间需要 32 分钟。

一、基础介绍

  多数据源字面意思,比如说二个数据库,甚至不同类型的数据库。在用SpringBoot开发项目时,随着业务量的扩大,我们通常会进行数据库拆分或是引入其他数据库,从而我们需要配置多个数据源。

二、项目目录截图

7cb10e621f8ebd8ecc829698f0d92181.png

 三、多数据源SQL结构设计如下(简单的主从关系):

066a87c6b7bf512e1d53d8508d1f286c.png

 PS:创建两个库用于搭建项目中主从使用不同的数据库,表可以随意定义。

 四、配置编码

1.数据源自定义注解,DataSource.java

 
/** * 数据源自定义注解 */@Target({ ElementType.METHOD, ElementType.TYPE })@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface DataSource {
     DataSourcesType name() default DataSourcesType.MASTER;}
 

2.数据源类型枚举类定义,DataSourcesType.java 

 
/** * 数据源类型 */public enum  DataSourcesType {
/** * 主库 */ MASTER, /** * 从库 */ SLAVE}
 

 3.多数据源application.yml配置文件配置

 
# 数据源配置spring:    datasource:      type: com.alibaba.druid.pool.DruidDataSource      driverClassName: com.mysql.cj.jdbc.Driver      druid:          master:              url: jdbc:mysql://127.0.0.1:3306/master?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8              username: root              password: 123456          slave:              enable: true              url: jdbc:mysql://127.0.0.1:3306/slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8              username: root              password: 123456          # 初始连接数          initialSize: 5          # 最小连接池数量          minIdle: 10          # 最大连接池数量          maxActive: 20          # 配置获取连接等待超时的时间          maxWait: 60000          # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒          timeBetweenEvictionRunsMillis: 60000          # 配置一个连接在池中最小生存的时间,单位是毫秒          minEvictableIdleTimeMillis: 300000          # 配置一个连接在池中最大生存的时间,单位是毫秒          maxEvictableIdleTimeMillis: 900000          validationQuery: SELECT 1 FROM DUAL          testWhileIdle: true          testOnBorrow: false          testOnReturn: false          # 打开PSCache,并且指定每个连接上PSCache的大小          poolPreparedStatements: true          maxPoolPreparedStatementPerConnectionSize: 20          # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,此处是filter修改的地方          filters:            commons-log.connection-logger-name: stat,wall,log4j          # 通过connectProperties属性来打开mergeSql功能;慢SQL记录          connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000          # 合并多个DruidDataSource的监控数据          useGlobalDataSourceStat: true          # 配置 DruidStatFilter          web-stat-filter:            enabled: true            url-pattern: /*            exclusions: .js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*          stat-view-servlet:            enabled: true            url-pattern: /druid/*            # IP 白名单,没有配置或者为空,则允许所有访问            allow: 127.0.0.1            # IP 黑名单,若白名单也存在,则优先使用            deny: 192.168.31.253            # 禁用 HTML 中 Reset All 按钮            reset-enable: false            # 登录用户名/密码            login-username: root            login-password: 123            # 慢SQL记录          filter:              stat:                enabled: true                # 慢SQL记录                log-slow-sql: true                slow-sql-millis: 1000                merge-sql: true              wall:                config:                  multi-statement-allow: true
 

4.数据源配置文件属性定义,DataSourceProperties.java

 
/** * 数据源配置文件 */@Setter@Configuration@ConfigurationProperties(prefix = "spring.datasource.druid")public class DataSourceProperties {
private int initialSize; private int minIdle; private int maxActive; private int maxWait; private int timeBetweenEvictionRunsMillis; private int minEvictableIdleTimeMillis; private int maxEvictableIdleTimeMillis; private String validationQuery; private boolean testWhileIdle; private boolean testOnBorrow; private boolean testOnReturn; public DruidDataSource setDataSource(DruidDataSource datasource) {
datasource.setInitialSize(initialSize); /** 配置初始化大小、最小、最大 */ datasource.setInitialSize(initialSize); datasource.setMaxActive(maxActive); datasource.setMinIdle(minIdle); /** 配置获取连接等待超时的时间 */ datasource.setMaxWait(maxWait); /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); /** * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 */ datasource.setValidationQuery(validationQuery); /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。*/ datasource.setTestWhileIdle(testWhileIdle); /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。*/ datasource.setTestOnBorrow(testOnBorrow); /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。*/ datasource.setTestOnReturn(testOnReturn); return datasource; }
 

5.多数据源切换处理,DynamicDataSourceContextHolder.java

 
/** * 数据源切换处理 */public class DynamicDataSourceContextHolder {
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /** *此类提供线程局部变量。这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。 */ private static final ThreadLocal contextHolder = new ThreadLocal<>(); /** * 设置当前线程的数据源变量 */ public static void setDataSourceType(String dataSourceType) {
log.info("已切换到{}数据源", dataSourceType); contextHolder.set(dataSourceType); } /** * 获取当前线程的数据源变量 */ public static String getDataSourceType() {
return contextHolder.get(); } /** * 删除与当前线程绑定的数据源变量 */ public static void removeDataSourceType() {
contextHolder.remove(); }}
 

 6.获取数据源(依赖于 spring) 定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换,DynamicDataSource.java 

 
/** * 获取数据源(依赖于 spring)  定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换 */public class DynamicDataSource extends AbstractRoutingDataSource {
public static DynamicDataSource build() {
return new DynamicDataSource(); } /** * 获取与数据源相关的key * 此key是Map resolvedDataSources 中与数据源绑定的key值 * 在通过determineTargetDataSource获取目标数据源时使用 */ @Override protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType(); }}
 

7.数据源核心配置类,DataSourceConfiguration.java 

 
/** * 数据源配置类 */@Configurationpublic class DataSourceConfiguration {
/** * 主库 */ @Bean @ConfigurationProperties("spring.datasource.druid.master") public DataSource masterDataSource(DataSourceProperties dataSourceProperties) {
return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build()); } /** * 从库 */ @Bean @ConditionalOnProperty( prefix = "spring.datasource.druid.slave", name = "enable", havingValue = "true")//是否开启数据源开关---若不开启 默认适用默认数据源 @ConfigurationProperties("spring.datasource.druid.slave") public DataSource slaveDataSource(DataSourceProperties dataSourceProperties) {
return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build()); } /** * 设置数据源 */ @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) {
Map targetDataSources = new HashMap<>(); DynamicDataSource dynamicDataSource = DynamicDataSource.build(); targetDataSources.put(DataSourcesType.MASTER.name(), masterDataSource); targetDataSources.put(DataSourcesType.SLAVE.name(), slaveDataSource); //默认数据源配置 DefaultTargetDataSource dynamicDataSource.setDefaultTargetDataSource(masterDataSource); //额外数据源配置 TargetDataSources dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.afterPropertiesSet(); return dynamicDataSource; }}
 

8.多数据源切面配置类,用于获取注解上的注解,进行动态切换数据源,DynamicDataSourceAspect.java

 
@Aspect@Component@Order(-1) // 保证该AOP在@Transactional之前执行public class DynamicDataSourceAspect {
protected Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.fuzongle.tankboot.common.annotation.DataSource)" + "|| @within(com.fuzongle.tankboot.common.annotation.DataSource)") public void dsPointCut() {
} @Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable {
Method targetMethod = this.getTargetMethod(point); DataSource dataSource = targetMethod.getAnnotation(DataSource.class);//获取要切换的数据源 if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.name().name()); } try {
return point.proceed(); } finally {
// 销毁数据源 在执行方法之后 DynamicDataSourceContextHolder.removeDataSourceType(); } } /** * 获取目标方法 */ private Method getTargetMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
Signature signature = pjp.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method agentMethod = methodSignature.getMethod(); return pjp.getTarget().getClass().getMethod(agentMethod.getName(), agentMethod.getParameterTypes()); }}
 
9.编写业务逻辑,切换从库查询数据。

d635b6f02dd4c0c8f57d21536c431e9b.png

 10.编写测试方法,调用查询业务,查看是否切换数据源是否生效。

100f07d0bf826c3e25692413393aa4b8.png

PS:这种多数据源的动态切换确实可以解决数据的主从分库操作,但是却有一个致命的BUG,那就是事务不但失效而且无法实现

一致性,因为涉及到跨库,因此我们必须另想办法来实现事务的ACID原则

以上配置所有源码地址:
https://gitee.com/fuzongle/java-bucket

注意:

1.如果有任何不懂的地方可以关注公众号就可以加我微信,随时欢迎互相帮助。

2.技术交流群QQ:422167709。

3.如果希望学习更多,希望微信扫码,长按扫码,帮忙关注一下,举手之劳,当您无助的时候真的能帮你。非常感谢您关注公众号 "编程小乐"。

999e348de2c57e30b3be8624cef43f21.png

转载地址:http://uvspa.baihongyu.com/

你可能感兴趣的文章
从不同层面看cocos2d-x
查看>>
Struts2技术详解
查看>>
MFC应用程序向导生成的文件
查看>>
Oracle体系结构之oracle密码文件管理
查看>>
【leetcode】Remove Element (easy)
查看>>
mysql多表查询及其 group by 组内排序
查看>>
alsa的snd_pcm_readi()函数和snd_pcm_writei()
查看>>
Android学习网站推荐(转)
查看>>
嵌入式根文件系统的移植和制作详解
查看>>
MEF部件的生命周期(PartCreationPolicy)
查看>>
LCD的接口类型详解
查看>>
nginx 基础文档
查看>>
LintCode: Unique Characters
查看>>
Jackson序列化和反序列化Json数据完整示例
查看>>
.net 中的DllImport
查看>>
nyoj 517 最小公倍数 【java睑板】
查看>>
include与jsp:include区别
查看>>
ftp的20 21端口和主动被动模式
查看>>
MySQL存储引擎选型
查看>>
Java中的statickeyword具体解释
查看>>