基于mybatisPlus的多库支持实践

开发中, 存在一种这样的业务场景,就是我们的应用要支持多库,同一个应用,我a项目用mysql,b项目用oracle

收获

充分利用sql的特性,和代码的特性,以及框架本身的特性,用最小的代价,来完成应用对多库的支持

多库支持原则

  1. 最小代价原则,充分利用sql的特性,充分利用代码的特性,利用框架的特性

分析

  1. 在使用mybatis plus过程中,有两种dao风格,一种是dsl风格,使用wrapper类,一种是需要join的sql,一般使用xml里面配置
  2. 从sql的角度入手,无论是oracle还是mysql,虽然差异也是有的,但是sql本身也是有规范的,所以,大部分简单sql本身就是支持oracle和mysql的,使用了函数的sql,或者groupby的sql一般是不能支持多db的,只需要关注差异化的sql即可
  3. 分页,分页每个数据库都不一样,mysql和postgresql完全一样,但是oracle比较古老,分页的sql也相对复杂,所以,分页各不一样

方案

差异语句,使用wrapper

一般wrapper使用了last,apply等函数的,一定是需要差异化处理的,这个时候,可以,自己配置springbootde的参数,根据参数来确定使用那种语句,而且多个
地方使用同一个语句的话,还可以抽象成一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private String substrNodeFlag() {
String subStr;
DbType dbType = plbPaginationProperties.getPagination();
switch (dbType) {
case ORACLE:
case DB2: {
subStr = "node_Flag = substr(node_Flag,0," + (Node.PUSH.getIndex() * 2) + ")||"
+ NodeStatus.Push.SUCCESS.code() + "||substr(node_Flag," + (Node.PUSH.getIndex() * 2 + 2) + ")";
break;
}
case MYSQL: {
subStr = "node_Flag = substring(node_Flag,0," + (Node.PUSH.getIndex() * 2) + ")||"
+ NodeStatus.Push.SUCCESS.code() + "||substring(node_Flag," + (Node.PUSH.getIndex() * 2 + 2) + ")";
break;
}
default: {
throw new IllegalArgumentException(String.format("unSupport dbType [%s]", dbType));
}
}
return subStr;
}

差异化sql,分开成不同的xml

因为mybatis允许一个dao文件,对应多个xml,如果一个xml文件里面的sql需要差异化处理,可以非差异化的sql放到一个文件里面,然后差异化的sql,放到各自的文件夹里面,
最后通过配置来区分

  1. oracle和mysql都可以用的文件放到这里
  2. mysql专用的sql
  3. oracle专用的sql

配置

1
2
3
4
5
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
mapperLocations: classpath*:mappers/*.xml,classpath*:oraclemappers/*.xml #这里是使用的oracle环境

分页插件

springboot里面配置一个参数,然后通过不同的参数,构建不同数据库的分页插件即可

1
2
3
4
5
6
7
8
9
10
11
@Autowired
private PaginationProperties paginationProperties; //配置

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(CustomDataPermissionInterceptor customDataPermissionInterceptor) {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(customDataPermissionInterceptor);
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(paginationProperties.getPagination())); //构建
return interceptor;
}

id自增问题

因为mysql很容易实现id自增,然后oracle id自增比较麻烦,所以,使用oracle的时候,可以自定义一个id序列,如使用雪花算法

1
2
3
4
5
@Bean
@ConditionalOnProperty(prefix = "ladon", matchIfMissing = true, name = "pagination", havingValue = "ORACLE")
public IdentifierGenerator customIdGenerator() {
return new CustomIdGenerator();
}

小技巧

  1. 如果查看项目里面那些地方有差异化sql,直接使用全文检索即可