一、技术选型
springboot2.4+、mybatisplus3.4+、mysql5.7+、redis3.0+
二、编写目的
如果产品开发默认数据库采用mysql,但是当客户提出数据库需要采用Sqlserver或者Oracle以及其他国产数据库时,程序中就不能出现方言性的sql语句,否则程序在个性化的sql上做不到数据库兼容,因此要想产品能兼容多种数据类型,则在编码时就要考虑将个性化的方言sql抽离出来。
三、数据库兼容方案
实现思路:
(1)应用启动时要根据数据源知道当前链接的数据库类型,并存到全局变量
(2)根据数据库类型自动设置mybatis-plus分页方言
(3)个性化方言实现不要在主业务中耦合(如:if mysql … else if sqlserver…)
(4)个性化方言实现可以多种数据库类型复用(如mysql、mariadb可以用一种实现)
(5)个性化方言实现类能根据数据库类型自动注入
代码实现:
根据url获取数据库类型
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
/**
* 数据库类型
*/
public class Dialect {
private static DbType dbType = null;
private static final String DB_URL = "spring.datasource.url";
/**
* 获取数据库类型
* @return
*/
public synchronized static DbType getDbType(){
if(dbType == null){
String url = PropUtil.getValue(DB_URL);
dbType = JdbcUtils.getDbType(url);
}
return dbType;
}
}
自定义注解(实现方言实现的选择注入)
import com.baomidou.mybatisplus.annotation.DbType;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
/**
* Configuration annotation for a conditional element that depends on the dbType.
*
* @author luoxiaolin5
* @date 14:34 2023/4/12
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(DbTypeCondition.class)
public @interface ConditionalOnDbType {
/**
* condition dbType or {@code false} if it fails.
* @return the DbType
*/
DbType[] types() default DbType.OTHER;
}
注解条件实现
import com.baomidou.mybatisplus.annotation.DbType;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* A Condition that match current dbType.
* @author luoxiaolin5
* @date 12:56 2023/4/12
*/
public class DbTypeCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
DbType[] dbTypes = (DbType[]) metadata.getAnnotationAttributes(ConditionalOnDbType.class.getName()).get("types");
for(DbType dbType : dbTypes){
if(Dialect.getDbType().equals(dbType)){
return true;
}
}
return false;
}
}
1、mybatisplus配置
分页方言配置
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(Dialect.getDbType()));
return interceptor;
}
}
2、数据库YAML配置
数据源配置上注意驱动和URL的替换。
mysql配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://ip:3306/db?serverTimezone=Asia/Shanghai&useUnicode=true
username: username
password: password
hikari:
## 最小空闲连接数
minimum-idle: 5
## 最大连接数
maximum-pool-size: 20
## 自动提交
auto-commit: true
## 连接池名称
pool-name: TmcHikariCP
## 超时时间(ms)
connection-timeout: 30000
sqlserver配置
spring:
datasource:
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://ip:1433;databasename=db_schema;trustServerCertificate=true;integratedSecurity=false;
username: username
password: password
hikari:
## 最小空闲连接数
minimum-idle: 5
## 最大连接数
maximum-pool-size: 20
## 自动提交
auto-commit: true
## 连接池名称
pool-name: TmcHikariCP
## 超时时间(ms)
connection-timeout: 30000
3、应用举例
方言抽离接口
/**
* 数据库兼容处理
* @author luoxiaolin5
* @date 12:39 2023/4/12
*/
public interface DialectService<T> {
/**
* json条件拼接查询
* @param queryWrapper
*/
void conditon(QueryWrapper<T> queryWrapper, Map<String, Clazz> attrMap,Object k,Object v);
}
Mysql方言实现
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* mysql 条件拼接实现
* @author luoxiaolin5
* @date 12:43 2023/4/12
*/
@Slf4j
@Service
@ConditionalOnDbType(types = {DbType.MYSQL,DbType.MARIADB})
public class MysqlFormDataDialectServiceImpl implements FormDataDialectService<FormDataEntity> {
@Override
public void conditon(QueryWrapper<FormDataEntity> queryWrapper, Map<String, Clazz> attrMap, Object k, Object v) {
try {
//替换时间类型判断(时间查询为时间段)
Clazz.Format format = attrMap.get(k).getFormat();
if(Clazz.Format.DateTime.equals(format) || Clazz.Format.Date.equals(format)){
String[] dateArray = ObjectMapperHelper.mapper().readValue(v.toString(), String[].class);
queryWrapper.ge("attr_json->'$." + k + "'", dateArray[0]);
queryWrapper.lt("attr_json->'$." + k + "'", dateArray[1]);
}else {
queryWrapper.eq("attr_json->'$." + k + "'", v);
}
}catch (Exception e){
log.error("form data query k:{},v:{} error:{}",k,v,e.getMessage());
}
}
}
Sqlserver方言实现
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* sql server 条件拼接实现
* @author luoxiaolin5
* @date 12:43 2023/4/12
*/
@Slf4j
@Service
@ConditionalOnDbType(types = {DbType.SQL_SERVER2005,DbType.SQL_SERVER})
public class SqlServerFormDataDialectServiceImpl implements FormDataDialectService<FormDataEntity> {
@Override
public void conditon(QueryWrapper<FormDataEntity> queryWrapper, Map<String, Clazz> attrMap, Object k, Object v) {
try {
//替换时间类型判断(时间查询为时间段)
Clazz.Format format = attrMap.get(k).getFormat();
if(Clazz.Format.DateTime.equals(format) || Clazz.Format.Date.equals(format)){
String[] dateArray = ObjectMapperHelper.mapper().readValue(v.toString(), String[].class);
queryWrapper.ge("JSON_VALUE(attr_json, '$." + k + "')", dateArray[0]);
queryWrapper.lt("JSON_VALUE(attr_json, '$." + k + "')", dateArray[1]);
}else {
queryWrapper.eq("JSON_VALUE(attr_json, '$." + k + "')", v);
}
}catch (Exception e){
log.error("form data query k:{},v:{} error:{}",k,v,e.getMessage());
}
}
}
4、数据库脚本转换
最后注意数据库脚本从mysql到sqlserver的转换,可以用微软的工具Microsoft SQL Server Migration Assistant for MySQL,
可参照官网实现。
下载链接:https://www.microsoft.com/en-us/download/details.aspx?id=54257&6B49FDFB-8E5B-4B07-BC31-15695C5A2143=1