好的,作为一个合格的bug生产者,我们直接进入主题,多数据源和读写分离实现方案。

首先多数据源和读写分离什么时候我们才需要呢?

多数据源:一个单体项目过于复杂,需要操作多个业务库的时候,就需要多数据源操作不同的数据

读写分离:数据库压力较大时,我们考虑读写分离,主库写,从库读,减少数据库的压力。多个库数据是一样的。

理解完使用场景后,再入主题,怎么实现呢?这里说三种实现方式

1、扩展Spring的AbstractRoutingDataSource
2、通过Mybatis 配置不同的 Mapper 使用不同的 SqlSessionTemplate
3、分库分表中间件,比如Sharding-JDBC 、Mycat等。

好的,再让我们直入主题

扩展Spring的AbstractRoutingDataSource

多数据源

基于Spring AbstractRoutingDataSource做扩展,通过继承AbstractRoutingDataSource抽象类,实现一个管理多个 DataSource的数据源管理类。Spring 在获取数据源时,可以通过 数据源管理类 返回实际的 DataSource 。

然后我们可以定义一个注解,添加到service、dao上,表示一个实际的对应的datasource。

不过这个方式,对于spring事物的支持不好,多个数据源无法保障事物。这个问题是多数据源的通用问题了。

废话不多说,下面我们说下具体实现把,首先pom要引入的依赖的话很简单,就是一个springboot项目。

pom.xml

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.2</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions>
</dependency>
<!--实现对 Druid 连接池的自动化配置-->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.21</version>
</dependency>

application配置多数据源

server:port: 8080spring:application:name: dynamicdatasource:mall:url: jdbc:mysql://rm-xxxxx.mysql.rds.aliyuncs.com/luu_mall?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driverusername: root # 数据库账号password: root0319@ # 数据库密码type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSourcemin-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。# 用户数据源配置users:url: jdbc:mysql://rm-xxxxxx.mysql.rds.aliyuncs.com/luu_user_center?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.jdbc.Driverusername: root # 数据库账号password: root0319@ # 数据库密码type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSource# Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性min-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。# Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性druid: # 设置 Druid 连接池的自定义配置。然后 DruidDataSourceAutoConfigure 会自动化配置 Druid 连接池。filter:stat: # 配置 StatFilter ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatFilterlog-slow-sql: true # 开启慢查询记录slow-sql-millis: 5000 # 慢 SQL 的标准,单位:毫秒merge-sql: true # SQL合并配置stat-view-servlet: # 配置 StatViewServlet ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AEenabled: true # 是否开启 StatViewServletlogin-username: root # 账号login-password: root # 密码mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.luu.druid.druid_demo.entity.*

配置多数据源DynamicDataSourceConfig

@Configuration
public class DynamicDataSourceConfig {/*** 创建 orders 数据源*/@Bean(name = "mallDataSource")@ConfigurationProperties(prefix = "spring.datasource.mall") // 读取 spring.datasource.orders 配置到 HikariDataSource 对象public DataSource ordersDataSource() {return DruidDataSourceBuilder.create().build();}/*** 创建 users 数据源*/@Bean(name = "usersDataSource")@ConfigurationProperties(prefix = "spring.datasource.users")public DataSource usersDataSource() {return DruidDataSourceBuilder.create().build();}@Bean@Primarypublic DynamiDataSource dataSource(DataSource mallDataSource, DataSource usersDataSource) {Map<Object, Object> targetDataSources = new HashMap<>(2);targetDataSources.put(DataSourceFlag.DATA_SOURCE_FLAG_MALL, mallDataSource);targetDataSources.put(DataSourceFlag.DATA_SOURCE_FLAG_USER, usersDataSource);// 还有数据源,在targetDataSources中继续添加System.out.println("DataSources:" + targetDataSources);//默认的数据源是oneDataSourcereturn new DynamiDataSource(mallDataSource, targetDataSources);}}

常量DataSourceFlag装载这我们区分数据源的key

public interface DataSourceFlag {

    public static String DATA_SOURCE_FLAG_MALL = "mall";
    public static String DATA_SOURCE_FLAG_USER = "user";

}

DynamiDataSource用来继承Spring AbstractRoutingDataSource来实现数据源切换,并且设置默认数据源。

public class DynamiDataSource extends AbstractRoutingDataSource {/*** 配置DataSource, defaultTargetDataSource为主数据库*/public DynamiDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {//设置默认数据源super.setDefaultTargetDataSource(defaultTargetDataSource);//设置数据源列表super.setTargetDataSources(targetDataSources);super.afterPropertiesSet();}@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceHolder.getRouteKey();}
}

通过DynamicDataSourceHolder操作ThreadLocal来保存当前线程操作的哪个数据源

/*** 数据源管路由*/
public class DynamicDataSourceHolder {private static ThreadLocal<String> routeKey = new ThreadLocal<String>();/*** 获取当前线程的数据源路由的key*/public static String getRouteKey() {String key = routeKey.get();return key;}/*** 绑定当前线程数据源路由的key* 使用完成后必须调用removeRouteKey()方法删除*/public static void setRouteKey(String key) {routeKey.set(key);}/*** 删除与当前线程绑定的数据源路由的key*/public static void removeRouteKey() {routeKey.remove();}
}

到这里配置基本完成来,那要怎么用呢,如何切换数据源呢,这里我们上面有说到,通过注解,来切换数据源。所以定义一个注解ChangeDataSource,不同的key切换不同的数据源

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ChangeDataSource {String value() default DataSourceFlag.DATA_SOURCE_FLAG_MALL;
}

我们把注解用在mapper方法上

@Mapper
public interface TestMapper {int test();String mallNoAnno();@ChangeDataSource(DataSourceFlag.DATA_SOURCE_FLAG_MALL)String mallExitAnno();@ChangeDataSource(DataSourceFlag.DATA_SOURCE_FLAG_USER)String userNoAnno();@ChangeDataSource(DataSourceFlag.DATA_SOURCE_FLAG_USER)String userExitAnno();}

然后通过切面DataSourceAspect更换ThreadLocal中key实现数据源切换

@Aspect
@Component
public class DataSourceAspect implements Ordered {protected Logger logger = LoggerFactory.getLogger(getClass());/*** 切点: 所有配置 ChangeDataSource 注解的方法*/@Pointcut("@annotation(com.luu.druid.druid_demo.common.ChangeDataSource)")public void dataSourcePointCut() {}@Around("dataSourcePointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();ChangeDataSource ds = method.getAnnotation(ChangeDataSource.class);// 通过判断 @ChangeDataSource注解 中的值来判断当前方法应用哪个数据源DynamicDataSourceHolder.setRouteKey(ds.value());System.out.println("当前数据源: " + ds.value());logger.debug("set datasource is " + ds.value());try {return point.proceed();} finally {DynamicDataSourceHolder.removeRouteKey();logger.debug("clean datasource");}}@Overridepublic int getOrder() {return 1;}
}

单元测试

@SpringBootTest
class DruidDemoApplicationTests {@AutowiredTestMapper testMapper;@Testvoid contextLoads() {int i = testMapper.test();String id = testMapper.mallNoAnno();String id2 = testMapper.mallExitAnno();String name = testMapper.userNoAnno();String name2 = testMapper.userExitAnno();}
}

到这里呢,代码基本写完来。这里就是多数据源的配置,然后还有读写分离怎么实现呢。

而上面我们说到事物上不起效果的,因为事物上要拿到数据源的连接对象,而这里我们在mapper层有更换数据源,所以是不行的,所以数据源无法切换成果,然后执行的时候会报错的。但是如果我们整个是在Service上使用这个注解,整个方法上同一个数据源就可以的。

实现读写分离

其实读写分离的实现通过上面的方式稍微修改下就可以来,就是在切面中,不在通过注解,根据方法名的前缀来判断是走主库,还是走从库。比如find、select这样读数据的就走从库,而insert这样的就走主库。具体的代码的话,摸一摸我发量不多的头,算了,偷一偷就不贴来,反正思路就是这样的。

通过Mybatis 配置不同的 Mapper 使用不同的 SqlSessionTemplate

根据不同操作类(就是mapper),然后创建不同的SqlSessionTemplate ,这样每个SqlSessionTemplate 就可以设置不同的数据源和扫描不同的mapper咯。听起来是不是很简单呢。不用管什么切面不切面的,不像上面那么麻烦咯。但是多数据源的通病还是在滴,那就是多数据源事物用起来不方便啦。

多数据源

还是用刚才的springboot项目吧,改动一下咯。pom文件啥的就不说咯,跟上面一样的。然后我们看下配置文件,数据源还是一样,两个数据源一样的配置,只不过这里没有mybatis的配置咯。

application

server:port: 8080spring:application:name: dynamicdatasource:mall:url: jdbc:mysql://rm-wz9yy0528x91z1iqdco.mysql.rds.aliyuncs.com/luu_mall?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.cj.jdbc.Driverusername: root # 数据库账号password: root0319@ # 数据库密码type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSourcemin-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。# 用户数据源配置users:url: jdbc:mysql://rm-wz9yy0528x91z1iqdco.mysql.rds.aliyuncs.com/luu_user_center?useSSL=false&useUnicode=true&characterEncoding=UTF-8driver-class-name: com.mysql.cj.jdbc.Driverusername: root # 数据库账号password: root0319@ # 数据库密码type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSource# Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性min-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。# Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性druid: # 设置 Druid 连接池的自定义配置。然后 DruidDataSourceAutoConfigure 会自动化配置 Druid 连接池。filter:stat: # 配置 StatFilter ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatFilterlog-slow-sql: true # 开启慢查询记录slow-sql-millis: 5000 # 慢 SQL 的标准,单位:毫秒merge-sql: true # SQL合并配置stat-view-servlet: # 配置 StatViewServlet ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AEenabled: true # 是否开启 StatViewServletlogin-username: root # 账号login-password: root # 密码

DBConstants常量类

public class DBConstants {public static final String TX_MANAGER_MALL = "malltransactionManager";public static final String TX_MANAGER_SER = "usertransactionManager";
}

然后就是创建不同的SqlSessionTemplate和数据源了。

DataSourceMallConfig配置mall的数据源,并且mallSqlSessionTemplate设置了扫面mapper包位置

@Configuration
@MapperScan(basePackages = "com.luu.druid.druid_demo.mapper.mall", sqlSessionTemplateRef = "mallSqlSessionTemplate")
public class DataSourceMallConfig {/*** 创建 mall 数据源*/@Bean(name = "mallDataSource")@ConfigurationProperties(prefix = "spring.datasource.mall")public DataSource mallDataSource() {return DruidDataSourceBuilder.create().build();}/*** 创建 MyBatis SqlSessionFactory*/@Bean(name = "mallSqlSessionFactory")public SqlSessionFactory sqlSessionFactory(DataSource mallDataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();// <2.1> 设置 orders 数据源bean.setDataSource(mallDataSource);// <2.2> 设置 entity 所在包bean.setTypeAliasesPackage("com.luu.druid.druid_demo.entity.*");// <2.3> 设置 config 路径
// bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));// <2.4> 设置 mapper 路径bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/mall/*.xml"));return bean.getObject();}/*** 创建 MyBatis SqlSessionTemplate*/@Bean(name = "mallSqlSessionTemplate")public SqlSessionTemplate sqlSessionTemplate(DataSource mallDataSource) throws Exception {return new SqlSessionTemplate(this.sqlSessionFactory(mallDataSource));}/*** 创建 mall 数据源的 TransactionManager 事务管理器*/@Bean(name = DBConstants.TX_MANAGER_MALL)public PlatformTransactionManager transactionManager(DataSource mallDataSource) {return new DataSourceTransactionManager(mallDataSource);}}

DataSourceUserConfig配置user的数据源,并且userSqlSessionTemplate设置了扫面mapper包位置

@Configuration
@MapperScan(basePackages = "com.luu.druid.druid_demo.mapper.user", sqlSessionTemplateRef = "userSqlSessionTemplate")
public class DataSourceUserConfig {/*** 创建 user 数据源*/@Bean(name = "userDataSource")@ConfigurationProperties(prefix = "spring.datasource.users")public DataSource userDataSource() {return DruidDataSourceBuilder.create().build();}/*** 创建 MyBatis SqlSessionFactory*/@Bean(name = "userSqlSessionFactory")public SqlSessionFactory sqlSessionFactory(DataSource userDataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();// <2.1> 设置 orders 数据源bean.setDataSource(userDataSource);// <2.2> 设置 entity 所在包bean.setTypeAliasesPackage("com.luu.druid.druid_demo.entity.*");// <2.3> 设置 config 路径
// bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));// <2.4> 设置 mapper 路径bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/user/*.xml"));return bean.getObject();}/*** 创建 MyBatis SqlSessionTemplate*/@Bean(name = "userSqlSessionTemplate")public SqlSessionTemplate sqlSessionTemplate(DataSource userDataSource) throws Exception {return new SqlSessionTemplate(this.sqlSessionFactory(userDataSource));}/*** 创建 user 数据源的 TransactionManager 事务管理器*/@Bean(name = DBConstants.TX_MANAGER_SER)public PlatformTransactionManager transactionManager(DataSource userDataSource) {return new DataSourceTransactionManager(userDataSource);}}

到这多数据源配置就差不多了,不同的mapper对应不同的数据源。这里mapper,entity啥的就不贴出来了,秉承着能偷懒就偷懒的一贯风格。直接把单元测试贴一下看下。

@SpringBootTest
class DruidDemoApplicationTests {@AutowiredUserMapper userMapper;@AutowiredTestMapper testMapper;@Testvoid contextLoads() {String id = testMapper.mallNoAnno();String id2 = testMapper.mallExitAnno();String name = userMapper.userNoAnno();String name2 = userMapper.userExitAnno();}
}

当然,上面说到说,依然是多数据源,所以呢对于事物的支持依然是有问题的。

读写分离

这种方式实现读写分离,就不用多说咯吧,我这个专业bug制造者都想的明白,各位大佬也能想明白的。

分库分表中间件,比如Sharding-JDBC 、Mycat等

对于分库分表的中间件,会解析我们编写的 SQL ,路由操作到对应的数据源。那么,它们天然就支持多数据源。如此,我们仅需配置好每个表对应的数据源,中间件就可以透明的实现多数据源或者读写分离。Sharding-JDBC 、Mycat是比较常用的中间件,这里使用的话就不写了,后面会专门写如何去使用它们的,Sharding-JDBC并且支持分布式事物的。

如果需要可以下载代码试试的dynamic-datasource-spring、dynamic-datasource-mybatis,到此完美收工:

https://github.com/servef-toto/luu_yinchuishiting.git​github.com

2 数据源配置_论多数据源(读写分离)的实现方案相关推荐

  1. 润乾报表 多数据源配置报错:数据源无数据库连接,且未设定数据连接工厂

    润乾报表 多数据源配置报错:数据源无数据库连接,且未设定数据连接工厂 具体报错如下图所示: 排查方法: 1.确定连接池配置是对的,包括URL 用户名 密码 2.确定数据库驱动jar放到了Tomcat的 ...

  2. 2 数据源配置_如何在程序运行中动态切换数据源?架构师必读秘笈

    说起动态数据源,大家应该也不陌生.例如在读写分离系统中,则要对请求中的读写操作进行分离,让读和写落在不同的数据库上:例如在多租户系统中,则要根据请求来源租户的不同,让其落在不同租户的数据库上:例如在分 ...

  3. mybatis多数据源配置_随笔:springboot+mybatis 配置双数据源

    山石彦 | 作者 urlify.cn/vQzIne | 来源 最近工作中有用到双数据源,一个项目(中台)中需要操作两个不同的数据库.当时考虑到了两种方式, 1.通过http请求访问(A项目访问d1数据 ...

  4. 2 数据源配置_[Mybatis]-[基础支持层]-数据源信息-数据源详解

    该系列文章针对 Mybatis 3.5.1 版本 在上一篇文章中,谈到了 <environment> 标签解析会构建 Environment 对象,Environment 对象中有两个关键 ...

  5. 2 数据源配置_Spring, MyBatis 多数据源的配置和管理

    作者:digdeep 出处:https://www.cnblogs.com/digdeep/p/4512368.html 热门推荐 vue+websocket+Springboot实现的即时通信开源项 ...

  6. hikari数据源配置类_SpringBoot多数据源配置详解

    开发环境:JDK1.8+SpringBoot2.1.4.RELEASE+Oracle 这里我们假设要使用两个数据源分别为:master和slave. pom.xml 依赖包 org.springfra ...

  7. spring配置主库从库_springboot集成mybatis配置主从复制双库实现读写分离

    一般情况下网站对数据库的读要比写多多了,所以当数据量大了的时候,使用读写分离是很有必要的 spring提供了数据源路由的类,正好拿它来实现一下 创建项目 简单的springboot项目,依赖有myba ...

  8. mysql读写分离和组复制_数据库主从复制,读写分离,负载均衡,分库分表分别表达的什么概念?...

    谢邀,这是个好问题,而且这个问题好在即使概念非常容易理解,但是这几个不同的概念细节太多太多,而且理解了概念,自己要用,又需要做很多的调研评估和开发工作.作为在这个领域爬坑多年的人,我这里就先介绍下概念 ...

  9. Django项目配置mysql主从数据库实现读写分离

    1.在配置文件中添加slave数据库的配置 DATABASES = {     'default': {         'ENGINE': 'django.db.backends.mysql',   ...

最新文章

  1. 进程与线程的一个简单解释
  2. 关于安装nagios make all时出现问题的解决方法
  3. C#中抽象类和接口的区别与使用
  4. Django静态文件配置
  5. Redis-17Redis内存回收策略
  6. Interview:算法岗位面试—10.16下午—上海某公司算法岗位(偏图像算法,国内顶端医疗行业)技术面试之一点技术都没问
  7. layui的表格可以动态添加行吗_答疑分享052:插入表格,数据分析更方便
  8. 翻转单词顺序和左旋转字符串
  9. python 代码片段6
  10. Oracle Golden Gate 系列十三 -- 配置GG进程检查点(checkpoint) 说明
  11. 诺基亚AirScale支持低频段和高频段5G服务 确保运营商投资收入
  12. java生成随机数的两种方式
  13. 武书连2019中国大学排行榜公布:浙大排名超越北大
  14. Java7并发编程指南——第六章:并发集合
  15. 模型师对初学者的经验之谈
  16. java blowfish ecb,node.js – 使用nodejs crypto和php的mcrypt解密blowfish-ecb
  17. 3D数学——Unity中的向量运算
  18. excel二极管伏安特性曲线_【电子知识点】半导体二极管amp;三极管
  19. BZOJ5192[Usaco2018 Feb] New Barns
  20. mysql用alter创建外键_MySQL入门(alter语法 与 外键)

热门文章

  1. android相册幻灯片功能,Android实现幻灯片式图片浏览器
  2. java的add方法的使用_Java HashSet add()方法与示例
  3. excel二极管伏安特性曲线_【刘敏蔷老师】半导体二极管的原理及应用
  4. OpenCV鼠标事件和滑动条事件
  5. Python中Turtle绘图函数-绘制时钟程序
  6. leetcode BFS(python+c++)
  7. ARM 汇编语言入门
  8. 小甲鱼 OllyDbg 教程系列 (十) : Windows 逆向常用 api 以及 XOFTSPY 逆向
  9. CompletableFuture详解~getNow
  10. 计算机不能显示可移动磁盘咋办,U盘插上电脑不显示“可移动磁盘”该怎么办...