sql if 和insert_拼多多面试:Mybatis是如何实现SQL语句复用功能的?
在工作中,往往有這樣的需求,對于同一個sql條件查詢,首先需要統計記錄條數,用以計算pageCount,然后再對結果進行分頁查詢顯示,看下面一個例子。
<sql id="studentProperties"><!--sql片段-->select stud_id as studId, name, email, dob, phonefrom students</sql><select id="countAll" resultType="int">select count(1) from (<include refid="studentProperties"></include><!--復用-->) tmp</select><select id="findAll" resultType="Student" parameterType="map">select * from (<include refid="studentProperties"></include><!--復用-->) tmp limit #{offset}, #{pagesize}</select>這就是sqlFragment,它可以為select|insert|update|delete標簽服務,可以定義很多sqlFragment,然后使用include標簽引入多個sqlFragment。在工作中,也是比較常用的一個功能,它的優點很明顯,復用sql片段,它的缺點也很明顯,不能完整的展現sql邏輯,如果一個標簽,include了四至五個sqlFragment,其可讀性就非常差了。
sqlFragment里的內容是可以隨意寫的,它不需要是一個完整的sql,它可以是“,phone”這么簡單的文本。
1.sqlFragment的解析過程
sqlFragment存儲于Configuration內部。
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");解析sqlFragment的過程非常簡單。
org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XNode)方法部分源碼。
// 解析sqlFragment sqlElement(context.evalNodes("/mapper/sql")); // 為select|insert|update|delete提供服務 buildStatementFromContext(context.evalNodes("select|insert|update|delete"));sqlFragment存儲于Map<String, XNode>結構當中。其實最關鍵的,是它如何為select|insert|update|delete提供服務的。
2.select|insert|update|delete標簽中,解析include標簽的過程
org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode()方法源碼。
// Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); // 重點關注的方法 includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);注釋“pre: <selectKey> and <include> were parsed and removed”,含義為解析完,并移除。為什么要移除呢?秘密都隱藏在applyIncludes()方法內部了。
org.apache.ibatis.builder.xml.XMLIncludeTransformer.applyIncludes(Node, Properties)方法源碼。
/*** Recursively apply includes through all SQL fragments.* @param source Include node in DOM tree* @param variablesContext Current context for static variables with values*/private void applyIncludes(Node source, final Properties variablesContext) {if (source.getNodeName().equals("include")) {// new full context for included SQL - contains inherited context and new variables from current include nodeProperties fullContext;String refid = getStringAttribute(source, "refid");// replace variables in include refid valuerefid = PropertyParser.parse(refid, variablesContext);Node toInclude = findSqlFragment(refid);Properties newVariablesContext = getVariablesContext(source, variablesContext);if (!newVariablesContext.isEmpty()) {// merge contextsfullContext = new Properties();fullContext.putAll(variablesContext);fullContext.putAll(newVariablesContext);} else {// no new context - use inherited fullyfullContext = variablesContext;}// 遞歸調用applyIncludes(toInclude, fullContext);if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {toInclude = source.getOwnerDocument().importNode(toInclude, true);}// 將include節點,替換為sqlFragment節點source.getParentNode().replaceChild(toInclude, source);while (toInclude.hasChildNodes()) {// 將sqlFragment的子節點(也就是文本節點),插入到sqlFragment的前面toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);}// 移除sqlFragment節點toInclude.getParentNode().removeChild(toInclude);} else if (source.getNodeType() == Node.ELEMENT_NODE) {NodeList children = source.getChildNodes();for (int i=0; i<children.getLength(); i++) {// 遞歸調用applyIncludes(children.item(i), variablesContext);}} else if (source.getNodeType() == Node.ATTRIBUTE_NODE && !variablesContext.isEmpty()) {// replace variables in all attribute valuessource.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));} else if (source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {// replace variables ins all text nodessource.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));}}上面是對源碼的解讀,為了便于理解,我們接下來采用圖示的辦法,演示其過程。
3.圖示過程演示
①解析節點
<select id="countAll" resultType="int">select count(1) from (<include refid="studentProperties"></include>) tmp</select>②include節點替換為sqlFragment節點
<select id="countAll" resultType="int">select count(1) from (<sql id="studentProperties">select stud_id as studId, name, email, dob, phonefrom students</sql> ) tmp</select>③將sqlFragment的子節點(文本節點)insert到sqlFragment節點的前面。注意,對于dom來說,文本也是一個節點,叫TextNode。
<select id="countAll" resultType="int">select count(1) from (select stud_id as studId, name, email, dob, phonefrom students<sql id="studentProperties">select stud_id as studId, name, email, dob, phonefrom students</sql> ) tmp</select>④移除sqlFragment節點
<select id="countAll" resultType="int">select count(1) from (select stud_id as studId, name, email, dob, phonefrom students ) tmp</select>⑤最終結果如圖所示
如此一來,TextNode1 + TextNode2 + TextNode3,就組成了一個完整的sql。遍歷select的三個子節點,分別取出TextNode的value,append到一起,就是最終完整的sql。
這也是為什么要移除<selectKey> and <include>節點的原因。
這就是Mybatis的sqlFragment,以上示例,均為靜態sql,即static sql。
最后
感謝大家看到最后,如文章有不足,歡迎大家在評論區支持,給予意見。如果覺得我的文章對你有幫助,那就給我一個贊同吧。
每天都會分享java相關技術文章或行業資訊。歡迎大家關注和轉發文章。
總結
以上是生活随笔為你收集整理的sql if 和insert_拼多多面试:Mybatis是如何实现SQL语句复用功能的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: redis过期监听性能_基于Redis的
- 下一篇: 海草的作用和功效