JDO+JSP2.0開發Web應用嚮導 - 開發篇

JDO+JSP2.0開發Web應用嚮導 – 開發篇

  在前面讀者看到的本文的《配置篇》中,我們介紹了用於本文中的基於JDO的WebApp開發的各種環境配置,尤其是本文選用的JDO產品:JDOGenie。JDOGenie的特點就是圖形配置工具功能強大,方便使用,學習JDO非常容易。

  我們前面已經完成了一個基本的數據對象模型,並且,每個數據類的屬性都用getter/setter進行了封裝。這裏重溫一下這個數據模型:
CSDN_Dev_Image_2003-12-62248490.gif

  這個類圖是非常重要的,相當於傳統JDBC開發的數據庫結構圖,但又比數據庫結構圖更高級一層。實際上,這個類圖很容易使用UML工具生成,包括所有數據類的Java源代碼。後面的真正的業務邏輯開發過程纔是我們的重點,但這個類圖卻是貫穿整個開發過程的核心,數據庫管理員可以根據這個類圖來配置並生成數據庫結構;開發人員按照這個類圖進行對象獲取和訪問;美工人員可以根據這個類圖來使用流行的可視化頁面設計工具美化JSP頁面(比如顯示某個回覆的主題標題,就按圖中的結構使用${a_reply.topic.title})。

  現在我們已經看到這個類圖的重要性,並且也知道這個類圖可以通過UML工具快速生成。因此,我們在開發之前幾乎不需要任何手工編碼,工作的重點就是與根據需求與相關開發人員討論並確定這個類圖結構。這裏也體現了JDO開發的一個優點,就是開發之前不需要太多地在意具體的代碼,而只需要在面向對象的層面上討論確立類圖的結構即可。討論類圖比討論數據庫結構要容易得多,一方面容易記憶,另一方面一般來說,一個類圖會比相應的數據庫結構圖簡單得多(比如一些雙向的關係直接在類圖上體現,不用象數據庫一樣需要一個額外的關聯表來對應)。

  接下來,我們在《配置篇》的基礎上繼續進行JDO版論壇的開發。請先快速地在腦海中回顧一下我們使用JDOGenie的工作臺生成本應用的項目配置文件,並將數據類導入的過程,下面介紹與前面那些圖形界面的操作等價的配置文件。對圖形界面不感興趣的讀者也可以直接從這裏開始閱讀。

1 等價的配置文件代碼

  實際上,在《配置篇》中的一切配置步驟,就是爲了生成兩個文件:一個是描述所有數據類的元數據文件:WEB-INF/classes/system.jdo,其源碼如下:

<?xml version="1.0" encoding="UTF-8"?>

<jdo>

<package name="jdobbs">

<class name="Post" />

<class name="Reply" persistence-capable-superclass="Post" />

<class name="Topic" persistence-capable-superclass="Post">

<field name="replies">

<collection element-type="Reply">

<extension vendor-name="jdogenie" key="inverse" value="topic" />

</collection>

</field>

</class>

</package>

</jdo>

  另一個生成的文件是JDOGenie專用的配置文件,包含數據庫連接、JDOGenie緩衝配置等等信息。源碼如下:

 
# Server name (must be unique on a machine)
server=jdogenie1
remote.access=true
remote.pm=false
hyperdrive=true

# JDO Genie version
version=2.2.0beta7 (28 Nov 2003)

# Data stores
storeCount=1
store0.name=main
store0.type=jdbc
store0.driver=com.mysql.jdbc.Driver
store0.url=jdbc/:mysql/://localhost/test?useUnicode/=true&characterEncoding/=GBK
store0.db=mysql
store0.user=
store0.password=
store0.properties=
store0.maxActive=5
store0.maxIdle=3
store0.minIdle=2
store0.init.sql=
store0.validate.sql=
store0.retry.interval.ms=1000
store0.retry.count=10
store0.pscache.max=
store0.ext.jdbc-key-generator=-
store0.jdbc.namegen=-

# .jdo resources
jdoFileCount=1
jdo0=system.jdo

# Properties for JDOHelper.getPersistenceManagerFactory(...)
javax.jdo.PersistenceManagerFactoryClass=za.co.hemtech.jdo.client.BootstrapPMF
javax.jdo.option.Optimistic=true
javax.jdo.option.RetainValues=false
javax.jdo.option.RestoreValues=false
javax.jdo.option.IgnoreCache=false
javax.jdo.option.NontransactionalRead=true
javax.jdo.option.NontransactionalWrite=false
javax.jdo.option.Multithreaded=false

# Event logging
event.logging=-
log.downloader=-

# Cache settings
cache.enabled=true
cache.maxobjects=10000
cache.listener=-
query.cache.enabled=true
query.cache.max.queries=10000

# Data store 0 mappings
store0.jdbc.type.count=0
store0.jdbc.javatype.count=0

# Workbench properties (not used at runtime)

mdedit.classPathCount=2
mdedit.cp0=
mdedit.cp1=../lib/mysql.jar

# Workbench Ant configuration (not used at runtime)
ant.disabled=false
ant.buildfile=../build.xml
ant.compile=enhance
ant.show.all.targets=false

# Workbench Scripts (not used at runtime)

# JDO Genie Class Diagrams (not used at runtime)
diagram.count=1

diagram0.name=Diagram 1
diagram0.legend=299,24
diagram0.general=13,Y,Y
diagram0.class=Y,N,Y,N,N,Y,N,Y,N,N,N,N,N,N,N,N,N
diagram0.field=Y,Y,N,N,N,N
diagram0.class.count=3
diagram0.class0=jdobbs.Post,127,48
diagram0.class1=jdobbs.Reply,213,222
diagram0.class2=jdobbs.Topic,33,206

  實際上,我們如果直接手工編寫這兩個文件的話,也可以不必進行前面那些配置工作。當然,估計很少有人會喜歡手工編碼多過使用圖形界面。

2 初步測試JDOGenie

  至此我們已經有了數據庫結構、有了增強過的數據類,已經可以正式開始使用JDO了。爲了加強感性認識,我們先看看JDOGenie的運行情況:我們點擊工作臺左邊的按鈕“CSDN_Dev_Image_2003-12-62248492.jpgGrid”進入數據類列表區,選中某個類,點擊上面工具條的第11個按鈕“View all instances of the selected class…”或者直接按Ctrl+E,系統就會列出該類在數據庫中的所有實例對象。不過我們這裏還沒有任何數據,所以什麼也沒列出來,以後可以通過這個功能查看某個類的所有實例。如果需要更細緻的查詢,可以進入左邊四個按鈕進入JDO查詢區,使用JDOQL或者SQL來查詢數據。JDOQL的查詢界面如圖:
CSDN_Dev_Image_2003-12-62248494.jpg

  好了,關於JDOGenie的細節已經講完了。之所以用這麼大的篇幅介紹JDOGenie的配置,目的是讓讀者能夠最快速地瞭解JDO需要的配置信息,並能最快地看到JDO產品的運行。下面我們來真正地開始實現我們的論壇功能。

3 業務邏輯的實現

  爲了簡單、直觀地顯示基於JDO的業務開發流程,我們在接下來的功能實現中採用JSP直接訪問JDO API來實現數據對象的獲取和操縱,就不再累贅地使用JavaBean對話控制器了。

  爲了使每個頁面的代碼更簡單,並且又具備調用JDO API、使用常用的Java包、使用JSTL的功能,我們寫一個專門的頭頁面,用於其它頁面包含,這樣,可以使用其它頁面的代碼更簡潔易讀。

<%@page

contentType="text/html; charset=GBK"

import="jdobbs.*,java.util.*,javax.jdo.*,java.io.*"

%>

<%@taglib prefix="c" uri="http://java.sun.com/jstl/core" %>

<%

//預置幾個日期格式化工具

pageContext.setAttribute("fullTime",new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

pageContext.setAttribute("shortTime",new java.text.SimpleDateFormat("MM-dd HH:mm"));

pageContext.setAttribute("fullDate",new java.text.SimpleDateFormat("yyyy-MM-dd"));

pageContext.setAttribute("shortDate",new java.text.SimpleDateFormat("MM-dd"));

%>

  這裏我們預置了幾個用於顯示日期的格式化對象,可以方便後面日期數據的顯示。在Resin3中,可以用${shortTime.format(someDate)}來以顯示類似“09-10 15:33”樣式的日期字符串。也許在JSP2.0最終出臺之前,這種方式未必規範,但既然服務器支持,就先這麼用吧。

  回顧一下論壇的功能需求,主要有以下幾點:

1. 網友進入主頁時,系統列出目前所有的主題,按時間順序從近到遠排列。

2. 網友可以在主頁下方的發貼表單中發表新主題,包括標題和內容

3. 其它網友在主頁的貼子列表中點擊某個標題可以閱讀這個主題的詳細內容,包括所有的回貼內容。所有回貼按照時間順序排列在主題貼後面 4. 可以在主題詳細內容頁面尾部的回貼表單中回覆這個主題

5. 每個網友發表主題或貼子時,系統需要記錄該貼子發表時的客戶端IP地址

6. 系統提供一個高級搜索功能,讓網友可以根據時間、主題標題、內容或回覆內容、IP搜索主題

  我們接下來一個一個地實現這些功能。

3.1 主頁:列出所有主題

  我們在index.jsp中執行一個不帶條件的JDO查詢,並按時間倒序用HTML中的表格來顯示一條一條的主題。我們將index.jsp的代碼改動如下:

<%@page contentType="text/html; charset=GBK" %>

<%@include file="/inc.jsp" %>

<%

//先取得主題列表:

Query q = Sys.pm().newQuery(Topic.class,"");

q.setOrdering("postTime descending");

pageContext.setAttribute("topics",q.execute());

%>

<title>JDO BBS on ${pageContext.request.serverName}</title>

您現在的位置:<b>JDO BBS 首頁</b>

<h3>歡迎訪問JDOBBS!</h3>

<table border=1>

<tr><th>標題</th><th>內容長度</th><th>發表時間</th><th>IP地址</th><th>回覆數</th><th>最後回覆</th></tr>

<c:forEach var="topic" items="${topics}">

<tr>

<td><a href="topic.jsp?id=${topic.jdoGetObjectId()}">${topic.title}</a></td>

<td>${topic.length}</td>

<td>${shortTime.format(topic.postTime)}</td>

<td>${topic.ip}</td>

<td>${topic.replyCount}</td>

<td>${shortTime.format(topic.lastUpdate)}</td>

</tr>

</c:forEach>

</table>

  在這段代碼中,我們先是使用了一人JDO的javax.jdo.Query來執行查詢,對這個Query設置了一個排序參數:“postTime descending”,這個設置使JDO生成“… order by POST_TIME”的SQL子句,完成排序。在JDO中,也可以使用多個排序字段,用“,”號隔開即可。值得一提的是,JDO的排序字段可以是通過引用到達的另一個對象的屬性,比如查詢Reply對象時,可以按其所回覆的主題的回覆數進行排序,代碼是:

Query query = pm.newQuery(Reply.class,””);

query.setOrdering(“topic.replyCount ascending”); //注意正序也必須寫上“ascending”

這兩行簡單易懂的代碼將產生類似

select …

FROM post a LEFT JOIN post AS b ON (a.topic = b.post_id)

WHERE a.jdo_class = 54451616

ORDER BY b.reply_count

  的SQL語句。如果數據庫不是MySQL而是Oracle或其它數據庫的話,這條SQL語句還會有不同的形式。從中也可以看出JDO的透明性,你只需要按最直接的想法設置排序(“topic.replyCount ascending”),而不用去考慮聯表的SQL如何編寫,更不用考慮不同的數據庫的語法細節。

  由於數據庫中還沒有數據,我們這裏就不給出顯示效果,留待後面再說。需要說明的一點是:使用JSP2.0編寫的這個index.jsp頁面很容易在一般的頁面編輯器中進行維護。比如這個頁面在DreamweaverMX中編輯時的界面是:
CSDN_Dev_Image_2003-12-62248496.jpg

  也許熟悉DreamWeaver的讀者會覺得這個表格會將頁面撐得比較寬影響美工設計,比如只需要顯示頂多3位數字的“回覆數”字段在編輯頁面時得寫成“${topic.replyCount}”,從而將這一單元格撐得過寬,實際上,這樣的問題可以通過一個小技巧解決:將該處先寫成一個隨意的數,再將這個數包(Wrap)上層JSTL表達式“c:out”即可,相應的HTML源碼是:<td><c:out value=”${topic.replyCount}”>123</c:out></td>。關於這方面的技巧還有一些,都屬於JSP2.0頁面美工的內容,這裏不再一一描述。

  現在我們繼續完成下面的“發表新主題”功能來產生數據。

3.2 發表新主題

  爲了新發表貼子,我們需要一個輸入表單,現在將這個表單做到首頁主題列表的後面,並讓這個表單提交到一個名爲index!post.jsp的頁面,這個接收頁面解析提交的參數,並使用JDO API生成新的主題貼子,保存到數據庫中。

  先在index.jsp尾部增加一個表單:

<form name=fmPost method=post action="index!post.jsp">

請在這裏發表新主題。

<br>標題:<br><input name=title maxlength=100 size=100>

<br>內容:<br><textarea name=content cols=100 rows=10></textarea>

<br><input type=submit value=提交>

</form>

  然後編寫接收發貼表單提交的index!post.jsp:

<%@page contentType="text/html; charset=GBK" %>

<%@include file="/inc.jsp" %>

<%

Topic topic = new Topic();

topic.setTitle(request.getParameter("title"));

topic.setContent(request.getParameter("content"));

topic.setLength(topic.getContent().length());

topic.setPostTime(new Date());

topic.setLastUpdate(new Date());

topic.setIp(request.getRemoteAddr());

Sys.pm().currentTransaction().begin();

Sys.pm().makePersistent(topic);

Sys.pm().currentTransaction().commit();

pageContext.setAttribute("topic",topic);

%>

您的主題已經發表,請選擇下列操作之一:

<p><a href="index.jsp">返回主題列表</a>

<p><a href="topic.jsp?id=${topic.jdoGetObjectId()}">進入該主題頁面</a>

  我們發貼的過程先是生成一個Topic對象,然後使用JDO的API開始一個事務,標記這個新生成的topic對象爲存儲實例,然後提交事務,這樣就完成了對象的保存過程。當事務提交後,這個topic對象就具備了由JDOGenie生成的一個數據庫標識,所以我們可以在頁面尾部的“進入該主題頁面”鏈接中使用其標識:${topic.jdoGetObjectId()}

  現在我們就可以發表主題了。發表了一系列主題後,我們的論壇主頁就比較充實了:
CSDN_Dev_Image_2003-12-62248498.jpg

3.3 閱讀主題及回覆

  前面的主題列表頁面中的每個主題已經有鏈接到topic.jsp頁面,現在我們來編寫這個頁面:

<%@page contentType="text/html; charset=GBK" %>

<%@include file="/inc.jsp" %>

<%

//先取得主題:

String oid = request.getParameter("id");

Object objectId = Sys.pm().newObjectIdInstance(Topic.class,oid);

Topic topic = (Topic)Sys.pm().getObjectById(objectId,false);

pageContext.setAttribute("topic",topic);

%>

<title>主題:${topic.title}</title>

您現在的位置:<a href=".">JDO BBS 首頁</a> --> <b>主題:${topic.title} </b>

<p>內容:<pre><b>${topic.content}</b></pre>

下面是回覆列表:

<table border=1>

<tr><th>回覆時間</th><th>IP地址</th><th>內容長度</th><th>回覆內容</th></tr>

<c:forEach var="reply" items="${topic.replies}">

<tr> <a name="#${reply.jdoGetObjectId()}">

<td>${shortTime.format(reply.postTime)}</td>

<td>${reply.ip}</td>

<td>${reply.length}</td>

<td><pre>${reply.content}</td>

</tr>

</c:forEach>

</table>

  我們在頁面開始先通過訪問本頁面時必須給出的“id”參數得到一個Topic對象,並將其設置到pageContext中,剩下的代碼就是利用JSP2.0顯示其內容,包括所有的回覆。我們先不給出顯示效果,而是接着實現回覆主題功能:

3.4 回覆主題

  先在剛纔的主題頁面尾部加上回復表單:

<form name=fmReply method=post action="topic!reply.jsp">

請在這裏回覆本主題。

<input type=hidden name=topicId value="${topic.jdoGetObjectId()}">

<br>內容:<br><textarea name=content cols=100 rows=10></textarea>

<br><input type=submit value=回覆>

</form>

  然後,編寫一個接收回復提交的頁面:topic!reply.jsp:

<%@page contentType="text/html; charset=GBK" %>

<%@include file="/inc.jsp" %>

<%

Sys.pm().currentTransaction().begin();

//先取得被回覆的主題:

String oid = request.getParameter("topicId"); //回覆表單中引用的主題貼標識

Object objectId = Sys.pm().newObjectIdInstance(Topic.class,oid);

Topic topic = (Topic)Sys.pm().getObjectById(objectId,false);

//增加回復:

Reply reply = new Reply();

reply.setContent(request.getParameter("content"));

reply.setLength(reply.getContent().length());

reply.setPostTime(new Date());

reply.setIp(request.getRemoteAddr());

topic.getReplies().add(reply); //將新回覆加到主題的回覆列表中

topic.setReplyCount(topic.getReplyCount()+1); //回覆計數加1

topic.setLastUpdate(new Date());

Sys.pm().currentTransaction().commit();

pageContext.setAttribute("topic",topic);

%>

您的回覆已經發表,請選擇下列操作之一:

<p><a href="index.jsp">返回主題列表</a>

<p><a href="topic.jsp?id=${param.topicId}">進入所回覆的主題頁面</a>

  在這個頁面的開始部分,我們先通過回覆表單傳遞過來的被回覆的主題標識取得該主題,然後新生成一個Reply對象,設置好其屬性後,將這個新生成的Reply對象添加到Topic的replies列表中。我們這段代碼中並沒有使用pm.makePersistent()方法,因爲將reply對象加到topic對象的replies列表中時,JDO已經標記該reply對象需要保存,這個特性就是JDO中的“可達性存儲”概念。如果一個新生成的對象被一個已經存在於數據庫中的對象引用,那麼,提交事務時這個新對象將被保存,甚至如果這個新對象又引用了另一個新生成的對象,另一個新對象也會被保存。這個特性有時候可以減少我們的代碼複雜性。

  細心的讀者可能注意到了,最後一行代碼中使用了“${param.topicId}”來直接將提交參數中的主題貼標識拼裝到返回主題頁面的鏈接中。這使用到了JSP2.0的隱含環境變量param。

  我們測試回覆功能,回覆了幾個貼子後,主題頁面的效果如圖所示:
CSDN_Dev_Image_2003-12-622484910.jpg

  而回到論壇首頁,界面如下圖:
CSDN_Dev_Image_2003-12-622484912.jpg

3.5 高級搜索功能

  到此,我們已經完成了基本的發貼和回覆功能,現在做一個組合條件搜索的功能,以體現JDOQL的特點。

  回顧一下搜索功能需求:“讓網友可以根據時間、主題標題、內容或回覆內容、IP搜索主題”,我們需要編寫一個搜索頁面,包含一個搜索條件輸入表單,這個表單直接提交到本頁面,提交後,表單中將顯示所輸入的條件,而在表單下方,列出符合條件的主題。

  源碼 search.jsp:

<%@page contentType="text/html; charset=GBK" %>

<%@include file="/inc.jsp" %>

<title>主題:${topic.title}</title>

您現在的位置:<a href=".">JDO BBS 首頁</a> --> <b>搜索論壇主題</b>

<form name=fmSearch>

請在下面輸入搜索條件:

<br>發貼日期:

<select name="postTime">

<option value="">不限</option>

<option value="week">一週內</option>

<option value="month">一月內</option>

<option value="year">一年內</option>

</select>

<script>fmSearch.postTime.value = "${param.postTime}"; </script>

<br>標題包含:

<input name=title value="${param.title}">

<br>內容或回覆貼的內容包含:

<input name=content value="${param.content}">

<br>發貼IP:

<input name=ip value="${param.ip}">

<br><input type=submit value=搜索>

</form>

<%

//這裏進行JDO查詢:

Query q = Sys.pm().newQuery(Topic.class);

String jdoql = "";

String paramNames = "";

List params = new ArrayList();

String postTime = request.getParameter("postTime");

if(postTime != null && !postTime.equals("")) {

jdoql += " && postTime >= _time";

paramNames += ",Date _time";

if(postTime.equals("week")) { //最近一週

params.add(new Date(System.currentTimeMillis()-7l*24*60*60*1000));

} else if(postTime.equals("month")) { //最近一月

params.add(new Date(System.currentTimeMillis()-30l*24*60*60*1000));

} else { //最近一年

params.add(new Date(System.currentTimeMillis()-365l*24*60*60*1000));

}

}

String title = request.getParameter("title");

if(title != null && !title.trim().equals("")) {

jdoql += " && title.startsWith(_title)";

paramNames += ",String _title";

params.add("%"+title);

}

String content = request.getParameter("content");

if(content != null && !content.trim().equals("")) {

//主題內容或者某個回覆的內容包含子串:

jdoql += " && (content.startsWith(_content) || replies.contains(aReply) && aReply.content.startsWith(_content))";

paramNames += ",String _content";

params.add("%"+content);

q.declareVariables("Reply aReply");

}

String ip = request.getParameter("ip");

if(ip != null && !ip.trim().equals("")) {

jdoql += " && ip == _ip";

paramNames += ",String _ip";

params.add(ip.trim());

}

if(jdoql.startsWith(" && ")) jdoql = jdoql.substring(4);

if(paramNames.startsWith(",")) paramNames = paramNames.substring(1);

q.setFilter(jdoql);

q.declareParameters(paramNames);

pageContext.setAttribute("topics",q.executeWithArray(params.toArray()));

%>

下面是符合條件的主題列表: &nbsp;&nbsp;<a href="search.jsp">搜索主題</a>&nbsp;&nbsp;<input type=button value="發表新主題" onclick="fmPost.title.focus()">

<table border=1>

<tr><th>標題</th><th>內容長度</th><th>發表時間</th><th>IP地址</th><th>回覆數</th><th>最後回覆</th></tr>

<c:forEach var="topic" items="${topics}">

<tr>

<td><a href="topic.jsp?id=${topic.jdoGetObjectId()}">${topic.title}</a></td>

<td>${topic.length}</td>

<td>${shortTime.format(topic.postTime)}</td>

<td>${topic.ip}</td>

<td>${topic.replyCount}</td>

<td>${shortTime.format(topic.lastUpdate)}</td>

</tr>

</c:forEach>

</table>

  這個頁面執行了一個根據提交的搜索條件拼裝的JDOQL,完成了搜索功能。界面如下:
CSDN_Dev_Image_2003-12-622484914.jpg

4 功能擴展

  本章中將討論一些對這個論壇功能進行增強或者解決一些傳統JDBC難以解決的問題。

4.1 字符串長度限制

  可能大家有過這樣的經驗:在一個論壇上原創了一篇很長的文章,結果提交時服務器卻提示數據庫字段長度不夠,只能拆成幾貼來發表。這種情況並不少見,有點影響發貼者的積極性。於是,我們希望能夠做到:對這種長文本類型的輸入信息不作長度限制,想輸多少就輸多少。即使要限制長度,也是通過其它方式限制,而不是被動地受數據庫服務器的限制。

  現在我們可以利用JDOGenie對java.util.List的支持(主流的JDO產品都支持這個JDO可選特性)來技巧性地實現無限制的字符串。方法是將Post.content屬性的聲明改爲List,並在system.jdo中設置該List的元素類型爲java.lang.String(通過JDOGenie工作臺進行設置),並在Post.content的getter/setter方法中進行一些處理(方法的聲明不需要改變,調用的JSP也不需要改變)。具體方法請參考JDOCentral上的筆者共享出來的一個JDO長字符串處理工具類:{http://www.jdocentral.com/forums/index.php?act=ST&f=11&t=564&s=56744171186d115a997fdefbdb46d4a5} 。

  原理清楚後,我們將這段工具代碼放到Sys類中,在Sys.java中加入兩個新的函數:

/**

* 將長字符串分割存放在一個列表中

* @param value 需要設置的字符串

* @return List類型的字符串片斷列表,請將之設到JDO對象的相關屬性中。

*/

public static List setLongString(String value) {

if(value == null) return null;

int len = value.length();

int count = (len+partSize-1)/partSize;

List list = new ArrayList(count);

for(int i = 0; i < count; i++) {

int from = i*partSize;

list.add(value.substring(from,Math.min(from+partSize,len)));

}

return list;

}

/**

* 從片斷列表中取得長字符串

* @param list 片斷列表

* @return 拼裝後的長字符串

*/

public static String getLongString(List list) {

if(list == null) return null;

if(list.size() == 1) return (String)list.get(0); //for better performance

//here some cache may be used for faster speed

StringBuffer sb = new StringBuffer();

for(Iterator itr = list.iterator(); itr.hasNext(); ) sb.append(itr.next());

return sb.toString();

}

private static int partSize = 2000;

  然後,在Post類中進行一下修改:

/** 貼子內容 */

List content;

public String getContent() {

return Sys.getLongString(content);

}

public void setContent(String value) {

content = Sys.setLongString(value);

}

最後,還需要修改一下元數據文件system.jdo(可在JDOGenie工作臺中進行):

<?xml version="1.0" encoding="UTF-8"?>

<jdo>

<package name="jdobbs">

<class name="Post">

<field name="content">

<collection element-type="java.lang.String" />

</field>

</class>

<class name="Reply" persistence-capable-superclass="Post" />

<class name="Topic" persistence-capable-superclass="Post">

<field name="replies">

<collection element-type="Reply">

<extension vendor-name="jdogenie" key="inverse" value="topic" />

</collection>

</field>

</class>

</package>

</jdo>

  到此爲止,代碼就算是修改完成了,現在只剩下數據庫結構還沒有同步。如果不用保留前面的數據,我們簡單地在JDOGenie工作臺中重建數據庫即可,如果需要保留數據,我們可以看看新的數據結構與舊的有什麼差別,進行相應的“ALTER TABLE …”和數據複製“INSERT INTO … ”即可。本例中需要進行的SQL操作是:

create table post_content (

post_id INTEGER not null,

seq INTEGER not null,

val VARCHAR(255),

constraint pk_post_content primary key (post_id, seq)

) TYPE = InnoDB;

insert into post_content

select post_id,1,content from post;

alter table post drop column content;

  還有一點需要注意的是,針對Post.content進行查詢的JDOQL需要從

content.startsWith(…)

  改成:

content.contains(s) && s.startsWith(…)

4.2 更多擴展功能

  到現在爲止,我們已經用JDO和JSP2.0完成了一個具備基本功能的論壇系統,在此基礎上,我們還可以擴展更多的功能,比如:

l 貼子列表的分頁處理

2 論壇分板塊

3 會員功能

4 個性化

5 版主功能

6 審覈
   對發貼內容進行審查,通過才能顯示出來

7 積分
  爲調動用戶積極性,加上積分功能,以及引申而出的排行榜、評價投票等等

8 文件上傳
  允許用戶上傳圖片或其它與貼子相關的附件

9 反惡意灌水機制
  隔段時間才能再發貼,或圖片數字驗證等方式

l0 後臺日誌
  爲記錄論壇會員和管理員的各種動作,最好在系統中建立一套日誌機制,便於出現問題時查詢。這方面,首選Log4j({http://jakarta.apache.org/log4j/} )這個第三方組件來完成日誌輸出任務。

  不過這些功能已經超出本文的範圍,這裏不再深入開發,有興趣的讀者可以自己完成。

4.3 Resin數據庫連接池的使用

  如果希望將數據庫的配置放到應用之外進行,我們可以採用Resin的數據庫連接池作爲JDO的數據庫入口。這裏,我們可以採用一個虛擬的JDBC驅動來將Resin的連接池包裝一下,象普通數據庫一樣給JDOGenie提供數據庫連接服務。

  具體的方法請參考JDOCentral上的代碼共享論壇中的貼子:

{http://www.jdocentral.com/forums/index.php?act=ST&f=11&t=547&s=a8f11e9b4343a987a4dc8ed4988c7607}

4.4 團隊開發的建議

  如果讀者在閱讀完本篇文章後,帶領一個團隊JDO來真正開發WebApp數據庫應用的話,最好採用CVS(客戶端建議WinCVS)和BugZilla來控制代碼變更、Bug跟蹤和需求變化。當然,這些與JDO沒有多大的關係,不過都是筆者實踐過程中非常有用的經驗。

5 JDO1.0侷限性與JDO2.0

  目前我們還只能在JDO1.0上開發數據庫應用,由於JDO還比較年輕,自然存在一些侷限性,下面列出影響較大的一些:

1. 增加額外步驟,配置複雜(相對於直接的JDBC)

2. 對數據模型有一定限制(必須有一個無參構造器,屬性訪問需要getter和setter)

3. 雙向對象關係的處理太欠缺(JDO2.0計劃中的自動維護的對象關係將解決這些)

4. JDOQL的API稍顯累贅(declare一大堆東西,比ODMG的OQL標準還是不如)

5. 沒有數據庫統計功能(count(),max(),avg()等等,不過已經在JDO2.0計劃中)

  JDO2.0將會有專門針對關係數據庫的子規範:JDO/R,其中將使JDO能覆蓋大多數常用的數據庫功能(基於SQL92標準)。當然,有些複雜的SQL操作還是需要JDBC才能完成的,筆者就處理過一些長達上百行的SQL語句。

  JDO2.0專家組目前已經成立,並且已經召開JDO2.0啓動會議,指派了負責規範的各個方面的人員,相信很快就會有一個新的Java規範請求(JSR)正式出現在{www.jcp.org}中。據專家估計,一年半後,JDO2.0的規範將會最終完成,同時,也將出現各個廠家推出的JDO2.0產品。

6 參考資料

  JDO出世後,在Java世界引起很大反響,各種褒貶不一的文章層出不窮,包括JavaPro、IBM開發社區、JavaSkyline等技術網站中都有很多文章發表。O’Reilly、PrenticeHall等出版社也先後出版了幾本JDO的書。相信以後還會有更多的資料出現。下面介紹一些最爲重要的資源。

6.1 主力網站:{www.jdocentral.com}

  這個網站是JDO的核心網站,是推動JDO發展的主力。主流的JDO產品和文章都在這個網站上。Sun公司本身也是這個網章的主要成員之一。

有趣的是,這個網站的經典文章區不光收錄了主要媒體上發表過的一些JDO相關文章,還收錄了幾乎所有的反對JDO的文章,這方面也體現了一點客觀、公正的態度。

6.2 主力討論區:{www.jdocentral.com/forums}

  作爲JDO主力網站中的論壇,這裏是JDO的功能、規範、接口、實例等等方面的熱烈討論的地方,很多JDO規範和細節在這裏都有體現。這也是作爲局外人(JDO Specification Expert Group之外)對JDO提出各種改進意見的最好的地點。目前規範中的一些API和JDO2.0提出的一些API方案就是來源於這裏的討論。

6.3 中文資源

  前面提到的都主要是英文方面的網站,雖然權威,但對不熟英文的開發人員來說,幫助不是很大,下面介紹一些中文方面的JDO資源。

6.3.1 文章與評論集錦{www.CSDN.net}

  在{www.csdn.net}上的文檔搜索條中,輸入“JDO”,並選擇“文檔標題”作爲要搜索的目標,並點擊“搜索”,將會列出一堆關於JDO的文章。當然,也有很多筆者的文章在其中,讀者也可以查看筆者的專欄:{http://www.csdn.net/develop/author/netauthor/sun2bin/}

6.3.2 Q&A論壇:{http://www.javaresearch.org/forum/forum.jsp?column=308}

  這是一箇中文的專門爲JDBC和JDO開發設立的論壇板塊,有什麼問題可以直接去這個論壇提問,或者看看別人都經歷過什麼樣的問題。有很多常見問題都已經有詳細的回答。

6.3.3 精華資料下載:{http://www.javaresearch.org/dn.jsp}

  這個下載區中有一些關於JDO的書籍(PDF)、教程、示範代碼等等,是學習的好材料,也是筆者當初學習JDO的入門資料。

  本文的版權屬於筆者本人,但歡迎轉載,前提是註明出處和原作者。另外,歡迎在我的專欄中查看我的另幾篇文章,並提出寶貴意見!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章