第四课:存储数据
关于”基于flex4技术从零开发flex博客系统”这个系列,我已经写过三课。今天在Google中搜索了一下标题, 发现有许多网站已经偷偷转载了。转载没有问题,但是拜托,请附上原文链接好不好,这不仅是对作者的尊重,也是对读者的负责。昨天我对前三课进行了一些相当 程度的更新,由于这些转载者的小气,他们的读者是不可能获取更新了。不过没有关系,有意继续关注的朋友,只要在Google中搜索”基于flex4技术从零开发flex博客系统“就可以找到这个系列的最新版本,相信我的博客凭借原创以及等于5的Google PR值,会一直稳居搜索排行之首。
通过前三课我艰苦卓绝的努力,客户端与服务端通讯已经没有问题了。这对于一个没有学过flex4,没有用过java的初学者,已经相当不容易了。到目前为止,开发博客系统的准备工作,已经仅剩最后一项了:数据存储。
Google App Engine没有数据库的概念,不过app engine提供了JDO存储接口,google充许开发者直接定义、存储、查询、修改实体(entity)。
一,数据定义
我在sban.flexblog package下添加一个名为Greeting的实体类,这个一个POJO(Plain Old Java Object),意即简单朴素的java对象。Greeting.java类的代码如下:
import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
/**
* @author sban.li
*
*/
@PersistenceCapable(identityType=IdentityType.APPLICATION)
public class Greeting {
@PrimaryKey
@Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private String user;
@Persistent
private String greetingContent;
@Persistent
private Date date;
public Greeting(String user, String content, Date date)
{
this.user = user;
this.greetingContent=content;
this.date = date;
}
public String getUser()
{
return this.user;
}
public Long getId()
{
return this.id;
}
public String getGreetingContent()
{
return this.greetingContent;
}
public Date getDate()
{
return this.date;
}
}
关于Pojo实体定义的简要说明:
1,@PrimaryKey用于定义实体的主键
2,@Persistent用于标识该变量名称将被存储
3,valueStrategy=IdGeneratorStrategy.IDENTITY设置id为自增模式,这意味着实例化该实体类时不用给id赋值
4,identityType=IdentityType.APPLICATION是干什么用的,不清楚,暂时不用管它
二,数据存储
实体定义好了,如何存储呢?这要用到javax.jdo.PersistenceManager,在sban.flexblog.managers package下新建一个类,名为PMFactory.java,代码如下:
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;
/**
* @author sban.li
*
*/
public class PMFactory {
private static final PersistenceManagerFactory pmfInstance = JDOHelper
.getPersistenceManagerFactory("transactions-optional");
private PMFactory() {
}
public static PersistenceManagerFactory getInstance() {
return pmfInstance;
}
}
该工厂用于返回一个PersistenceManagerFactory的单例,其中transactions-optional参数是在src/META-INF/jdoconfig.xml中定义的:
…
</persistence-manager-factory>
但是PersistenceManagerFactory并不是数据的管理者,PersistenceManager才是,现在我们可以用 PMFactory.getInstance().getPersistenceManager()方法获取PersistenceManager了。
我们需要提供一个新接口,这个接口充许用户提交用户名与greeting内容参数,并存储到google app engine。修改HelloWorld.java,添加如下代码:
.getPersistenceManager();
public Boolean greet2(String user, String content) {
Boolean result = true;
Greeting greeting = new Greeting(user, content, new Date());
try {
pm.makePersistent(greeting);
} catch (Exception e) {
result = false;
} finally {
pm.close();
}
return result;
}
pm.makePersistent用于存储Greeting实例对象。使用之后记得要记得调用pm.close。该接口需要两个参数,一为用户名称,一为greeting内容。如果存储成功,将返回true。
三,客户端代码调用
好了,server端已经准备就绪,现在客户端可以调用了。修改Index.mxml,修改greetViaRemoting方法,代码如下:
<Script>
<![CDATA[
import mx.controls.Alert;
import flash.net.URLLoader;
import flash.net.URLRequest;
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.AbstractOperation;
import mx.rpc.remoting.RemoteObject;
import mx.messaging.ChannelSet;
import mx.messaging.channels.AMFChannel;
import mx.collections.ArrayCollection;
import mx.managers.CursorManager;
[Bindable]
private var _greetingData : ArrayCollection;
private var _remotingObj : RemoteObject = new RemoteObject("GenericDestination");
private function configRemoting() : void
{
_remotingObj.source = "sban.flexblog.HelloWorld";
_remotingObj.endpoint = "weborb.wo";
}
private function greetViaRemoting() : void
{
var op : AbstractOperation = _remotingObj.getOperation("greet2");
op.addEventListener(ResultEvent.RESULT,
function(event : ResultEvent) : void
{
Alert.show( event.result.toString() );
};
);
op.send(vNameTxt.text,vContentTxt.text);
}
]]>
</Script>
<layout>
<BasicLayout />
</layout>
<VGroup width="100%">
<HGroup>
<Label text="user:" />
<FxTextInput id="vNameTxt" text="sban" />
</HGroup>
<HGroup>
<Label text="content:" />
<FxTextInput id="vContentTxt" text="greeting content" />
</HGroup>
<HGroup>
<FxButton id="vSendBtn" label="remoting greet" click="greetViaRemoting()" />
</HGroup>
</VGroup>
<TextBox text="by sban" color="gray" bottom="10" right="10" />
</FxApplication>
其中configRemoting将在Application initialize时调用,用于初始化RemoteObject对象。BasicLayout用于声明当前Application使用absolate布局,详见new language tag Private and layout in flex4。
编译,运行,点击greeting,弹出如下提示框:
四,有问题,需要调试了
貌似没有问题,貌似数据已经存进Google了。但是第二次单击提交,就有问题了,Alert了两次。为什么点击一次button,弹出了两个提示框?难道接口被调用了二次?
为了找到原因所在,我必须要采用Debug模式了。在本地采用Debug模式之前,需修改Flex Project的属性里的output folder url为http://localhost:8080/,并把RemoteObject的endpoint设为http: //localhost:8080/weborb.wo。但是在正式布署到Google app engine时,需记得移除这些设置。
通过debug,我发现greet2接口确实仅调用了一次,但是Alert.show却执行了两次。这说明AbstractOperation是一 个引用对象,第一次调用_remotingObj.getOperation(”greet2″)返回的对象与第二次调用返回的对象是同一个对象,我们在 同一个对象上添加了两次ResultEvent事件监听。handler做为内嵌函数变量,虽然前后名称相同,但因是局部函数变量,所以内部ID并不相 同,是两个完全不同的对象。结果便是,每单击一次,Alert便会多一次。
为了验证我的想法,我查看了RemoteObject的sdk源码,在其及其父类AbstractService中,有如下相关代码:
{
var op:AbstractOperation = super.getOperation(name);
if (op == null)
{
op = new Operation(this, name);
_operations[name] = op;
op.asyncRequest = asyncRequest;
}
return op;
}
…
public function getOperation(name:String):AbstractOperation
{
var o:Object = _operations[name];
var op:AbstractOperation = (o is AbstractOperation) ? AbstractOperation(o) : null;
return op;
}
由上面源码可以看出,在第一次调用时,flex会实例化一个Operation,并缓存在_operations之中。第二次,便是直接取出了。
思考1:读者可以思考一下,为什么flex要在父类中取出AbstractOperation引用,而在子类中实例化Operation并存储。
签于以上原因,我修改了我的greetViaRemoting的代码,如下:
{
var op : AbstractOperation = _remotingObj.getOperation("greet2");
var handler : Function = function(event : ResultEvent) : void
{
op.removeEventListener(ResultEvent.RESULT, handler);
Alert.show( event.result.toString() );
};
op.addEventListener(ResultEvent.RESULT, handler);
op.send(vNameTxt.text,vContentTxt.text);
}
修改之后,便没有重复弹出的问题了。
sban:在flex开发中,使用之后的事件监听一定要记得移除,特别对于通过内嵌函数添加的事件监听。反复的移除与添加事件监听,并不会 影响程序性能,相反如果只添加而不移除,才会让程序运行愈加沉重。这就好比做人,财富、名誉、资历、爱情等等这些身外之物,多了并不是负担,但要是每一样 都放在心里,就累了。
有些学者(学习者)可能说了,貌似数据存储成功了,但是我并不知道到底有没有真正存进Google云啊,怎么查看我存储的数据呢?这些存储如何更 新,如何删除?可惜Google并没有直接给我们开放直接访问App Engine数据库的权限。在我想继续描述app engine是如何让开发者查询、修改、删除数据时,我发现这一课已经写的相当长了。我的肩膀有点酸了,我想我应该去健身了。读者们也需要体息了。
本课最终源码:见下一课源码
sban 2009/4/26 于北京
本文为sban原创,作者保留所有权利,如需转载请保留作者及原文链接。
flex选修课系列:基于flex4技术从零开发flex博客系统