文章目录
- 一、多数据源的典型使用场景
- 1 业务复杂(数据量大)
- 2 读写分离
- 二、如何实现多数据源
- 通过AbstractRoutingDataSource动态指定数据源
- 多数据源切换方式
- AOP
- MyBatis插件
- 三、spring集成多个Mybatis框架 实现多数据源控制
- 四、dynamic-datasource 多数据源组件
一、多数据源的典型使用场景
实际开发中,进场可能遇到在一个引用中可能需要访问多个数据库的情况,以下是两种典型场景:
1 业务复杂(数据量大)
数据分布在不同的数据库汇总,数据库拆了,应用没拆。一个公司多个子项目,各用各的数据库,涉及数据共享。。。。
2 读写分离
为了解决数据库的读性能瓶颈(读比写性能更高,写锁会影响读阻塞,从而影响读的性能)。
很多数据拥主从架构。也就是,一台主数据库服务器,对外提供增删改查的生产服务器;另一台从数据库服务器,主要进行读的操作。
可以通过中间件(ShardingSphere、mycat、mysql-proxy 、TDDL 。。。。) 但是有一些规模较小的公司,没有专门的中间件团队搭建读写分离基础设施,因此需要业务开发人员自行实现读写分离。
这里的框架与上图类似。 不同的是,在读写分离中,主库和从库的数据库是一致的(不考虑主从延迟)。数据更新操作(insert 、update、delete)都是在主库上进行的,主库将数据更新信息同步给
从库。在查询时,可以在从库上进行。从而分担主库的压力。
二、如何实现多数据源
对于大多数的java应用,都是用了spring框架,spring-jdbc模块提供AbstractRoutingDataSource,其内部可以包含了多个DataSource, 然后在 运行时来动态的访问哪个数据库。这种方式访问数据的框架图如下所示:
应用直接操作的 AbstractRoutingDataSource的实现类,告诉AbstractRoutingDataSource访问哪个数据库,然后由AbstractRoutingDataSource从事先配置好的数据源(db1,db2) 选择一个,来访问对应的数据库。
通过AbstractRoutingDataSource动态指定数据源
多数据源切换方式
应用直接操作的是abstractRoutingDataSource的实现类,告诉AbstractRoutingDataSource访问哪个数据库,然后由AbstractRoutingDataSource从事先配置好的数据源 (ds1,ds2)选择哪一个 ,来访问对应的数据库。
AOP
@Component
@Aspect
public class DynamicDataSourceAspect {
//前置
@Before(value ="within(com.example.dynamic.datasource.service.impl.*) && @annotation(wr)")
public void before( WR wr){
String value = wr.value();
RoutingDataSourceContext order = new RoutingDataSourceContext(value);
System.out.println("数据源===="+value);
}
}
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface WR {
String value() default "query";
}
@RestController
@RequestMapping("frend")
@Slf4j
public class FrendController {
@WR
@GetMapping("query")
public List<menu> query(){
List<menu> list = menuService.list();
return list;
}
}
MyBatis插件
- 读写分离的数据源:如果是Mybaits 可以结合插件实现读写分离动态切换数据源
package com.example.dynamic.datasource.plugin;
import com.alibaba.druid.sql.ast.statement.SQLCommentStatement;
import com.example.dynamic.datasource.config.DynamicDataSource;
import com.example.dynamic.datasource.config.RoutingDataSourceContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Properties;
/**
* @ClassName:DynamicDataSourcePlugin
* @Description:
* @Author:
* @Date:8/20/23 10:21 下午
* @Versiion:1.0
*/
//1.如下配置
//2. 将文件加入到mybatis中去
//Intercepts 声明mybatis 固定的写法
//Signature 代表为mybatis 底层的哪个对象,去进行插件代理
@Intercepts({@Signature(type = Executor.class,method = "update",args = {MappedStatement.class,Object.class}),
@Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class,
ResultHandler.class})})
public class DynamicDataSourcePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//拿到当前方法【update、query】的所有参数
Object[] objects =invocation.getArgs();
//MappedStatement 封装sql
MappedStatement ms = (MappedStatement) objects[0];
//
if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)){
//读方法
RoutingDataSourceContext order = new RoutingDataSourceContext("query");
}else{
//写方法
RoutingDataSourceContext order = new RoutingDataSourceContext("update");
}
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) {
}
}