使用databaseIdProvider标签实现多数据库支持
当应用需要支持不同数据库产商,并且依赖了不同数据库特有的函数或者语法时,我们通常需要书写2套或以上是SQL来支持不同的场景。
场景
- 获取数据库时间:Oracle使用
sysdate,而MySQL使用now() - 获取列表:Oracle 12c后使用
listagg(),而MySQL使用group_concat() - 其他场景…
一些可能的方案
假设没有<databaseIdProvider/>标签的情况下,我们可以如何实现我们的需求?我们用获取列表的场景来展示:
- 使用不同的
statemetnId区分不用数据库的SQL,缺点是API会十分臃肿。
- AccountMapper.xml
<!-- oracle use listagg -->
<select id="listAccountWithOracle" resultType="hashmap">
select owner, listagg(name, ',') as accounts from account group by owner
</select>
<!-- mysql use group_concat -->
<select id="listAccountWithMySQL" resultType="hashmap">
select owner, group_concat(name) as accounts from account group by owner
</select>
- AccountMapper.java
public interface AccountMapper {
/**
* support oracle listagg function
*
* @return list of owner and its accounts
*/
List<Map<String, Object>> listAccountWithOracle();
/**
* support mysql group_concat function
*
* @return list of owner and its accounts
*/
List<Map<String, Object>> listAccountWithMySQL();
}
- 定义
2套mapper.xml来区分,保持API简洁,缺点是需要手动转换以加载不同的mapper文件,而且通用的语句也需要复制一份到不同的mapper文件中。
- AccountMySQLMapper.xml
<!-- 省略其他的statement -->
<select id="listAccount" resultType="hashmap">
select owner, group_concat(name) as accounts from account group by owner
</select>
- AccountOracleMapper.xml
<!-- 省略其他的statement -->
<select id="listAccount" resultType="hashmap">
select owner, listagg(name, ',') as accounts from account group by owner
</select>
- AccountMapper.java
public interface AccountMapper {
/**
* listAccount方法会根据背后加载mapper.xml不同而使用不同的数据库语法
*
* @return list of owner and its accounts
*/
List<Map<String, Object>> listAccount();
}
- mybatis-config.xml
<configuration>
<!-- 省略其他的配置 -->
<mappers>
<!-- 根据使用的数据库加载对应的mapper文件,这里使用mysql的实现 -->
<mapper resource="databaseid/AccountMySQLMapper.xml"/>
</mappers>
</configuration>
优雅的方案
回顾以上2个方案的缺点
- 不同的statementId:导致接口Mapper的方法大量增加,
接口庞大而臃肿 - 不同的mapper文件:导致需要
维护多份通用的sql,因为每份mapper文件都需要实现所有statement
因此,使用mybatis提供的<databaseIdProvider/>可以解决不同数据库的SQL执行。
问题1:如何告诉mybatis,当前运行SQL是哪个数据库产商语法?
聪明的你肯定会想到,能否给statement添加一个标签/属性来指定其所属于的数据库语法,答案是肯定的。
属性databaseId
mybatis支持给每个statement添加属性databaseId,关于statement支持的属性,可以阅读官方文档有关的这部分描述。
- AccountMapper.xml,给statement加上databaseId属性:
<!-- 添加属性databaseId="oracle"来指明这是oracle的语法 -->
<select id="listAccount" resultType="hashmap" databaseId="oracle">
select owner, listagg(name, ',') as accounts from account group by owner
</select>
<!-- 添加属性databaseId="mysql"来指明这是mysql的语法 -->
<select id="listAccount" resultType="hashmap" databaseId="mysql">
select owner, group_concat(name) as accounts from account group by owner
</select>
问题2:随之产生的疑问:mybatis怎么知道databaseId="oracle"是使用Oracle数据库呢?毕竟这只是一个字符串值。
mybatis是不允许使用相同的statementid的,对于上面提供的<select/>语句,使用的却是相同的id,只是databaseId不一样。难道是mybatis是组合多种属性来区分的?No,答案是否定的。
标签databaseIdProvider
mybatis会在启动的时候解析<databaseIdProvider/>标签,获取支持的数据库产品。
- mybatis-config.xml,增加databaseIdProvider配置
<configuration>
<!-- 省略其他的配置 -->
<databaseIdProvider type="DB_VENDOR">
<property name="Oracle" value="oracle" />
<property name="MySQL" value="mysql" />
<!-- 省略其他数据库产品 -->
</databaseIdProvider>
</configuration>
mybatis启动的时候,会侦测当前使用的数据库,如果是Oracle(name=“Oracle”)的话,对于拥有相同statementid的语句,则会选择执行带有databaseId="oracle"的语句。
问题3:如果细心你的阅读到这里,肯定会发现一个问题,上述name=“Oracle”,其中Oracle也是一个字符串,如果我要支持postgreSQL,这个字符串值我应该如何定?遗憾的是这些字符串值并不是定义在mybatis的源代码中的,请往下看。
DatabaseMetadata
mybatis会在启动的过程中解析标签<dataSource/>和<databaseIdProvider/>;在后续解析mapper文件之前,会通过dataSource获取connection对象,然后通过connection对象获取到DatabaseMetadata对象,这里面有一个很关键的接口方法:
- java.sql.DatabaseMetadata.java
/**
* Retrieves the name of this database product.
*
* @return database product name
* @exception SQLException if a database access error occurs
*/
String getDatabaseProductName() throws SQLException;
该方法会返回当前数据库的产品名字,所以mybatis会用这个名字来匹配在<databaseIdProvider/>标签中配置的名字(name属性),进一步确定哪些statement会生效。大家可以写一段简单的JDBC连接,然后调用该接口方法来确定具体的名字,如下:
// 省略非关键代码
Connection conn = DriverManager.getConnection();
DatabaseMetaData metaData = conn.getMetaData();
String productName = metaData.getDatabaseProductName();
最后
- AccountMapper.java
public interface AccountMapper {
/**
* support mysql group_concat function & oracle listagg function
*
* @return list of owner and its accounts
*/
List<Map<String, Object>> listAccount();
}
- AccountMapper.xml
<!-- oracle use listagg -->
<select id="listAccount" resultType="hashmap" databaseId="oracle">
select owner, listagg(name, ',') as accounts from account group by owner
</select>
<!-- mysql use group_concat -->
<select id="listAccount" resultType="hashmap" databaseId="mysql">
select owner, group_concat(name) as accounts from account group by owner
</select>
- mybatis-config.xml
<configuration>
<!-- 省略其他的配置 -->
<databaseIdProvider type="DB_VENDOR">
<property name="Oracle" value="oracle" />
<property name="MySQL" value="mysql" />
<!-- 省略其他数据库产品 -->
</databaseIdProvider>
</configuration>
欢迎留言讨论,代码的例子可以在这里获取。
MyBatis实现多数据库支持的方案
本文介绍了如何使用MyBatis的databaseIdProvider标签来实现多数据库支持。通过设置statement的databaseId属性和配置databaseIdProvider,可以根据当前数据库类型选择执行相应的SQL,解决了在不同数据库环境下需要编写不同SQL的问题。详细解释了databaseId的工作原理,包括通过DatabaseMetadata获取数据库产品名进行匹配。

被折叠的 条评论
为什么被折叠?



