mybatis 多数据库支持

MyBatis实现多数据库支持的方案
本文介绍了如何使用MyBatis的databaseIdProvider标签来实现多数据库支持。通过设置statement的databaseId属性和配置databaseIdProvider,可以根据当前数据库类型选择执行相应的SQL,解决了在不同数据库环境下需要编写不同SQL的问题。详细解释了databaseId的工作原理,包括通过DatabaseMetadata获取数据库产品名进行匹配。

使用databaseIdProvider标签实现多数据库支持

当应用需要支持不同数据库产商,并且依赖了不同数据库特有的函数或者语法时,我们通常需要书写2套或以上是SQL来支持不同的场景。

场景

  • 获取数据库时间:Oracle使用sysdate,而MySQL使用now()
  • 获取列表:Oracle 12c后使用listagg(),而MySQL使用group_concat()
  • 其他场景…

一些可能的方案

假设没有<databaseIdProvider/>标签的情况下,我们可以如何实现我们的需求?我们用获取列表的场景来展示:

  1. 使用不同的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();
}
  1. 定义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>

欢迎留言讨论,代码的例子可以在这里获取。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值