1. Mybatis是如何實(shí)現(xiàn)SQL語(yǔ)句復(fù)用功能的?

        共 3778字,需瀏覽 8分鐘

         ·

        2020-08-24 05:01

        點(diǎn)擊上方藍(lán)色“程序猿DD”,選擇“設(shè)為星標(biāo)”

        回復(fù)“資源”獲取獨(dú)家整理的學(xué)習(xí)資料!

        來(lái)源 |?https://my.oschina.net/zudajun/blog/687326

        今天,我們將分析Mybatis之sqlFragment,可以翻譯為sql片段,它的存在價(jià)值在于可復(fù)用sql片段,避免到處重復(fù)編寫(xiě)。


        在工作中,往往有這樣的需求,對(duì)于同一個(gè)sql條件查詢(xún),首先需要統(tǒng)計(jì)記錄條數(shù),用以計(jì)算pageCount,然后再對(duì)結(jié)果進(jìn)行分頁(yè)查詢(xún)顯示,看下面一個(gè)例子。


        <sql id="studentProperties">    select       stud_id as studId      , name, email      , dob      , phone    from 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標(biāo)簽服務(wù),可以定義很多sqlFragment,然后使用include標(biāo)簽引入多個(gè)sqlFragment。在工作中,也是比較常用的一個(gè)功能,它的優(yōu)點(diǎn)很明顯,復(fù)用sql片段,它的缺點(diǎn)也很明顯,不能完整的展現(xiàn)sql邏輯,如果一個(gè)標(biāo)簽,include了四至五個(gè)sqlFragment,其可讀性就非常差了。


        sqlFragment里的內(nèi)容是可以隨意寫(xiě)的,它不需要是一個(gè)完整的sql,它可以是“,phone”這么簡(jiǎn)單的文本。


        1.sqlFragment的解析過(guò)程


        sqlFragment存儲(chǔ)于Configuration內(nèi)部。

        protected final Map<String, XNode> sqlFragments = new StrictMap("XML fragments parsed from previous mappers");


        解析sqlFragment的過(guò)程非常簡(jiǎn)單。


        org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XNode)方法部分源碼。

        // 解析sqlFragmentsqlElement(context.evalNodes("/mapper/sql"));// 為select|insert|update|delete提供服務(wù)buildStatementFromContext(context.evalNodes("select|insert|update|delete"));


        sqlFragment存儲(chǔ)于Map結(jié)構(gòu)當(dāng)中。其實(shí)最關(guān)鍵的,是它如何為select|insert|update|delete提供服務(wù)的。


        2.select|insert|update|delete標(biāo)簽中,解析include標(biāo)簽的過(guò)程


        org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode()方法源碼。

        // Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);// 重點(diǎn)關(guān)注的方法includeParser.applyIncludes(context.getNode());
        // Parse selectKey after includes and remove them.processSelectKeyNodes(id, parameterTypeClass, langDriver);
        // Parse the SQL (pre: and were parsed and removed)SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);


        注釋“pre: and were parsed and removed”,含義為解析完,并移除。為什么要移除呢?秘密都隱藏在applyIncludes()方法內(nèi)部了。


        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 node      Properties fullContext;
        String refid = getStringAttribute(source, "refid"); // replace variables in include refid value refid = PropertyParser.parse(refid, variablesContext); Node toInclude = findSqlFragment(refid); Properties newVariablesContext = getVariablesContext(source, variablesContext); if (!newVariablesContext.isEmpty()) { // merge contexts fullContext = new Properties(); fullContext.putAll(variablesContext); fullContext.putAll(newVariablesContext); } else { // no new context - use inherited fully fullContext = variablesContext; } // 遞歸調(diào)用 applyIncludes(toInclude, fullContext); if (toInclude.getOwnerDocument() != source.getOwnerDocument()) { toInclude = source.getOwnerDocument().importNode(toInclude, true); } // 將include節(jié)點(diǎn),替換為sqlFragment節(jié)點(diǎn) source.getParentNode().replaceChild(toInclude, source); while (toInclude.hasChildNodes()) { // 將sqlFragment的子節(jié)點(diǎn)(也就是文本節(jié)點(diǎn)),插入到sqlFragment的前面 toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude); } // 移除sqlFragment節(jié)點(diǎn) toInclude.getParentNode().removeChild(toInclude); } else if (source.getNodeType() == Node.ELEMENT_NODE) { NodeList children = source.getChildNodes(); for (int i=0; i // 遞歸調(diào)用 applyIncludes(children.item(i), variablesContext); } } else if (source.getNodeType() == Node.ATTRIBUTE_NODE && !variablesContext.isEmpty()) { // replace variables in all attribute values source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext)); } else if (source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) { // replace variables ins all text nodes source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext)); } }

        上面是對(duì)源碼的解讀,為了便于理解,我們接下來(lái)采用圖示的辦法,演示其過(guò)程。


        3.圖示過(guò)程演示


        ①解析節(jié)點(diǎn)

        <select id="countAll" resultType="int">    select count(1) from (      <include refid="studentProperties">include>    ) tmp  select>


        ②include節(jié)點(diǎn)替換為sqlFragment節(jié)點(diǎn)

        <select id="countAll" resultType="int">    select count(1) from (        "studentProperties">          select             stud_id as studId            , name, email            , dob            , phone          from students        ) tmp  select>


        ③將sqlFragment的子節(jié)點(diǎn)(文本節(jié)點(diǎn))insert到sqlFragment節(jié)點(diǎn)的前面。注意,對(duì)于dom來(lái)說(shuō),文本也是一個(gè)節(jié)點(diǎn),叫TextNode。

        <select id="countAll" resultType="int">    select count(1) from (        select             stud_id as studId            , name, email            , dob            , phone          from students        "studentProperties">          select             stud_id as studId            , name, email            , dob            , phone          from students        ) tmp  select>


        ④移除sqlFragment節(jié)點(diǎn)

        <select id="countAll" resultType="int">    select count(1) from (        select             stud_id as studId            , name, email            , dob            , phone          from students) tmp  select>


        ⑤最終結(jié)果如圖所示

        (Made In QQ截圖及時(shí)編輯)


        如此一來(lái),TextNode1 + TextNode2 + TextNode3,就組成了一個(gè)完整的sql。遍歷select的三個(gè)子節(jié)點(diǎn),分別取出TextNode的value,append到一起,就是最終完整的sql。


        這也是為什么要移除 and 節(jié)點(diǎn)的原因。


        這就是Mybatis的sqlFragment,以上示例,均為靜態(tài)sql,即static sql。


        往期推薦

        扛住100億次請(qǐng)求?我們來(lái)試一試!

        SpringBoot + Mybatis + Druid + PageHelper 實(shí)現(xiàn)多數(shù)據(jù)源分頁(yè)

        Java 中的 BigDecimal,你真的會(huì)用嗎?

        華為阿里下班時(shí)間曝光:所有的光鮮,都有加班的味道

        MySQL 的 Binlog 日志處理工具(Canal,Maxwell,Databus,DTS)對(duì)比

        Serverless:為我們到底帶來(lái)了什么


        星球限時(shí)拼團(tuán)優(yōu)惠進(jìn)行中

        我的星球是否適合你?

        點(diǎn)擊閱讀原文看看我們都聊過(guò)啥?

        瀏覽 54
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 大香蕉狠狠操 | 奇米影视7777人人妻首页 | 色色草视频 | 无码福利导航 | 午夜精品一区二区三区免费观看 |