X-Spirit的陋室铭

生活有的时候面临许多选择,这些选择让人困惑。 生活有的时候没有任何选择,这个时候让人压抑。 但无论生活有没有给你选择,你只能做一件事情: 排除困难,勇往直前!

2008年1月25日星期五

身心俱疲的我

从来不担心自己的未来

从来不去想自己的现在

四年的时间一晃而过

我用6个月的时间

换来了一个如此丑陋的自己

自私、敏感、不负责任……

所有这些

一个新的自卑的我诞生了!

 

我奔跑在发狂的路上

疯狂的笑着

可是当我回头

却发现四周一片沉寂

我彻底的感到疲惫

 

死亡?毁灭?

我现在还惧怕什么

这一切很快就结束了

 

路的尽头也许只有无尽的路

而我

在这漫长的路上该做些什么?

2008年1月9日星期三

理解Struts流程和ActionServlet

Struts controller基本功能是:
1. 截获用户的Http请求
2. 把这个请求映射到相应的Action类,如果这是此类收到的第一个请求,将初始化实例并缓存。
3. 创建或发现一个ActionForm bean实例(看配置文件是否定义),然后将请求过程移植到bean,填充所需的各个参数。
4. 调用Action实例的perform()方法并将ActioForm bean,Action Mapping对象,request和response对象传给它。

如:public ActionForword perform(ActionMapping mapping, ActionForm form,HttpServletRequest request,HttpServletResponse response)
5.perform返回一个ActionForword对象,此对象连接到相应的jsp 。

6.  ActionForward 出来后,还是交给 ActionServlet ,form / request 还在。只要没有 response ,request 还可以由 ActionServlet 转交给别的 aciton 而做别的事情。

ActionServlet使用ActionForm bean来保存请求的参数,这些bean的属性名称与HTTP请求参数的名称相对应,控制器将请求参数传递到ActionForm bean的实例,然后将这个实例传送到Action类。
      典型的ActionFrom bean只有属性的设置与读取方法(getXXX),而没有实现事务逻辑的方法。只有简单的输入检查逻辑,使用的目的是为了存储用户在相关表单中输入的最新数据,以便可以将同一网页进行再生,同时提供一组错误信息,这样就可以让用户修改不正确的输入数据。而真正对数据有效性进行检查的是ACTION类或适当的事务逻辑bean。

有几个部分共同组成了Struts 的Controller,用户的请求发送到ActionServlet中,ActionServlet调用RequestProssor开始处理用户请求的流程,在这个流程中,会查找ApplicationConfig,得到用户请求对应的Action,调用相应的Action来具体执行用户的请求,最后返回ActionForward,转向相应的流程。
        org.apache.struts.action.ActionServlet  是Struts Controller中最主要的部分,所有用户请求都会被发送到这里,所有的其它处理也必须从这里经过。它是从 HttpServlet中继承过来的, 当它接收到HTTP request的时候,不管是doGet()或者doPost()方法,都会调用process()方法。
protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException

{ RequestUtils.selectApplication( request, getServletContext() ); 

getApplicationConfig(request).getProcessor().process( request, response );

}
        一般情况下,我们不需要自己实现或者修改ActionServlet类,仅仅使用就可以了。某些情况下,我们可以自己扩展 ActionServlet类,从ActionServlet继承,实现自己的MyActionServlet类。覆盖其中的一些方法来达到你的特殊处理的需要。

       ActionServlet继承自javax.servlet.http.HttpServlet,所以在本质上它和一个普通的servlet没有区别,你完全可以把它当做一个servlet来看待,只是在其中完成的功能不同罢了。

        RequestProssor具体处理用户的request,作为一个request handler存在。同样,处理request的时候,会执行RequestProcessor类中的process(execute)方法。
      process 中调用的方法都是可以重载的,如果有需要,可以实现为自己特定的方法。比如,对于Locale问题,通常都是在系统最一开始加载的时候读取的,如果用户想在任何时刻都可以切换或者选择自己的Locale,我们就可以重载processLocale()方法。然后只需要在配置文件中加入段就可以了
        Action 类是实现整个体系的核心部分,它在客户请求、界面表示和业务逻辑之间起到一个桥梁的作用。每一个Action都用来处理某一项任务,或者进行一个业务操作。当然了,我们说一项任务不是说Action只实现一个业务操作方法,而是集中实现某一个功能单元。比如登录用的LogonAction、查找用的 SearchAction等等。Action是在RequestProcessor中,由processActionPerform方法调用的
      非常重要的一点:不要在Action中包含任何业务逻辑操作,而是应该调用一个Model层的JavaBean来实现你的业务逻辑操作。在某些情况下,可能包含少许表现逻辑。这样,就可以充分进行代码重用,比如上例中调用的IStorefrontService接口,这个接口在实现时完全可以不用考虑客户端的事情,所以它可以被其它部分或者其它系统所使用。否则的话,Action会变得非常难于理解,难于维护,代码也不能重用。
       struts-example工程的设计就是一个bug,它把业务逻辑封装到了Action类中
      在Action 的execute方法中,返回一个ActionForward类。

       ActionForward把配置文件中forward部分的信息包装起来,减少了应用程序和物理资源信息之间的耦合性。通过ActionMapping类,可以在配置文件中查找相应的forward信息。例如,对于一个 LoginAction它的配置信息可能是这样的:
     返回的ActionForward就会包含段中的信息。在ActionMapping类的findForward方法中,首先会根据查找forward的name查找是否有相应的forward段,如果没有,则在配置文件中的段中进行查找,如果还没有就会抛出一个例外。
        以前,页面上的输入数据都通过HTTP request提交,服务方检索出输入的数据,进行验证,然后将这些数据传递给其它组件进行业务处理。一切基本都需要手工编写代码进行操作,比较麻烦,也使代码变得复杂。
       ActionForm[org.apache.struts.action.ActionForm]用来收集用户的输入,并且把这些信息传递给Action对象,然后,在Action对象中,ActionForm中的数据被取出来传递给业务逻辑层进行处理。
ActionForm一方面作为一个缓冲区,临时存储用户输入的数据;另一方面,可以把ActionForm当成是HTTP和Action之间的一个防火墙,它可以验证输入数据的正确性,如果验证不通过,这个request是不会发送给Action进行处理的。
        ActionForm可以有两种Scope,request或者session。request就是只能在rquest到response,之后ActionForm就不可见了;session可以保存时间长一点。
       在ActionForm的Validate方法中返回的是ActionErrors对象。这个对象可以将错误信息都封装起来,并且自动把它们显示给用户。
       在相应JSP页面上添加,可以自动将ActionErrors中的错误信息显示出来。包括,每一个具体的,通过add添加的错误信息,和一个ErrorHeader和一个ErrorFooter,这些都可以通过配置文件指定,并且可以包含HTML语法。
Struts提供了四种自定义Tag库:
bean:struts-bean taglib包含在访问bean和bean属性时使用的tag,也包含一些消息显示的tag。
html:struts-html taglib包含用来创建struts输入表单的tag,和其它通常用来创建基于HTML用户界面的tag。
logic:struts-logic taglib包含的tag用来管理根据条件生成输出文本,和其它一些用来控制的信息。
template:struts-template taglib包含的tag用来定义模板机制。

以下是一个范例:

reguser.jsp:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/Struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/Struts-html.tld" prefix="html" %>
<html:html locale="true">
<head>
<title>RegUser</title>
<html:base/>
</head>
<body bgcolor="white">
<html:errors/>
<html:form action="/regUserAction" focus="logname">
<table border="0" width="100%">
  <tr>
    <th align="right">
      Logname:
    </th>
    <td align="left">
      <html:text property="logname" size="20" maxlength="20"/>
    </td>
  </tr>
  <tr>
    <th align="right">
      Password:
    </th>
    <td align="left">
      <html:password property="password" size="20" maxlength="20"/>
    </td>
  </tr>
  <tr>
    <th align="right">
      E-mail:
    </th>
    <td align="left">
      <html:password property="email" size="30" maxlength="50"/>
    </td>
  </tr>
  <tr>

struts-config.xml:

<Struts-config>
<form-beans>
<form-bean      name="regUserForm"
type="org.cjea.Struts.example. RegUserForm "/>
</form-beans>
<action-mappings>
<action  path="/regUserAction"
        type=" org.cjea.Struts.example.RegUserAction "
        attribute=" regUserForm "
        scope="request"
        validate="false">
      <forward name="failure"   path="/ messageFailure.jsp"/>
      <forward name="success"  path="/ messageSuccess.jsp"/>
</action>
</action-mappings>
</Struts-config>

RegUserForm:

import javax.Servlet.http.HttpServletRequest;
import org.apache.Struts.action.ActionForm;
import org.apache.Struts.action.ActionMapping;

public final class RegUserForm extends ActionForm{

  private String logname;
  private String password;
  private String email;

  public RegUserForm(){
    logname = null;
    password = null;
    email = null;
  }

  public String getLogName() {
    return this.logname;
  }
  public void setLogName(String logname) {
    this.logname = logname;
  }
  public void setPassWord(String password) {
    this.password = password;
  }
  public String getPassWord() {
    return this.password;
  }
  public void setEmail(String email) {
    this.email = email;
  }
  public String getEmail() {
    return this.email;
  }

  public void reset(ActionMapping mapping, HttpServletRequest request)
    {
        logname = null;
        password = null;
        email = null;
    }
}

RegUserAction :

import javax.Servlet.http.*;
import org.apache.Struts.action.*;

public final class RegUserAction extends Action
{

public ActionForward perform(ActionMapping mapping,
  ActionForm form,  HttpServletRequest req,
  HttpServletResponse res)
{ 
  String title = req.getParameter("title");
  String password = req.getParameter("password");
  String email = req.getParameter("email");
  }
}

2008年1月7日星期一

变化

这个周一似乎特别不寻常。

起先是看到大勇的博客,里面叙述了他经历了城市的变化以后的心情。看着他的博客从离开北京以后就一直以“离开北京的第XX天”为标题,突然有一种特别的伤感涌上心头。特别是当我看到那句“不知道班长起床有没有快一点……不知道现在谁在陪小三抽烟……”,心里一阵阵难受。。。想想自己也曾在某个晚上下班回家的路上跟小三提起了大勇,当时心里也是有一点点伤感,但是毕竟还不很强烈。但是看到大勇写的这些字句,心里就特别涌起一股伤感的热流,而这种感觉是在虹口足球场的地铁站入口处第一次碰到的。。。

接着就是Alb-Skyer回复的短信,搞得自己一头雾水。好像是出了什么特别麻烦的事情,让我一下子就担心起来。虽然AS不希望我多过问,但是心里总是有点放不下,不知道他到底怎样了。唉。。。或许是我太喜欢操心了吧。。。

晚上回来,Fbo也异常的忙,都没有时间回复我的QQ留言,而且变得超级冷漠。。。

不知道为什么,感觉自己是一成不变,但是周围的一切确实变化万千。。。

我真的晕了……

什么时候我也能发生变化呢?

一切都不确定……

2007年12月18日星期二

[转]采用左右值编码来存储无限分级树形结构的数据库表设计

之前我介绍过一种按位数编码保存树形结构数据的表设计方法,详情见:
  浅谈数据库设计技巧(上)

  该设计方案的优点是:只用一条查询语句即可得到某个根节点及其所有子孙节点的先序遍历。由于消除了递归,在数据记录量较大时,可以大大提高列表效率。但是,这种编码方案由于层信息位数的限制,限制了每层能所允许的最大子节点数量及最大层数。同时,在添加新节点的时候必须先计算新节点的位置是否超过最大限制。

  上面的设计方案必须预先设定类别树的最大层数以及最大子节点数,不是无限分级,在某些场合并不能采用,那么还有更完美的解决方案吗?通过 google的搜索,我又探索到一种全新的无递归查询,无限分级的编码方案——左右值。原文的程序代码是用php写的,但是通过仔细阅读其数据库表设计说明及相关的sql语句,我彻底弄懂了这种巧妙的设计思路,并在这种设计中新增了删除节点,同层平移的需求(原文只提供了列表及插入子节点的sql语句)。

  下面我力图用比较简短的文字,少量图表,及相关核心sql语句来描述这种设计方案:

  首先,我们弄一棵树作为例子:

商品
|---食品
|    |---肉类
|    |    |--猪肉
|    |---蔬菜类
|          |--白菜
|---电器
     |--电视机
     |--电冰箱

 
采用左右值编码的保存该树的数据记录如下(设表名为tree):
Type_id
Name
Lft
Rgt
1
商品
1
18
2
食品
2
11
3
肉类
3
6
4
猪肉
4
5
5
蔬菜类
7
10
6
白菜
8
9
7
电器
12
17
8
电视机
13
14
9
电冰箱
15
16
 
第一次看见上面的数据记录,相信大部分人都不清楚左值(Lft)和右值(Rgt)是根据什么规则计算出来的,而且,这种表设计似乎没有保存父节点的信息。下面把左右值和树结合起来,请看:
          1商品18
     +---------------------------------------+
                2食品11                                    12电器17
          +-----------------+                     +---------------------+
    3肉类6          7蔬菜类10          13电视机14       15电冰箱16
    4猪肉5           8白菜9
请用手指指着上图中的数字,从1数到18,学习过数据结构的朋友肯定会发现什么吧?对,你手指移动的顺序就是对这棵树的进行先序遍历的顺序。接下来,让我讲述一下如何利用节点的左右值,得到该节点的父节点,子孙节点数量,及自己在树中的层数。
 
假定我们要对节点“食品”及其子孙节点进行先序遍历的列表,只需使用如下一条sql语句:
select * from tree where Lft between 2 and 11 order by Lft asc
查询结果如下:
Type_id
Name
Lft
Rgt
2
食品
2
11
3
肉类
3
6
4
猪肉
4
5
5
蔬菜类
7
10
6
白菜
8
9
 
那么某个节点到底有多少子孙节点呢?很简单,子孙总数 =(右值-左值-1)/2
以节点“食品”举例,其子孙总数=(11-2-1)/ 2 = 4
 
同时,我们在列表显示整个类别树的时候,为了方便用户直观的看到树的层次,一般会根据节点所处的层数来进行相应的缩进,那么,如何计算节点在树中的层数呢?还是只需通过左右值的查询即可,以节点“食品”举例,sql语句如下:
select count(*) from tree where lft <= 2 and rgt >= 11
为了方便列表,我们可以为tree表建立一个视图,添加一个层数列,该类别的层数可以写一个自定义函数来计算。该函数如下:
CREATE FUNCTION dbo.CountLayer
(   
   
@type_id int
)
RETURNS int
AS
begin
   
declare @result int
   
set @result=0
   
declare @lft int
   
declare @rgt int
   
if exists (select 1 from tree where type_id=@type_id)
   
begin
       
select @lft=lft,@rgt=rgt from tree where type_id=@type_id
       
select @result = count(*) from tree where lft <= @lft and rgt >= @rgt
   
end   
   
return @result
end
GO
然后,我们建立如下视图:
CREATE VIEW dbo.TreeView
AS
SELECT type_id, name, lft, rgt, dbo.CountLayer(type_id) AS layer FROM dbo.tree ORDER BY lft
GO
 
给出对于给定某个节点,对该节点及其子孙节点进行先序遍历的存储过程:
CREATE PROCEDURE [dbo].[GetTreeListByNode]
(
   
@type_id int --给定节点标识
)
AS
declare @lft int
declare @rgt int
if exists (select 1 from tree where type_id=@type_id)
   
begin
       
select @lft=lft,@rgt=rgt from tree where type_id=@type_id
       
select * from TreeView where lft between @lft and @rgt order by lft asc
   
end
go
 
现在,我们使用上面的存储过程来列表节点“食品”及其所有子孙节点,查询结果如下:
Type_id
Name
Lft
Rgt
Layer
2
食品
2
11
2
3
肉类
3
6
3
4
猪肉
4
5
4
5
蔬菜类
7
10
3
6
白菜
8
9
4
 
 
采用左右值编码的设计方案,在进行类别树的遍历时,由于只需进行2次查询,消除了递归,再加上查询条件都为数字比较,效率极高,类别树的记录条目越多,执行效率越高。看到这里,相信不少人对这种设计方案有所心动了,下面让我们接着看看如何在这种表结构中实现插入、删除、同层平移节点(变更同层节点排序)的功能。
 
假定我们要在节点“肉类”下添加一个子节点“牛肉”,该树将变成:
                                     1商品18+2
                      +--------------------------------------------+
                2食品11+2                                  12+2电器17+2
          +-----------------+                                    +-------------------------+
    3肉类6+2   7+2蔬菜类10+2         13+2电视机14+2    15+2电冰箱16+2
    +-------------+
4猪肉5  6牛肉7  8+2白菜9+2
 
看完上图相应节点左右值的变化后,相信大家都知道该如何写相应的sql脚本吧?下面我给出相对完整的插入子节点的存储过程:
CREATE PROCEDURE [dbo].[AddSubNodeByNode]
(
   
@type_id int,
   
@name varchar(50)
)
AS
declare @rgt int
if exists (select 1 from tree where type_id=@type_id)
   
begin
      
SET XACT_ABORT ON
      
BEGIN TRANSACTION
       
select @rgt=rgt from tree where type_id=@type_id
       
update tree set rgt=rgt+2 where rgt>=@rgt
       
update tree set lft=lft+2 where lft>=@rgt
       
insert into tree (name,lft,rgt) values (@name,@rgt,@rgt+1)   
       
COMMIT TRANSACTION
      
SET XACT_ABORT OFF   
   
end
go
 
然后,我们删除节点“电视机”,再来看看该树会变成什么情况:
                                            1商品20-2
                       +-----------------------------------+
                2食品13                                14电器19-2
          +-----------------+               
       3肉类8          9蔬菜类12              17-2电冰箱18-2
    +----------+
4猪肉5  6牛肉7  10白菜11
 
  相应的存储过程如下:
CREATE PROCEDURE [dbo].[DelNode] 
   
@type_id int
AS
declare @lft int
declare @rgt int
if exists (select 1 from tree where type_id=@type_id)
   
begin
      
SET XACT_ABORT ON
      
BEGIN TRANSACTION
       
select @lft=lft,@rgt=rgt from tree where type_id=@type_id
       
delete from tree where lft>=@lft and rgt<=@rgt
       
update tree set lft=lft-(@rgt-@lft+1) where lft>@lft
       
update tree set rgt=rgt-(@rgt-@lft+1) where rgt>@rgt
       
COMMIT TRANSACTION
      
SET XACT_ABORT OFF   
End
 
  注意:因为删除某个节点会同时删除该节点的所有子孙节点,而这些被删除的节点的个数为:(被删节点的右值-被删节点的左值+1)/2,而任何一个节点同时具有唯一的左值和唯一的右值,故删除作废节点后,其他相应节点的左、右值需要调整的幅度应为:减少(被删节点的右值-被删节点的左值+1)。
 
  最后,让我们看看平移节点“电器”,将其和其所有子孙节点移动到节点“食品”之前后,该树会变成什么情况:
 
             1商品18
+-----------------------------------+
                14-12电器17-12                      2+4食品13+4
                                                               +----------------------+               
             15-12电冰箱16-12      3+4肉类8+4      9+4蔬菜类12+4       
                                                +-------------------+
                                      4+4猪肉5+4     6+4牛肉7+4 10+4白菜11+4
 
大家仔细观察一下交换后同层2个节点和其所有子孙节点左右值的变化,可以发现一个明显的规律,那就是,节点“电器”及其所有子孙节点的左右值均减少12,而节点“食品”及其所有子孙节点的左右值均增加4。而节点“电器”+其子孙节点的数量为2,节点“食品”+其子孙节点的数量为6,这其中有什么联系吗?还记得我在删除节点的存储过程后面的注释吗?任何一个节点同时具有唯一的左值和唯一的右值。让我们把节点数量*2,正好和节点左右值需要调整的幅度相等。由此规律,我们可以编写出类似下面的存储过程来实现节点同层前移的功能:
CREATE PROCEDURE [dbo].[MoveNodeUp] 
   
@type_id int
AS
declare @lft int
declare @rgt int
declare @layer int
if exists (select 1 from tree where type_id=@type_id)
   
begin
      
SET XACT_ABORT ON
      
BEGIN TRANSACTION
       
select @lft=lft,@rgt=rgt,@layer=layer from TreeView where type_id=@type_id
       
if exists (select * from TreeView where rgt=@lft-1 and layer=@layer)
          
begin
              
declare @brother_lft int
              
declare @brother_rgt int
              
select @brother_lft=lft,@brother_rgt=rgt from TreeView where rgt=@lft-1 and layer=@layer
              
update tree set lft=lft-(@brother_rgt-@brother_lft+1) where lft>=@lft and rgt<=@rgt
              
update tree set lft=lft+(@rgt-@lft+1) where lft>=@brother_lft and rgt<=@brother_rgt
              
update tree set rgt=rgt-(@brother_rgt-@brother_lft+1) where rgt>@brother_rgt and rgt<=@rgt
              
update tree set rgt=rgt+(@rgt-@lft+1) where lft>=@brother_lft+(@rgt-@lft+1) and rgt<=@brother_rgt
          
end
       
COMMIT TRANSACTION
      
SET XACT_ABORT OFF   
   
end
 
  注意:节点的同层平移可以采用临时表来做中介,降低代码的复杂度。不用临时表来处理也行,但是update语句顺序一定要考虑周详。否则,一旦出现bug,对整个类别表的破坏是惊人的,强烈推荐在做上述工作前对类别表进行完整备份。
 
  同层下移的存储过程和同层上移类似,有兴趣的朋友可以自己动手编写体味一下其中的细节,我就不在这里列出来了。
 
  最后,我对上面这种左右值编码实现无限分级类别树的方案做一个总结:
  优点:在消除递归的前提下实现了无限分级,而且查询条件是基于整形数字比较的,效率很高。可以进行先序列表,添加,修改,删除,同层平移等常规操作,基本满足需求。
  缺点:由于这种左右值编码的方式和常见的阿拉伯数字直观排序不同,再加上节点在树中的层次,顺序不是直观显示出来,而必须通过简单的公式计算后得到,需要花费一定的时间对其数学模型进行深入理解。而且,采用该方案编写相关存储过程,新增,删除,同层平移节点需要对整个树进行查询修改,由此导致的代码复杂度,耦合度较高,修改维护的风险较高。