有什么作用
databaseIdProvider和databaseId的作用简单来说就是让一个项目支持不同的数据库。
让一个项目支持不同的数据库在企业开发中是一个比较常见的需求。由于不同的数据库支持的sql语法稍有差别,所以某些功能需要根据数据库的不同书写不同的sql语句。对于这种需求,首先能够想到的解决方案就是针对不同的数据库维护不同的mapper.xml文件,但是这种方案会严重增加开发和维护的成本。因为不同数据库支持的语法大部分都是相同的,不同的毕竟是少数,我们希望只重写不同的部分而重用相同的部分。
针对这种情况,MyBatis提供了解决方案,即databaseIdProvider和databaseId。通过MyBatis提供的这种功能,我们就只需要维护一套mapper.xml文件便可。
下面先讲解如何配置,然后在从源码层面对这个功能进行解读,最后探讨一下如何通过自定义来定制这个功能。
如何配置
配置databaseIdProvider(在mybatis的配置文件中配置,比如mybatis-config.xml)
代码段一:
<databaseIdProvider type="DB_VENDOR">
<property name="DB2" value="db2" />
<property name="Oracle" value="oracle" />
<property name="Adaptive Server Enterprise" value="sybase" />
<property name="MySQL" value="mysql" />
</databaseIdProvider>
上述配置用于决定当前databaseId的名称。在每一个property标签中,name代表数据库的productName(DatabaseMetaData#getDatabaseProductName()),value是用户自定义的databaseId名称。mybatis在初始化的时候会根据所使用的数据源得到当前databaseId的名称,得到的databaseId的名称供mybatis选择映射文件中相应的语句。比如我们使用的是Mysql数据库,则得到的databaseId名称为“mysql”。
配置databaseId(在映射文件中配置,比如:BlogMapper.xml)
代码段二:
<mapper namespace="info.songjie365.mybatis.sample.mapper.BlogMapper">
<select id="selectBlog" resultType="info.songjie365.mybatis.sample.domain.Blog" databaseId="mysql">
select * from blog where id = #{id}
</select>
</mapper>
官方文档对databaseId的解释为:“如果配置了 databaseIdProvider,MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略”。也就是说如果在mybatis的配置文件中没有配置 databaseIdProvider,则在映射文件中配置的databaseId不会生效。
由于我们使用的是Mysql数据库,得到的databaseId为“mysql”,所以上述的映射片段会在生效。
源码解读
上面我们讲解了如何配置,下面我们从源码的角度来剖析一下mybatis是如何实现这个功能的。
databaseIdprovider的默认实现是VendorDatabaseIdProvider,其接口为DatabaseIdProvider。先看DatabaseIdProvider的定义:
代码片段三:
public interface DatabaseIdProvider {
void setProperties(Properties p);
String getDatabaseId(DataSource dataSource) throws SQLException;
}
接口很简单,就两个方法。
void setProperties(Properties p);:该方法会把databaseIdprovider中配置的属性(见代码片段一)通过这个方法传递进来。
String getDatabaseId(DataSource dataSource):该方法会将返回一个databaseId。
再看一下默认实现VendorDatabaseIdProvider的源码:
代码片段四:
public class VendorDatabaseIdProvider implements DatabaseIdProvider {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
private Properties properties;
@Override
public String getDatabaseId(DataSource dataSource) {
if (dataSource == null) {
throw new NullPointerException("dataSource cannot be null");
}
try {
return getDatabaseName(dataSource);
} catch (Exception e) {
log.error("Could not get a databaseId from dataSource", e);
}
return null;
}
@Override
public void setProperties(Properties p) {
this.properties = p;
}
private String getDatabaseName(DataSource dataSource) throws SQLException {
String productName = getDatabaseProductName(dataSource);
if (this.properties != null) {
for (Map.Entry<Object, Object> property : properties.entrySet()) {
if (productName.contains((String) property.getKey())) {
return (String) property.getValue();
}
}
// no match, return null
return null;
}
return productName;
}
private String getDatabaseProductName(DataSource dataSource) throws SQLException {
Connection con = null;
try {
con = dataSource.getConnection();
DatabaseMetaData metaData = con.getMetaData();
return metaData.getDatabaseProductName();
} finally {
if (con != null) {
try {
con.close();
} catch (SQLException e) {
// ignored
}
}
}
}
}
默认实现就是根据数据库的productName去匹配在mybatis配置文件中配置的属性,然后选出databaseId。
至此,我们分析完了databaseIdprovider的源码,下面接着看一下databaseId是如何发挥作用的。
mybatis在解析映射文件的时候,首先会根据mybatis的配置文件解析出当前数据库对应的databaseId,然后根据mybatis的映射文件判断配置了databaseId属性的语句是否和接卸出的databaseId匹配,核心代码如下:
代码片段五:
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
if (requiredDatabaseId != null) {
if (!requiredDatabaseId.equals(databaseId)) {
return false;
}
} else {
if (databaseId != null) {
return false;
}
// skip this statement if there is a previous one with a not null databaseId
id = builderAssistant.applyCurrentNamespace(id, false);
if (this.configuration.hasStatement(id, false)) {
MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2
if (previous.getDatabaseId() != null) {
return false;
}
}
}
return true;
}
自定义
这里的自定义只是针对databaseIdProvider的自定义,对映射文件中的databaseId不能自定义也没自定义的必要。
通过上述的分析,可以看出,如果要自定义databaseIdProvider只要实现接口DatabaseIdProvider便可。然后在mybatis的配置文件中将databaseIdProvider的type置为自定义的实现便可。
自定义的databaseIdProvider为:
代码片段六:
public class MyDatavbaseIdProvider implements DatabaseIdProvider {
Properties props = null;
@Override
public void setProperties(Properties p) {
//p代表mybatis中针对databaseIdProvider配置的属性
props = p;
}
@Override
public String getDatabaseId(DataSource dataSource) throws SQLException {
//根据使用的数据源,返回不同的databaseId。这里没有给出具体实现
return null;
}
}
使用自定义的databaseIdProvider,则mybatis配置文件中的配置需要做相应的调整。
代码片段七:
<databaseIdProvider type="info.songjie365.mybatis.sample.custom.MyDatavbaseIdProvider">
<property name="DB2" value="db2" />
<property name="Oracle" value="oracle" />
<property name="Adaptive Server Enterprise" value="sybase" />
<property name="MySQL" value="mysql" />
</databaseIdProvider>