在获取到子节点类列表后,接下来要做的事情是遍历列表,然后将子节点作为参数进行递归调用。在上面三个子节点中,子节点1和子节点3都是文本节点,调用过程一致。因此,下面我只会演示子节点1和子节点2的递归调用过程。先来演示子节点1的调用过程,如下:
节点1的调用过程比较简单,只有两层调用。然后我们在看一下子节点2的调用过程,如下:
上面是子节点2的调用过程,共有四层调用,略为复杂。大家自己也对着配置,把源码走一遍,然后记录每一次调用的一些状态,这样才能更好的理解 applyIncludes 方法的逻辑。
好了,本节内容先到这里,继续往下分析。
2.1.5.2 解析 <selectKey> 节点对于一些不支持自增主键的数据库来说,我们在插入数据时,需要明确指定主键数据。以 Oracle 数据库为例,Oracle 数据库不支持自增主键,但它提供了自增序列工具。我们每次向数据库中插入数据时,可以先通过自增序列获取主键数据,然后再进行插入。这里涉及到两次数据库查询操作,我们不能在一个 <select> 节点中同时定义两个 select 语句,否者会导致 SQL 语句出错。对于这个问题,MyBatis 的 <selectKey> 可以很好的解决。下面我们看一段配置:
<insert> <selectKey keyProperty="id" resultType="int" order="BEFORE"> select author_seq.nextval from dual </selectKey> insert into Author (id, name, password) values (#{id}, #{username}, #{password}) </insert>在上面的配置中,查询语句会先于插入语句执行,这样我们就可以在插入时获取到主键的值。关于 <selectKey> 的用法,这里不过多介绍了。下面我们来看一下 <selectKey> 节点的解析过程。
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) { List<XNode> selectKeyNodes = context.evalNodes("selectKey"); if (configuration.getDatabaseId() != null) { // 解析 <selectKey> 节点,databaseId 不为空 parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId()); } // 解析 <selectKey> 节点,databaseId 为空 parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null); // 将 <selectKey> 节点从 dom 树中移除 removeSelectKeyNodes(selectKeyNodes); }从上面的代码中可以看出,<selectKey> 节点在解析完成后,会被从 dom 树中移除。这样后续可以更专注的解析 <insert> 或 <update> 节点中的 SQL,无需再额外处理 <selectKey> 节点。继续往下看。
private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) { for (XNode nodeToHandle : list) { // id = parentId + !selectKey,比如 saveUser!selectKey String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX; // 获取 <selectKey> 节点的 databaseId 属性 String databaseId = nodeToHandle.getStringAttribute("databaseId"); // 匹配 databaseId if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) { // 解析 <selectKey> 节点 parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId); } } } private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) { // 获取各种属性 String resultType = nodeToHandle.getStringAttribute("resultType"); Class<?> resultTypeClass = resolveClass(resultType); StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString())); String keyProperty = nodeToHandle.getStringAttribute("keyProperty"); String keyColumn = nodeToHandle.getStringAttribute("keyColumn"); boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER")); // 设置默认值 boolean useCache = false; boolean resultOrdered = false; KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE; Integer fetchSize = null; Integer timeout = null; boolean flushCache = false; String parameterMap = null; String resultMap = null; ResultSetType resultSetTypeEnum = null; // 创建 SqlSource SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass); /* * <selectKey> 节点中只能配置 SELECT 查询语句, * 因此 sqlCommandType 为 SqlCommandType.SELECT */ SqlCommandType sqlCommandType = SqlCommandType.SELECT; /* * 构建 MappedStatement,并将 MappedStatement * 添加到 Configuration 的 mappedStatements map 中 */ builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null); // id = namespace + "." + id id = builderAssistant.applyCurrentNamespace(id, false); MappedStatement keyStatement = configuration.getMappedStatement(id, false); // 创建 SelectKeyGenerator,并添加到 keyGenerators map 中 configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore)); }上面的源码比较长,但大部分代码都是一些基础代码,不是很难理解。以上代码比较重要的步骤如下:
创建 SqlSource 实例
构建并缓存 MappedStatement 实例
构建并缓存 SelectKeyGenerator 实例