在前面讀者看到的本文的《配置篇》中,我們介紹了用于本文中的基于JDO的WebApp開發的各種環境配置,尤其是本文選用的JDO産品:JDOGenie。JDOGenie的特點就是圖形配置工具功能強大,友善使用,學習JDO非常容易。
我們前面已經完成了一個基本的資料對象模型,并且,每個資料類的屬性都用getter/setter進行了封裝。這裡重溫一下這個資料模型:
[img]http://lcspace.nease.net/c-j2ee/new-9.files/CSDN_Dev_Image_2003-12-62248490.gif[/img]
這個類圖是非常重要的,相當于傳統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的運作情況:我們點選工作台左邊的按鈕“Grid”進入資料類清單區,選中某個類,點選上面工具條的第11個按鈕“View all instances of the selected class…”或者直接按Ctrl+E,系統就會列出該類在資料庫中的所有執行個體對象。不過我們這裡還沒有任何資料,是以什麼也沒列出來,以後可以通過這個功能檢視某個類的所有執行個體。如果需要更細緻的查詢,可以進入左邊四個按鈕進入JDO查詢區,使用JDOQL或者SQL來查詢資料。JDOQL的查詢界面如圖:
[img]http://lcspace.nease.net/c-j2ee/new-9.files/CSDN_Dev_Image_2003-12-62248494.jpg[/img]
好了,關于JDOGenie的細節已經講完了。之是以用這麼大的篇幅介紹JDOGenie的配置,目的是讓讀者能夠最快速地了解JDO需要的配置資訊,并能最快地看到JDO産品的運作。下面我們來真正地開始實作我們的論壇功能。
3 業務邏輯的實作
為了簡單、直覺地顯示基于JDO的業務開發流程,我們在接下來的功能實作中采用JSP直接通路JDO API來實作資料對象的擷取和操縱,就不再累贅地使用JavaBean對話控制器了。
為了使每個頁面的代碼更簡單,并且又具備調用JDO API、使用常用的Java包、使用JSTL的功能,我們寫一個專門的頭頁面,用于其它頁面包含,這樣,可以使用其它頁面的代碼更簡潔易讀。
<%@page
contentType="text/html; CHARSET=utf8"
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=utf8" %>
<%@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()}" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >${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中編輯時的界面是:
[img]http://lcspace.nease.net/c-j2ee/new-9.files/CSDN_Dev_Image_2003-12-62248496.jpg[/img]
也許熟悉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=utf8" %>
<%@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" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >傳回主題清單</a>
<p><a href="topic.jsp?id=${topic.jdoGetObjectId()}" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >進入該主題頁面</a>
我們發貼的過程先是生成一個Topic對象,然後使用JDO的API開始一個事務,标記這個新生成的topic對象為存儲執行個體,然後送出事務,這樣就完成了對象的儲存過程。當事務送出後,這個topic對象就具備了由JDOGenie生成的一個資料庫辨別,是以我們可以在頁面尾部的“進入該主題頁面”連結中使用其辨別:${topic.jdoGetObjectId()}
現在我們就可以發表主題了。發表了一系列主題後,我們的論壇首頁就比較充實了:
[img]http://lcspace.nease.net/c-j2ee/new-9.files/CSDN_Dev_Image_2003-12-62248498.jpg[img]
3.3 閱讀主題及回複
前面的主題清單頁面中的每個主題已經有連結到topic.jsp頁面,現在我們來編寫這個頁面:
<%@page contentType="text/html; CHARSET=utf8" %>
<%@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="." target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >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=utf8" %>
<%@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" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >傳回主題清單</a>
<p><a href="topic.jsp?id=${param.topicId}" target="_blank" rel="external nofollow" >進入所回複的主題頁面</a>
在這個頁面的開始部分,我們先通過回複表單傳遞過來的被回複的主題辨別取得該主題,然後新生成一個Reply對象,設定好其屬性後,将這個新生成的Reply對象添加到Topic的replies清單中。我們這段代碼中并沒有使用pm.makePersistent()方法,因為将reply對象加到topic對象的replies清單中時,JDO已經标記該reply對象需要儲存,這個特性就是JDO中的“可達性存儲”概念。如果一個新生成的對象被一個已經存在于資料庫中的對象引用,那麼,送出事務時這個新對象将被儲存,甚至如果這個新對象又引用了另一個新生成的對象,另一個新對象也會被儲存。這個特性有時候可以減少我們的代碼複雜性。
細心的讀者可能注意到了,最後一行代碼中使用了“${param.topicId}”來直接将送出參數中的主題貼辨別拼裝到傳回主題頁面的連結中。這使用到了JSP2.0的隐含環境變量param。
我們測試回複功能,回複了幾個貼子後,主題頁面的效果如圖所示:
[img]http://lcspace.nease.net/c-j2ee/new-9.files/CSDN_Dev_Image_2003-12-622484910.jpg[/img]
而回到論壇首頁,界面如下圖:
[img]http://lcspace.nease.net/c-j2ee/new-9.files/CSDN_Dev_Image_2003-12-622484912.jpg[/img]
3.5 進階搜尋功能
到此,我們已經完成了基本的發貼和回複功能,現在做一個組合條件搜尋的功能,以展現JDOQL的特點。
回顧一下搜尋功能需求:“讓網友可以根據時間、主題标題、内容或回複内容、IP搜尋主題”,我們需要編寫一個搜尋頁面,包含一個搜尋條件輸入表單,這個表單直接送出到本頁面,送出後,表單中将顯示所輸入的條件,而在表單下方,列出符合條件的主題。
源碼 search.jsp:
<%@page contentType="text/html; CHARSET=utf8" %>
<%@include file="/inc.jsp" %>
<title>主題:${topic.title}</title>
您現在的位置:<a href="." target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >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()));
%>
下面是符合條件的主題清單: &&<a href="search.jsp" target="_blank" rel="external nofollow" >搜尋主題</a>&&<input type=button value="發表新主題" οnclick="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()}" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" >${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,完成了搜尋功能。界面如下:
[img]http://lcspace.nease.net/c-j2ee/new-9.files/CSDN_Dev_Image_2003-12-622484914.jpg[/img]
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中加入兩個新的函數:
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;
}
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 主力網站:{file:///D:/_BB/JavaResearch/www.jdocentral.com}
這個網站是JDO的核心網站,是推動JDO發展的主力。主流的JDO産品和文章都在這個網站上。Sun公司本身也是這個網章的主要成員之一。
有趣的是,這個網站的經典文章區不光收錄了主要媒體上發表過的一些JDO相關文章,還收錄了幾乎所有的反對JDO的文章,這方面也展現了一點客觀、公正的态度。
6.2 主力讨論區:{file:///D:/_BB/JavaResearch/www.jdocentral.com/forums}
作為JDO主力網站中的論壇,這裡是JDO的功能、規範、接口、執行個體等等方面的熱烈讨論的地方,很多JDO規範和細節在這裡都有展現。這也是作為局外人(JDO Specification Expert Group之外)對JDO提出各種改進意見的最好的地點。目前規範中的一些API和JDO2.0提出的一些API方案就是來源于這裡的讨論。
6.3 中文資源
前面提到的都主要是英文方面的網站,雖然權威,但對不熟英文的開發人員來說,幫助不是很大,下面介紹一些中文方面的JDO資源。
6.3.1 文章與評論集錦{file:///D:/_BB/JavaResearch/www.CSDN.net}
在{file:///D:/_BB/JavaResearch/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的入門資料。
本文的版權屬于筆者本人,但歡迎轉載,前提是注明出處和原作者。另外,歡迎在我的專欄中檢視我的另幾篇文章,并提出寶貴意見!
對該文的評論 人氣:5318
JasonCao (2004-2-13 0:16:10)
建議開發使用MVC模式(比如,應用struts架構),這樣就可以專注使用JDO構造MODEL一塊的功能了。并且建議不要使用PersistentCapable接口的方法,因為這是JDOHancer自動在位元組碼上增加的,可以使用JDOHelper的一些方法,比如JDOHelper.getObjectId(pc)等。不過,本文也可以作為基本教程了,但作為實際應用開發,還是要使用MVC的,否則無法控制項目規模。
Reve (2004-1-29 23:51:06)
tutorial不錯,學習這個tutorial,感覺不錯,也準備用JDO的方法來改造自己的一個東西。不過JDOGenie并非免費,感覺可能會受限制其實有點盼望,mysql自己出自己的JDO的實作
andersonmao (2004-1-18 17:17:33)
JSP2.0的JSTL(如J2SDKEE1.4中) 不是 <%@taglib prefix="c" uri="http://java.sun.com/jstl/core" %> 是 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> =============================================================== JSP EL 在Tomcat裡 ${topic.jdoGetObjectId()} ${shortTime.format(topic.lastUpdate)} 方法不能運作,不是JSP2.0的規範嗎?
andersonmao (2004-1-5 16:29:44)
支援一下
sun2bin (2003-12-19 19:08:35)
最新消息:JDOGenie2.2.0beta9今天釋出了,改動資料類後可自動同步資料庫結構! http://www.jdogenie.com/download.html
ycflypig (2003-12-18 22:22:17)
最近的J2EE中有JSP2.0
nanye18 (2003-12-18 12:28:07)
jeffyan77你搞錯了!上SUN上看看哪個是新版吧。
jeffyan77 (2003-12-18 6:15:16)
JSP的最新版本是1.2,怎麼會有JSP 2.0?
dengzi725 (2003-12-15 11:50:13)
lihai
mechiland (2003-12-12 18:48:49)
作者很用心,不錯,捧一下場,辛苦了!