在JSP頁面交換XML和數據庫內容的定制標記庫
Mark A. Kolb
軟件工程師
2003 年 8 月 11 日
Web應用程序的標志是多個子系統的集成。SQL和XML是在這類子系統之間交換數據的兩種最通用的機制。在本文中,Mark Kolb介紹訪問JSP頁面數據庫和XML內容的sql和xml庫并對JSTL進行了總結。
Web應用程序的模板式(stereotypical)架構分為三層:處理請求的Web服務器、實施業務邏輯的應用程序服務器以及管理永久性數據的數據庫。應用程序和數據庫層之間的聯接通常采用關系數據庫中的SQL調用格式。當業務邏輯被寫入到Java語言中時,JDBC用于實現這些調用。
如果應用程序調用與其它服務器(本地或遠程)的集成,我們將需要用于在不同子系統之間交換數據的更深層次的機制。在Web應用程序內部和應用程序之間傳送數據采用的越來越普遍的一種方法是XML文件的交換。
迄今為止,在我們的JSTL之旅中,我們討論了JSTL 表達式語言(expression language,EL)和 core和 fmt標記庫。在最后一部分,我們將考慮sql和xml庫--正如它們的名字表示的一樣 -- 提供定制標記來接入和管理從SQL數據庫和XML文件檢索到的數據。
xml庫
根據設計,XML提供靈活的方式來表示結構化數據,這些數據同時準備進行驗證,因此它尤其適應于在松散聯合的系統之間交換數據。這反過來使其成為Web應用程序極具吸引力的集成技術。
與使用XML表示的數據進行交互的第一步是把數據作為一個XML文件,對其進行檢索并進行分解,以創建數據結構來接入該文件中的內容。在分解文件后,您可以有選擇的對其進行轉換以創建新的XML文件,您可以對新的XML文件進行相同的操作。最終,文件中的數據可以被提取,然后顯示或使用作為輸入數據來運行其它操作。
這些步驟都在用于控制XML的JSTL標記中反映出。根據我們在第2部分 探討核心中所討論的,我們使用core庫中的<c:import>標記來檢索XML文件。然后使用<x:parse>標記來分解該文件,支持標準的XML分解技術,如文件對象模式(Document Object Model,DOM)和簡單XML API(Simple API for XML,SAX)。<x:transform>標記可用于轉換XML文件并依賴標準技術來轉換XML數據:擴展樣式表語言( Extensible Stylesheet Language,XSL)。最后,我們提供多個標記來接入和控制分解后的XML數據,但是所有這一切都依賴于另一種標準- XML路徑語言(XML Path Language,XPath),以引用分解后的XML文件中的內容。
分解XML
<x:parse>標記有多種格式,取決于用戶希望的分解類型。這一項操作最基本的格式使用以下語法:
<x:parse xml="
expression" var="
name" scope="
scope"
filter="
expression" systemId="
expression"/>
|
在這五種屬性中,只有xml屬性是需要的,其值應該是包含要分解的XML文件的字符串,或者是java.io.Reader實例,通過它可以讀取要被分解的文件。此外,您可以使用以下語法,根據<x:parse>標記的主體內容來規定要被分解的文件:
<x:parse var="
name" scope="
scope"
filter="
expression" systemId="
expression">
body content
</x:parse>
|
var和scope屬性規定存儲分解后的文件的scoped變量。然后xml庫中的其它標記可以使用這一變量來運行其它操作。注意,當var和 scope 屬性存在時,JSTL用于表示分解后的文件的數據結構類型以實施為導向,從而廠商可以對其進行優化。
如果應用程序需要對JSTL提供的分解后的文件進行處理,它可以使用另一種格式的<x:parse>,它要求分解后的文件堅持使用一個標準接口。在這種情況下,該標記的語法如下:
<x:parse xml="
expression" varDom="
name" scopeDom="
scope"
filter="
expression" systemId="
expression"/>
|
當您使用<x:parse>的這一版本時,表示分解后的XML文件的對象必須使用org.w3c.dom.Document接口。當根據<x:parse>中的主體內容來規定XML文件時,您還可以使用varDom和scopeDom屬性來代替var 和 scope屬性,語法如下:
<x:parse varDom="
name" scopeDom="
scope"
filter="
expression" systemId="
expression">
body content
</x:parse>
|
其它兩個屬性filter 和 systemId 可以實現對分解流程的精確控制。filter 屬性規定org.xml.sax.XMLFilter類的一個實例,以在分解之前對文件進行過濾。如果要被分解的文件非常大,但目前的工作只需要處理一小部分內容時這一屬性尤其有用。systemId屬性表示要被分解的文件的URI并解析文件中出現的任何相關的路徑。當被分解的XML文件使用相關的URL來引用分解流程中需要接入的其它文件或資源時需要這種屬性
清單1展示了<x:parse> 標記的使用,包括與 <c:import>的交互。此處<c:import> 標記用于檢索眾所周知的Slashdot Web 網站的RDF Site Summary (RSS)反饋,然后使用<x:parse>分解表示RSS 反饋的XML文件,表示分解后的文件的以實施為導向的數據結構被保存到名為rss的變量(帶有page 范圍)中。
清單1:<x:parse>與<c:import>的交互
<c:import var="rssFeed" url="http://slashdot.org/slashdot.rdf"/>
<x:parse var="rss" xml="${rssFeed}"/>
|
轉換XML
XML通過XSL樣式表來轉換。JSTL使用<x:transform>標記來支持這一操作。與<x:parse>的情況一樣,<x:transform> 標記支持多種不同的格式。<x:transform> 最基本的格式的語法是:
<x:transform xml="
expression" xslt="
expression"
var="
name" scope="
scope"
xmlSystemId="
expression" xsltSystemId="
expression">
<x:param name="
expression" value="
expression"/>
...
</x:transform>
|
關于 RSS RDF Site Summary (RSS) 是許多以新聞為導向的網站公布的XML文件格式,它列出它們當前的標題,提供鏈接到相關文章的URL。同樣,它提供在Web上聯合新聞的簡單機制。關于RSS的更詳細信息,請參閱 參考資料。 |
此處,xml 屬性規定要被轉換的文件,xslt 屬性規定定義這次轉換的樣式表。這兩種屬性是必要的,其它屬性為可選。
與<x:parse>的xml屬性一樣,<x:transform>的xml 屬性值可以是包含XML文件的字符串,或者是接入這類文件的Reader。此外,它還可以采用 org.w3c.dom.Document 類或javax.xml.transform.Source 類的實例格式。最后,它還可以是使用<x:parse> 操作的var或varDom屬性分配的變量值。
而且,您可以根據<x:transform> 操作的主體內容來包含要被轉換的XML文件。在這種情況下,<x:transform> 的語法是:
<x:transform xslt="
expression"
var="
name" scope="
scope"
xmlSystemId="
expression" xsltSystemId="
expression">
body content
<x:param name="
expression" value="
expression"/>
...
</x:transform>
|
在這兩種情況下,規定XSL 樣式表的xslt 屬性應是字符串、Reader或javax.xml.transform.Source實例。
如果var 屬性存在,轉換后的XML文件將分配給相應的scoped變量,作為org.w3c.dom.Document 類的一個實例。通常,scope屬性規定這類變量分配的范圍。
<x:transform> 標記還支持將轉換結果存儲到javax.xml.transform.Result 類的一個實例中,而不是作為org.w3c.dom.Document的一個實例。如果var 和 scope 屬性被省略,result對象規定作為result屬性的值,<x:transform>標記將使用該對象來保存應用該樣式表的結果。清單2中介紹了使用<x:transform> 的result屬性的這兩種語法的變化:
清單2:使用result屬性來提供javax.xml.transform.Result實例時,<x:transform>操作的語法變化
<x:transform xml="
expression" xslt="
expression"
result="
expression"
xmlSystemId="
expression" xsltSystemId="
expression">
<x:param name="
expression" value="
expression"/>
...
</x:transform>
<x:transform xslt="
expression"
result="
expression"
xmlSystemId="
expression" xsltSystemId="
expression">
body content
<x:param name="
expression" value="
expression"/>
...
</x:transform>
|
無論您采用這兩種<x:transform>格式中的那一種,您都必須從定制標記單獨創建javax.xml.transform.Result對象。該對象自身作為result屬性的值提供。
如果既不存在var 屬性,也不存在result屬性,轉換的結果將簡單地插入到JSP頁面,作為處理<x:transform> 操作的結果。當樣式表用于將數據從XML轉換成HTML時尤其有用,如清單3所示:
清單3:在JSP頁面直接顯示轉換的XML數據
<c:import var="rssFeed" url="http://slashdot.org/slashdot.rdf"/>
<c:import var="rssToHtml" url="/WEB-INF/xslt/rss2html.xsl"/>
<x:transform xml="${rssFeed}" xslt="${rssToHtml}"/>
|
在本例中,使用 <c:import> 標記來讀取RSS反饋和適當的樣式表。樣式表的輸出結果是HTML,通過忽略<x:transform>的var和result 屬性來直接顯示。圖1顯示了實例結果:
圖1:清單3的輸出結果
與<x:parse>的systemId 屬性一樣,<x:transform>的xmlSystemId 和 xsltSystemId 屬性用于解析XML文件中相關的路徑。在這種情況下,xmlSystemId 屬性應用于根據標記的 xml屬性值提供的文件,而xsltSystemId 屬性用于解析根據標記的xslt屬性規定的樣式表中的相關路徑。
如果正在推動文件轉換的樣式表使用了參數,我們使用<x:param> 標記來規定這些參數。如果參數存在,那么這些標記必須在<x:transform> 標記主體內顯示。如果根據主體內容規定了要被轉換的XML文件,那么它必須先于任何 <x:param> 標記。
<x:param> 標記有兩種必要的屬性 -- name 和 value -- 就象本系列 第2部分 和 第3部分中討論的<c:param> 和 <fmt:param> 標記一樣。
處理XML內容
XML文件的分解和轉換操作都是基于整個文件來進行。但是,在您將文件轉換成一種可用的格式之后,一項應用程序通常只對文件中包含的一部分數據感興趣。鑒于這一原因,xml 庫包括多個標記來接入和控制XML文件內容的各個部分。
如果您已經閱讀了本系列第2部分( 探討核心),您將對這些xml 標記的名字非常熟悉。它們都基于JSTL core 庫相應的標記。但是,這些core 庫標記使用EL表達式,通過它們的value屬性來接入JSP容器中的數據,而它們在xml 庫中的副本使用XPath表達式,通過select屬性接入XML文件中的數據。
XPath是引用XML文件中元素及它們的屬性值和主體內容的標準化符號。正如其名字代表的一樣,這種符號與文件系統路徑的表示方法類似,使用短斜線來分開XPath語句的組分。這些組分對映于XML文件的節點,連續的組分匹配嵌套的Element。此外,星號可以用于作為通配符來匹配多個節點,括號內的表達式可以用于匹配屬性值和規定索引。有多種在線參考資料介紹XPath和它的使用(見 參考資料)。
要顯示XML文件的數據的一個Element,使用<x:out> 操作,它與core 庫的<c:out> 標記類似。 但是,<c:out> 使用名為value 和escapeXml的屬性,<x:out> 的屬性為select 和escapeXml:
<x:out select="
XPathExpression" escapeXml="
boolean"/>
|
當然,兩者的區別在于<x:out> 的select 屬性值必須是XPath表達式,而<c:out> 的value 屬性必須是EL表達式。兩種標記的escapeXml 屬性的意義是相同的。
清單4顯示了<x:out> 操作的使用。注意,規定用于select 屬性的XPath表達式由一個EL表達式規定為scoped變量,尤其是$rss。這一EL表達式根據將被求值的XPath語句來識別分解后的XML文件。該語句在此處查找名為title且父節點名為channel的Element,從而選擇它找到的第一個Element(根據表達式尾部[1]索引規定)。這一<x:out> 操作的結果是顯示這一Element的主體內容,關閉正在轉義(Escaping)的XML字符。
清單4:使用<x:out>操作來顯示XML Element的主體內容
<c:import var="rssFeed" url="http://slashdot.org/slashdot.rdf"/>
<x:parse var="rss" xml="${rssFeed}"/>
<x:out select="$rss//*[name()='channel']/*[name()='title'][1]"
escapeXml="false"/>
|
除了<x:out>之外,JSTL xml 庫包括以下控制XML數據的標記:
- <x:set> ,向JSTL scoped 變量分配XPath表達式的值
- <x:if> ,根據XPath表達式的布爾值來條件化內容
- <x:choose>、<x:when>和<x:otherwise>,根據XPath表達式來實施互斥的條件化
- <x:forEach> ,迭代根據XPath表達式匹配的多個Elements
每個這些標記的操作與core庫中相應的標記類似。例如,清單5中顯示的<x:forEach>的使用, <x:forEach> 操作用于迭代XML文件中表示RSS反饋的所有名為item 的Element。注意,<x:forEach>主體內容中嵌套的兩個<x:out> 操作中的XPath表達式與<x:forEach>標記正在迭代的節點相關。它們用于檢索每個item element的子節點link 和 title。
清單5:使用<x:out> 和<x:forEach>操作來選擇和顯示XML數據
<c:import var="rssFeed" url="http://slashdot.org/slashdot.rdf"/>
<x:parse var="rss" xml="${rssFeed}"/>
<a href="<x:out select="$rss//*[name()='channel']/*[name()='link'][1]"/>"
><x:out select="$rss//*[name()='channel']/*[name()='title'][1]"
escapeXml="false"/></a>
<x:forEach select="$rss//*[name()='item']">
<li> <a href="<x:out select="./*[name()='link']"/>"
><x:out select="./*[name()='title']" escapeXml="false"/></a>
</x:forEach>
|
清單5中JSP程序代碼的輸出結果與 清單3類似,它在 圖1中顯示。xml 庫以XPath為導向的標記提供備選的樣式表來轉換XML內容,尤其是當最后的輸出結果是HTML的情況。
sql庫
JSTL第4個也是最后一個操作是sql定制標記庫。正如其名字代表的一樣,該庫提供與關系數據庫交互的標記。尤其是sql 庫定義規定數據源、發布查詢和更新以及將查詢和更新編組到事務處理中的標記。
Datasource
Datasource是獲得數據庫連接的工廠。它們經常實施某些格式的連接庫來最大限度地降低與創建和初始化連接相關的開銷。Java 2 Enterprise Edition (J2EE)應用程序服務器通常內置了Datasource支持,通過 Java命名和目錄接口( Java Naming and Directory Interface,JNDI)它可用于J2EE應用程序。
JSTL的sql 標記依賴于Datasource來獲得連接。實際上包括可選的dataSource 屬性以明確規定它們的連接工廠作為 javax.sql.DataSource 接口實例,或作為JNDI名。
您可以使用<sql:setDataSource> 標記來獲得javax.sql.DataSource 實例,它采用以下兩種格式:
<sql:setDataSource dataSource="
expression"
var="
name" scope="
scope"/>
<sql:setDataSource url="
expression" driver="
expression"
user="
expression" password="
expression"
var="
name" scope="
scope"/>
|
第一種格式只需要dataSource 屬性,而第二種格式只需要url 屬性。
通過提供JNDI名作為dataSource屬性值,您可以使用第一種格式來接入與JNDI名相關的datasource。第二種格式將創建新的datasource,使用作為url屬性值提供的JDBC URL。可選的driver 屬性規定實施數據庫driver的類的名稱,同時需要時user 和 password 屬性提供接入數據庫的登錄證書。
對于<sql:setDataSource>的任何一種格式而言,可選的var 和 scope 屬性向scoped變量分配特定的datasource。如果var屬性不存在,那么 <sql:setDataSource> 操作設置供sql 標記使用的缺省 datasource,它們沒有規定明確的datasource。
您還可以使用javax.servlet.jsp.jstl.sql.dataSource 參數來配置sql 庫的缺省datasource。在實踐中,在應用程序的Web.xml文件中添加與清單6中顯示的類似的程序代碼是規定缺省datasource最方便的方式。使用<sql:setDataSource> 來完成這一操作要求使用JSP頁面來初始化該應用程序,因此可以以某種方式自動運行這一頁面。
清單6:使用JNDI名來設置JSTL在web.xml部署描述符中的缺省datasource
<context-param>
<param-name>javax.servlet.jsp.jstl.sql.dataSource</param-name>
<param-value>jdbc/blog</param-value>
</context-param>
|
提交查詢和更新
在建立了datasource接入之后,您可以使用<sql:query> 操作來執行查詢,同時使用<sql:update> 操作來執行數據庫更新。查詢和更新使用SQL語句來規定,它可以是使用基于JDBC的java.sql.PreparedStatement 接口的方法來實現參數化。參數值使用嵌套的<sql:param> 和 <sql:dateParam> 標記來規定。
支持以下三種<sql:query> 操作:
<sql:query sql="
expression" dataSource="
expression"
var="
name" scope="
scope"
maxRows="
expression" startRow="
expression"/>
<sql:query sql="
expression" dataSource="
expression"
var="
name" scope="
scope"
maxRows="
expression" startRow="
expression">
<sql:param value="
expression"/>
...
</sql:query>
<sql:query dataSource="
expression"
var="
name" scope="
scope"
maxRows="
expression" startRow="
expression">
SQL statement
<sql:param value="
expression"/>
...
</sql:query>
|
前兩種格式只要求sql 和 var 屬性,第三種格式只要求var 屬性。
var 和 scope 屬性規定存儲查詢結果的scoped 變量。maxRows 屬性可以用于限制查詢返回的行數,startRow 屬性允許忽略一些最開始的行數(如當結果集(Result set)由數據庫來構建時忽略)。
在執行了查詢之后,結果集被分配給scoped變量,作為javax.servlet.jsp.jstl.sql.Result 接口的一個實例。這一對象提供接入行、列名稱和查詢的結果集大小的屬性,如表1所示:
表1:javax.servlet.jsp.jstl.sql.Result 接口定義的屬性
屬性 |
說明 |
rows |
一排SortedMap 對象,每個對象對映列名和結果集中的單行 |
rowsByIndex |
一排數組,每個對應于結果集中的單行 |
columnNames |
一排對結果集中的列命名的字符串,采用與rowsByIndex屬性相同的順序 |
rowCount |
查詢結果中總行數 |
limitedByMaxRows |
如果查詢受限于maxRows 屬性值為真 |
在這些屬性中,rows 尤其方便,因為您可以使用它來在整個結果集中進行迭代和根據名字訪問列數據。我們在 清單7中闡述了這一操作,查詢的結果被分配到名為queryResults的scoped變量中,然后使用core 庫中的<c:forEach>標記來迭代那些行。嵌套的<c:out> 標記利用EL內置的Map 收集支持來查找與列名稱相對應的行數據。(記得在 第1部分 ,${row.title}和${row["title"]} 是相等的表達式。)
清單7還展示了使用<sql:setDataSource> 來關聯datasource 和scoped變量,它由<sql:query> 操作通過其dataSource 屬性隨后接入。
清單7:使用<sql:query>來查詢數據庫,使用<c:forEach>來迭代整個結果集
<sql:setDataSource var="dataSrc"
url="jdbc:mysql:///taglib" driver="org.gjt.mm.mysql.Driver"
user="admin" password="secret"/>
<sql:query var="queryResults" dataSource="${dataSrc}">
select * from blog group by created desc limit ?
<sql:param value="${6}"/></sql:query>
<table border="1">
<tr>
<th>ID</th>
<th>Created</th>
<th>Title</th>
<th>Author</th>
</tr>
<c:forEach var="row" items="${queryResults.rows}">
<tr>
<td><c:out value="${row.id}"/></td>
<td><c:out value="${row.created}"/></td>
<td><c:out value="${row.title}"/></td>
<td><c:out value="${row.author}"/></td>
</tr>
</c:forEach>
</table>
|
圖2顯示了清單7中JSTL程序代碼的實例頁面輸出結果。注意:清單7中<sql:query>操作主體中出現的SQL語句為參數化語句。
圖2:清單7的輸出
在<sql:query> 操作中,SQL語句根據主體內容來規定,或者使用?字符,通過sql 屬性實現參數化。對于SQL語句中每個這樣的參數來說,應有相應的<sql:param> 或 <sql:dateParam> 操作嵌套到<sql:query> 標記的主體中。<sql:param> 標記只采用一種屬性 -- value --來規定參數值。此外,當參數值為字符串時,您可以忽略value 屬性并根據<sql:param> 標記的主體內容來提供參數值。
表示日期、時間或時間戳的參數值使用<sql:dateParam> 標記來規定,使用以下語法:
<sql:dateParam value="
expression" type="
type"/>
|
對于<sql:dateParam>來說,value 屬性的表達式必須求 java.util.Date 類實例的值,同時type 屬性值必須是date、time或timestamp,由SQL語句需要那類與時間相關的值來決定。
與<sql:query> 一樣,<sql:update> 操作支持三種格式:
<sql:update sql="
expression" dataSource="
expression"
var="
name" scope="
scope"/>
<sql:update sql="
expression" dataSource="
expression"
var="
name" scope="
scope">
<sql:param value="
expression"/>
...
</sql:update>
<sql:update dataSource="
expression"
var="
name" scope="
scope">
SQL statement
<sql:param value="
expression"/>
...
</sql:update>
|
sql 和dataSource 屬性有與<sql:query>相同的<sql:update> 語義。同樣,var 和 scope 屬性可以用于規定scoped變量,但在這種情況下,分配給scoped變量的值將是java.lang.Integer 的一個實例,顯示作為數據庫更新結果而更改的行數。
管理事務處理
事務處理用于保護作為一個組必須成功或失敗的一系列數據庫操作。事務處理支持已經嵌入到JSTL的sql 庫中,通過將相應的<sql:query>和<sql:update>操作嵌套到<sql:transaction>標記的主體內容中,從而將一系列查詢和更新操作打包到一個事務處理中也就顯得微不足道了。
<sql:transaction> 語法如下:
<sql:transaction dataSource="
expression" isolation="
isolationLevel">
<sql:query .../>
or <sql:update .../>
...
|
<sql:transaction> 操作沒有必需的屬性。如果您忽略了dataSource 屬性,那么使用JSTL的缺省datasource。isolation 屬性用于規定事務處理的隔離級別,它可以是read_committed、read_uncommitted、repeatable_read或serializable。如果您未規定這一屬性,事務處理將使用datasource的缺省隔離級別。
您可能希望所有嵌套的查詢和更新必須使用與事務處理相同的datasource。實際上,嵌套到<sql:transaction>操作中的<sql:query> 或 <sql:update> 不能用于規定dataSource 屬性。它將自動使用與周圍的<sql:transaction>標記相關的datasource (顯性或隱性)。
清單8顯示了如何使用<sql:transaction> 的一個實例:
清單:使用<sql:transaction>來將數據庫更新聯合到事務處理中
<sql:transaction>
<sql:update sql="update blog set title = ? where id = ?">
<sql:param value="New Title"/>
<sql:param value="${23}"/>
</sql:update>
<sql:update sql="update blog set last_modified = now() where id = ?">
<sql:param value="${23}"/>
</sql:update>
</sql:transaction>
|
注意事項
JSTL的xml 和 sql 庫使用定制標記,從而能夠在JSP頁面上實施復雜的功能,但是在您的表示層實施這類功能可能不是最好的方法。
對于多位開發人員長期編寫的大型應用程序來說,實踐證明,用戶接口、基本的業務邏輯和數據倉庫之間的嚴格分離能夠長期簡化軟件維護。廣泛流行的模式/視圖/控制器( Model-View-Controller,MVC)設計模板是這一“最佳實踐”的公式化。在J2EE Web應用程序領域中,模式是應用程序的業務邏輯,視圖是包含表示層的JSP頁面。(控制器是form handlers和其它服務器端機制,使瀏覽器操作能夠開始更改模式并隨后更新視圖。) MVC規定應用程序的三個主要elements--模式、視圖和控制器 --相互之間有最小的相關性,從而限制相互之間的交互到統一、精心定義的接口。
應用程序依賴XML文件來進行數據交換以及關系數據庫來提供數據永久性都是應用程序業務邏輯(也就是其模式)的特征。因此,使用MVC設計模板建議無需在應用程序的表示層(也就是其視圖)反映這些實施細節。如果JSP用于實施表示層,那么使用 xml 和 sql 庫將違反MVC,因為它們的使用將意味著在表示層內暴露基本業務邏輯的elements。
鑒于這一原因,xml 和 sql 庫最適用于小型項目和原型工作。應用程序服務器對JSP頁面的動態編譯也使得這些庫中的定制標記可以用于作為調試工具。
結束語
在本系列中,我們討論了4個JSTL定制標記庫的功能及它們的使用。在 第1部分 和 第2部分,我們討論通過El和core 庫標記的使用,您如何在許多常見情況下避免JSP腳本程序。 第3部分 關注使用fmt 庫來本地化Web內容。
在最后一部分,我們討論了xml 和 sql 庫的功能。如果您愿意接受將業務邏輯包含到表示層的結果,這兩個庫中的標記都使其能夠非常輕松地將XML文件和關系數據庫中的內容結合到JSP頁面。這兩個庫還展示了當集成<sql:query> 和<c:forEach>時,JSTL庫如何構建和集成,以及xml 庫利用<c:import> 操作的能力。
參考資料
posted on 2005-06-06 15:30
小米 閱讀(357)
評論(0) 編輯 收藏 所屬分類:
Java