借助Mybatis提供的 databaseId特性,我们可以实现让应用同时支持多种类型的数据库。
相关的配置和测试用例如下 (这里我们以源生的mybatis为例,与SpringBoot的集成留待读者自行解读):
mybatis-config.xml
配置文件<environments default="development"> <environment id="development"> .... </environment> </environments> <!-- 启用databaseId支持 --> <databaseIdProvider type="DB_VENDOR"> <property name="Oracle" value="oracle"/> </databaseIdProvider> <mappers> ...... </mappers>
// Mapper public interface MultiDbTestMapper { int getMtHisTableExists(@Param("dbName") String dbName, @Param("tableName") String tableName); }
// mapper xml <mapper namespace="xxx.mapper.MultiDbTestMapper"> <!-- 查看某表是否存在 --> <select id="getMtHisTableExists" resultType="java.lang.Integer"> select count(*) ICOUNT from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA=#{dbName} and TABLE_NAME=upper(#{tableName}) </select> <!-- oracle方言查看某表是否存在 --> <select id="getMtHisTableExists" resultType="java.lang.Integer" databaseId="oracle"> SELECT COUNT(*) FROM USER_TABLES WHERE TABLE_NAME=upper(#{tableName}) </select> <!-- sqlserver方言查看某表是否存在 --> <select id="getMtHisTableExists" resultType="java.lang.Integer" databaseId="sqlserver"> select count(*) from dbo.sysobjects where id = object_id(upper(#{tableName})) and OBJECTPROPERTY(id, N'IsUserTable') = 1 </select> </mapper>
@Autowired private MultiDbTestMapper mapper; @Test public void testName() throws Exception { Console.log(mapper.getMtHisTableExists("xxx_boot", "xxx_code")); }
<select>/<insert>/<update>/<delete>
)中抓取相对应的进行执行。先说结论:
源码解读:
与当前环境下所使用的DataBase相适应的databaseId的获取。
//================= XMLConfigBuilder.databaseIdProviderElement() , 对应于上述测试用例中mybatis-config.xml的配置 // 1. 以下方法将为 Configuration 中的 databaseId 字段赋值。 // 2. 赋值逻辑大致是通过用户注册的自定义DatabaseIdProvider实现类来完成 // 3. DatabaseIdProvider默认实现为 VendorDatabaseIdProvider . 其中databaseId的获取逻辑为:根据实际的DataSource类型来获取配置的对应databaseId private void databaseIdProviderElement(XNode context) throws Exception { DatabaseIdProvider databaseIdProvider = null; if (context != null) { String type = context.getStringAttribute("type"); // awful patch to keep backward compatibility if ("VENDOR".equals(type)) { // 在 Configuration 的构造函数中注册 type = "DB_VENDOR"; } Properties properties = context.getChildrenAsProperties(); databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance(); databaseIdProvider.setProperties(properties); } Environment environment = configuration.getEnvironment(); if (environment != null && databaseIdProvider != null) { // 从实际使用的数据源中获取当前适用的databaseId String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource()); configuration.setDatabaseId(databaseId); } }
databaseId生效。
//================= XMLStatementBuilder.parseStatementNode() public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); // 如果当前 <select>/<update>/<insert>/<delete>节点上的 databaseId 属性值 与 全局配置上的 databaseId 不一致, 则直接跳过对其的解析, 不会将其加载到内存中 if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } String nodeName = context.getNode().getNodeName(); ...... } private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) { if (requiredDatabaseId != null) { return requiredDatabaseId.equals(databaseId); } if (databaseId != null) { return false; } id = builderAssistant.applyCurrentNamespace(id, false); if (!this.configuration.hasStatement(id, false)) { return true; } // skip this statement if there is a previous one with a not null databaseId MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2 return previous.getDatabaseId() == null; }
databaseId的另外一种使用方式: @Select
注解。
// 注解的方式实现SQL, 效果等同于上述测试用例中的映射文件 // 底层支撑类: MapperAnnotationBuilder.AnnotationWrapper @Select(value = "select count(*) ICOUNT from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA=#{dbName} and TABLE_NAME=upper(#{tableName})", databaseId = "mysql") @Select(value = "SELECT COUNT(*) FROM USER_TABLES WHERE TABLE_NAME=upper(#{tableName})", databaseId = "oracle") int getMtHisTableExistsByAnnotation(@Param("dbName") String dbName, @Param("tableName") String tableName);
通过占位符,在映射语句中获取当前的databaseId。(支撑类为DynamicContext
)
<select id="getMtHisTableExists" resultType="java.lang.Integer" databaseId="oracle"> SELECT '${_databaseId}' as databaseId FROM USER_TABLES WHERE TABLE_NAME=upper(#{tableName}) </select>