JSP内建对象

J2EE — 作者: LiuYi @ 2005-09-28, 13:28

① out - javax.servlet.jsp.jspWriter
   out对象用于把结果输出到网页上。

方法:
1. void clear() ;
   清除输出缓冲区的内容,但是不输出到客户端。

2. void clearBuffer() ;
   清除输出缓冲区的内容,并输出到客户端。

3. void close() ;
   关闭输出流,清除所有内容。

4. void flush() ;
   输出缓冲区里面的数据。

5. int getBufferSize() ;
   获取以kb为单位的目前缓冲区大小。

6. int getRemaining() ;
   获取以kb为单位的缓冲区中未被占用的空间大小。

7. boolean isAutoFlush() ;
   是否自动刷新缓冲区。

8. void newLine() ;
   输出一个换行字符。

9. void print( boolean b ) ;
   void print( char c ) ;
   void print( char[] s ) ;
   void print( double d ) ;
   void print( float f ) ;
   void print( int i ) ;
   void print( long l ) ;
   void print( Object obj ) ;
   void print( String s ) ;
   将指定类型的数据输出到Http流,不换行。

10. void println( boolean b ) ;
    void println( char c ) ;
    void println( char[] s ) ;
    void println( double d ) ;
    void println( float f ) ;
    void println( int i ) ;
    void println( long l ) ;
    void println( Object obj ) ;
    void println( String s ) ;
    将指定类型的数据输出到Http流,并输出一个换行符。
   
11. Appendable append( char c ) ;
    Appendable append( CharSequence cxq, int start, int end ) ;
    Appendable append( CharSequence cxq ) ;
    将一个字符或者实现了CharSequence接口的对象添加到输出流的后面。

成员:
int DEFAULT_BUFFER = 0    - 缺省缓冲区大小
int NO_BUFFER = -1        - writer是否处于缓冲输出状态
int UNBOUNDED_BUFFER = -2 - 是否限制缓冲区大小


② request - javax.servlet.http.HttpServletRequest
   request对象包含所有请求的信息,如请求的来源、标头、cookies和请求相关的参数值等。

方法:
1. Object getAttribute( String name ) ;
   返回由name指定的属性值,该属性不存在时返回null。

2. Enumeration getAttributeNames() ;
   返回request对象的所有属性名称的集合。

3. String getAuthType() ;
   返回用来保护servlet的认证方法的名称,未受保护时返回null。

4. String getCharacterEncoding() ;
   返回请求中的字符编码方法,可以在response对象中设置。

5. int getContentLength() ;
   返回请求的BODY的长度,不能确定长度时返回-1。可以在response中设置。

6. String getContentType() ;
   返回在response中定义的内容类型。

7. String getContentPath() ;
   返回请求的路径。

8. Cookie[] getCookies() ;
   返回客户端所有的Cookie的数组。

9. Enumeration getHeaderNames() ;
   返回所有HTTP头的名称的集合。

10. Enumeration getHeaders( String name ) ;
    返回指定HTTP头的所有值的集合。

11. String getHeader( String name ) ;
    返回指定名称的HTTP头的信息。

12. long getDateHeader( String name ) ;
    返回指定名称的Data类型的HTTP头的信息。

13. int getIntHeader( String name ) ;
    返回指定名称的Int类型的HTTP头的信息。

14. ServletInputStream getInputStream() ;
    返回请求的输入流。

15. Locale getLocale() ;
    返回当前页的Locale对象,可以在response中设定。

16. Enumeration getLocales() ;
    返回请求中所有的Locale对象的集合。

17. String getLocalName() ;
    获取响应请求的服务器端主机名。

18. String getLocalAddr() ;
    获取响应请求的服务器端地址。

19. int getLocalPort() ;
    获取响应请求的服务器端端口

20. String getMethod() ;
    获取客户端向服务器端发送请求的方法(GET、POST)。

21. String getParameter( String name ) ;
    获取客户端发送给服务器端的参数值。

22. Map getParameterMap() ;
    该方法返回包含请求中所有参数的一个Map对象。

23. Enumeration getParameterNames() ;
    返回请求中所有参数的集合。

24. String[] getParameterValues( String name ) ;
    获得请求中指定参数的所有值。

25. String getQueryString() ;
    返回get方法传递的参数字符串,该方法不分解出单独的参数。

26. String getPathInfo() ;
    取出请求中处于ServletPath和QueryString之间的额外信息。

27. String getPathTranslated() ;
    返回用getPathInfo()方法取得的路径信息的实际路径。

28. String getProtocol() ;
    返回请求使用的协议。可以是HTTP1.1或者HTTP1.0。

29. BufferedReader getReader() ;
    返回请求的输入流对应的Reader对象,该方法和getInputStream()方法在一个页面中只能调用一个。

30. String getRemoteAddr() ;
    获取发出请求的客户端IP地址。

31. String getRemoteHost() ;
    获取发出请求的客户端主机名

32. String getRemoteUser() ;
    返回经过客户端验证的用户名,未经验证返回null。

33. int getRemotePort() ;
    返回发出请求的客户端主机端口。

34. String getRealPath( String path ) ;
    返回给定虚拟路径的物理路径。

35. RequestDispatcher getRequestDispatcher( String path ) ;
    按给定的路径生成资源转向处理适配器对象。

36. String getRequestedSessionId() ;
    返回请求的session的标识。

37. String RequestURI() ;
    返回发出请求的客户端地址,但是不包括请求的参数字符串。

38. StringBuffer getRequestURI() ;
    返回响应请求的服务器端地址

39. String getScheme() ;
    获取协议名称,缺省值为HTTP协议。

40. String getServerName() ;
    返回响应请求的服务器名称。

41. String getServletPath() ;
    获取客户端所请求的脚本文件的文件路径。

42. int getServerPort() ;
    获取响应请求的服务器端主机端口号。

43. void removeAttribute( String name ) ;
    在属性列表中删除指定名称的属性。

44. void setAttribute( String name, Object value ) ;
    在属性列表中添加/删除指定的属性。

45. void setCharacterEncoding( String name ) ;
    设置请求的字符编码格式。

46. HttpSession getSession() ;
    HttpSession getSession( boolean create ) ;
    获取session,如果create为true,在无session的情况下创建一个。
   
47. boolean isRequestedSessionIdFromCookie() ;
    检查请求的会话ID是否为通过Cookie传入。

48. boolean isRequestedSessionIdFromURL() ;
    检查请求的会话ID是否为通过URL传入。

49. boolean isRequestedSessionIdValid() ;
    检查请求的会话ID是否仍然有效。

50. boolean isSecure() ;
    检查请求是否使用安全链接,如果HTTPS等。

51. boolean isUserInRole( String role ) ;
    检查已经通过验证的用户是否在是role所指定的角色。

52. Principal getUserPrincipal() ;
    返回包含用户登陆名的一个java.security.Principal对象。

成员:
String BASIC_AUTH = "BASIC"             -
String CLIENT_CERT_AUTH = "CLIENT_CERT" -
String DIGEST_AUTH = "DIGEST"           -
String FORM_AUTH = "FORM"               -


③ response - javax.servlet.http.HttpServletResponse
   response对象主要将JSP容器处理后的结果传回到客户端。

方法:
1. void addCookie( Cookie cookie ) ;
   添加一个Cookie对象,保存客户端信息。

2. void addDateHeader( String name, long value ) ;
   添加一个日期类型的HTTP头信息,覆盖同名的HTTP头信息。

3. void addHeader( String name, String value ) ;
   添加一个HTTP头,覆盖同名的旧HTTP头。

4. void addIntHeader( String name, int value ) ;
   添加一个整型的HTTP头,覆盖同名的旧HTTP头。

5. boolean containsHeader( String name ) ;
   判断指定的HTTP头是否存在。

6. String encodeRedirectURL( String url ) ;
   对sendRedirect()方法使用的URL进行编码。

7. String encodeURL( String url ) ;
   将URL予以编码,回传包含session ID的URL。
  
8. void flushBuffer() ;
   强制把当前缓冲区的内容发送到客户端。

9. int getBufferSize() ;
   取得以kb为单位的缓冲区大小。

10. String getCharacterEncoding() ;
    获取响应的字符编码格式。

11. String getContentType() ;
    获取响应的类型。

12. Locale getLocale() ;
    获取响应的Locale对象。

13. ServletOutputStream getOutputStream() ;
    返回客户端的输出流对象。

14. PrintWriter getWriter() ;
    获取输出流对应的writer对象。

15. boolean isCommitted() ;
    判断服务器端是否已经将数据输出到客户端。

16. void reset() ;
    清空buffer中的所有内容。

17. void resetBuffer() ;
    情况buffer中所有的内容,但是保留HTTP头和状态信息。

18. void sendError( int xc, String msg ) ;
    void sendError( int xc ) ;
    发送错误,包括状态码和错误信息。

19. void sendRedirect( String locationg ) ;
    把响应发送到另外一个位置进行处理。

20. void setBufferSize( int size ) ;
    设置以kb为单位的缓冲区大小。

21. void setCharacterEncoding( String charset ) ;
    设置响应使用的字符编码格式。

22. void setContentLength( int length ) ;
    设置响应的BODY长度。

23. void setContentType( String type ) ;
    设置响应的类型。

24. void setDateHeader( String name, long value ) ;
    设置指定名称的Data类型的HTTP头的值。

25. void setHeader( String name, String value ) ;
    设置指定名称的HTTP头的值。

26. void setIntHeader( String name, int value ) ;
    设置指定名称的int类型的HTTP头的值。

27. void setStatus( int xc ) ;
    设置响应状态码,新值会覆盖当前值。

成员(HTTP状态码):
int SC_CONTINUE = 100                      int SC_SWITCHING_PROTOCOLS = 101
int SC_OK = 200                            int SC_NON_AUTHORITATIVE_INFORMATION = 203
int SC_ACCEPTED = 202                      int SC_CREATED = 201
int SC_NO_CONTENT = 204                    int SC_RESET_CONTENT = 205
int SC_PARTIAL_CONTENT = 206               int SC_MULTIPLE_CHOICES = 300
int SC_MOVED_PERMANENTLY = 301             int SC_MOVED_TEMPORARILY = 302
int SC_FOUND = 302                         int SC_SEE_OTHER = 303
int SC_NOT_MODIFIED = 304                  int SC_USE_PROXY = 305
int SC_TEMPORARY_REDIRECT = 307            int SC_BAD_REQUEST = 400
int SC_UNAUTHORIZED = 401                  int SC_PAYMENT_REQUIRED = 402
int SC_FORBIDDEN = 403                     int SC_NOT_FOUND = 404
int SC_METHOD_NOT_ALLOWED = 405            int SC_NOT_ACCEPTABLE = 406
int SC_PROXY_AUTHENTICATION_REQUIRED = 407 int SC_REQUEST_TIMEOUT = 408
int SC_CONFLICT = 409                      int SC_GONE = 410
int SC_LENGTH_REQUIRED = 411               int SC_PRECONDITION_FAILED = 412
int SC_REQUEST_ENTITY_TOO_LARGE = 413      int SC_REQUEST_URI_TOO_LONG = 414
int SC_UNSUPPORTED_MEDIA_TYPE = 415        int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416
int SC_EXPECTATION_FAILED = 417            int SC_INTERNAL_SERVER_ERROR = 500
int SC_NOT_IMPLEMENTED = 501               int SC_BAD_GATEWAY = 502
int SC_SERVICE_UNAVAILABLE = 503           int SC_GATEWAY_TIMEOUT = 504
int SC_HTTP_VERSION_NOT_SUPPORTED = 505


④ session - javax.servlet.http.HttpSession
   session对象表示目前个别用户的会话状态,用来识别每个用户。

方法:
1. Object getAttribute( String name ) ;
   获取与指定名字相关联的session属性值。

2. Enumeration getAttributeNames() ;
   取得session内所有属性的集合。

3. long getCreationTime() ;
   返回session的创建时间,最小单位千分之一秒。

4. String getId() ;
   取得session标识。

5. long getLastAccessedTime() ;
   返回与当前session相关的客户端最后一次访问的时间,由1970-01-01算起,单位毫秒。

6. int getMaxInactiveInterval( int interval ) ;
   返回总时间,以秒为单位,表示session的有效时间(session不活动时间)。-1为永不过期。

7. ServletContext getServletContext() ;
   返回一个该JSP页面对应的ServletContext对象实例。

8. HttpSessionContext getSessionContext() ;
  

9. Object getValue( String name ) ;
   取得指定名称的session变量值,不推荐使用。

10. String[] getValueNames() ;
    取得所有session变量的名称的集合,不推荐使用。

11. void invalidate() ;
    销毁这个session对象。

12. boolean isNew() ;
    判断一个session是否由服务器产生,但是客户端并没有使用。

13. void pubValue( String name, Object value ) ;
    添加一个session变量,不推荐使用。

14. void removeValue( String name ) ;
    移除一个session变量的值,不推荐使用。

15. void setAttribute( String name, String value ) ;
    设置指定名称的session属性值。

16. void setMaxInactiveInterval( int interval ) ;
    设置session的有效期。

17. void removeAttribute( String name ) ;
    移除指定名称的session属性。


⑤ pageContext - javax.servlet.jsp.PageContext
   pageContext对象存储本JSP页面相关信息,如属性、内建对象等。

方法:
1. void setAttribute( String name, Object value, int scope ) ;
   void setAttribute( String name, Object value ) ;
   在指定的共享范围内设置属性。

2. Object getAttribute( String name, int scope ) ;
   Object getAttribute( String name ) ;
   取得指定共享范围内以name为名字的属性值。

3. Object findAttribute( String name ) ;
   按页面、请求、会话和应用程序共享范围搜索已命名的属性。

4. void removeAttribute( String name, int scope ) ;
   void removeAttribute( String name ) ;
   移除指定名称和共享范围的属性。

5. void forward( String url ) ;
   将页面导航到指定的URL。

6. Enumeration getAttributeNamesScope( int scope ) ;
   取得指定共享范围内的所有属性名称的集合。

7. int getAttributeScope( String name ) ;
   取得指定属性的共享范围。

8. ErrorData getErrorDate() ;
   取得页面的errorData对象。

9. Exception getException() ;
   取得页面的exception对象。

10. ExpressionEvaluator getExpressionEvaluator() ;
    取得页面的expressionEvaluator对象。

11. JspWriter getOut() ;
    取得页面的out对象。

12. Object getPage() ;
    取得页面的page对象。

13. ServletRequest getRequest() ;
    取得页面的request对象。

14. ServletResponse getResponse() ;
    取得页面的response对象。

15. ServletConfig getConfig() ;
    取得页面的config对象。

16. ServletContext getServletContext() ;
    取得页面的servletContext对象。

17. HttpSession getSession() ;
    取得页面的session对象。

18. VariableResolver getVariableResolver() ;
    取得页面的variableResolver对象。

19. void include( String url, boolean flush ) ;
    void include( String url ) ;
    包含其他的资源,并指定是否自动刷新。

20. void release() ;
    重置pageContext内部状态,释放所有内部引用。

21. void initialize( Servlet servlet, ServletRequest request, ServletResponse response,
                     String errorPageURL, boolean needSession, int bufferSize, boolean autoFlush ) ;
    初始化未经初始化的pageContext对象。

22. BodyContext pushBody() ;
    BodyContext pushBody( Writer writer ) ;
    保存当前的out对象,并更新pageContext中page范围内的out对象。

23. JspWrite popBody() ;
    取出由pushBody()方法保存的out对象。

24. void handlePageException( Exception e ) ;
    void handlePageException( Thrwoable t ) ;
   

成员:
int PAGE_SCOPE = 1        - 页面共享范围
int REQUEST_SCOPE = 2     - 请求共享范围
int SESSION_SCOPE = 3     - 会话共享范围
int APPLICATION_SCOPE = 4 - 应用程序共享范围
String PAGE = "javax.servlet.jsp.jspPage"
String PAGECONTEXT = "javax.servlet.jsp.jspPageContext"
String REQUEST = "javax.servlet.jsp.jspRequest"
String RESPONSE = "javax.servlet.jsp.jspResponse"
String CONFIG = "javax.servlet.jsp.jspConfig"
String SESSION = "javax.servlet.jsp.jspSession"
String OUT = "javax.servlet.jsp.jspOut"
String APPLICATION = "javax.servlet.jsp.jspApplication"
String EXCEPTION = "javax.servlet.jsp.jspException"


⑥ application - javax.servlet.ServletContext
   application主要功用在于取得或更改Servlet的设定。

方法:
1. Object getAttribute( String name ) ;
   返回由name指定的application属性。

2. Enumeration getAttributes() ;
   返回所有的application属性。

3. ServletContext getContext( String uripath ) ;
   取得当前应用的ServletContext对象。

4. String getInitParameter( String name ) ;
   返回由name指定的application属性的初始值。

5. Enumeration getInitParameters() ;
   返回所有的application属性的初始值的集合。

6. int getMajorVersion() ;
   返回servlet容器支持的Servlet API的版本号。

7. String getMimeType( String file ) ;
   返回指定文件的类型,未知类型返回null。一般为"text/html"和"image/gif"。

8. int getMinorVersion() ;
   返回servlet容器支持的Servlet API的副版本号。

9. String getRealPath( String path ) ;
   返回给定虚拟路径所对应物理路径。

10. RequestDispatcher getNamedDispatcher( String name ) ;
    为指定名字的Servlet对象返回一个RequestDispatcher对象的实例。

11. RequestDispatcher getRequestDispatcher( String path ) ;
    返回一个RequestDispatcher对象的实例。

12. URL getResource( String path ) ;
    返回指定的资源路径对应的一个URL对象实例,参数要以"/"开头。

13. InputStream getResourceAsStream( String path ) ;
    返回一个由path指定位置的资源的InputStream对象实例。

14. Set getResourcePaths( String path ) ;
    返回存储在web-app中所有资源路径的集合。

15. String getServerInfo() ;
    取得应用服务器版本信息。

16. Servlet getServlet( String name ) ;
    在ServletContext中检索指定名称的servlet。

17. Enumeration getServlets() ;
    返回ServletContext中所有servlet的集合。

18. String getServletContextName() ;
    返回本web应用的名称。

19. Enumeration getServletContextNames() ;
    返回ServletContext中所有servlet的名称集合。

20. void log( Exception ex, String msg ) ;
    void log( String msg, Throwable t ) ;
    void log( String msg ) ;
    把指定的信息写入servlet log文件。

21. void removeAttribute( String name ) ;
    移除指定名称的application属性。

22. void setAttribute( String name, Object value ) ;
    设定指定的application属性的值。


⑦ config - javax.servlet.ServletConfig
   config对象用来存放Servlet初始的数据结构。

方法:
1. String getInitParameter( String name ) ;
   返回名称为name的促使参数的值。

2. Enumeration getInitParameters() ;
   返回这个JSP所有的促使参数的名称集合。

3. ServletContext getContext() ;
   返回执行者的servlet上下文。

4. String getServletName() ;
   返回servlet的名称。


⑧ exception - java.lang.Throwable
   错误对象,只有在JSP页面的page指令中指定isErrorPage="true"后,才可以在本页面使用exception对象。

方法:
1. Throwable fillInStackTrace() ;
   将当前stack信息记录到exception对象中。

2. String getLocalizedMessage() ;
   取得本地语系的错误提示信息。

3. String getMessage()
   取得错误提示信息。

4. StackTrackElement[] getStackTrace() ;
   返回对象中记录的call stack track信息。

5. Throwable initCause( Throwable cause ) ;
   将另外一个异常对象嵌套进当前异常对象中。
  
6. Throwable getCause() ;
   取出嵌套在当前异常对象中的异常。

7. void printStackTrace() ;
   void printStackTrace( printStream s ) ;
   void printStackTrace( printWriter s ) ;
   打印出Throwable及其call stack trace信息。

8. void setStackTrace( StackTraceElement[] stackTrace )
   设置对象的call stack trace信息。


⑨ page - javax.servlet.jsp.HttpJspPage
   page对象代表JSP对象本身,或者说代表编译后的servlet对象,
   可以用( (javax.servlet.jsp.HttpJspPage)page )来取用它的方法和属性。



struts中,解决中文显示问题个人体会!

J2EE — 作者: LiuYi @ 2005-09-28, 08:20

学习struts过程,碰到中文乱码问题,不管3721,go出一大堆关于这方面的文章,看了几个,觉得好象讲的都差不多,但是具体细节有些遗漏,摸索了两天,仔细体会了一些原理性文章。终于得到解决。

我的解决办法是这样的:

首先要搞清楚以下几个概念:

1.首先是.properties,.jsp文件的编码格式(eclipse中来说吧,就是文件Properties的Info的Text file encoding )

2.jsp中的"charset"的设置值

首先建立建立一个ApplicationResources_cn.properties文件

内容如下:

errors.name=用户名错误

在里面写入一些信息。并以UTF-8格式保存(我使用的是eclipse,在eclipse中设置文件的Properties属性为UTF-8既可).然后在dos下,先进入ApplicationResources_cn.properties文件所在的目录。在输入以下命令:

native2ascii -encoding UTF-8  ApplicationResources_cn.properties ApplicationResources.properties

(这里要注意 -encoding 后面的参数 UTF-8,因为前面ApplicationResources_cn.properties 是以UTF-8格式保存的,所以这里一定要要用UTF-8,要不后面再怎么弄,永远也显示不出中文。我曾经就被网上一篇文章误导了,前面说用UTF-8,后面native2ascii参数既然用gb2312!!!!!*&*&&&^&^(*#&#$&#^@)*#))

 

ApplicationResources_cn.properties文件也可以以gbk格式保存(eclipse中只有gbk没有gb2312)

然后native2ascii -encoding gbk ApplicationResources_cn.properties ApplicationResources.properties

不管用gbk,还是UTF-8都可以(因为这2种都支持中文显示),并且最后得到的 ApplicationResources.properties文件内容都会一样:

errors.name=u7528u6237u540du9519u8bef

然而ApplicationResources.properties文件保存的编码格式有什么限制呢?目前,个人感觉随便什么都可以,因为这个文件中没有任何中文。

.jsp文件中charset设置为gb2312,UTF-8都可以。

.jsp文件保存的编码格式和charset一一对应.

综上所述:

因为UTF-8支持所有的国际字符,所以个人建议使用 UTF-8(包括charset等 )既碰到关于字符编码的都使用UTF-8.这样会省去很多麻烦的。




利用JSF应对应用程序开发的未来

个人空间 — 作者: LiuYi @ 2005-09-27, 09:04


作者 Budi Kurniawan

JavaServer Face为Java应用程序的开发提速。

JavaServer Faces(JSF)是一项使用Java技术来快速构建Web应用程序的新技术。JSF通过提供以下特性来加速开发进程:标准和可扩展的用户界面组件;极易配置的页面导航;用于输入验证的组件;自动化的Bean管理;事件处理;轻松的错误处理,以及内置的对国际化的支持。

本文介绍如何使用JSF来构建在线比萨(pizza)订购系统。

项目描述

该应用程序被称之为PizzaRia,是一个在线商店,允许用户选择比萨饼并递交选定的比萨饼。PizzaRia与其他在线商店类似,用户可以浏览菜单,向购物车中添加所选产品并进行结账。

该应用程序的用户界面由5个JSP文件组成,它们是index.jsp, details.jsp, shoppingCart.jsp, checkOut.jsp以及order.jsp 。每个用户界面的页面包括3个其它页面:header.jsp, menu.jsp和 footer.jsp 。

Figure 1

 

Figure 2

 

Figure 3

 

Figure 4

 

Figure 5

数据库

该程序的数据存储在3张表中:products(产品)、orders(定单)和OrderDetails(订单详细项目)。Products表存储产品信息,具有4列:ProductId(产品标识), Name(名称), Description(说明)和 Price(价格)。

Orders表中的每一行存储一个单独的订单,其中的信息包括联系人姓名、送货地址以及信用卡细目。Orders表有6列:OrderId(定单标识), ContactName(联系人姓名), DeliveryAddress(送货地址), CCName(信用卡所属人姓名), CCNumber(信用卡号码)和 CCExpiryDate(信用卡有效期限)。

每个订单的详细项目被存储在OrderDetails表中。OrderDetails表有4列:OrderId(定单标识), ProductId(产品标识), Quantity(数量)和 Price(价格)。Orders与OrderDetails表通过OrderID列有一对多的对应关系。请注意,OrderDetails表在用户下订单的时候就保存相关的价格信息。该价格可能与当前产品价格不同,后者存储在Products表的Price列中。

用于在一个Oracle数据库中创建所需的表的脚本文件pizzaria-oracle.sql存放在 pizzaria.zip 文件中。

业务对象

以下是在该应用程序中使用的业务对象:

ProductBean 用于封装一个产品信息。它具有如下属性:id(标识)、name(名称)、description(说明)和price(价格)。每次details.asp页被访问的时候,JSF实现(implementation)就会自动创建一个ProductBean实例。该JSF实现调用ProductBean的无参数构造器,从数据库中获取相关的数据,并且将其填入相应的字列中。

ProductSummary。 ProductSummary(产品概要)用于表示产品的概要。该类包含2个属性:id(标识)和name(名称)。

ShoppingItemBean。 ShoppingItemBean用于表示购物项目。该类包含4个属性:productId(产品标识), productName(产品名称), price(价格)以及 quantity(数量)。

ShoppingCartBean。 ShoppingCartBean用于表示一个存储在对话(session)对象中的购物车。该类允许用户添加购物项目(使用addShopping方法),获取包含所有购物项目的列表(使用getShoppingItems方法),获得所购货物的总价值(使用getTotal方法)。

OrderBean。 OrderBean表示一个订单。该类具有如下5个属性:contactName, deliveryAddress, creditCardName, creditCardNumber以及 creditCardExpiryDate。

MenuBean。 MenuBean使用getMenu方法显示可供产品的目录。该方法返回一个包含到产品细节的链接的HTML表。

DatabaseUtil。 DatabaseUtil提供了以下3种方法以便访问和操作数据:

 

  • GetProductSummaries:该方法返回一个包含了产品表中所有产品概要的列表。一个产品概要通过ProductSummary类来表示。

     

     

  • GetProductDetails:该方法返回一个ProductBean对象,该对象封装具有特定标识符的产品细节。

     

     

  • InsertOrder:该方法向Orders表和OrderDetails表插入客户订单。

 

应用程序上下文监听器

应用程序上下文监听器(AppContextListener类)从web.xml文件读出用于访问数据库的初始参数,然后将其写入ServletContext对象。用到的初始参数如下:jdbcDriver, dbUrl, dbUserName和 dbPassword。在你的web.xml文件中编辑这些值,以便反应你数据库的真实值。

JSF应用程序配置

JSF允许编程人员仅仅通过应用程序配置文件就可以轻松配置应用程序。该文件如果存在的话,则它应该被命名为faces-config.xml,并且应该位于你应用程序下的WEB-INF 目录。

可以在faces-config.xmlz文件中对该应用程序的多个方面进行配置,包括bean管理、页面导航、定制UI(用户界面)组件、定制验证程序和消息资源。在 PizzaRia 应用程序中,我将该faces-config.xml用于bean管理和页面导航的配置。

JavaBean管理。 对于JavaBean管理,可以使用应用程序配置文件faces-config.xml中的managed-bean元件。每个managed-bean元件都会注册一个JavaBean--JSF会将该JavaBean在特定的作用域内实例化和进行储存。managed-bean元件定义如下:

 

<!ELEMENT managed-bean (description*, display-name*, icon*, managed-bean
name, managed-bean-class, managed-bean-scope, (managed-property* | map-entries |
list-entries))>

 

每个managed-bean元件都必须包含一个managed-bean-name元件,一个managed-bean-class元件,以及一个managed-bean-scope元件,并且可选择性地包含一些描述、显示名、图标和managed-property/map-entries/list-entries元件。

managed-bean-name指定了被用来在整个应用程序中引用该JavaBean的名称。managed-bean-class元件包含该JavaBean的完全限度的类名。managed-bean-scope元件定义该JavaBean的作用域。该元件可能的值是:application、session、request或者none。如果managed-bean-scope元件是none以外的其他值,那么,所创建的该JavaBean元件将会被存储在相应的对象中。比如说,如果值是"session",那么,该JavaBean就会被存储在一个给定用户的session对象中。

在PizzaRia应用程序中,我注册了如 代码清单1 所示的4个JavaBeans。

页面导航: 页面导航决定了Web应用程序的控制流。本节演示如何在JSF中创立一个页面导航。

JSF使用navigation-rule元件来为页面导航定义规则。其定义如下:

 

<!ELEMENT navigation-rule 
  (description*, display-name*, icon*, from-view-id?, navigation-case*)>

 

from-view-id元件是首页(起始页)的标识符。为了说明被称之为index.jsp的JSP页面的导航规则,下面给出子元件from-view-id的值:

 

<from-view-id>/index.jsp</from-view-id>

 

navigation-case元件表示一个可能的目标页面。navigation-rule一个元件可以有零个或者数个navigation-case子元件。

每个navigation-case元件都指定from-view-id的特定处理结果的目标页面。结果可以来自from-view-id元件中 UICommand组件的行动(action)属性。

navigation-case元件由如下所示的代码描述:

 

<!ELEMENT navigation-case 
(description*, display-name*, icon*, from-action?, from-outcome?, 
to-view-id, redirect?)>

 

to-view-id元件指定目标页面。from-outcome值是处理from-view-id的结果。该值来自于在from-view-id中触发了ActionEvent的 UICommand组件的行动属性。

from-action元件也表示处理from-view-id的结果。但其值来自于引发了ActionEvent的UICommand组件的行动属性的运算值。

代码清单2 展示了在PizzaRia应用程序中使用的navigation-rule元件。

在JSP页面中使用UI组件

JSF提供两个定制标记库来帮助用户快速编写Web应用程序:HTML和Core。HTML定制标记库定义了用来表示UI组件的标记。Core定制标记库使用具有组件的验证器(validators)定义了注册事件处理器的核心行动,以及其他一些行动。你可以在自己的JSF应用程序的JSP页面中使用这两个库的标记。

为了在JSP页面中使用HTML和Core定制标记库,必须在页面中包含如下所示的taglib指令:

 

<%@ taglib uri="http://java.sun.com
/jsf/html/" prefix="h" %>
<%@ taglib uri="http://java.sun.com/
jsf/core/" prefix="f" %>

 

Prefix的属性值可以是任意值。但是,根据惯例,最好是使用"h"和"f"。

在JSF应用程序中编写JSP页面是每一个页面制作者的责任。除了布置组件之外,他们的责任还包括把组件绑定到模型对象数据并且把Core标记(诸如事件监听器和验证器)添加到组件标记中。

在HTML定制标记库中有25个标记。每个组件都呈现为一个HTML元件,而多个标记被呈现为同一个HTML元件。表1列出了HTML定制标记库中的标记。

标记 说明
Column 在UIData组件内表示一个数据列。
command_button 表示一个向服务器提交表单的按钮。
command_link 表示一个指向另一页面或者本页面内其他位置的超链接。
data_table 表示一个支持将数据绑定到一个数据对象的集合上的表。
Form 表示一个表单。
graphic_image 显示一张图片。
input_hidden 表示一个隐藏的元件。
input_secret 表示一个密码输入框。
input_text 表示一个可接受单个字符串的文本输入框。
input_textarea 表示一个可接受多个字符串的文本输入区。
Message 显示给定组件的信息。
Messages 表示一个从FacesContext中获取消息并且将其显示给用户的组件。
output_label 显示文本。
output_link 显示一个超链接。
output_message 显示给定组件的信息。
output_text 显示一行文本。
panel_grid 显示一张表。
panel_group 将一个组件集合分组。
selectboolean_checkbox 表示一个单选文本框。
selectmany_checkboxlist 显示一套复选框,用户从中可以选择多个值。
selectmany_listbox 表示一个多选下拉选择框,用户从中可以选择多个项目。
selectmany_menu 表示一个多选项目列表,用户从中可以选择多个项目。
selectone_listbox 表示一个单选下拉选择框,用户从中只能选择一个项目。
selectone_menu 表示单选项目列表,用户从中只能选择一个项目。
selectone_radio 表示一套单选按钮。

 

使用验证器

验证器使得输入确认简单化并且可以节省开发人员的大量编程时间。JSF提供一套验证器类用于确认输入到输入组件中的值。另外一种方法就是,如果现有的标准验证器不符合需要,那么开发人员还可以编写自己的验证器。

验证器是一个实现类(implementation class),它可以验证输入值,如果是非法输入,就会发出一个错误信息。可以通过将一个验证器嵌入一个其输入需要验证的输入组件中来使用它。如果该验证器判断出用户的输入是非法的,那么JSF servlet就会重新显示刚才提交了表单的那个JSP页面,而不会将本地值复制给绑定到该输入组件上的JavaBean实例。

JSF实现为通用的验证任务提供了3个标准验证器,包括检查必填的域内已填入内容、输入的内容符合长度和范围要求。
表2 列举了标准的验证器。

验证器类 标记 说明
LengthValidator validate_length 确保组件的本地值的长度在规定的范围之内。该值必须是字符串型。
LongRangeValidator validate_longrange 确保组件的本地值在规定的范围之内。该值必须能够被转换成长型。
DoubleRangeValidator validate_doublerange 确保组件的本地值在规定的范围内。该值必须能够被转换成浮点型。

 

另外,HTML定制标记库中的input_text和input_textarea标记有必填的属性。如果将该属性标赋值为真,那么用户在继续进行操作之前,就必须对文本输入框元件或者文本输入区域进行填写。

在PizzaRia应用程序中,checkOut.jsp页面使用该必填的属性以便保证没有一个域是空的。


事件处理

JSP应用程序是事件驱动型的程序。在JSF中处理事件令人惊奇的简单。以下是处理步骤:

 

  1. 编写事件监听器。
  2. 在程序目录下的WEB-INF/classes or WEB-INF/lib目录中部署事件监听器。
  3. 在表示组件(其事件被捕获)的标记中,使用Core定制标记库中定义的action_listener或者 valuechange_listener标记。

 

在JSF中的事件对象。 JSF中的所有事件对象必须提供javax.faces .event.FacesEvent类,以便这些事件被请求处理生命周期支持。FacesEvent类是java.util.EventObject的子类,并添加了getComponent方法,该方法返回引发该事件的UIComponent组件。

FacesEvent类有两个子类:ActionEvent和 ValueChangeEvent。ActionEvent类激活诸如UICommand组件之类的UI组件。

ValueChangeEvent类会发出一个通知,告知本地UIInput组件的值被修改了。然而,如果新值没有被成功地验证为合法的,则不会发出ValueChangeEvent通知。被加入到该类中的两个重要方法是getOldValue 和 getNewValue。getOldValue方法返回引发该事件的组件的旧值。getNewValue方法返回相应的新值。这两种方法的返回值类型都是java .lang.Object。
第三,JSF中的事件监听器。

为捕获一个JSF事件, 需要使用一个事件监听器。JSF程序中的所有监听器都必须实现javax.faces.event.FacesListener接口。该接口提供java.util.EventListener接口,后者是必须由所有Java事件监听器实现的接口。

Faces Listener接口有两个子接口:ActionListener 和 ValueChangeListener。ActionListener接口是为了捕获ActionEvent而必须被实现的接口。该接口添加了一个新的方法--processAction--该方法请求处理生命周期来调用。当为之注册了ActionListener 的ActionEvent发生事件时,就会调用processAction。processAction方法的代码如下:

 

public void processAction(ActionEvent 
  event) 
 throws AbortProcessingException

 

ValueChangeListener接口是为了捕获ValueChangeEvent而实现的接口。该接口添加了一个方法:processValueChange。当ValueChangeEvent动作被其监听者监听到时,就会调用processValueChange方法。processValueChange方法的代码如下:

 

public void processValueChange(ValueChangeEvent
  event) 
throws AbortProcessingException

 

下一步

 

下载
JavaServer Faces (JSF)

PizzaRia应用程序

阅读
关于JavaServer Faces的更多信息
java.sun.com/j2ee/javaserverfaces/download.html
java.sun.com/webservices/downloads/webservicespack.html

在PizzaRia应用程序中,开发人员需要一个名为AppAction Listener 的ActionListener。其processAction方法从ActionEvent对象的getLocalValue方法获取传递给该方法的本地值。如果本地值为"Buy(购买)",则processAction获取与用户相关的ShoppingCartBean对象,并且将shoppingItem加入到bean中去。如果本地值为"pay(付款)",那么processAction就从session(会话)对象获取OrderBean对象和ShoppingCartBean对象,并调用DatabaseUtil对象的insertOrder方法。 代码清单3 描述了processAction方法。

AppActionListener类使用两个非常有用的方法:getValueBinding 和getDatabaseUtil。getValueBinding接受指定对象名的字符串,并返回一个可以向下转换类型为对象类型的ValueBinding对象。比如说,为获得用户的在应用程序配置文件中被注册成shoppingCartBean 的ShoppingCartBean实例,开发人员需要通过传递"shoppingCartBean"来调用getValueBinding。

 

ShoppingCartBean cart = (ShoppingCartBean) 
getValueBinding("#{shoppingCartBean}").getValue(facesContext);

 

getValueBinding方法如下:

 

private ValueBinding getValueBinding(String valueRef) {
  ApplicationFactory factory =
  (ApplicationFactory)FactoryFinder
.getFactory(FactoryFinder
.APPLICATION_FACTORY);
  Application application = factory.getApplication();
return
  application.createValueBinding
(valueRef);
  }

 

getDatabaseUtil方法返回一个对ServletContext中的DatabaseUtil实例的引用:

 

private DatabaseUtil getDatabaseUtil() {
FacesContext facesContext = FacesContext.getCurrentInstance();
ServletContext servletContext = (ServletContext)
facesContext.getExternalContext()
.getContext();
return (DatabaseUtil) servletContext
.getAttribute("DATABASE_UTIL");
  }

 

运行该应用程序

该PizzaRia JSF应用程序用JSF参考实现(JavaServer Faces [JSF] Beta 1.0)已做过测试。请参看该应用程序的zip文件( pizzaria.zip )中的readme.txt文件,以便获得有关部署PizzaRia应用程序的更详细信息。



利用JSF、SpringFramework和Hibernate构建Web应用的实例讲述

个人空间 — 作者: LiuYi @ 2005-09-26, 09:02

利用JSF、SpringFramework和Hibernate构建Web应用的实例讲述


(来源:http://blog.csdn.net/ylong/archive/2004/07/24/50810.aspx)

[原作者] Derek Yang Shen
[原文链接] http://www.javaworld.com/javaworld/jw-07-2004/jw-0719-jsf.html
[源码链接] http://www.javaworld.com/javaworld/jw-07-2004/jsf/jw-0719-jsf.zip
[翻译] 本人
[点评] 该文是我看的第一篇讲述JSF与Spring整合的文章,是一个很好的范例,比较适合于对Spring有了一定了解人学习。其中大量篇幅讲述的JSF,对JSF感兴趣的也可以来看看。
[声明] 该文是本人第一次翻译大块头的文章,由于本人才疏学浅、英语较烂,但考虑到和我一样不喜欢看洋文的大有人在,遂用近4小时的时间翻译该文,对一些技术名词和不会翻译的地方用原文代替,其中难免有很多错误,欢迎批评指正。

[译文]

JSF是一种新的用于构架j2ee应用用户界面的技术,它尤其适合于基于MVC架构的应用中。虽已有很多文章介绍过了JSF,然而它们大多从理论高度来介绍JSF而不是面向于实际应用。目前对于实际应用,JSF仍有很多问题没有解决,例如:如何使JSF适应于MVC整体构架中?如何将JSF与其他Java 框架整合起来?是否应该将业务逻辑放置在JSF的backing beans中?如何处理JSF中的安全机制?更为重要的是如何利用JSF构架现实世界的Web应用?

本文将涉及到上面的这些问题,它将演示如何将JSF、Spring和Hibernate整合在一起,构架出一个名为JCatalog的
在线产品价目系统。利用该Demo,本文涵盖了Web应用开发的每一个阶段,包括需求收集、分析,技术选择,系统架构和实现。本文讨论了在JCatalog中涉及到的各种技术的优点和缺点并展示了一些关键部分的设计方法。

本文的对象是从事基于J2ee的Web应用架构人员和开发人员,它并不是对JSF、SpringFramework和Hibernate的简单介绍,如果对这些领域不甚了解,请参看相关资源。

该范例的功能需求
JCatalog是一个现实世界的Web应用,我首先描述JCatalog的需求,在通篇的技术决策和架构设计时都将涉及到本部分。

在设计Web应用的第一阶段是收集系统的功能需求,范例应用是一个典型的电子商务应用系统,用户可以浏览产品的catalog并查看产品的详细情况,而管理员可以管理产品的catalog。通过增加一些其他功能,如inventory管理和订单处理等,该应用可成为一个成熟的电子商务系统。

Use cases
Use-case分析被用来展示范例应用的功能需求,图1就是该应用的use-case图。
    


use-case图用于表示系统中的actors以及可能进行的operations,在该应用中将有七个use-case,用户能够浏览产品 catalog和查看产品的详细情况,一旦用户登录到系统中,她将成为管理员,从而可以创建新的产品,编辑已存在的产品或者删除老的产品等。

Business rules
JCatalog必须符合以下business rules:
  • 每个产品必须具有唯一的ID
  • 每个产品必须属于至少一个category
  • 产品ID一旦创立不得修改
Assumptions
我们在系统的设计和实现中做以下假定:
  • 英语讲是缺省语言,且不需事先国际化
  • 在Catalog不讲不会超过500个产品
  • catalog将不会被频繁的修改
Page flow
图2显示了所有的JCatalog的pages以及它们之间的transitions关系:
    


该应用中存在两组pages:公开的internet和用于管理的intranet,其中intranet只能被那些成功登录到系统的用户访问。 ProductSummary不作为一个单独的page展示给用户,它显示在Catalog page中的frame中。ProductList只对管理员可视,它包含用于创建、编辑和删除产品的链接。

图3是一个Catalog页面的示意图,理想状况下,在需求文档中应该包含每一页的详细示意图。
        


构架设计
Web应用开发的下一个阶段是构架设计,它包括将应用划分为多个功能组件并将这些组件分割组合成层,高层的构架设计应该中立于所选用的特定技术。

多层架构
多层架构是将整个系统清晰的分为多个功能单元:client、presentation、business-logic、integration和 EIS,这将确保职责得到清晰的划分,使得系统更易于维护和扩展。具有三层或等多层的系统被证明比C/S模型具有更好的伸缩性和灵活性。

client层是使用和表示数据模型的地方,对于一个Web应用,client层通常是浏览器,基于浏览器的瘦客户端不包含任何表示逻辑,它依赖于
presentation层。

presentation层将business-logic层的服务展示给用户,它应知道如何处理用户的请求,如何同business-logic层交互,并且知道如何选择下一个视图显示给用户。

business-logic层包含应用的business objects和business services。它接受来在于presentation层的请求、基于请求处理业务逻辑。业务逻辑层组件将受益于系统级的服务,如安全管理、事务管理和资源管理等。

integration层是介于
business-logic层和EIS层之间的桥梁,它封装了与EIS层交互的逻辑。有时,将integration层和business-logic层合称为中间层。

应用的数据被保存在EIS层中,它包括关系数据库、面向对象数据库和以及遗留系统等。

JCatalog的构架设计
图4显示了JCatalog的构架设计以及如何应用于多层构架系统中。
 


该应用采用了多层非分布式的构架,图4展示了系统的分层以及每一层中选择的技术,它同时又是该范例的部署图,它的presentation、 business-logic和integration层将存在于同一个web容器中。定义良好的接口将孤立每一层的职责,这一架构使得应用更为简单和更好的伸缩性。

对于presentation层,经验表明,最好的方法是选择已存在的并已得到证明了的Web应用框架,而不是自己去设计和开发新的框架。我们拥有多个可选择的框架,如Struts,WebWork和JSF等,在JCatalog中,我们选择采用JSF。

EJB和POJO都可以用来创建业务逻辑层,如果应用是分布式的,采用具有remote接口的EJB是一个好的选择;由于JCatalog是一个典型的不需要远程访问的Web应用,因此选用POJO,并充分利用Spring Framework的帮助,将是实现业务逻辑层的更好选择。

integration层利用关系型数据库事先数据的持续化,存在多种方法可用来实现:
  • JDBC:这是最为灵活的方法,然而,低级的JDBC难以使用,而且质量差的JDBC代码很难运转良好
  • Entity beans:CMP的Entity bean是一种分离数据访问代码和处理ORM的昂贵的方法,它是以应用服务器为中心的方法,即entity bean不是将应用与某种数据库类型而是EJB容器约束在一起。
  • O/R mapping framework:一个ORM框架采用以对象为中心的方法实现数据持续化,一个以对象为中心的应用易于开发并具有高度的可移植性。在该领域中存在几个框架可用—JDO、Hibernate、TopLink以及CocoBase等。在我们的范例中将选用Hibernate。
现在,我们将讨论每一层中的设计问题,由于JSF是一个相对较新的技术,因此将着重于它的使用:

presentation层和JSF
表示层的功能是收集用户的输入、展示数据、控制页面导航并将用户的输入传递给业务逻辑层,表示层同时需要验证用户的输入以及维护应用的session状态。在下面几部分中,我将讨论表示层设计时的考虑和模式,并说明选择JSF作为JCatalog表示层的原因。

MVC
MVC是Java-Blueprints推荐的架构设计模式,MVC将几个方面分离开来,从而减少代码的重复,它以控制为中心并使得应用更具扩展性。MVC同时可帮助具有不同技能的用户更关注于自己的技能,通过定义良好的接口进行相互合作。MVC是表示层的架构设计模式。

JSF
JSF是Web应用的服务器端用户组件框架,它包含以下API:表示UI组件、管理它们的状态、处理事件、服务器端验证、数据转换、定义页面导航、支持国际化,并为这些特性提供扩展能力。它同时包括两个JSP的tag库以在JSP页面中表示UI组件,以及将组件wire为服务器端对象。

JSF和MVC
JSF非常适合于基于MVC的表示层架构,它在行为和表示之间提供了清晰的分离,它使得你可以采用熟悉的UI组件和web层概念而无需受限于某种特殊的脚本技术或标记语言。

JSF backing beans是JSF的Model层,此外,它同样包含actions,action是controller层的扩展,用于将用户的请求委派给业务逻辑层。这里请注意,从整体的应用构架看,业务逻辑层也被称为model层。包含JSF标签的JSP页面是表示层,Faces Servlet提供了controller的功能。

为什么选用JSF?

JSF不仅仅是另外一个Web框架,下面这些特性是JSF区别于其他Web框架之所在:
  • 类Swing的面向对象的Web应用开发:服务器端有状态的UI组件模型,配合event listeners和handlers,促进了面向对象的Web应用开发。
  • backing-bean管理: backing bean是与页面中使用的UI组件相关联的javabean组件,backing-bean管理将UI组件对象的定义同执行应用相关处理和拥有数据的对象分离开来。JSF在合适的范围内保存和管理这些backing-bean实例。
  • 可扩展的UI模型:JSF的UI模型是可配置的、可重用的,用以构建JSF应用的用户界面。你可以通过扩展标准的UI组件来开发出更为复杂的组件,例如菜单条、树组件等。
  • 灵活的rendering模型:renderer分离了UI组件的功能和显示,多个renderers可创建和用来为同一客户端或不同的客户端定义不同的显示。
  • 可扩展的转换和验证模型:基于标准的converter和validator,你可以开发出自己的可提供更好的模型保护的converter和validator。
尽管如此,JSF目前尚未成熟,随同JSF发布的 components、converters和validators都是最基础的,而且per-component验证模型不能处理components 和validators间的many-to-many验证。此外,JSF标签不能与JSTL间无缝的整合在一起。

在下面的章节中,我将讨论几个在JCatalog实现中的关键部分和设计决策。我首先解释managed bean的定义和使用以及JSF中的backing bean,然后,我将说明如何处理安全、分页、caching、file upload、验证以及错误信息定制。

Managed bean,backing bean,view object 和domain object model
JSF中引入了两个新的名词:managed bean和backing bean。JSF提供了一个强大的managed-bean工具,由JSF来管理的JavaBean对象称为managed-bean,一个 managed bean表述了一个bean如何被创建和管理,它不包含该bean的任何功能性描述。

backing bean定义了与页面中使用的UI组件相关联的属性和处理逻辑。每一个backing-bean属性邦定于一个组件实例或某实例的value。一个 backing-bean同时定义了一组执行组件功能的方法,例如验证组件的数据、处理组件触发的事件、实施与组件相关的导航等。

一个典型的JSF应用将其中的每个页面和一个backing-bean结合起来,然而在现实应用中,强制的执行这种one-on-one的关系不是一种理想的解决方案,它可能会导致代码重复等问题。在现实的应用中,多个页面可以共享一个backing-bean,例如在JCatalog中, CreateProduct和EditProduct将共享同一个ProductBean定义。

model对象特定于表示层中的一个view对象,它包含必须显示在view层的数据以及验证用户输入、处理事件和与业务逻辑层交互的处理逻辑等。在基于 JSF的应用中backing bean就是view对象,在本文中backing bean和view对象是可互换的名词。

对比于struts中的ActionForm和Action,利用JSF中的backing-bean进行开发将能更好的遵循面向对象方法,一个 backing-bean不仅包含view数据,而且还包含与这些数据相关的行为,而在struts中,Action和ActionForm分别包含数据和逻辑。

我们都应该听说过domain object model,那么,domain object model和view对象之间有什么区别呢?在一个简单的Web应用中,一个domain object model能够横穿所有层中,而在复杂的应用中,需要用到一个单独的view对象模型。domain object model应该属于业务逻辑层,它包含业务数据和与特定业务对象相关的业务逻辑;一个view对象包含presentation-specific的数据和逻辑。将view对象从domain object model中分离出来的缺点是在这两个对象模型之间必将出现数据映射。在JCatalog中,ProductBeanBuilder和 UserBeanBuilder利用reflection-based Commons BeanUtils来实现数据映射。

安全
目前,JSF没有内建的安全特性,而对于范例应用来说安全需求是非常基础的:用户登录到administration intranet中仅需用户名和密码认证,而无需考虑授权。
针对于JSF的认证,已有几种方法提出:
  • 利用一个backing bean:这一个方法非常简单,然而它却将backing bean与特殊的继承关系结合起来了
  • 利用JSF的ViewHandler decorator:这一方法中,安全逻辑紧密地与一特定Web层技术联系在了一起
  • 利用servlet filter:一个JSF应用与其他的Web应用没有什么两样,filter仍是处理认证检查的最好地方,这种方法中,认证逻辑与Web应用分离开来
在我们的范例程序中,SecurityFilter类被用来处理用户的认证,目前,受保护的资源只包含三个页面,出于简单的考虑,将它们的位置被硬编码到Filter类中。

分页
该应用中的Catalog页面需要分页,表示层可用来处理分页,即它取出所有的数据并保存在这一层;分页同样可在business-logic层、 integration层、甚至EIS层中实现。由于在JCatalog中假设不超过500个产品,因此所有的产品信息能存放在一个user session中,我们将分页逻辑放在了ProductListBean中,与分页相关的参数将通过JSF managed-bean工具配置。

Caching
Caching是提高Web应用性能的最重要技术之一,在应用构建中的很多层中都可以实现caching。JSF managed-bean工具可以使在表示层实现caching非常容易。通过改变一个managed bean的范围,这个managed bean中包含的数据可以在不同的范围内缓存。

范例应用中采用了两级caching,第一级caching存在于业务逻辑层,CachedCatalogServiceImpl类维护了一个所有产品和目录的读写cache,Spring将该类作为一个singleton service bean来管理,所以,一级cache是一个应用范围的读写cache。

为了简化分页逻辑并进而提高应用的速度,产品同样在session范围内缓存到表示层,每一个用户维护着他自己的ProductListBean,这一方法的缺点是内存的消耗和数据的失效问题,在一个用户session中,如果管理员更改了catalog,用户可到的将是失效的数据,然而,由于我们假设应用的数据不会经常的改变,所以这些缺点将能够忍受。

File upload
目前的JSF Sun参考实现中不支持file upload。Struts虽已具有非常不错的file upload能力,然而要想使用这一特性需要Struts-Faces整合库。在JCatalog中,一个图像与一个产品相关联,在一个用户创建了新的产品后,她必须将相应的图片上传,图片将保存在应用服务器的文件系统里,产品的ID就是图像名称。

范例应用中采用、Servlet和Jakarta Common的file-upload API来实现简单的文件上传功能,该方法包含两个参数:图像路径和图像上传结果页面。它们将通过ApplicationBean来配置,详细内容请参看 FileUploadServlet类。

Validation
JSF中发布的标准validator是非常基础的,无法满足现实的需要,但很容易开发出自己的JSF validator,在范例中,我开发了SelectedItemsRange validator,它用来验证UISelectMany组件中选择的数量:

 

详细情况请参看范例。

定制错误信息
在JSF中,你可以为converters和validators创建resource bundle和定制错误信息,一个resource bundle可在faces-config.xml中创建:

  catalog.view.bundle.MessagesBR>
并将错误信息的key-value对加到Message.properties文件中:

  javax.faces.component.UIInput.CONVERSION=Input data is not in the correct type.
  javax.faces.component.UIInput.REQUIRED=Required value is missing.

业务逻辑层和Spring Framework
业务对象和业务服务存在于业务逻辑层中,一个业务对象不仅包含数据,而且包含相应的逻辑,在范例应用中包含三个业务对象:Product、Category和User。

业务服务与业务对象交互并提供更高级的业务逻辑,需要首先定义一个正式的业务接口,它是直接与终端用户交互的服务接口。在JCatalog中,通过在 Spring Framework帮助下的POJO实现业务逻辑层,其中共有两个业务服务:CatalogService包含Catalog管理相关的业务逻辑, UserService中包含User管理逻辑。

Spring是基于IoC概念的框架,在范例应用中用到的Spring特性包括:
  • Bean management with application contexts:Spring可以有效地组织我们的中间层对象,它能够消除singleton的proliferation,并易于实现良好的面向对象编程方法,即“编程到接口”。
  • Declarative Transaction management: Spring利用AOP实现事务管理,而无需借助于EJB容器,利用这种方法,事务管理可以用于任何POJO中。Spring的事务管理不局限于JTA,而是可以采用不同的事务策略,在范例应用中,我们将使用declarative transaction management with Hibernate transaction。
  • Data-access exception hierarchy:Spring提供了非常好的异常来代替SQLException,为利用Spring的异常,必须在Spring的配置文件中定义以下异常转换:
       
         
             
         

        BR>
        在范例应用中,如果一个具有重复ID的新产品被插入,将会抛出DataIntegrityViolationException,这一异常将被
        catch并rethrown一个DuplicateProductIdException。这样,该异常就可以与其它的异常区别处理。
  • Hibernate integration:Spring与Hibernate这样的ORM框架整合的非常好,Spring提供了对Hibernate session的高效和安全的处理,它可通过application context配置Hibernate的SessionFactories和JDBC数据源,并使得应用易于测试。

Integration层和Hibernate
Hibernate是一个开源的ORM框架,它可以支持所有主流SQL数据库系统,Hibernate的查询语言为对象和关系架起了非常好的桥梁。Hibernate提供了强大的功能以实现:数据读取和更新、事务管理、数据连接池、查询和实体关系管理等。

Data Access Ojbect(DAO)
JCatalog中采用了Dao模式,该模式抽象和封装了所有对数据源的访问,该应用中包括两个DAO接口:CatalogDao和UserDao,它们相应的实现HibernateCatalogDaoImpl和HibernateUserDAoImpl包含了Hibernate特定的逻辑来实现数据的管理和持久化。

实现
现在我们来看看如何将上面讨论的这些东西包装在一起以实现JCatalog,你可以从这个地址下载源码:source code

数据库设计
我们为该范例应用创建了包含4个表的数据库,如图5所示:


类设计
图6显示了JCatalog的类图


“编程到接口”的思想贯穿了整个设计实现中,在表示层,共用到四个backing bean:ProductBean、ProductListBean、UserBean和MessageBean;业务逻辑层包含两个业务服务 (CatalogService和UserService)和三个业务对象(Product、Category和User);Integration层有两个Dao接口和它们相应的Hibernate实现,Spring的application context用来管理绝大多数的业务逻辑层和integration层的对象;ServiceLocator将JSF和业务逻辑层整合在了一起。

Wire everything up
由于篇幅所限,我们仅举例说明,范例中use case CreateProduct展示了如何装配和构建应用,在详细讲述细节前,我们利用sequence图(图7)来说明所有层的end-tp-end整合。



表示层
表示层实现包括创建JSP页面、定义页导航、创建和配置backing bean以及将JSF与业务逻辑层整合。
  • JSP page:createProduct.jsp是用来创建新产品的页面,它包含UI组件并将组件打包成ProductBean,ValidateItemsRange标签用来验证用户选择的种类数量,对每一个产品至少要有一个种类被选中。
  • 页面导航:应用中的导航被定义在应用的配置文件faces-navigation.xml中,CreateProduct的导航准则如下:

   */FONT>
  
      createProduct/FONT>
      /createProduct.jsp/FONT>
   /FONT>
/FONT>

   /createProduct.jsp/FONT>
  
      success/FONT>
      /uploadImage.jsp/FONT>
   /FONT>
  
      retry/FONT>
      /createProduct.jsp/FONT>
   /FONT>
  
      cancel/FONT>
      /productList.jsp/FONT>
   /FONT>
/FONT>
  • Backing bean:ProductBean不仅包含有将数据映射到页面上的UI组件的属性,还包括三个action:createAction、editAction和deleteAction,下面是createAction方法的代码:
public String createAction() {
   try {
      Product product = ProductBeanBuilder.createProduct(this);

      //Save the product.
      this.serviceLocator.getCatalogService().saveProduct(product);

      //Store the current product id inside the session bean.
      //For the use of image uploader.
      FacesUtils.getSessionBean().setCurrentProductId(this.id);

      //Remove the productList inside the cache.
      this.logger.debug("remove ProductListBean from cache");
      FacesUtils.resetManagedBean(BeanNames.PRODUCT_LIST_BEAN);
   } catch (DuplicateProductIdException de) {
      String msg = "Product id already exists";
      this.logger.info(msg);
      FacesUtils.addErrorMessage(msg);

      return NavigationResults.RETRY;
   } catch (Exception e) {
      String msg = "Could not save product";
      this.logger.error(msg, e);
      FacesUtils.addErrorMessage(msg + ": Internal Error");

      return NavigationResults.FAILURE;
   }
   String msg = "Product with id of " + this.id + " was created successfully.";
   this.logger.debug(msg);
   FacesUtils.addInfoMessage(msg);

   return NavigationResults.SUCCESS;
}
  • Managed-bean声明:ProductBean必须在JSF配置文件faces-managed-bean.xml中配置:

  
      Backing bean that contains product information.
   /FONT>
   productBean/FONT>
   catalog.view.bean.ProductBean/FONT>
   requestnbsp;  
  
      id
      #{param.productId}/FONT>
   /FONT>
  
      serviceLocator
      #{serviceLocatorBean}/FONT>
   /FONT>
/FONT>
  •  表示层和业务逻辑层之间的整合: ServiceLocator抽象了查找服务的逻辑,在范例应用中,ServiceLocator被定义为一个接口,该接口实现为一个JSF的 managed bean,即ServiceLocatorBean,它将在Spring的application context中寻找服务:
ServletContext context = FacesUtils.getServletContext();
this.appContext = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
this.catalogService = (CatalogService)this.lookupService(CATALOG_SERVICE_BEAN_NAME);
this.userService = (UserService)this.lookupService(USER_SERVICE_BEAN_NAME);
业务逻辑层
  • 业务对象:由于采用Hibernate提供持久化,因此Product和Category两个业务对象需要为它们的所有field提供getter和setter。
  • 业务服务:CatalogService接口中定义了所有的与Catalog management相关的服务:
public interface CatalogService {
   public Product saveProduct(Product product) throws CatalogException;
   public void updateProduct(Product product) throws CatalogException;
   public void deleteProduct(Product product) throws CatalogException;
   public Product getProduct(String productId) throws CatalogException;
   public Category getCategory(String categoryId) throws CatalogException;
   public List getAllProducts() throws CatalogException;
   public List getAllCategories() throws CatalogException;
}
  • Spring Configuration:这里是CatalogService的Spring配置:


  
/FONT>



  
/FONT>



  
  
  
     
         PROPAGATION_REQUIRED,readOnly/FONT>
       PROPAGATION_REQUIRED/FONT>
       PROPAGATION_REQUIRED/FONT>
       PROPAGATION_REQUIRED/FONT>
      /FONT>
  
/FONT>
  • Spring和Hibernate的整合:下面是HibernateSessionFactory的配置:

<!-- Hibernate SessionFactory Definition -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
   <property name="mappingResources">
      <list>
         <value>catalog/model/businessobject/Product.hbm.xml</value>
         <value>catalog/model/businessobject/Category.hbm.xml</value>
         <value>catalog/model/businessobject/User.hbm.xml</value>
      </list>
   </property>
   <property name="hibernateProperties">
      <props>
         <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
       <prop key="hibernate.show_sql">true</prop>
       <prop key="hibernate.cglib.use_reflection_optimizer">true</prop>
       <prop key="hibernate.cache.provider_class">net.sf.hibernate.cache.HashtableCacheProvider</prop>
      </props>
   </property>
   <property name="dataSource">
      <ref bean="dataSource"/>
   </property>
</bean>

CatalogDao uses HibernateTemplate to integrate between Hibernate and Spring. Here's the configuration for HibernateTemplate:

<!-- Hibernate Template Defintion -->
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate.HibernateTemplate">
   <property name="sessionFactory"><ref bean="sessionFactory"/></property>
   <property name="jdbcExceptionTranslator"><ref bean="jdbcExceptionTranslator"/></property>
</bean>

BR>

Integration层
Hibernate通过xml配置文件来映射业务对象和关系数据库,在JCatalog中,Product.hbm.xml表示了Product对象的映射,Category.hbm.xml则用来表示Category的映射,Product.hbm.xml如下:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
      "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
      "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping package="catalog.model.businessobject">
   <class name="Product" table="product">
      <id name="id" column="ID" unsaved-value="null">
         <generator class="assigned"/>
      </id>
      <property name="name" column="NAME" unique="true" not-null="true"/>
      <property name="price" column="PRICE"/>    
      <property name="width" column="WIDTH"/>      
      <property name="height" column="height"/>      
      <property name="description" column="description"/>   
      <set name="categoryIds" table="product_category" cascade="all">
         <key column="PRODUCT_ID"/>
         <element column="CATEGORY_ID" type="string"/>
      </set>
   </class>
</hibernate-mapping>

CatalogDao is wired with HibernateTemplate by Spring:

<!-- Catalog DAO Definition: Hibernate implementation -->
<bean id="catalogDao" class="catalog.model.dao.hibernate.CatalogDaoHibernateImpl">
   <property name="hibernateTemplate"><ref bean="hibernateTemplate"/></property>
</bean>

 

结论
本文主要讲述了如何将JSF与Spring、Hibernate整合在一起来构建实际的Web应用,这三种技术的组合提供了一个强大的Web应用开发框架。在Web应用的高层设计中应该采用多层构架体系,JSF非常适合MVC设计模式以实现表示层,Spring可用在业务逻辑层中管理业务对象,并提供事物管理和资源管理等,Spring与Hibernate结合的非常出色,Hibernate是强大的O/R映射框架,它可以在integration层中提供最好的服务。

通过将整个Web应用分割成多层,并借助于“编程到接口”,应用程序的每一层所采用的技术都是可替换的,例如Struts可以用来替换JSF,JDO可替换Hibernate。各层之间的整合不是不值得研究,采用IoC和ServiceLocator设计模式可使得整合非常容易。JSF提供了其它Web框架欠缺的功能,然而,这并不意味着你马上抛弃Struts而开始使用JSF,是否采用JSF取决于项目目前的状况和功能需求,以及开发团队的意见等。


实用助手类读文件中的对应字符串

个人空间 — 作者: LiuYi @ 2005-09-23, 16:30

package mysqlpro;

import java.util.Properties;


public class SqlHelper {
 private static final String maps = "db.properties";
 
 private static SqlHelper helper = new SqlHelper();
 
 private SqlHelper(){}
 
 public static SqlHelper CreateHelper(){
  if(helper==null)
    helper = new SqlHelper();
  return helper;
 }
 
 public String getValue(String key){
  
  String value="";
  Properties pro = new Properties();
  try{
   pro.load(getClass().getResourceAsStream(maps));/**getClass取得一个正在运行的类,getResourceAsStream读取文件为一个流**/
   if(pro.containsKey(key)){/**看是否有这个KEY值**/
    value = pro.getProperty(key);/**取对应KEY的值**/
   }
  }
  catch(Exception ex){
   ex.printStackTrace();
  }
  
  
  
  return value;
 }
}

 

 

 

 

 

 


package mysqlpro;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;

public class MainApps {
 private Connection con = null;
 private ResultSet res = null;
 public MainApps(){
  try{
  Class.forName(SqlHelper.CreateHelper().getValue("driver"));
  con = DriverManager.getConnection(
   SqlHelper.CreateHelper().getValue("url"),
   SqlHelper.CreateHelper().getValue("user"),
   SqlHelper.CreateHelper().getValue("password")
  );
  
   res = con.createStatement().executeQuery("select * from stu");
  
   while(res.next()){
    System.out.println(res.getString(1)+""+res.getString(2));
   }
  
  
  }
  catch(ClassNotFoundException ex){
   ex.printStackTrace();
  }
  catch(SQLException ex){
   ex.printStackTrace();
  }
  
  
  
 }
 
 public static void main(String[] args){
  new MainApps();
 }
}



数据库中数据项变化不定,如何设计Java Beans

个人空间 — 作者: LiuYi @ 2005-09-23, 10:34

     

  1.  

    我们不只一次抱怨信息系统中数据项变化不定,无法设计和实现Java Beans。我们也不只一次作过这样的事情:数据项增加或减少了,我需要修改信息系统以求适应。我们对付这种变化莫定的需求还有一招:天天催企业领导或业务人员决定数据项,而不开始下面的设计和开发,还美名其为一个需求的"需求里程碑"没到,至少这个需求相关的设计和开发绝对不能开始。本文为这种情况提供了一种解决办法,并美名其为"以动制动"。

     

  2.  

    Java Beans 作为一种组件技术,其结构体系主要由属性、方法和事件构成。象在其它面向对象技术中一样,在Beans 中,属性同样起决定其当前状态的作用。一个Bean的属性的访问和设置都必须通过访问方法和设置方法来进行。

    下面我们先举一个的示例,然后对Beans 组件技术中的属性支持进行解释。

    																				
    public class Author{
    protected string name;
    protected boolean married;
    protected string[] books;
    public string[] getBooks(){}
    public void setBooks(integer[] x){}
    public void setName(string n){}
    public string getName(){}
    public boolean isMarried(){}
    public void setMarried(boolean bl){}
    ......
    }
    
    																		

    这是一个非常简单的Bean,其中类的修饰符必须是public还有就是setXXX()/getXXX()方法必须遵循Beans内部的命名规则,因为Beans是根据这两个方法来确定属性的。其实,setXXX()/getXXX()方法是Beans的属性机制的核心技术。

    2.1 setXXX()/getXXX()方法

    一个Bean属性的定义完全取决于有无访问者方法:设置器(setXXX())和获取器(getXXX()),而与在类定义中有无显示说明字段毫无关系,即上例中删去那些protected修饰的字段与Bean毫无影响,因为Beans内部是根据有无访问方法来确定属性的存在与否的。为了使Beans能确认一个属性,其设置器(setXXX())和获取器(getXXX())必须遵循下列命名规则:

     

    • 一个属性名在访问方法中必须以大写字母开头;
    • 在其它地方以小写字母开头。

     

    当然我们并不要求每个属性都必须同时拥有这两种访问者,因为我们并不排除某个属性只可读或可写。每种类型的属性的设计必须遵循的规则叫这种属性的设计模板,下面介绍各种类型属性的设计模板。

    2.1.1 简单属性

    一个属性为简单属性,当这个属性不与外界有连带关系时。简单属性中由于类型的复杂程度又有简单类型属性和数组属性之分。

     

    1. 简单类型属性的设计模板

      布尔型:

      设置器:public boolean is<属性名>(){}
      获取器:public void set<属性名> (boolean bl ){}

      其它类型的属性的设计模板如下:

      设置器:public void set<属性名>( <属性类型> x ){}
      获取器:public <属性类型> get<属性名>( ){}
    2. 数组属性的设计模板

      单个元素的设计模板

      设置器:public void set<属性名>( int i ,<属性元素类型> x ){}
      获取器:public <属性元素类型> get<属性名>( int i ){}

      整个数组的设计模板:

      设置器:public void set<属性名>( <属性元素类型> [] x){}
      获取器:public <属性元素类型>[] get<属性名>( ){}

      对于简单属性,不需要另外的附加类或接口。

     

    2.1.2 相关属性

    相关属性是这样的一种属性,它的改变能以事件的形式通知给对它感兴趣的部分,即事件收听者或目标。很明显,这种属性的作用在于它能使收听者接到其改变事件后根据其中的信息产生一些行为,从而达到两者之间的默契。相关属性的访问者方法遵循与简单属性相同的形式,就是说单从访问者方法是看不出其与简单属性的区别,但它要另外的附加类或接口以及事件的传播机制的支持(后面,我们会看到这同样适用于约束属性)。

    实现一个关联属性涉及到三方,源Bean,目标Bean和协调代码:

     

    • 源Bean

      源Bean必须提供属性变化事件监听器的注册和解册入口:

      
      public void addpropertyChangeListener (propertyChangeListener pcListener){}
      public void removepropertyChangeListener (propertyChangeListener pcListener){}
      

      如只想通知目标Bean某个特定属性的变化,可用下面特定属性的注册和解册方法:

      
      public void add<属性名>Listener (propertyChangeListener pcListener){}
      public void remove<属性名>Listener (propertyChangeListener pcListener){}
      

      这样,目标Bean只会接到源Bean此属性的变化的事件通知,减少了不必要的信息通信。另外,为了实现关联属性的方便,系统提供了一个帮助者类propertyChangeSupport,源Bean可实例化这个帮助者类,让它来为我们管理和维护收听者列表以及属性变化事件的通知的触发等工作。

    • 目标Bean

      目标Bean除了要实现propertyChangeListener接口外,还要用源Bean提供的注册方法注册自己。这样,目标Bean的实现大体框架如下:

      
      public class targetBean implements propertyChangeListener{
        protected SourceBean source;
         ……
        source=new SourceBean();
        source.addpropertyChangeListener(this);
        public void propertyChange(propertyChangeEvent e){
        ……
        }
      }
      
    • 协调代码

      协调代码的工作职责分为以下几步:

       

      1. 负责创建源Bean和目标Bean;
      2. 利用源Bean的属性变化事件监听器的注册入口注册目标Bean;
      3. 改变源Bean的属性的属性
      4. 利用源Bean的属性变化事件监听器的解册入口解册目标Bean;

       

     

    2.1.3 约束属性

    约束属性是Beans所支持的最复杂最高级的属性,它允许收听者对属性的改变提出否定意见。

    与相关属性类似,其设计与实现也要涉及到源Bean、目标Bean和协调代码。只要把相关属性设计中的property改成Vetoable(除了propertyChangeEvent外),不同的是为了能使目标Bean"反对"源Bean属性的变化。Beans提供了一种异常propertyVetoException,只要目标Bean收到属性改变的事件通知后,查看属性的新值,如果不满意,可抛出一个异常,让源Bean放弃改变属性到这个新值的念头,这就是约束属性中给目标Bean增加的"反对权利"。下面的简单源Bean和目标Bean的伪代码表述了约束属性的实现视图。

    • 源Bean

      
      public class SourceBean {
      public void addVetoChangeListener (VetoChangeListener vpListener){}
      public void removeVetoChangeListener (VetoChangeListener vpListener){}
      

      /*由于属性设置器本身不想处理异常,所以我们抛出异常,当然你也可以在属性设置器处理异常,属性变化监听者对属性的变化作出同意还是反对就是通过抛出异常的实现的。*/

      
      public void setName(String n) throws propertyVetoException{
                 /*从下面目标的代码可能抛出一个异常从而终止代码的执行
      */
      实例化一个propertyChangeEvent对象
      执行属性变化监听者的vetoChange方法
                 /*如果上面的代码抛出异常,下面这行代码不会被执行,
      也就是说监听者阻止了属性的变化
      */
                 name=n  //修改属性的值
              }
          }
      
    • 目标Bean

      
      public class TargetBean implements VetoChangeListener{
      public void vetoChange(propertyChangeEvent e) throws propertyVetoException{
      if e中的新值不满意 then 
      生成 并抛出一个propertyVetoException实例
      else
      ……
      endif
      }
      }
      
    • 协调代码

      协调代码的工作职责分为以下几步:

      1. 负责创建源Bean和目标Bean;
      2. 利用源Bean的属性变化事件监听器的注册入口注册目标Bean;
      3. 改变源Bean的属性的属性,并捕获异常
      4. 利用源Bean的属性变化事件监听器的解册入口解册目标Bean;

    2.2 标准Java Bean属性总结

    图一 Java Bean属性综合图解

    如图一所示,Java语言为Java Bean组件的属性机制提供了良好的基础,这些语言元素包括Java的面向对象技术,接口技术和异常技术等。Java Bean属性命名规则和Java Bean属性设计模板是Java Bean组件的属性机制的规范。遵行这些规范,Java Bean组件的属性可以分为三种:最基本的为简单属性,这种属性只涉及属性所在的Java Bean组件本身;相关属性涉及到三方,包括源Bean、目标bean和协调代码,源Bean为属性的拥有者,目标bean为属性变化事件的监听者,协调代码负责组织双方,另外源Bean还可能实例化一个propertyChangeSupport对象来管理所有目标Bean,propertyChangeSupport对象的工作主要是保存所有目标Bean实例,并激发这些目标Bean的事件变化监听方法;约束属性在原理上和相关属性一样,只是增加了目标Bean对源Bean属性变化的"反对权利"。

    Java Bean组件技术是建立在Java基础上的组件技术,它继承了其的所有特点(如跨平台和面向对象),又引进了其它组件技术的思想,这两个方面恰好是其能成为后起之秀的主要原因。它所能支持的属性如相关属性和约束属性是其它组件技术所不能及的。

     

  3.  

    无论是设计模式中值对象、视图助手,MVC框架中的模型(Model),还是Enterprise Bean中的会话Bean,实体Bean,都和javaBean属性息息相关。JavaBean组件属性的优点我们前面已经总结过,随着J2EE平台在企业应用中的广泛使用,JavaBean组件属性的缺陷也就显露了出来:无法满足企业应用动态变化的需要,原因在于javaBean属性是编译时的语言特性,它必须遵行一套命名规则和设计魔板。比如我按照某个企业的要求设计出了2000个实体Bean来满足该企业对信息系统中业务数据模型的需要,过了一定时间后,他们的业务发生了一定的变化,要对数据模型扩充一部分数据项,可想而知我会有多辛苦。扩展javaBean属性机制定义了五种属性访问策略,使得属性的访问代码像脚本一样在运行时决定,另外一个进步就是它支持List和Map属性的元素属性,也就是扩展javaBean属性机制它不把一个Bean的某个List和Map成员看成一个整体属性,而是动态地把这个List和Map成员的元素看成属性,这样无疑提供了一种无限扩展Bean属性的能力,为解决由于数据项变化带来的设计和实现的变更提供了技术基础。

    3.1 五种属性访问格式

    Common-beanutils 1.6中的propertyUtils实用类使用Java语言的内省反射功能实现扩展属性的设置器和获取器。propertyUtils定义了引用一个特定Java bean属性的五种格式:

    1. 简单属性,格式beanName.propName。propName标识了JavaBean beanName的一个属性,这个属性的获取器和设置器的方法是通过JavaBean的标准内省功能决定的。如果要改变简单属性的值,必须要有设置器操作,可以想象成类似调用beanName.[getpropName()|setpropName(value)];
    2. 嵌套属性,格式beanName.propName1.propName2.propName3。像简单属性一样,第一个propName1元素被用来选择一个属性获取器方法,在这个方法返回的对象上使用propName2的获取器方法返回下一个对象,最后这个对象的属性propName3被访问或设置,可以想象成类似调用beanName.getpropName1().getpropName2().[getpropName3()|setpropName3(value)];
    3. 索引属性,格式beanName.propName[index]。属性propName1可以是一个数组, java.util.List或者JavaBean beanName有索引属性获取器和设置器操作。bean只需propName的获取器方法,可以想象成类似调用beanName. [getpropName (index)|setpropName(index,value)];
    4. 映射属性,格式beanName. propName(key)。propName是一个java.util.Map实现。bean只需propName的获取器方法,可以想象成类似调用beanName. getpropName ().[get("key")|set("key",value);
    5. 组合属性,格式beanName. propName1.propName2[index].propName3(key)。

    3.2 代码解释

    为了更有效和直观的解释扩展属性的使用,在这里列出了两段代码,一段代码是Java Bean 代码,一段为propertyUtils以五中格式访问扩展属性的代码。

    3.2.1 扩展属性Java Bean

    下面是支持这些扩展属性的Java Bean代码:

    																				
    //TestBean.java
    import java.util.*;
    import java.io.*;
    public class TestBean {
        private String dupproperty[] =
        { "Dup 0", "Dup 1", "Dup 2", "Dup 3", "Dup 4" };
      //propertyUtils只需要该索引属性的一个获取器操作就能
      //使用get/setIndexedproperty方法访问和设置索引和元素值
        public String[] getDupproperty() {
        	 System.out.println("getDupproperty");
            return (this.dupproperty);
        }
     //下面的方法对propertyUtils的get/setIndexedproperty方法不关键,有则会调用这些方法
        public String getDupproperty(int index) {
        	 System.out.println("getDupproperty index");
            return (this.dupproperty[index]);
        }
    
        public void setDupproperty(int index, String value) {
        	 System.out.println("setDupproperty index value");
        	
            this.dupproperty[index] = value;
        }
    
        public void setDupproperty(String dupproperty[]) {
        	System.out.println("setDupproperty du[]");
            this.dupproperty = dupproperty;
        }
       //这是一个索引属性,除了支持"[]"型的数组属性外,还支持申明为List类型的属性
         /**
         * A List property accessed as an indexed property.
         */
        private static List listIndexed = new ArrayList();
    
        static {
            listIndexed.add("String 0");
            listIndexed.add("String 1");
            listIndexed.add("String 2");
            listIndexed.add("String 3");
            listIndexed.add("String 4");
        }
    
        public List getListIndexed() {
            return (listIndexed);
        }
        
        //嵌套属性
        private TestBean nested = null;
        public TestBean getNested() {
        	System.out.println("getNested");
            if (nested == null)
                nested = new TestBean();
            return (nested);
        }
    
      //这是一个映射属性,必须申明为Map类型,propertyUtils只需要该属性的一个获取器操作就能
      //使用get/setMappedproperty方法访问和设置键和值
       private  Map hash = null;
       public Map getHash(){
      	System.out.println("getHash");
      	if (hash == null) {
                hash = new HashMap();
                hash.put("First Key", "First Value");
                hash.put("Second Key", "Second Value");
            }
            return (hash);
       }
      //下面的方法对在common-beanutils 1.6.1中propertyUtils的getMappedproperty方法不起作用,中不调用这些方法,
      //而且不支持嵌套的映射属性
      //propertyUtils.setMappedproperty(bean, "nested.hash(Fifth Key)", "Fifth Value"); don't work!!
       public Object getHash(String Key){
       	System.out.println("getHash  Key");
      	return hash.get(Key);
       }
    
       public void setHash(String Key,Object value){
     	System.out.println("setHash  Key value ");
            hash.put(Key,value);
       }
       //这是一个简单属性,想在propertyUtils中修改必须有设置器操作
      private String sample = null;
      public String getSample() {
         return sample;
      }
      public void setSample(String sample){
     	this.sample = sample;
      }
    }
    
    																		

    3.2.2 使用propertyUtils实用类访问扩展属性

    下面propertyUtils以五中格式访问扩展属性的代码:

    																				
    //testpropertyUtils.java
    import org.apache..commons.beanutils.*;
    
    import junit.framework.TestCase;
    import junit.framework.Test;
    import junit.framework.TestSuite;
    
    public class testpropertyUtils extends TestCase {
    public propertyUtilsTestCase(String name) {
            super(name);
    }
    /**
    * 实例化TestBean
    */
    public void setUp() {
            bean = new TestBean();
    }
    /** 
    * 测试索引属性
    */
    public void testSetIndexedValues() {
            Object value = null;
            //测试数组索引属性
    try {
                propertyUtils.setproperty(bean,
                        "dupproperty(0)",
                        "New 0");
                value =
                        propertyUtils.getproperty(bean,
                                "dupproperty(0) ");
                assertNotNull("Returned new value 0", value);
                assertTrue("Returned String new value 0",
                        value instanceof String);
                assertEquals("Returned correct new value 0", "New 0",
                        (String) value);
            } catch (Throwable t) {
                fail("Threw " + t);
            }
    	    //测试List索引属性
    try {
                propertyUtils.setproperty(bean,
                        "listIndexed(0) " ,
                        "New value");
                value =
                        propertyUtils.getproperty(bean,
                                " listIndexed(0)");
                assertNotNull("Returned new value 0", value);
                assertTrue("Returned String new value 0",
                        value instanceof String);
                assertEquals("Returned correct new value 0", " New value ",
                        (String) value);
            } catch (Throwable t) {
                fail("Threw " + t);
            }
    }
    /** 
    * 测试映射属性
    */
    public void testSetMappedValues() {
    
            Object value = null;
            try {
                propertyUtils.setproperty(bean,
                        "hash(key1)",
                        "New 0");
                value =
                        propertyUtils.getproperty(bean,
                                "hash(key1)");
                assertNotNull("Returned new value 0", value);
                assertTrue("Returned String new value 0",
                        value instanceof String);
                assertEquals("Returned correct new value 0", "New 0",
                        (String) value);
            } catch (Throwable t) {
                fail("Threw " + t);
            }
    }
    /** 
    * 测试嵌套属性
    */
    public void testNestedValues() {
    	….
    }
    
    }
    
    																		

     

  4.  

    相对标准Java Bean的编译时静态决定一个Bean的属性,利用扩展javaBean属性机制,能在运行时决定属性的bean为动态bean。动态bean既有标准Java Bean的类型检查机制又有扩展javaBean属性机制的动态特点。下面我们从创建动态Bean和在配置文件中定义动态Bean的属性两方面介绍common-beanutils中动态bean机制。

    4.1 运行时创建动态bean

    动态bean具有动态属性,也就是说可以由程序运行时构造bean的属性,而不是像标准的javaBean在编译时决定一个bean的属性。

    定义和访问一个动态bean的步骤如下:

    1. 定义一个动态属性Dynaproperty数组,动态属性Dynaproperty定义了一个属性的名字和对象类型;
    2. 用定义好的动态属性数组实例化一个动态类;
    3. 由动态类返回一个动态bean;
    4. 可以用propertyUtils访问和设置动态bean的属性。

    下面是定义和访问动态bean的代码

    																				
    // TestDynaBean.java
    import org.apache.commons.beanutils.*;
    import java.util.*;
    public class TestDynaBean {
      public static void main(String[] args) {
    	TestBean bean = new TestBean();
    	Object value = null;
    	try{
    	      Dynaproperty[] px = {
    	      new Dynaproperty("subordinate", bean.getClass()),
    	      new Dynaproperty("firstName", Class.forName("java.lang.String")),
    	      new Dynaproperty("lastName", Class.forName("java.lang.String"))
    		};
    	     DynaClass dynaClass = new BasicDynaClass("employee",null,
    	        px );
    	    
    	      DynaBean employee = dynaClass.newInstance();
    	      propertyUtils.setproperty(employee,"subordinate", bean);
    	      propertyUtils.setproperty(employee,"subordinate.listIndexed[0]","dy bean set");
    	      propertyUtils.setproperty(employee,"firstName", "Fred");
    	      propertyUtils.setproperty(employee,"lastName", "Flintstone");
    	      System.out.println("subordinate.listIndexed[0]:");
    	      System.out.println(propertyUtils.getproperty(employee,"subordinate.listIndexed[0]")); 
    	      System.out.println("firstName:" + propertyUtils.getproperty(employee,  "firstName"));
    	      System.out.println("lastName:" + propertyUtils.getproperty(employee,  "lastName"));
    	}catch (Exception e ){
    		System.out.println(e.toString());
    	}
      }
    }
    
    																		

    4.2 由文件配置动态bean的动态属性

    从配置文件配置动态bean的动态属性好处在于既能固定又能更改动态Bean的属性,这些属性是这个动态Bean对外界的宣布的"访问协议"。

    从上面的代码可以看出动态属性Dynaproperty的主要构造函数接受两个参数:第一个为属性名称,为字符串性,第二个为属性的类型,为Class类型。从配置文件读取的资料普通为字符串型,我们可以用ClassLoader把配置文件中字符串型的属性类型转化成Class类型的属性类型。

    下面struts 1.1 中struts-example.war的 formbeans.xml中的片断体现了如何在配置文件中定一个动态bean的动态属性:

    																				
    <form-bean name="logonForm" type="org.apache.struts.validator.DynaValidatorForm">
    <form-property name="username" type="java.lang.String"/>
    <form-property name="password" type="java.lang.String"/>
    </form-bean>
    
    																		

    下面RequestUtils的代码片断体现了如何从字符串表示的属性类型转化成Class型的属性类型:

    																				
    public static Class applicationClass(String className)
            throws ClassNotFoundException {
    
            // Look up the class loader to be used
            ClassLoader classLoader =
                Thread.currentThread().getContextClassLoader();
            if (classLoader == null) {
                classLoader = RequestUtils.class.getClassLoader();
            }
    
            // Attempt to load the specified class
            return (classLoader.loadClass(className));
    
        }
    
    																		

    这就是关于动态bean的所有奥秘,RequestUtils类代码片断public static ActionForm createActionForm就是这样创建动态bean的。

     

  5.  

    我们说Java Bean所能支持的属性如相关属性和约束属性是其它组件技术所不能及的,这是非常先进的设计观念和模式。但是标准Java Bean属性是静态的,是在编译时决定的,不能满足变化不定的企业数据项的需求。扩展Java Bean属性机制提供了很好的思想,它不以Bean的List或Map成员看成一个整体属性,而把其中的动态元素看成属性,而且提供了脚本式的属性访问方法,从而为运行时动态管理(增,删和改)属性铺平了道路。动态属性继续发扬了扩展Java Bean属性机制的思路,考虑到增加程序的健壮性,保留了静态属性的类型检查机制,另外动态属性和属性配置文件结合可以提供良好的"静态和动态"平衡点,保证了动态Bean的动态属性不会漫无边际的扩张,这种动态性是有"章"可循的。

    图二 三种属性机制的关系图

    这三种属性机制的关系图表明了它们之间的这种依赖和发展关系;扩展Java Bean属性机制和动态属性机制都可以很好地解决数据项变化的需求。



Hibernate包的作用详解

个人空间 — 作者: LiuYi @ 2005-09-23, 10:32

Hibernate一共包括了23个jar包,令人眼花缭乱。本文将详细讲解Hibernate每个jar包的作用,便于你在应用中根据自己的需要进行取舍。

下载Hibernate,例如2.0.3稳定版本,解压缩,可以看到一个hibernate2.jar和lib目录下有22个jar包:

hibernate2.jar:

Hibernate的库,没有什么可说的,必须使用的jar包

cglib-asm.jar:

CGLIB库,Hibernate用它来实现PO字节码的动态生成,非常核心的库,必须使用的jar包

dom4j.jar:

dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的。dom4j是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件,可以在SourceForge上找到它。在IBM developerWorks上面可以找到一篇文章,对主流的Java XML API进行的性能、功能和易用性的评测,dom4j无论在那个方面都是非常出色的。我早在将近两年之前就开始使用dom4j,直到现在。如今你可以看到越来越多的Java软件都在使用dom4j来读写XML,特别值得一提的是连Sun的JAXM也在用dom4j。这是必须使用的jar包,Hibernate用它来读写配置文件。

odmg.jar:

ODMG是一个ORM的规范,Hibernate实现了ODMG规范,这是一个核心的库,必须使用的jar包。

commons-collections.jar:

Apache Commons包中的一个,包含了一些Apache开发的集合类,功能比java.util.*强大。必须使用的jar包。

commons-beanutils.jar:

Apache Commons包中的一个,包含了一些Bean工具类类。必须使用的jar包。

commons-lang.jar:

Apache Commons包中的一个,包含了一些数据类型工具类,是java.lang.*的扩展。必须使用的jar包。

commons-logging.jar:

Apache Commons包中的一个,包含了日志功能,必须使用的jar包。这个包本身包含了一个Simple Logger,但是功能很弱。在运行的时候它会先在CLASSPATH找log4j,如果有,就使用log4j,如果没有,就找JDK1.4带的java.util.logging,如果也找不到就用Simple Logger。commons-logging.jar的出现是一个历史的的遗留的遗憾,当初Apache极力游说Sun把log4j加入JDK1.4,然而JDK1.4项目小组已经接近发布JDK1.4产品的时间了,因此拒绝了Apache的要求,使用自己的java.util.logging,这个包的功能比log4j差的很远,性能也一般。后来Apache就开发出来了commons-logging.jar用来兼容两个logger。因此用commons-logging.jar写的log程序,底层的Logger是可以切换的,你可以选择log4j,java.util.logging或者它自带的Simple Logger。不过我仍然强烈建议使用log4j,因为log4j性能很高,log输出信息时间几乎等于System.out,而处理一条log平均只需要5us。你可以在Hibernate的src目录下找到Hibernate已经为你准备好了的log4j的配置文件,你只需要到Apache 网站去下载log4j就可以了。commons-logging.jar也是必须的jar包。

使用Hibernate必须的jar包就是以上的这几个,剩下的都是可选的。



ant.jar:

Ant编译工具的jar包,用来编译Hibernate源代码的。如果你不准备修改和编译Hibernate源代码,那么就没有什么用,可选的jar包

optional.jar:

Ant的一个辅助包。



c3p0.jar:

C3PO是一个数据库连接池,Hibernate可以配置为使用C3PO连接池。如果你准备用这个连接池,就需要这个jar包。

proxool.jar:

也是一个连接池,同上。

commons-pool.jar, commons-dbcp.jar:

DBCP数据库连接池,Apache的Jakarta组织开发的,Tomcat4的连接池也是DBCP。

实际上Hibernate自己也实现了一个非常非常简单的数据库连接池,加上上面3个,你实际上可以在Hibernate上选择4种不同的数据库连接池,选择哪一个看个人的偏好,不过DBCP可能更通用一些。另外强调一点,如果在EJB中使用Hibernate,一定要用App Server的连接池,不要用以上4种连接池,否则容器管理事务不起作用。


connector.jar:

JCA 规范,如果你在App Server上把Hibernate配置为Connector的话,就需要这个jar。不过实际上一般App Server肯定会带上这个包,所以实际上是多余的包。

jaas.jar:

JAAS是用来进行权限验证的,已经包含在JDK1.4里面了。所以实际上是多余的包。

jcs.jar:

如果你准备在Hibernate中使用JCS的话,那么必须包括它,否则就不用。

jdbc2_0-stdext.jar:

JDBC2.0的扩展包,一般来说数据库连接池会用上它。不过App Server都会带上,所以也是多余的。

jta.jar:

JTA规范,当Hibernate使用JTA的时候需要,不过App Server都会带上,所以也是多余的。

junit.jar:

Junit包,当你运行Hibernate自带的测试代码的时候需要,否则就不用。

xalan.jar, xerces.jar, xml-apis.jar:

Xerces是XML解析器,Xalan是格式化器,xml-apis实际上是JAXP。一般App Server都会带上,JDK1.4也包含了解析器,不过不是Xerces,是Crimson,效率比较差,不过Hibernate用XML只不过是读取配置文件,性能没什么紧要的,所以也是多余的。



Hibernate的批量删除的效率分析

个人空间 — 作者: LiuYi @ 2005-09-23, 10:31

Hibernate作为ORM,有一个ORM固有的问题,就是由于为了持久对象的同步,不能够使用批量删除和批量更新的sql,只能按照主键一条条来操作。因此效率相对JDBC来说是比较低的。然而事情也并不总是那么绝望,只要你对Hibernate进行优化,也可以得到相当满意的速度。

代码:
 session.delete("from Cat as c where ...");


该语句实际上发送sql:
代码:
==> select id,name,sex,weight from cat;
==> delete from cat where id = ?


Hibernate先查询数据,确实要消耗一些时间,但是select只读操作和insert,delete,update这些数据库修改操作在速度上有一个以上的数量级的差距。所以 Hibernate虽然查询数据要多耗时,但是消耗的这点时间影响不是很大,主要是内存消耗的多。而delete的速度,我们知道调节Hibernate的Batch Size可以提供大大提高insert, delete和update的速度。

我的测试:

Oracle817,ojdbc14.jar 表记录1万条,全部删除。

JDBC:

sql语句
代码:
delete from cat

速度:平均6s

Hibernate:

代码:
session.delete("from Cat as c");


Batch Size = 0 速度: 25s
Batch Size = 50 速度: 6s

批量删除和批量更新建议用JDBC,这是一个原则,当然有的时候可能必须用Hibernate来批量更新和批量删除,那么这个时候我想说的就是,Hibernate批量更新和删除效率并非传说中的那么差,只要优化的好,速度也非常快。



Hibernate查询语言:HQL

个人空间 — 作者: LiuYi @ 2005-09-23, 10:29

 

HQL:Hibernate Qusery Language,如果你已经熟悉它,就会发现它跟SQL非常相像。不过 你不要被表面的假象迷惑,HQL是面向对象的(OO,用生命的眼光看待每一个对象,他们是如此 鲜活)。如果你对JAVA和SQL语句有一定了解的话,那么HQL对你简直易如反掌,你完全可以利用在公车上的时间掌握它。

以下从几个方面进行慢慢深入:

1。大小些敏感
大家知道Query是对大小写不敏感的,但是在HQL(前面提到它是OO的)中那么对象类的名称和属性确实大小写敏感的(符合java编程语法)。
如:sElect cat.name from Cat as cat和select cat.name from Cat as cat是一样的
但是:
sElect cat.name from CAT as cat和select cat.name from Cat as cat确实不一样的。

2。from语句
最简单的:
from eg.Cat
它只是简单的返回所有eg.Cat的实例
通常我们此时会为eg.Cat其个别名,因为在query的其余部分可能会用到(参看上边关于大小写
敏感时的例子情形),如:
from eg.Cat as cat 这里as可以省略。
上边只是单表查询,多表的情况如下写法:
from eg.Cat,eg.Dog
from eg.Cat as cat,eg.Dog as dog

3。join相关
(inner) join
left (outer) join
right (outer) join
full join
HQL同样对SQL中的这些特性支持
下面插播一个小话题,关于上边的那些特性,我一直都没怎么用,今天既然说到这里,就想
把上边的几个特性的用法说一下,也算对自己的一个补充:
假设有两个表:部门、员工,下面列举一些数据:
员工(Employee):
ID Name DepNo
001 Jplateau 01
002 Jony 01
003 Camel 02
部门(Department):
ID Name
01 研发部
02 营销部

在Hibernate中我们操纵的都是对象,所以我们操纵的是部门类和员工类
1).(inner) join
select employee.ID as id1,employee.Name as name1,department.ID as id2,department.Name
as name2 from Employee as employee join Department as department on employee.DepNo=
department.ID (注意到条件语句我用on 没有用where)
那么执行结果是什么呢?
id1 name1 id2 name2
++++++++++++++++++++++++++++++++++++++
001 Jplateau 01 研发部
002 Jony 01 研发部

2).left (outer) join
select employee.ID as id1,employee.Name as name1,department.ID as id2,department.Name
as name2 from Employee as employee left join Department as department on employee.DepNo=
department.ID
那么执行结果又该是什么呢?
id1 name1 id2 name2
++++++++++++++++++++++++++++++++++++++
001 Jplateau 01 研发部
002 Jony 01 研发部
003 Camel null null
{就是说此时我要已第一个表的记录多少为准,第二个表中没有相应纪录的时候填充null}
3). right (outer) join
select employee.ID as id1,employee.Name as name1,department.ID as id2,department.Name
as name2 from Employee as employee right join Department as department on employee.DepNo=
department.ID
那么执行结果又该是什么呢?
id1 name1 id2 name2
++++++++++++++++++++++++++++++++++++++
001 Jplateau 01 研发部
002 Jony 01 研发部
null null 02 营销部
{就是说此时我要已第二个表的记录多少为准,第一个表中没有相应纪录的时候填充null}

4。select语句
就是要确定你要从查询中返回哪些对象或者哪些对象的属性。写几个例子吧:
select employee form Employee as employee
select employee form Employee as employee where employee.Name like 'J%'
select employee.Name form Employee as employee where employee.Name like 'J%'
select employee.ID as id1,employee.Name as name1,department.ID as id2,department.Name
as name2 from Employee as employee right join Department as department on employee.DepNo=
department.ID

select elements(employee.Name) from Employee as employee
(不明白elements到底是做什么用的?望给于说明)
等等
5。数学函数
JDO目前好像还不支持此类特性。
avg(...), sum(...), min(...), max(...)

count(*)

count(...), count(distinct ...), count(all...)

其用法和SQL基本相同

select distinct employee.name from Employee as employee
select count(distinct employee.name),count(employee) from Employee as employee

6。polymorphism (暂时不知道如何解释?)
from com.test.Animal as animal
不光得到所有Animal得实例,而且可以得到所有Animal的子类(如果我们定义了一个子类Cat)
一个比较极端的例子
from java.lang.Object as o
可以得到所有持久类的实例

7。where语句
定义查询语句的条件,举几个例子吧:
from Employee as employee where employee.Name='Jplateau'
from Employee as employee where employee.Name like 'J%'
from Employee as employee where employee.Name like '%u'
在where语句中“=”不光可以比较对象的属性,也可以比较对象,如:
select animal from com.test.Animal as animal where animal.name=dog

8。表达式

在SQL语句中大部分的表达式在HQL中都可以使用:
mathematical operators +, -, *, /

binary comparison operators =, >=, <=, <>, !=, like

logical operations and, or, not

string concatenation ||

SQL scalar functions like upper() and lower()

Parentheses ( ) indicate grouping

in, between, is null

JDBC IN parameters ?

named parameters :name, :start_date, :x1 (这种应该是另一种"?"的变通解决方法)

SQL literals 'foo', 69, '1970-01-01 10:00:01.0'

Java public static final constants eg.Color.TABBY

其他不必解释了,在这里我只想对查询中的参数问题说明一下:
大家知道在SQL中进行传递参数进行查询的时候,我们通常用PreparedStatement,在语句中写一大堆的“?”,
在hql中也可以用这种方法,如:
List mates = sess.find(
"select employee.name from Employee as employee " +
"where employee.Name=? ",
name,
Hibernate.STRING
);
(说明:上面利用Session里的find方法,在hibernate的api Session中重载了很多find方法,它可以满足你多种形式的查询)
上边是一个参数的情形,这种情况下紧接着引入参数和定义参数的类型,当为多个参数,调用另一个find方法,它的后两个
参数都是数组的形式。

还有另外一种方法来解决上边的问题,JDO也有这样的方法,不过和hibernate的表现形式上有差别,但他们两个骨子里却是
一样的,如:
Query q = sess.createQuery("select employee.name from Employee as employee where employee.Name=:name");
q.setString("name", "Jplateau");
//当有多个参数的时候在此逐一定义
Iterator employees = q.iterate();

9。order 语句
和sql语句没什么差别,如:
select employee.name from Employee as employee where employee.Name like 'J%' order by employee.ID desc (或者asc)

10。group by 语句
同样和sql语句没什么差别,如:

select employee.name,employee.DepNo from Employee as employee group by employee.DepNo

select foo.id, avg( elements(foo.names) ), max( indices(foo.names) ) from eg.Foo foo group by foo.id
{Note: You may use the elements and indices constructs inside a select clause, even on databases with no subselects.}
谁帮我解释一下上边两句,谢过!

11。子查询
hibernate同样支持子查询,写几个例子:

from eg.Cat as fatcat where fatcat.weight > ( select avg(cat.weight) from eg.DomesticCat cat )

小节:
其实HQL和SQL是非常相似的,在写的时候你只要时刻想到对象的概念,那么你完全可以用SQL的思想来写HQL.

参考资源:Hibernate参考手册第七章以及第九章,建议由时间的并且要用hibernate开发项目的朋友仔细研读该手册。


Hibernate 针对 Oracle 特有函数及自定义函数的实现办法

个人空间 — 作者: LiuYi @ 2005-09-23, 10:26

Hibernate 能支持数据库特有的函数,如 Oracle 的 connect by,Substr吗?能支持自己写的函数吗?答案是完全可以的!
最近有个项目在使用 Struts + Hibernate 开发,也碰到使用 Oracle connect by、自定义写的函数的困扰问题,后来查了很多相关的资料,
终于找到一个解决办法(不知是不是最好的?呵,希望你们指点一下):

1、首先把 hibernate.properties 文件的下面这行代码前面的 '#' 符号去掉,加入相应的函数转换另命名,
如下面的sf_getchannelstaff=sf_getchannelstaff,substr=substr[说明:sf_getchannelstaff这个函数是自己写]
hibernate.query.substitutions true 1, false 0, yes 'Y', no 'N',sf_getchannelstaff=sf_getchannelstaff,substr=substr

2、使用 Hibernate 的 createSQLQuery() 函数,关于 createSQLQuery() 函数如何使用,请参考相关文档。下面我使用 createSQLQuery()
函数的一段代码:
                String[] ret = new String[] { "", "", "", "" };
  try {
   this.initSession();
   StringBuffer sql = new StringBuffer();
   sql.append("select {c}.CHANNEL_ID AS {c.channelId},");
   sql.append("{c}.NAME AS {c.name},");
   sql.append("substr(sf_getchannelstaff({c}.CHANNEL_ID,'A'),1,254) AS {c.channelTypeId},");
   sql.append("{c}.UPPER_CHANNEL_ID AS {c.upperChannelId},");
   sql.append("{c}.IS_PRE_DEFINE AS {c.isPreDefine},");
   sql.append("substr(sf_getchannelstaff({c}.CHANNEL_ID,'B'),1,254) AS {c.effDate},");
   sql.append("substr(sf_getchannelstaff({c}.CHANNEL_ID,'C'),1,254) AS {c.expDate},");
   sql.append("{c}.AREA_ID AS {c.areaId},");
   sql.append("{c}.IS_VIRTUAL AS {c.isVirtual}");
   sql.append(" from k_channel {c} where {c}.CHANNEL_ID=:channelId");
   Query query = this.session.createSQLQuery(sql.toString(), "c", ynt43.dao.KChannel.class);
   query.setLong("channelId", channelId);
   List result = query.list();
   if (!result.isEmpty()) {
    ynt43.dao.KChannel t = (ynt43.dao.KChannel) result.get(0);
    ret[0] = UtilBean.GBToUnicode(t.getName());
    ret[1] = UtilBean.GBToUnicode(t.getChannelTypeId());
    ret[2] = UtilBean.GBToUnicode(t.getEffDate());
    ret[3] = UtilBean.GBToUnicode(t.getExpDate());
   }
  }
  finally {
   this.closeSession();
  }
  return ret;
特别说明一下:使用 createSQLQuery() 函数后写 hql 语句时,from 子句后面是跟着数据库表名,而不是对象类名。



Hibernate实现分页查询的原理分析

个人空间 — 作者: LiuYi @ 2005-09-23, 10:23

Hibernate 可以实现分页查询,例如:
从第2万条开始取出100条记录

代码:
Query q = session.createQuery("from Cat as c");
q.setFirstResult(20000);
q.setMaxResults(100);
List l = q.list();


那么Hibernate底层如何实现分页的呢?实际上Hibernate的查询定义在net.sf.hibernate.loader.Loader这个类里面,仔细阅读该类代码,就可以把问题彻底搞清楚。

Hibernate2.0.3的Loader源代码第480行以下:

代码:
if (useLimit) sql = dialect.getLimitString(sql);      
PreparedStatement st = session.getBatcher().prepareQueryStatement(sql, scrollable);



如果相应的数据库定义了限定查询记录的sql语句,那么直接使用特定数据库的sql语句。

然后来看net.sf.hibernate.dialect.MySQLDialect:

代码:
public boolean supportsLimit() {
  return true;
}
public String getLimitString(String sql) {
  StringBuffer pagingSelect = new StringBuffer(100);
  pagingSelect.append(sql);
  pagingSelect.append(" limit ?, ?");
  return pagingSelect.toString();
}


这是MySQL的专用分页语句,再来看net.sf.hibernate.dialect.Oracle9Dialect:

代码:
public boolean supportsLimit() {
  return true;
}

public String getLimitString(String sql) {
  StringBuffer pagingSelect = new StringBuffer(100);
  pagingSelect.append("select * from ( select row_.*, rownum rownum_ from ( ");
  pagingSelect.append(sql);
  pagingSelect.append(" ) row_ where rownum <= ?) where rownum_ > ?");
  return pagingSelect.toString();
}


Oracle采用嵌套3层的查询语句结合rownum来实现分页,这在Oracle上是最快的方式,如果只是一层或者两层的查询语句的rownum不能支持order by。

除此之外,Interbase,PostgreSQL,HSQL也支持分页的sql语句,在相应的Dialect里面,大家自行参考。

如果数据库不支持分页的SQL语句,那么根据在配置文件里面
#hibernate.jdbc.use_scrollable_resultset true
默认是true,如果你不指定为false,那么Hibernate会使用JDBC2.0的scrollable result来实现分页,看Loader第430行以下:


代码:
if ( session.getFactory().useScrollableResultSets() ) {
  // we can go straight to the first required row
  rs.absolute(firstRow);
}
else {
  // we need to step through the rows one row at a time (slow)
  for ( int m=0; m<firstRow; m++ ) rs.next();
}



如果支持scrollable result,使用ResultSet的absolute方法直接移到查询起点,如果不支持的话,使用循环语句,rs.next一点点的移过去。

可见使用Hibernate,在进行查询分页的操作上,是具有非常大的灵活性,Hibernate会首先尝试用特定数据库的分页sql,如果没用,再尝试Scrollable,如果不行,最后采用rset.next()移动的办法。

在查询分页代码中使用Hibernate的一大好处是,既兼顾了查询分页的性能,同时又保证了代码在不同的数据库之间的可移植性。



spring + hibernate 数据话持久层(转)

个人空间 — 作者: LiuYi @ 2005-09-23, 10:00
张利海 于 2004年11月15日 11:08 发表
关键词 : spring hibernate 数据话持久层

对于J2EE 应用程序而言,事务的处理一般有两种模式:
1. 依赖特定事务资源的事务处理
这是应用开发中最常见的模式,即通过特定资源提供的事务机制进行事务管理。
        如通过JDBC、JTA 的rollback、commit方法;Hibernate Transaction 的rollback、commit方法等。这种方法大家已经相当熟悉。
2. 依赖容器的参数化事务管理
通过容器提供的集约式参数化事务机制,实现事务的外部管理,如EJB 中的事务管理模式。
        如,下面的EJB事务定义中,将SessionBean MySession的doService方
法定义为Required。也就是说,当MySession.doServer 方法被某个线程调用时,容器将此线程纳入事务管理容器,方法调用过程中如果发生异常,当前事务将被容器自动回滚,如果方法正常结束,则容器将自动提交当前事务。
<container-transaction >
<method >
<ejb-name>MySession</ejb-name>
<method-intf>Remote</method-intf>
<method-name>doService</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
容器管理的参数化事务为程序开发提供了相当的灵活性,同时因为将事务委托给容器进行管理,应用逻辑中无需再编写事务代码,大大节省了代码量(特别是针对需要同时操作多个事务资源的应用),从而提高了生产率。然而,使用EJB 事务管理的代价相当高昂,撇开EJB 容器不菲的价格,EJB的学习成本,部署、迁移、维护难度,以及容器本身带来的性能开销(这往往意味着需要更高的硬件配置)都给我们带来了相当的困惑。此时事务管理所带来的优势往往还不能抵消上面这些负面影响。

Spring事务管理能给我们带来什么?

下面这段xml配置片断展示了Spring中的事务设定方式:
<beans>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>org.gjt.mm.mysql.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost/sample</value>
</property>
<property name="username">
<value>user</value>
</property>
<property name="password">
<value>mypass</value>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTr
ansactionManager">
<property name="dataSource">
<ref local="dataSource" />
</property>
</bean>
<bean id="userDAO" class="net.xiaxin.dao.UserDAO">
<property name="dataSource">
<ref local="dataSource" />
</property>
</bean>
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
<bean id="userDAOProxy"
class="org.springframework.transaction.interceptor.Tran
sactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<ref local="userDAO" />
</property>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="get*">
PROPAGATION_REQUIRED,readOnly
</prop>
</props>
</property>
</bean>
</beans>
配置中包含了dataSource,transactionManager 等资源定义。这些资源都为一个名为userDAOProxy 的TransactionProxyFactoryBean 服务, 而userDAOProxy 则对包含实际数据逻辑的userDAO进行了事务性封装。
可以看到,在userDAOProxy 的"transactionAttributes"属性中,我们定义了针对userDAO 的事务策略,即将所有名称以insert 开始的方法(如UserDAO.insertUser方法)纳入事务管理范围。如果此方法中抛出异常,则Spring
将当前事务回滚,如果方法正常结束,则提交事务。而对所有名称以get 开始的方法(如UserDAO.getUser 方法)则以只读的事务处理机制进行处理。(设为只读型事务,可以使持久层尝试对数据操作进行优化,如对
于只读事务Hibernate将不执行flush操作,而某些数据库连接池和JDBC 驱动也对只读型操作进行了特别化。)
        结合上面这段申明带来的感性认知,看看Spring 的事务管理机制与EJB 中事务管理有何不同,或者有何优势。这里自然有许多方面可以比较,不过,笔者认为其中最为关键的两点是:
1. Spring可以将任意Java Class 纳入事务管理
这里的UserDAO只是我们编写的一个普通Java Class,其中包含了一些基本的数据应用逻辑。通过Spring,我们即可简单的实现事务的可配置化。也就是说,我们可以随意为某个类的某个方法指定事务管理机制。与之对比,如果使用EJB容器提供的事务管理功能,我们不得不按照EJB规范编将UserDAO 进行改造,将其转换为一个标准的EJB。
2. Spring事务管理并不依赖特定的事务资源。
EJB 容器必须依赖于JTA 提供事务支持。而Spring 的事务管理则支持JDBC、JTA 等多种事务资源。这为我们提供了更多的选择,从而也使得我们的系统部署更加灵活。
        对Spring事务管理机制进行简单分析之后,我们将结合持久层封装的具体事务应用机制,对Spring中的事务管理进行更具实效的探讨。
                                                                      持久层封装
                                                                           JDBC
Spring对JDBC进行了良好的封装,通过提供相应的模板和辅助类,在相当程度上降低了JDBC操作的复杂性。并且得益于Spring良好的隔离设计,JDBC封装类库可以脱离Spring Context独立使用,也就是说,即使系统并没有采用Spring作为结构性框架,我们也可以单独使用Spring的JDBC部分(spring-dao.jar)来改善我们的代码。作为对比,首先让我们来看一段传统的JDBC代码:
Connection conn =null;
Statement stmt = null;
try {
conn = dataSource.getConnection();
stmt = con.createStatement();
stmt.executeUpdate("UPDATE user SET age = 18 WHERE id = 'erica'");
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException ex) {
logger.warn("Exception in closing JDBC Statement", ex);
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException ex) {
logger.warn("Exception in closing JDBC Connection", ex);
}
}
}
类似上面的代码非常常见。为了执行一个SQL语句,我们必须编写22行代码,而其中21行与应用逻辑并无关联,并且,这样的代码还会在系统其他地方(也许是每个需要数据库访问的地方)重复出现。
于是,大家开始寻找一些设计模式以改进如此的设计,Template模式的应用是其中一种典型的改进方案。Spring的JDBC封装,很大一部分就是借助Template模式实现,它提供了一个优秀的JDBC模板库,借助这个工具,我们可以简单有效的对传统的JDBC编码方式加以改进。下面是借助Spring JDBC Template修改过的代码,这段代码完成了与上面代码相同的功能。

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("UPDATE user SET age = 10 WHERE id = 'erica'");

可以看到,两行代码完成了上面需要19行代码实现的功能。所有冗余的代码都通过合理的抽象汇集到了JdbcTemplate中。无需感叹,借助Template模式,我们大致也能实现这样一个模板,不过,Spring的设计
者已经提前完成了这一步骤。org.springframework.jdbc.core.JdbcTemplate中包含了这个模板实现的代码,经过Spring设计小组精心设计,这个实现可以算的上是模板应用的典范。特别是回调(CallBack)的使用,使得整个模板结构清晰高效。值得一读。

Tips:实际开发中,可以将代码中硬编码的SQL语句作为Bean的一个String类型属性,借助DI机制在配置文件中定义,从而实现SQL的参数化配置。

再对上面的例子进行一些改进,通过PrepareStatement执行update操作以避免SQL
Injection 漏洞 9:
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate
.update(
"UPDATE user SET age = ? WHERE id = ?",
new PreparedStatementSetter() {
public void setValues(PreparedStatementSetter ps)
throws SQLException {
ps.setInt(1, 18);
ps.setString(2, "erica");
}
}
);
可以看到,上面引用了update方法的另一个版本,传入的参数有两个,第一个用于创建
PreparedStatement的SQL。第二个参数是为PreparedStatement设定参数的PreparedStatementSetter。
第二个参数的使用方法比较独到,我们动态新建了一个PreparedStatementSetter类,并实现了这个抽象类的setValues方法。之后将这个类的引用作为参数传递给update。update接受参数之后,即可调用第二个参数提供的方法完成PreparedStatement的初始化。
Spring JDBC Template中大量使用了这样的Callback机制,这带来了极强的灵活性和扩展性。
上面演示了update方法的使用(同样的操作适用于update、insert、delete)。下面是一个查询的示例。
final List userList = new ArrayList();
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate
.query(
"SELECT name, sex, address FROM user WHERE age > 18",
9 SQL Injection: SQL语句中直接引入参数值而导致的系统漏洞,具体请参见以下论文:
http://www.governmentsecurity.org/articles/SQLInjectionModesofAttackDefenceandWhyItMatters.php
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getString("name"));
user.setSex(rs.getString("sex"));
user.setAddress(rs.getString("address"));
userList.add(product);
}
}
);
这里传入query方法的有两个参数,第一个是Select查询语句,第二个是一个RowCallbackHandler实例,我们通过RowCallbackHandler对Select语句得到的每行记录进行解析,并为其创建一个User数据对象。实现了手动的OR映射。此外,我们还可以通过JdbcTemplate.call方法调用存储过程。query、update方法还有其他很多不同参数版本的实现,具体调用方法请参见SpringJavaDoc。
                                                                
                                                              JdbcTemplate与事务

上例中的JdbcTemplate操作采用的是JDBC默认的AutoCommit模式,也就是说我们还无法保证数据操作的原子性(要么全部生效,要么全部无效),如:
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("UPDATE user SET age = 10 WHERE id = 'erica'");
jdbcTemplate.update("UPDATE user SET age = age+1 WHERE id = 'erica'");
由于采用了AutoCommit模式,第一个update操作完成之后被自动提交,数据库中”erica”对应的记录已经被更新,如果第二个操作失败,我们无法使得整个事务回滚到最初状态。对于这个例子也许无关紧要,但是对于一个金融帐务系统而言,这样的问题将导致致命错误。
为了实现数据操作的原子性,我们需要在程序中引入事务逻辑,在JdbcTemplate中引入事务机制,在Spring中有两种方式:
1. 代码控制的事务管理
2. 参数化配置的事务管理
下面就这两种方式进行介绍。
u 代码控制的事务管理
首先,进行以下配置,假设配置文件为(Application-Context.xml):
<beans>
<bean id="dataSource"
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>net.sourceforge.jtds.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:jtds:sqlserver://127.0.0.1:1433/Sample</value>
</property>
<property name="username">
<value>test</value>
</property>
<property name="password">
<value>changeit</value>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransac
tionManager">
<property name="dataSource">
<ref local="dataSource" />
</property>
</bean>
<bean id="userDAO" class="net.xiaxin.dao.UserDAO">
<property name="dataSource">
<ref local="dataSource" />
</property>
<property name="transactionManager">
<ref local="transactionManager" />
</property>
</bean>
</beans>
配置中包含了三个节点:
Ø dataSource
这里我们采用了apache dhcp组件提供的DataSource实现,并为其配置了JDBC驱动、数据库URL、用户名和密码等参数。
Ø transactionManager
针对JDBC DataSource类型的数据源,我们选用了DataSourceTransactionManager作为事务管理组件。
如果需要使用基于容器的数据源(JNDI),我们可以采用如下配置:
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
<bean id="dataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>jdbc/sample</value>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTrans
actionManager"
/>
Ø userDAO
申明了一个UserDAO Bean,并为其指定了dataSource和transactionManger资源。
UserDAO对应的代码如下:
public class UserDAO {
private DataSource dataSource;
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
private PlatformTransactionManager transactionManager;
public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(PlatformTransactionManager
transactionManager) {
this.transactionManager = transactionManager;
}
public DataSource executeTestSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void insertUser() {
TransactionTemplate tt =
new TransactionTemplate(getTransactionManager());
tt.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
JdbcTemplate jt = new JdbcTemplate(executeTestSource());
jt.update(
"insert into users (username) values ('xiaxin');");
jt.update(
"insert into users (id,username) values(2,
'erica');");
return null;
}
});
}
}
可以看到,在insertUser方法中,我们引入了一个新的模板类:
org.springframework.transaction.support.TransactionTemplate。
TransactionTemplate封装了事务管理的功能,包括异常时的事务回滚,以及操作成功后的事务提交。和JdbcTemplate一样,它使得我们无需在琐碎的try/catch/finally代码中徘徊。
在doInTransaction中进行的操作,如果抛出未捕获异常将被自动回滚,如果成功执行, 则将被自动提交。
这里我们故意制造了一些异常来观察数据库操作是否回滚(通过在第二条语句中更新自增ID字段故意触发一个异常):
编写一个简单的TestCase来观察实际效果:
InputStream is = new FileInputStream("Application-Context.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
UserDAO userDAO = (UserDAO) factory.getBean("userDAO");
userDAO.insertUser();
相信大家多少觉得上面的代码有点凌乱,Callback类的编写似乎也有悖于日常的编程习惯(虽然笔者觉得这一方法比较有趣,因为它巧妙的解决了笔者在早期自行开发数据访问模板中曾经遇到的问题)。如何进一步避免上面这些问题?Spring 的容器事务管理机制在这里即体现出其强大的能量。u 参数化配置的事务管理在上面的Application-Context.xml增加一个事务代理(UserDAOProxy)配置,同时,由于事务由容器管理,UserDAO不再需要TransactionManager设定,将其移除:
<bean id="UserDAOProxy"
class="org.springframework.transaction.interceptor.Transac
tionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<ref local="userDAO" />
</property>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<bean id="userDAO" class="net.xiaxin.dao.UserDAO">
<property name="dataSource">
<ref local="dataSource" />
</property>
</bean>
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
上面的配置中,UserDAOProxy节点配置了一个针对userDAO bean的事务代理(由target属性指定)。通过transactionAttributes属性,我们指定了事务的管理策略,即对所有以insert开头的方法进行事务管理,如果被管理方法抛出异常,则自动回滚方法中的事务,如果成功执行,则在方法完成之后进行事务提交。另一方面对于其他方法(通过通配符*表示),则进行只读事务管理,以获得更好的性能。与之对应UserDAO.insertUser的代码修改如下:
public void insertUser(RegisterInfo regInfo) {
JdbcTemplate jt = new JdbcTemplate(executeTestSource());
jt.update("insert into users (username) values ('xiaxin');");
jt.update("insert into users (id,username) values (2,'erica');");
}
测试代码修改如下:
InputStream is = new FileInputStream("Application-Context.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
//注意这里须通过代理Bean"userDAOProxy"获得引用,而不是直接getBean(“userDAO”)
//此外这里还存在一个有关强制转型的潜在问题,请参见Hibernate in Spring一节后
//关于强制转型的补充描述。
UserDAO userDAO = (UserDAO) factory.getBean("userDAOProxy");
userDAO.insertUser();
可以看到,insertUser变得非常简洁。数据逻辑清晰可见,对比前面代码控制的事务管理,以及传统的JDBC操作,相信大家会有一些霍然开朗的感觉。细心的读者会说,这只不过将代码转移到了配置文件,并没有减少太多的工作量。这点区别也许并不重要,从应用维护的角度而言,配置化的事务管理显然更具优势。何况,实际开发中,如果前期设计细致,方法的事务特性确定之后一般不会发生大的变动,之后频繁的维护过程中,我们只需面对代码中的数据逻辑即可。上面我们结合JdbcTemplate介绍了Spring中的模板操作以及事务理机制。Spring作为一个开放式的应用开发平台。同时也针对其他组件提供了良好的支持。在持久层,Spring提供面向了Hibernate、ibatis和JDO的模板实现,同样,这些实现也为我们的开发提供了强有力的支持。
下面我们就hibernate、ibatis这两种主流持久层框架在Spring中的使用进行介绍。至于JDO,由于实际开发中使用并不广泛(实际上笔者觉得JDO前景堪忧),这里也就不重点介绍,有兴趣的读者可参见Spring-Reference中的相关章节。
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
Hibernate in Spring
Hibernate在开源的持久层框架中无疑是近期最为鲜亮的角色,其作者甚至被邀请加入
新版EJB设计工作之中,足见Hibernate设计的精彩贴切。关于Hibernate的使用,在笔者
的另外一篇文档中进行了探讨:
《Hibernate开发指南》
http://www.xiaxin.net/Hibernate_DEV_GUIDE.rar
下面主要就Hibernate在Spring中的应用加以介绍,关于Hibernate本身就不多加描
述。
另外考虑到Spring对容器事务的良好支持,笔者建议在基于Spring Framework的应
用开发中,尽量使用容器管理事务,以获得数据逻辑代码的最佳可读性。下面的介绍中,将
略过代码控制的事务管理部分,而将重点放在参数化的容器事务管理应用。代码级事务管理
实现原理与上面JdbcTemplate中基本一致,感兴趣的读者可以参见Spring-Reference中
的相关内容。
出于简洁,我们还是沿用上面的示例。首先,针对Hibernate,我们需要进行如下配置:
Hibernate-Context.xml:
<beans>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>net.sourceforge.jtds.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:jtds:sqlserver://127.0.0.1:1433/Sample</value>
</property>
<property name="username">
<value>test</value>
</property>
<property name="password">
<value>changeit</value>
</property>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate.LocalSessionFactoryBean"
>
<property name="dataSource">
<ref local="dataSource" />
</property>
<property name="mappingResources">
<list>
<value>net/xiaxin/dao/entity/User.hbm.xml</value>
</list>
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
net.sf.hibernate.dialect.SQLServerDialect
</prop>
<prop key="hibernate.show_sql">
true
</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate.HibernateTransactionMana
ger">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
<bean id="userDAO" class="net.xiaxin.dao.UserDAO">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
<bean id="userDAOProxy"
class="org.springframework.transaction.interceptor.TransactionPro
xyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<ref local="userDAO" />
</property>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
</property>
</bean>
</beans>
与上面JDBC中的配置相对比,区别主要在于:
1. SessionFactory的引入
Hibernate中通过SessionFactory创建和维护Session。Spring对SessionFactory的配置也进行了整合,无需再通过Hibernate.cfg.xml对SessionFactory进行设定。
SessionFactory节点的mappingResources属性包含了映射文件的路径,list节点下可配置多个映射文件。
hibernateProperties节点则容纳了所有的属性配置。
可以对应传统的Hibernate.cfg.xml文件结构对这里的SessionFactory配置进行解读。
2. 采用面向Hibernate的TransactionManager实现:
org.springframework.orm.hibernate.HibernateTransactionManag
er
可以看到,对于事务管理配置,基本与上一章节中相同。对应刚才的Users表,建立如下映射类:
User.java:
/**
* @hibernate.class table="users"
*/
public class User {
public Integer id;
public String username;
public String password;
/**
* @hibernate.id
* column="id"
* type="java.lang.Integer"
* generator-class="native"
*/
public Integer getId() {
return id;
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
}
public void setId(Integer id) {
this.id = id;
}
/**
* @hibernate.property column="password" length="50"
*/
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
/**
* @hibernate.property column="username" length="50"
*/
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
上面的代码中,通过xdoclet指定了类/表;属性/字段的映射关系,通过xdoclet anttask 我们可以根据代码生成对应的user.hbm.xml文件。具体细节请参见《hibernate开发指南》一文。
下面是生成的user.hbm.xml:
<hibernate-mapping>
<class
name="net.xiaxin.dao.entity.User"
table="users"
dynamic-update="false"
dynamic-insert="false"
>
<id
name="id"
column="id"
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
type="java.lang.Integer"
>
<generator class="native">
</generator>
</id>
<property
name="password"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="password"
length="50"
/>
<property
name="username"
type="java.lang.String"
update="true"
insert="true"
access="property"
column="username"
length="50"
/>
</class>
</hibernate-mapping>
UserDAO.java:
public class UserDAO extends HibernateDaoSupport implements IUserDAO
{
public void insertUser(User user) {
getHibernateTemplate().saveOrUpdate(user);
}
}
看到这段代码想必会有点诧异,似乎太简单了一点……,不过这已经足够。短短一行代码我们已经实现了与上一章中示例相同的功能,这也正体现了Spring+Hibernate的威力所在。
上面的UserDAO实现了自定义的IUserDAO接口(这里的IUserDAO接口仅包含insertUser方法的定义,不过除此之外,它还有另一层含义,见下面的代码测试部分),
并扩展了抽象类:
HibernateDaoSupport
HibernateSupport实现了HibernateTemplate和SessionFactory实例的关联。与JdbcTemplate类似,HibernateTemplate对Hibernate Session操作进行了封装,而HibernateTemplate.execute方法则是一封装机制的核心,感兴趣的读者可以
研究一下其实现机制。借助HibernateTemplate我们可以脱离每次数据操作必须首先获得Session实例、启动事务、提交/回滚事务以及烦杂的try/catch/finally的繁琐操作。从而获得以上代码中精干集中的逻辑呈现效果。
对比下面这段实现了同样功能的Hibernate原生代码,想必更有体会:
Session session
try {
Configuration config = new Configuration().configure();
SessionFactory sessionFactory =
config.buildSessionFactory();
session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = new User();
user.setName("erica");
user.setPassword("mypass");
session.save(user);
tx.commit();
} catch (HibernateException e) {
e.printStackTrace();
tx.rollback();
}finally{
session.close();
}
测试代码:
InputStream is = new FileInputStream("Hibernate-Context.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
IUserDAO userDAO = (IUserDAO)factory.getBean("userDAOProxy");
SpringFrameWork Developer’s Guide Version 0.6
October 8, 2004 So many open source projects. Why not Open your Documents?
User user = new User();
user.setUsername("erica");
user.setPassword("mypass");
userDAO.insertUser(user);
这段代码似乎并没有什么特殊,但有一个细微之处:
IUserDAO userDAO = (IUserDAO)factory.getBean("userDAOProxy");
这里并没有直接用UserDAO对获得的Bean实例进行强制转型。这与上面JdbcTemplate的测试代码不同。并非完全出自设计上的考虑,这里情况有些特殊,我们可以尝试一下用UserDAO类对bean实例进行强制转型,不过将得到一个ClassCastException,程序异常中止。
为什么会出现这样的问题?是不是只有在使用Hibernate才会出现这样的问题?事实并非如此,如果对上面基于JdbcTempate的UserDAO进行改造,使之实现IUserDAO接口,同样的问题也将会出现。IUserDAO接口本身非常简单(仅包含一个insertUser方法的定义),显然也不是导致异常的原因所在。原因在于Spring的AOP实现机制,前面曾经提及,Spring中的事务管理实际上是基于动态AOP机制实现,为了实现动态AOP,Spring在默认情况下会使用Java DynamicProxy,但是,Dynamic Proxy要求其代理的对象必须实现一个接口,该接口定义了准备进行代理的方法。而对于没有实现任何接口的Java Class,需要采用其他方式,Spring通过CGLib10实现这一功能。
当UserDAO没有实现任何接口时(如JdbcTemplate示例中)。Spring通过CGLib对UserDAO进行代理,此时getBean返回的是一个继承自UserDAO类的子类实例,可以通过UserDAO对其强制转型。而当UserDAO实现了IUserDAO接口之后,Spring将通过JavaDynamic Proxy机制实现代理功能,此时返回的Bean,是通过java.lang.reflect.Proxy.newProxyInstance方法创建的IUserDAO接口的一个代理实现,这个实例实现了IUserDAO接口,但与UserDAO类已经没有继承关系,因此无法通过UserDAO强制转型。由于此问题牵涉到较为底层的代理机制实现原理,下面的AOP章节中我们再进行详细探讨。
实际开发中,应该面向接口编程,通过接口来调用Bean提供的服务。
10 CGLib可以在运行期对Class行为进行修改。由于其功能强大,性能出众,常常被作为Java Dynamic Proxy
之外的动态Proxy模式的实现基础。在Spring、Hibernate中都用到了CGLib类库。



JSF与Struts的异同

个人空间 — 作者: LiuYi @ 2005-09-21, 11:32

JSF与Struts的异同

板桥里人 http://www.jdon.com 2005/09/05

  Struts和JSF/Tapestry都属于表现层框架,这两种分属不同性质的框架,后者是一种事件驱动型的组件模型,而Struts只是单纯的MVC模式框架,老外总是急吼吼说事件驱动型就比MVC模式框架好,何以见得,我们下面进行详细分析比较一下到底是怎么回事?

  首先事件是指从客户端页面(浏览器)由用户操作触发的事件,Struts使用Action来接受浏览器表单提交的事件,这里使用了Command模式,每个继承Action的子类都必须实现一个方法execute。

  在struts中,实际是一个表单Form对应一个Action类(或DispatchAction),换一句话说:在Struts中实际是一个表单只能对应一个事件,struts这种事件方式称为application event,application event和component event相比是一种粗粒度的事件。

  struts重要的表单对象ActionForm是一种对象,它代表了一种应用,这个对象中至少包含几个字段,这些字段是Jsp页面表单中的input字段,因为一个表单对应一个事件,所以,当我们需要将事件粒度细化到表单中这些字段时,也就是说,一个字段对应一个事件时,单纯使用Struts就不太可能,当然通过结合JavaScript也是可以转弯实现的。

  而这种情况使用JSF就可以方便实现,


  valueChangeListenertype="logindemo.UserLoginChanged" />

  #{login.userId}表示从名为login的JavaBean的getUserId获得的结果,这个功能使用struts也可以实现,name="login" property="userId"

  关键是第二行,这里表示如果userId的值改变并且确定提交后,将触发调用类UserLoginChanged的processValueChanged(...)方法。

  JSF可以为组件提供两种事件:Value Changed和 Action. 前者我们已经在上节见识过用处,后者就相当于struts中表单提交Action机制,它的JSF写法如下:


  actionListenertype=”logindemo.LoginActionListener” />

  从代码可以看出,这两种事件是通过Listerner这样观察者模式贴在具体组件字段上的,而Struts此类事件是原始的一种表单提交Submit触发机制。如果说前者比较语言化(编程语言习惯做法类似Swing编程);后者是属于WEB化,因为它是来自Html表单,如果你起步是从Perl/PHP开始,反而容易接受Struts这种风格。

基本配置

  Struts和JSF都是一种框架,JSF必须需要两种包JSF核心包、JSTL包(标签库),此外,JSF还将使用到Apache项目的一些commons包,这些Apache包只要部署在你的服务器中既可。

  JSF包下载地址:http://java.sun.com/j2ee/javaserverfaces/download.html选择其中Reference Implementation。

  JSTL包下载在http://jakarta.apache.org/site/downloads/downloads_taglibs-standard.cgi

  所以,从JSF的驱动包组成看,其开源基因也占据很大的比重,JSF是一个SUN伙伴们工业标准和开源之间的一个混血儿。

  上述两个地址下载的jar合并在一起就是JSF所需要的全部驱动包了。与Struts的驱动包一样,这些驱动包必须位于Web项目的WEB-INF/lib,和Struts一样的是也必须在web.xml中有如下配置:


  
    Faces ServletBR>    javax.faces.webapp.FacesServletBR>    1BR>  

  
    Faces ServletBR>    *.facesBR>  
/P>

  这里和Struts的web.xml配置何其相似,简直一模一样。

  正如Struts的struts-config.xml一样,JSF也有类似的faces-config.xml配置文件:



  
    /index.jspBR>    
      loginBR>      /welcome.jspBR>    BR>  

  
    userBR>    com.corejsf.UserBean
    session
  BR>/P>

 

  在Struts-config.xml中有ActionForm Action以及Jsp之间的流程关系,在faces-config.xml中,也有这样的流程,我们具体解释一下Navigation:

  在index.jsp中有一个事件:

  action的值必须匹配form-outcome值,上述Navigation配置表示:如果在index.jsp中有一个login事件,那么事件触发后下一个页面将是welcome.jsp

  JSF有一个独立的事件发生和页面导航的流程安排,这个思路比struts要非常清晰。

  managed-bean类似Struts的ActionForm,正如可以在struts-config.xml中定义ActionForm的scope一样,这里也定义了managed-bean的scope为session。

  但是如果你只以为JSF的managed-bean就这点功能就错了,JSF融入了新的Ioc模式/依赖性注射等技术。

Ioc模式

  对于Userbean这样一个managed-bean,其代码如下:

public class UserBean {
  private String name;
  private String password;

  // PROPERTY: name
  public String getName() { return name; }
  public void setName(String newValue) { name = newValue; }

  // PROPERTY: password
  public String getPassword() { return password; }
  public void setPassword(String newValue) { password = newValue; }
}

 


  userBR>  com.corejsf.UserBeanBR>  sessionBR>
  
    name
    meBR>  BR>
  
    password
    secretBR>  BR>/P>

  faces-config.xml这段配置其实是将"me"赋值给name,将secret赋值给password,这是采取Ioc模式中的Setter注射方式

Backing Beans

  对于一个web form,我们可以使用一个bean包含其涉及的所有组件,这个bean就称为Backing Bean, Backing Bean的优点是:一个单个类可以封装相关一系列功能的数据和逻辑。

  说白了,就是一个Javabean里包含其他Javabean,互相调用,属于Facade模式或Adapter模式。


  对于一个Backing Beans来说,其中包含了几个managed-bean,managed-bean一定是有scope的,那么这其中的几个managed-beans如何配置它们的scope呢?


  ...
  
    visit
    #{sessionScope.visit}BR>  BR>

  这里配置了一个Backing Beans中有一个setVisit方法,将这个visit赋值为session中的visit,这样以后在程序中我们只管访问visit对象,从中获取我们希望的数据(如用户登陆注册信息),而visit是保存在session还是application或request只需要配置既可。

UI界面

  JSF和Struts一样,除了JavaBeans类之外,还有页面表现元素,都是是使用标签完成的,Struts也提供了struts-faces.tld标签库向JSF过渡。

  使用Struts标签库编程复杂页面时,一个最大问题是会大量使用logic标签,这个logic如同if语句,一旦写起来,搞的JSP页面象俄罗斯方块一样,但是使用JSF标签就简洁优美:

  icon="/images/inbox.gif"
  action="inbox"
  disabled="#{!authenticationBean.inboxAuthorized}"/>

  如果authenticationBean中inboxAuthorized返回是假,那么这一行标签就不用显示,多干净利索!

  先写到这里,我会继续对JSF深入比较下去,如果研究过Jdon框架的人,可能会发现,Jdon框架的jdonframework.xml中service配置和managed-bean一样都使用了依赖注射,看来对Javabean的依赖注射已经迅速地成为一种新技术象征,如果你还不了解Ioc模式,赶紧补课。

附Jsf核心教程一个JSF案例:login.rar