## 关键字
Java,Springboot,Vscode,支持多种数据库
## 背景环境
我在实际项目开发工程中遇到这样一个问题,用户 A 使用 Oracle 数据库,用户 B 使用 Postgresql 数据库,但是用户 AB 都使用我们的项目。所以项目必须同时支持 Oracle 和 Postgresql。目前,我们项目的开发流程是这样的,Oracle 版本先行开发,然后再把代码手动合并到 Postgresql,测试修改 Postgresql 的 sql 部分。这个过程极大地增加了维护成本。
## 原因分析
那有没有更好的解决方案呢?首先,两个版本部分 sql 是可以共用的。然后能不能省去合并代码的过程?通过一些尝试,通过 Mybatis 指定**databaseId**可以实现我想要的效果。
## 解决过程
### 1. 添加依赖
确保 `pom.xml` 中已引入 MyBatis-Spring-Boot-Starter 依赖:
```xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version> <!-- 使用最新版本 -->
</dependency>
```
### 2. 配置 VendorDatabaseIdProvider Bean
在 Spring Boot 的配置类中注册 `VendorDatabaseIdProvider`:
```java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
@Configuration
public class MyBatisConfig {
@Bean
public DatabaseIdProvider databaseIdProvider() {
VendorDatabaseIdProvider provider = new VendorDatabaseIdProvider();
Properties properties = new Properties();
properties.setProperty("Oracle", "oracle");
properties.setProperty("PostgreSQL", "postgresql");
provider.setProperties(properties);
return provider;
}
// 选配
//@Bean
//public ConfigurationCustomizer configurationCustomizer() {
// return configuration -> configuration.setDatabaseId("default");
//}
}
```
### 3. 配置多数据源
`application.yml` 示例:
```yaml
spring:
datasource:
# oracle 数据库
db1:
jdbc-url: jdbc:oracle:thin:@localhost:1521:ORCL
username: user
password: pass
driver-class-name: oracle.jdbc.OracleDriver
# postgresql 数据库
db2:
jdbc-url: jdbc:postgresql://localhost:5432/mydb
username: user
password: pass
driver-class-name: org.postgresql.Driver
```
### 4. 在 Mapper XML 中使用 databaseId
示例分页查询:
```xml
<!-- Oracle 分页 -->
<select id="selectUsers" resultType="User" databaseId="oracle">
SELECT * FROM (
SELECT tmp.*, ROWNUM row_num FROM (
SELECT * FROM users ORDER BY id
) tmp WHERE ROWNUM <= #{end}
) WHERE row_num >= #{start}
</select>
<!-- PostgreSQL 分页 -->
<select id="selectUsers" resultType="User" databaseId="postgresql">
SELECT * FROM users ORDER BY id LIMIT #{limit} OFFSET #{offset}
</select>
```
### 5. 动态 SQL 适配
使用 `_databaseId` 变量:
```xml
<select id="selectDate" resultType="string">
SELECT
<choose>
<when test="_databaseId == 'oracle'">
TO_CHAR(sysdate, 'YYYY-MM-DD') AS current_date
</when>
<when test="_databaseId == 'postgresql'">
TO_CHAR(NOW(), 'YYYY-MM-DD') AS current_date
</when>
<otherwise>
CURRENT_DATE
</otherwise>
</choose>
FROM dual
</select>
```
### 6. 验证配置
#### 方法 1:日志配置
```yaml
logging:
level:
org.mybatis: DEBUG
```
#### 方法 2:代码获取
```java
@Autowired
private SqlSessionFactory sqlSessionFactory;
public void printDatabaseId() {
String databaseId = sqlSessionFactory.getConfiguration().getDatabaseId();
System.out.println("Current DatabaseId: " + databaseId);
}
```
## 关键点分析
### 显式指定 PostgreSQL 的 databaseId
```java
@Configuration
public class MyBatisConfig {
@Bean
public DatabaseIdProvider databaseIdProvider() {
VendorDatabaseIdProvider provider = new VendorDatabaseIdProvider();
Properties properties = new Properties();
properties.setProperty("PostgreSQL", "postgresql");
properties.setProperty("Oracle", "oracle");
provider.setProperties(properties);
return provider;
}
}
```
### 强制设置默认 databaseId 为 PostgreSQL
```java
@Configuration
public class MyBatisConfig {
// 强制指定默认 databaseId 为 postgresql
@Bean
ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setDatabaseId("postgresql");
}
}
```
**效果说明**:
- 无论实际连接的数据库类型如何,MyBatis 会始终使用 `databaseId="postgresql"`
- XML 中无 databaseId 的 SQL 会被忽略
- 需要确保所有 SQL 都标记了 `databaseId="postgresql"`
### 实际使用
若希望保留多数据库支持,同时让 PostgreSQL 作为默认数据库,最佳实践是**为所有 PostgreSQL 专用的 SQL 显式标记 databaseId="postgresql",并保留无 databaseId 的 SQL 作为“通用后备”**(适用于简单兼容场景)。
```xml
<!-- 通用 SQL(兼容 PostgreSQL 和 Oracle) -->
<select id="getAllUsers" resultType="User">
SELECT * FROM users
</select>
<!-- PostgreSQL 专用 -->
<select id="getUser" resultType="User" databaseId="postgresql">
SELECT * FROM users WHERE id = #{id}
</select>
<!-- Oracle 专用 -->
<select id="getUser" resultType="User" databaseId="oracle">
SELECT * FROM users WHERE ROWNUM = 1
</select>
```
**效果说明**:
- 当连接 PostgreSQL 时:
- 执行 getUser 时使用 databaseId="postgresql" 的 SQL。
- 执行 getAllUsers 时使用无 databaseId 的 SQL。
- 当连接 Oracle 时:
- 执行 getUser 时使用 databaseId="oracle" 的 SQL。
- 执行 getAllUsers 时使用无 databaseId 的 SQL。
## 总结
这个方法我使用两周了,总的来说实现效果和我预想中一样,我在同一个分支做兼容,省去了大量合并代码,测试的工作量,别的不说,这个项目至少节省了20%~30%的工作量。
还有一种情况会经常用到:
```xml
SELECT
<if test="_databaseId == 'oracle'">ROWNUM,</if>
<if test="_databaseId == 'postgresql'">ROW_NUMBER() OVER() as rownum</if>
FROM table1
```