其实刚开始的时候我也不知道是啥意思,一般也不会去用这个功能,不过今天在看源码的时候理解了这个问题,不过这个东西不会讲全部,主要是讲下怎么使用,官网的文档为
刚开始看的时候其实不知道啥意思,直到看到了源码才明白了
首先先解析下这个功能是在干啥,就是MyBatis 可以根据不同的数据库厂商执行不同的语句,这是官网原话,看起来可能有点懵,举个例子你就知道了
以下是xml里面关于数据库厂商的配置
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>其中name是数据库的名字,而值是一个标识来的,而数据库名字是每个厂商自己返回的,获取代码在
org.apache.ibatis.mapping.VendorDatabaseIdProvider#getDatabaseProductName
看到代码你就明白了
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
}
}
}
}其实就是jdbc的一些规范进行获取的
而value是用来干啥的呢,就是用来配置在你mapper.xml里面的标识
看个例子
<select id="selectList" resultType="com.zxc.study.test.bean.User" databaseId="mysql">
select * from user
</select>上面这个databaseId就代表这个sql语句只有当你当前的数据库返回的是MySQL的时候才能使用到,否则你是拿不到这个语句的,这么说应该还好理解吧,这个功能就等于mybatis进行了数据隔离,但是我们一般不用这个功能,所以也就比较少使用了
<select id="selectUser" resultType="com.zxc.study.test.bean.User" flushCache="true">
select * from user where id = #{param1}
</select>对于这种没有指定databaseId的语句就是每个数据源都是可以获取到的
这篇就是随便讲讲databaseIdProvider的使用,估计业务中也不这么去用,不过mybatis确实提供了这个支持
源码
源码的话就稍微看下,不过这里只是一部分,要看全部的还要去其他地方看,这里不细说了
org.apache.ibatis.builder.xml.XMLConfigBuilder#databaseIdProviderElement
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)) {
type = "DB_VENDOR";
}
//获取设置的熟悉以及设置给DatabaseIdProvider
Properties properties = context.getChildrenAsProperties();
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
//环境不为空和数据库厂商不为空时会进入这个判断
if (environment != null && databaseIdProvider != null) {
//通过数据源获取对应的数据标识,就是上面说的意思
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
//标记当前应用使用的数据库厂商标识
configuration.setDatabaseId(databaseId);
}
}
再稍微看下是如何获取数据库厂商id的,估计你点开就明白了
org.apache.ibatis.mapping.VendorDatabaseIdProvider#getDatabaseId
@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
}
}
}
}
至于他是怎么把数据标识id存起来的对应关系,这里就不说了,大概就是解析的时候把它做了一个标识吧,有兴趣的可以参考一下
怎么设置的
这里还是稍微说下Mybatis是怎么设置这个关系的,但是也只是简单说下,其实也并不难
还是复习下原来的方法
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//解析二级缓存相关的
cacheRefElement(context.evalNode("cache-ref"));
//解析缓存
cacheElement(context.evalNode("cache"));
//解析parameterMap,现在已经基本不用了
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql片段,应该是要放到某个map中,其他地方引用的时候再从map拿出来
sqlElement(context.evalNodes("/mapper/sql"));
//核心的sql语句解析逻辑,增删改查
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
最核心的方法为
org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)
private void buildStatementFromContext(List<XNode> list) {
//如果数据库标识不为空,则设置一下,这个标识是在前面设置的
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
那么最终就是看 statementParser.parseStatementNode()方法是怎么处理的
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
//判断是否与厂商一致
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
}
后面的去掉了,因为之前已经说过了,最重要是看看下面这个方法
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
//数据库厂商设置了
if (requiredDatabaseId != null) {
//核心在这里,但是mapper里面配置的跟当前的数据库厂商不一致,直接返回,也就是不把当前
//的mapper加到配置中,那么你在获取的时候自然就找不到了
if (!requiredDatabaseId.equals(databaseId)) {
return false;
}
} else {
//如果本地环境没有设置数据库厂商,但是mapper里面设置了,那也是不添加到配置文件中的,因为根本就不知道属于哪个数据库厂商
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;
}
这样应该就比较清晰了