- 浏览: 19674 次
- 性别:
-
文章分类
最新评论
在前面读者看到的本文的《配置篇》中,我们介绍了用于本文中的基于JDO的WebApp开发的各种环境配置,尤其是本文选用的JDO产品:JDOGenie。JDOGenie的特点就是图形配置工具功能强大,方便使用,学习JDO非常容易。
我们前面已经完成了一个基本的数据对象模型,并且,每个数据类的属性都用getter/setter进行了封装。这里重温一下这个数据模型:
这个类图是非常重要的,相当于传统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的查询界面如图:
好了,关于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()}">${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中编辑时的界面是:
也许熟悉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">返回主题列表</a>
<p><a href="topic.jsp?id=${topic.jdoGetObjectId()}">进入该主题页面</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=".">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">返回主题列表</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。
我们测试回复功能,回复了几个贴子后,主题页面的效果如图所示:
而回到论坛首页,界面如下图:
3.5 高级搜索功能
到此,我们已经完成了基本的发贴和回复功能,现在做一个组合条件搜索的功能,以体现JDOQL的特点。
回顾一下搜索功能需求:“让网友可以根据时间、主题标题、内容或回复内容、IP搜索主题”,我们需要编写一个搜索页面,包含一个搜索条件输入表单,这个表单直接提交到本页面,提交后,表单中将显示所输入的条件,而在表单下方,列出符合条件的主题。
源码 search.jsp:
<%@page contentType="text/html; CHARSET=utf8" %>
<%@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()));
%>
下面是符合条件的主题列表: &&<a href="search.jsp">搜索主题</a>&&<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,完成了搜索功能。界面如下:
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 主力网站:{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)
作者很用心,不错,捧一下场,辛苦了!
我们前面已经完成了一个基本的数据对象模型,并且,每个数据类的属性都用getter/setter进行了封装。这里重温一下这个数据模型:

这个类图是非常重要的,相当于传统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的查询界面如图:

好了,关于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()}">${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中编辑时的界面是:

也许熟悉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">返回主题列表</a>
<p><a href="topic.jsp?id=${topic.jdoGetObjectId()}">进入该主题页面</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=".">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">返回主题列表</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。
我们测试回复功能,回复了几个贴子后,主题页面的效果如图所示:

而回到论坛首页,界面如下图:

3.5 高级搜索功能
到此,我们已经完成了基本的发贴和回复功能,现在做一个组合条件搜索的功能,以体现JDOQL的特点。
回顾一下搜索功能需求:“让网友可以根据时间、主题标题、内容或回复内容、IP搜索主题”,我们需要编写一个搜索页面,包含一个搜索条件输入表单,这个表单直接提交到本页面,提交后,表单中将显示所输入的条件,而在表单下方,列出符合条件的主题。
源码 search.jsp:
<%@page contentType="text/html; CHARSET=utf8" %>
<%@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()));
%>
下面是符合条件的主题列表: &&<a href="search.jsp">搜索主题</a>&&<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,完成了搜索功能。界面如下:

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 主力网站:{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)
作者很用心,不错,捧一下场,辛苦了!
相关推荐
7.4 保护Web应用程序 7.4.1 代理Spring Security的过滤器 7.4.2 处理安全上下文 7.4.3 提示用户登录 7.4.4 处理安全例外 7.4.5 强制Web安全性 7.4.6 确保一个安全的通道 7.5 视图层安全 7.5.1 有条件地渲染...
7.4 保护Web应用程序 7.4.1 代理Spring Security的过滤器 7.4.2 处理安全上下文 7.4.3 提示用户登录 7.4.4 处理安全例外 7.4.5 强制Web安全性 7.4.6 确保一个安全的通道 7.5 视图层安全 7.5.1 有条件地渲染...
7.4保护web应用程序 7.4.1代理springsecurity的过滤器 7.4.2处理安全上下文 7.4.3提示用户登录 7.4.4处理安全例外 7.4.5强制web安全性 7.4.6确保一个安全的通道 7.5视图层安全 7.5.1有条件地渲染内容 7.5.2...
在智慧园区建设的浪潮中,一个集高效、安全、便捷于一体的综合解决方案正逐步成为现代园区管理的标配。这一方案旨在解决传统园区面临的智能化水平低、信息孤岛、管理手段落后等痛点,通过信息化平台与智能硬件的深度融合,为园区带来前所未有的变革。 首先,智慧园区综合解决方案以提升园区整体智能化水平为核心,打破了信息孤岛现象。通过构建统一的智能运营中心(IOC),采用1+N模式,即一个智能运营中心集成多个应用系统,实现了园区内各系统的互联互通与数据共享。IOC运营中心如同园区的“智慧大脑”,利用大数据可视化技术,将园区安防、机电设备运行、车辆通行、人员流动、能源能耗等关键信息实时呈现在拼接巨屏上,管理者可直观掌握园区运行状态,实现科学决策。这种“万物互联”的能力不仅消除了系统间的壁垒,还大幅提升了管理效率,让园区管理更加精细化、智能化。 更令人兴奋的是,该方案融入了诸多前沿科技,让智慧园区充满了未来感。例如,利用AI视频分析技术,智慧园区实现了对人脸、车辆、行为的智能识别与追踪,不仅极大提升了安防水平,还能为园区提供精准的人流分析、车辆管理等增值服务。同时,无人机巡查、巡逻机器人等智能设备的加入,让园区安全无死角,管理更轻松。特别是巡逻机器人,不仅能进行360度地面全天候巡检,还能自主绕障、充电,甚至具备火灾预警、空气质量检测等环境感知能力,成为了园区管理的得力助手。此外,通过构建高精度数字孪生系统,将园区现实场景与数字世界完美融合,管理者可借助VR/AR技术进行远程巡检、设备维护等操作,仿佛置身于一个虚拟与现实交织的智慧世界。 最值得关注的是,智慧园区综合解决方案还带来了显著的经济与社会效益。通过优化园区管理流程,实现降本增效。例如,智能库存管理、及时响应采购需求等举措,大幅减少了库存积压与浪费;而设备自动化与远程监控则降低了维修与人力成本。同时,借助大数据分析技术,园区可精准把握产业趋势,优化招商策略,提高入驻企业满意度与营收水平。此外,智慧园区的低碳节能设计,通过能源分析与精细化管理,实现了能耗的显著降低,为园区可持续发展奠定了坚实基础。总之,这一综合解决方案不仅让园区管理变得更加智慧、高效,更为入驻企业与员工带来了更加舒适、便捷的工作与生活环境,是未来园区建设的必然趋势。
labelme标注的json转mask掩码图,用于分割数据集 批量转化,生成cityscapes格式的数据集
(参考GUI)MATLAB GUI漂浮物垃圾分类检测.zip
人脸识别项目源码实战
人脸识别项目实战
本仿真模型基于MATLAB/Simulink(版本MATLAB 2016Rb)软件。建议采用matlab2016 Rb及以上版本打开。(若需要其他版本可联系代为转换) CSDN详情地址:https://blog.csdn.net/qq_50594161/article/details/146242453sharetype=blogdetail&sharerId=146242453&sharerefer=PC&sharesource=qq_50594161&spm=1011.2480.3001.8118
实战练习分词、创建词表、文本处理
在智慧园区建设的浪潮中,一个集高效、安全、便捷于一体的综合解决方案正逐步成为现代园区管理的标配。这一方案旨在解决传统园区面临的智能化水平低、信息孤岛、管理手段落后等痛点,通过信息化平台与智能硬件的深度融合,为园区带来前所未有的变革。 首先,智慧园区综合解决方案以提升园区整体智能化水平为核心,打破了信息孤岛现象。通过构建统一的智能运营中心(IOC),采用1+N模式,即一个智能运营中心集成多个应用系统,实现了园区内各系统的互联互通与数据共享。IOC运营中心如同园区的“智慧大脑”,利用大数据可视化技术,将园区安防、机电设备运行、车辆通行、人员流动、能源能耗等关键信息实时呈现在拼接巨屏上,管理者可直观掌握园区运行状态,实现科学决策。这种“万物互联”的能力不仅消除了系统间的壁垒,还大幅提升了管理效率,让园区管理更加精细化、智能化。 更令人兴奋的是,该方案融入了诸多前沿科技,让智慧园区充满了未来感。例如,利用AI视频分析技术,智慧园区实现了对人脸、车辆、行为的智能识别与追踪,不仅极大提升了安防水平,还能为园区提供精准的人流分析、车辆管理等增值服务。同时,无人机巡查、巡逻机器人等智能设备的加入,让园区安全无死角,管理更轻松。特别是巡逻机器人,不仅能进行360度地面全天候巡检,还能自主绕障、充电,甚至具备火灾预警、空气质量检测等环境感知能力,成为了园区管理的得力助手。此外,通过构建高精度数字孪生系统,将园区现实场景与数字世界完美融合,管理者可借助VR/AR技术进行远程巡检、设备维护等操作,仿佛置身于一个虚拟与现实交织的智慧世界。 最值得关注的是,智慧园区综合解决方案还带来了显著的经济与社会效益。通过优化园区管理流程,实现降本增效。例如,智能库存管理、及时响应采购需求等举措,大幅减少了库存积压与浪费;而设备自动化与远程监控则降低了维修与人力成本。同时,借助大数据分析技术,园区可精准把握产业趋势,优化招商策略,提高入驻企业满意度与营收水平。此外,智慧园区的低碳节能设计,通过能源分析与精细化管理,实现了能耗的显著降低,为园区可持续发展奠定了坚实基础。总之,这一综合解决方案不仅让园区管理变得更加智慧、高效,更为入驻企业与员工带来了更加舒适、便捷的工作与生活环境,是未来园区建设的必然趋势。
人脸识别项目源码实战
学生信息管理系统是一个基于Java Web技术的综合性管理平台。通过此系统,可以实现对学生、教师、选课信息等的动态管理, 提升学校管理效率。系统采用分层架构设计,前端使用HTML、CSS,JavaScript和jQuery,后端基于Servlet,JSP和Spring框架,数据库采用MySQL。主要有四个大功能,学生管理( 增加学生信息、删除学生信息、修改学生信息、查询学生信息)、教师管理(增加教师信息、删除教师信息、修改教师信息、查询教师信息)、选课信息管理(添加选课、查询选课情况、删除选课记录)、系统管理( 登录与注册功能、 用户角色管理(老师,学生,管理员)、系统日志查看)。 技术架构 1.前端技术 HTML,CSS:静态页面布局与样式 JavaScript,jQuery:动态交互、DOM操作和AJAX请求 2.后端技术 Servlet:控制层,处理用户请求 JSP:页面动态生成 Spring:依赖注入,业务逻辑分离 3.数据库 MySQL:存储学生、教师,课程等数据 JDBC:数据库连接与操作
本课程是 PHP 进阶系列之 Swoole 入门精讲,系统讲解 Swoole 在 PHP 高性能开发中的应用,涵盖 协程、异步编程、WebSocket、TCP/UDP 通信、任务投递、定时器等核心功能。通过理论解析和实战案例相结合,帮助开发者掌握 Swoole 的基本使用方法及其在高并发场景下的应用。 适用人群: 适合 有一定 PHP 基础的开发者、希望提升后端性能优化能力的工程师,以及 对高并发、异步编程感兴趣的学习者。 能学到什么: 掌握 Swoole 基础——理解 Swoole 的核心概念,如协程、异步编程、事件驱动等。 高并发处理——学习如何使用 Swoole 构建高并发的 Web 服务器、TCP/UDP 服务器。 实战项目经验——通过案例实践,掌握 Swoole 在 WebSocket、消息队列、微服务等场景的应用。 阅读建议: 建议先掌握 PHP 基础,了解 HTTP 服务器和并发处理相关概念。学习过程中,结合 官方文档和实际项目 进行实践,加深理解,逐步提升 Swoole 开发能力。
人脸识别项目实战
人脸识别项目实战
功能简介:本工具可实现批量对照片文件的人脸识别,并按指定分辨率进行转换保存。 可为人脸识别采集系统提供很好的辅助工具。 软件基本于OPENVC开发,识别精确,转换高效。 人脸识别工具 +人脸采集处理
内容概要:本文探讨了利用肌长变化反馈控制(FCM-ML)和演员-评论家强化学习(ACRL-NGN)来有效实现人体上肢和下肢无意识姿态稳定的算法方法。通过构建一个包含949条肌肉和22个关节的全身计算模型,在不同初始姿势的情况下进行模拟试验,验证了这些方法的有效性和鲁棒性,结果显示FCM-ML方法比其他传统方法更适用于此类任务。研究指出人类及其他脊椎动物在无意识状态下,通过抗拮抗性的肌肉长度变化反馈机制来维持舒适状态下的自然身体姿势(NBP)。此外,研究还表明这种控制策略有助于机器人设计、运动员训练以及康复患者的治疗。 适用人群:生物力学、机器人学以及神经科学领域的研究人员、工程师,以及关注人体姿态控制及其应用的学者和技术人员。 使用场景及目标:①解释人和非人的脊椎动物如何在无意识情况下维持最佳姿势,特别是处于重力环境中的自然身体姿势(NBP)。②为机器人肌肉控制提供理论支持和发展方向,特别是在模拟多肌肉协调控制方面。③指导运动训练及病患恢复计划的设计与优化。 其他说明:研究发现ACRL-NGN结合FCM-ML不仅能够迅速有效地实现期望的姿态稳定性,而且不需要对肌肉分类,这使其在复
反编译apk重要的工具之一