sql节点用来定义一些可重用的 SQL 语句片段,比如表名,或表的列名等。在映射文件中,我们可以通过include节点引用sql节点定义的内容。
1 2 3 4 5 6 7 8 9 10 <sql id="table"> article </sql> <select id="findOne" resultType="Article"> SELECT id, title FROM <include refid="table"/> WHERE id = #{id} </select> <update id="update" parameterType="Article"> UPDATE <include refid="table"/> SET title = #{title} WHERE id = #{id} </update>
上面的配置比较常规,除了静态文本,sql节点还支持属性占位符${}
1 2 3 <sql id="table"> ${table_prefix}_article </sql>
sql 节点的解析过程 1 2 3 4 5 6 7 8 private void sqlElement(List<XNode> list) throws Exception { if (configuration.getDatabaseId() != null) { // 调用 sqlElement 解析 <sql> 节点 sqlElement(list, configuration.getDatabaseId()); } // 再次调用 sqlElement,不同的是,这次调用,该方法的第二个参数为 null sqlElement(list, null); }
如果 Configuration的databaseId不为空,sqlElement方法会被调用了两次。第一次传入具体的 databaseId,用于解析带有databaseId属性,且属性值与此相等的sql节点。第二次传入的 databaseId 为空,用于解析未配置 databaseId 属性的sql节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception { for (XNode context : list) { // 获取 id 和 databaseId 属性 String databaseId = context.getStringAttribute("databaseId"); String id = context.getStringAttribute("id"); // id = currentNamespace + "." + id id = builderAssistant.applyCurrentNamespace(id, false); // 检测当前 databaseId 和 requiredDatabaseId 是否一致 if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) { // 将 <id, XNode> 键值对缓存到 sqlFragments 中 sqlFragments.put(id, context); } } }
首先是获取sql节点的id和databaseId属性,然后为id属性值拼接命名空间。最后,通过检测当前 databaseId 和 requiredDatabaseId是否一致,来决定保存还是忽略当前的节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private boolean databaseIdMatchesCurrent( String id, String databaseId, String requiredDatabaseId) { if (requiredDatabaseId != null) { // 当前 databaseId 和目标 databaseId 不一致时,返回 false if (!requiredDatabaseId.equals(databaseId)) { return false; } } else { // 如果目标requiredDatabaseId为空,但当前databaseId不为空。两者不一致,返回false if (databaseId != null) { return false; } // 如果当前sql节点的 id 与之前的sql节点重复,且先前节点 // databaseId 不为空。则忽略当前节点,并返回 false if (this.sqlFragments.containsKey(id)) { XNode context = this.sqlFragments.get(id); if (context.getStringAttribute("databaseId") != null) { return false; } } } return true; }
这里总结一下 databaseId 的匹配规则。
databaseId 与 requiredDatabaseId 不一致,即失配,返回 false
当前节点与之前的节点出现id重复的情况,若之前的sql节点databaseId属性不为空,返回 false
若以上两条规则均匹配失败,此时返回 true
第二条规则稍微难理解一点
1 2 3 4 5 6 7 8 9 <!-- databaseId 不为空 --> <sql id="table" databaseId="mysql"> article </sql> <!-- databaseId 为空 --> <sql id="table"> article </sql>
两个sql节点的 id 属性值相同,databaseId属性不一致,且configuration.databaseId=mysql
第一次调用sqlElement方法,第一个节点对应的 XNode会被放入到 sqlFragments 中。
第二次调用 sqlElement 方法时,requiredDatabaseId 参数为空。
由于 sqlFragments 中已包含了一个id节点,且该节点的databaseId不为空,此时匹配逻辑返回 false,第二个节点不会被保存到 sqlFragments。
databaseId用于标明数据库厂商的身份,不同厂商有自己的SQL方言,MyBatis可以根据 databaseId 执行不同SQL语句。
databaseId 在sql节点中有什么用呢? sql节点用于保存 SQL 语句片段,如果SQL语句片段中包含方言的话,那么该sql节点只能被同一databaseId 的查询语句或更新语句引用