前言

我们知道在mapper下可以使用 Mybatis 的内置变量 databaseid 来动态组装sql,但是在多数据源情况下,系统中会有多个 DataSource ,使用 @DS("master") 注解动态切换数据源,调用的后续的查询都是切换后的数据源中的数据,在代码中能不能友好的判断当前数据源是 MySQL 还是 Oracle 类型?有,还真有,直接上结果:DSUtils.getDbType() == DbType.MYSQL

最终工具类

该工具类避免了注入 DataSource 去查询 databaseProductName 导致的事务中断。

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.mybatisplus.annotation.DbType;
import com.xxx.common.util.SpringContextUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;

/**
 * @description DatabaseUtils 为了跟@DS对齐,所以命名 DSUtils
 * 该方法需要 dynamic-datasource-spring-boot-starter 插件
 * @className DSUtils
 * @author monkeyhi
 * @date 2025-01-08 15:40
 * @version v1.0
 */
public class DSUtils {

    private DSUtils()  {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 获取当前的数据库类型
     * @return 不支持的,返回 OTHER
     */
    public static DbType getDbType() {
        DynamicRoutingDataSource routingDataSource = SpringContextUtils.getBean(DynamicRoutingDataSource.class);
        DataSource dataSource = routingDataSource.determineDataSource();
        try (Connection connection = dataSource.getConnection()) {
            DatabaseMetaData metaData = connection.getMetaData();
            String databaseProductName = metaData.getDatabaseProductName();
            return DbType.getDbType(databaseProductName);
        } catch (SQLException e) {
            return DbType.OTHER;
        }
    }
}

抛砖引玉

获取数据源类型这件事,一般有如下两种情况:

  1. 获取配置的类似 databaseId 这样固定的标签判断
    通常使用 dynamic-datasource 等框架,使用 @DS("mysql") 等注解动态切换数据源时,SqlSessionFactorydatabaseId 是固定的,无法动态反映当前数据源的数据库类型,通常有团队使用固定的标识来标明使用的数据库,如 @DS("mysql") 中的 “mysql”,这怎么说呢,需要团队和后续其他交互团队的协商或者默契。
  2. 使用 DataSource 获取数据库元数据判断
    另外一种情况在动态数据源切换的场景下,可以通过 DataSource 获取当前连接的数据库元数据(DatabaseMetaData),从而动态识别数据库类型
@Autowired
private DataSource dataSource;

try (Connection connection = dataSource.getConnection()) {
    DatabaseMetaData metaData = connection.getMetaData();
    String databaseProductName = metaData.getDatabaseProductName();
    return resolveDatabaseId(databaseProductName);
} catch (SQLException e) {
    throw new RuntimeException("Failed to get database metadata", e);
}

但是如上这种依靠 @Autowired 情况不适用于动态切换数据源的方法逻辑片段,在使用事务的时候,注入的 dataSource 可能和当前方法执行的数据源不一致(请理解一下这句话),数据源不一致会中断当前的事务,这坑可能在编译期根本发现不了。

总结

如果你使用的是 dynamic-datasource 等动态数据源框架,可以通过框架提供的工具类或上下文获取当前数据源的数据库类型。
假设你使用的是 dynamic-datasource-spring-boot-starter,可以通过 DataSourceContextHolder 获取当前数据源的 key,然后根据 key 映射到数据库类型。
最优雅的方式当然是不依赖key的定义和在不破坏逻辑的情况下获取到同一个 DataSource 去获取数据库的元数据去判断的方式,就是上面工具类中的 DynamicRoutingDataSource 获取 DatabaseMetaData 的形式。

最后,祝大家玩的愉快~~~

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐