輕量級J2EE架構


目錄
前言
在java的應用領域,有許多成熟的開源軟件,利用它們一樣可以打造優越、靈巧的應用框架,本文首先將先介紹所要構建的系統結構和藉助的開源產品。然後逐一講解各開源軟件的具體運用。希望本文能給那些正在學習這些 開源軟件的同行提供參考。續該文之後筆者將結合這些開源軟件,藉助實際項目,做更深入的應用講解。
本文中涉及到的例子、源代碼均經過本人調試,且正常運行的,但是不否定這其中有不合理的使用。運行環境: win2000 adivance server + tomcat5.0.25 + mysql-4.0.14-nt
第 1 章 整體框架
筆者在項目中所搭建的架構參考了 “Mark Eagle” 的《Wiring Your Web Application with Open Source Java一文中所闡述的架構思想。
圖 1.1. 架構圖:引用《Wiring Your Web Application with Open Source Java》一文
從架構圖中可以看出系統分爲四層:
  • UI層:藉助Struts實現
  • 業務層:藉助SpringFramework進行業務組件的組裝關聯。
  • 數據持久層:藉助Hibernate實現
  • 域對象層:將所有域對象劃分在一個層面
爲什麼採用這樣的四層架構?
  • 通過成熟的開源產品實現各層,同自己編寫代碼實現,相比之下能縮短開發週期,且架構所用到的開源產品均有 很廣泛的用戶羣,經受過實踐的考驗,質量和性能更有保障。
  • 層與層之間鬆散耦合,增加代碼重用率。
  • 各層分工明確,這樣也利於團隊的明確分工。
1.1. UI
UI是User Interface的縮寫,這一層是面向用戶的界面,是用戶與系統之間交互的媒介。如,用戶在界面發送請求, 系統接收請求,進行處理,然後通過界面將結果呈現於用戶。這一過程包括了用戶動作、數據傳遞、界面顯示。大家熟悉的MVC模式就是將這三者分離,減少三者耦合。
我們在該層藉助了Struts來實現:
  • 用ActionForm類封裝與用戶互動的數據元素。
  • 用Action類實現業務邏輯、動作處理、鏈接轉向。實現MVC中的C
  • 藉助Struts標籤來完成數據呈現。實現MVC中的V。
關於struts具體的應用請參考相關章節。
1.2. 業務層
在實際的項目開發中,每個領域都會有自己獨特的業務邏輯,正因爲這樣,致使項目中代碼高度耦合,原本有可能被重用的代碼或功能,因爲與具體的業務邏輯綁定在一塊而導致很難被重用。因此我們將實現這些具體邏輯的代碼抽取出來分爲單獨的一層, 其目的是希望通過層,來降低它與系統其他部分的耦合度。
現實中世界是變化的,既然該層實現的是現實中具體的業務邏輯,那該層的實現代碼不可避免的會發生變更。怎樣讓該層適應最大的變化,做到最小的改動?通常我們在編碼的時候會盡量考慮到同一業務多種實現的兼容和可擴展的能力。因此我們在 該層藉助了Spring,通過依賴注入、AOP應用、面向接口編程,來降低業務組件之間的耦合度,增強系統擴展性。
關於Spring的具體使用請參考相關章節。
1.3. 數據持久層
開發中與數據庫進行數據交互必不可少,通常我們歸爲CRUD(添加、讀取、修改、刪除),這些操作佔據了系統開發中大部分的時間, 同時我們還需要考慮與數據庫交互的性能問題,如連接池、數據緩存等等。因此該層實現我們藉助了Hibernate。
Hibernate是一個ORM工具,它不僅僅是實現了數據庫訪問性能優化和與數據庫交互的常用操作(CRUD),還將數據表與對象進行了關聯,讓我們可以脫離數據表,而直接針對對象來與數據庫交互,我們不再需要用字符串去描述表中字段,不再需要一個個”+“號去組裝Sql語句。這使得編碼中可書寫性提高。
該層的具體實現,請參看Hibernate相關章節。
1.4. 域對象層
該層應該說是ORM思想的產物,ORM用對象關聯數據表,我們將這些對象的集合歸爲一個專門的層即Domain Layer。 域對象是各層之間數據通信的載體。實際上域對象也是一個完完全全的業務對象,如User對象、Book對象。通過對業 務的對象化,這有利於業務邏輯的重用。
第 2 章 Struts
2.1. 概述
Struts是用於實現Web項目中UI層的開源產品,是MVC模式的經典實現案例。它屬於Apache組織中的開源產品之一, 我們可以從官方網站http://struts.apache.org得到其所有資料及源碼。
爲什麼使用Struts?
  • Struts將業務數據、頁面顯示、動作處理進行分離,這有利各部分的維護。
  • Struts採用Front Controller模式來實現動作處理,讓所有的動作請求都是經過一個統一入口, 然後進行分發。這樣方便我們在入口中加入一些全局控制代碼的實現,如安全控制、日誌管理、國際化 編碼等。
  • 通過Struts提供的ActionForm封裝web form 中的元素,使重用web表單成爲可能。
  • 藉助Struts Validator框架幫助完成web層的驗證工作,通常情況下我們不用再去爲每個web頁面寫其驗證代碼,只需通 過配置即可實現。這也減少了我們的開發量,由於驗證代碼的集中管理,也爲維護帶來便利。
2.2. 快速入門
1、下載Struts,將其相關jar包引用到Web項目。
2、在web項目的web.xml中配置Action影射,將相應請求動作交給Struts提供的ActionServlet類進行統一控制。
例 2.1. web.xml
 <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
               <param-name>config</param-name>
               <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <init-param>
               <param-name>debug</param-name>
               <param-value>2</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
 </servlet>
 <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
 </servlet-mapping> 
 
                      
在web.xml中定義了所有帶.do後綴的文件請求都會觸發ActionServlet類。在url-pattern節點可以靈活定義 適合自身的映射表達式,如,對某個目錄下請求的映射:/myDirectory/*.do
在配置org.apache.struts.action.ActionServlet類時設置一些參數其含義如下:
  • config:制定Struts配置文件路徑,默認爲/WEB-INF/struts-config.xml
  • debug:設定日誌記錄級別。
3、在web.xm配置所需要用到的的Struts標籤文件
例 2.2. web.xml
 <taglib>
    <taglib-uri>struts-bean.tld</taglib-uri>
    <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
 </taglib>
 <taglib>
    <taglib-uri>struts-html.tld</taglib-uri>
    <taglib-location>/WEB-INF/struts-html.tld</taglib-location>
 </taglib>
 <taglib>
    <taglib-uri>struts-logic.tld</taglib-uri>
    <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
 </taglib>
 <taglib>
    <taglib-uri>struts-template.tld</taglib-uri>
    <taglib-location>/WEB-INF/struts-template.tld</taglib-location>
 </taglib>
 <taglib>
    <taglib-uri>struts-tiles.tld</taglib-uri>
    <taglib-location>/WEB-INF/struts-tiles.tld</taglib-location>
 </taglib>
 <taglib>
    <taglib-uri>c.tld</taglib-uri>
    <taglib-location>/WEB-INF/c.tld</taglib-location>
 </taglib>
 <taglib>
    <taglib-uri>Validate.tld</taglib-uri>
    <taglib-location>/WEB-INF/Validate.tld</taglib-location>
 </taglib>
 
                      
4、建立ActionForm、Action。
例 2.3. struts-config.xml
<struts-config>
       
        <form-beans>
               <form-bean name="helloWorldForm" type ="struts.test.HelloWorldForm">
                       <form-property name="name" type="java.lang.String" />
               </form-bean>
        </form-beans>
 
        <action-mappings>
               <action path="/helloWorld" name="helloWorldForm" type="struts.test.HelloWorldAction"
               scope="request" input="/HelloWorld.jsp" validate="false" />
   </action-mappings> 
  
</struts-config>
 
                      
例 2.4. HelloWorldForm.java
public class HelloWorldForm extends ActionForm
{
        private String name = null;
              
        public String getName()
        {
               return this.name;
        }
        public void setName(String name)
        {
               this.name = name;
        }
}                     
                      
例 2.5. HelloWorldAction.java
public class HelloWorldAction extends Action
{
 
        public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
                       HttpServletResponse response) throws Exception
        {
               HelloWorldForm loginForm = (HelloWorldForm)form;
               String name = loginForm.getName();
               request.setAttribute("HelloWorldForm",loginForm);
               return mapping.getInputForward();
        }
 
}
                      
5、視圖呈現
例 2.6. HelloWorld.jsp
<%@page language="java" contentType = "text/html;charset=GBK"%>
<%@ taglib uri="struts-html.tld" prefix="html" %>
<%@ taglib uri="struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="struts-bean.tld" prefix="bean" %>
<table border="1">
        <tr>
               <html:form action="helloWorld.do" method="post">
               <td>
                       <table>
                               <tr>
                                      <td>用戶名:</td>
                                      <td><html:text property="name" /></td>
                               </tr>
                               <tr colspan="2">
                                      <td><html:submit property="show" value="提交" /></td>
                               </tr>
                       </table>
               </td>
               </html:form>
        </tr>
</table>
<bean:write name="name" />
 
                      
6、演示效果
圖 2.1. 訪問Helloworld.jsp
本章演示了一個簡單的示例,在文本框輸入內容,提交後顯示其內容。功能雖然很簡單但是涉及到了struts應用的大部分知識:
  • 在web.xml中配置Action影射規則,如,*.do文件的請求觸發Action。
  • ActionFrom中的屬性對應到表單域中表單元素,如,jsp頁面名爲name的Text,對應到ActionForm中name屬性。
  • 繼承Action類後即可實現具體的控制器,如,HelloAction類接受到請求後將ActionForm對象存放到request 範圍,然後轉發給配置的鏈接地址。
  • 藉助struts提供的標籤進行視圖呈現。如,bean:write 標籤訪問ActionForm對象的屬性值。
2.3. ActionForm
ActionFrom是用來傳輸表單數據的對象,通過配置文件可以關聯到對應的Action,實現在UI層與業務層之間的數據傳輸。 實現機制如下:
  • Struts提供了一個org.apache.struts.action.ActionForm類,裏面實現了將請求表單中的元素賦值給其具體的 實現類屬性。因此自定義ActionForm類時,只需繼承該類即可。在自定義ActionForm類時請保證其屬性名稱與所對應 表單名稱一致。
  • 當觸發Action時,通過配置文件匹配到對應的ActionFrom實例,以參數形式傳入。
ActionForm的實現雖然簡單,但是隨着界面的增加,ActionForm類也會增加,造成代碼膨脹。在Struts1.1以上版本提供了 ActionForm的另一個實現類,org.apache.struts.action.DynaActionForm,該類充當所有ActionForm的代理類,只需在 配置ActionFrom時指定用該類實現即可:
例 2.7. struts-config.xml
<form-beans>
               <form-bean name="helloWorldForm" type ="org.apache.struts.action.DynaActionForm">
                       <form-property name="name" type="java.lang.String" />
               </form-bean>
</form-beans>
 
                      
在訪問helloWorldForm實例時如下:
例 2.8. HelloAction.java
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
                       HttpServletResponse response) throws Exception
{
        DynaActionForm dynaForm = (DynaActionForm)form;
        String name =(String)dynaForm.get("name");
        return null;
}
                      
2.4. Action
Action充當了控制器的角色,每個請求都會觸發到Action類,我們在這裏處理邏輯業務,分發數據,鏈接轉向。 其實現機制:
  • 在web.xml配置影射規則,符合其影射條件的請求都會交給org.apache.struts.action.ActionServlet類處理, 在該類中將會實現org.apache.struts.action.Action類中的相應方法。在自定義Action類時只需繼承該類, 即可讓自定義的Action得到觸發。
  • execute()方法中 ActionMapping、ActionForm實例通過讀取配置文件獲得。
2.5. 客戶端驗證
在UI中對用戶提交的數據作驗證是必不可少的,Struts也提供了相應的實現。下面將演示一個登錄界面的校驗:
1、在配置文件中申明校驗實現類,Struts中校驗工作通過org.apache.struts.validator.ValidatorPlugIn類實現。
例 2.9. struts-config.xml
 <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
    <set-property property="pathnames" value="/WEB-INF/validator-rules.xml,/WEB-INF/validation.xml"/>
 </plug-in>
 
                      
在實例化ValidatorPlugIn類時會裝載validator-rules.xml、validation.xml文件,這些文件中包含了驗證規則 和要被驗證的具體表單信息。
2、完善校驗規則文件。在validator-rules.xml中定義了常用的客戶端驗證規則,如,不爲空,只允許 數字等。特殊的實現需要在此擴充。
3、定義要被驗證的表單信息。
例 2.10. validation.xml
<form-validation>
        <formset>
               <form name="loginForm">               (1)
                       <field property="name" page="0" depends="required">   (2)
                               <arg0 key="register.username"/>       (3)
                       </field>
        </form>    
 </formset>
</form-validation>
                      
定義要被驗證的ActionForm名稱。
定義要被驗證的具體屬性,及規則。depends="required"指明將調用validator-rules.xml中的required規則。
從資源文件中匹配具體的驗證信息。
4、建立存放驗證提示信息的資源文件。如當用戶名爲空時顯示提示信息“必須填寫用戶名”
例 2.11. ApplicationResources_zh_CN.properties
 register.username=/u7528/u6237/u540d
                      
5、在界面設置觸發條件,如onsubmit時響應驗證動作。
例 2.12. Login.jsp
<%@page language="java" contentType = "text/html;charset=GBK"%>
<%@ taglib uri="struts-html.tld" prefix="html" %>
<table border="1">
        <tr>
               <html:form action="login.do" method="post" οnsubmit="return validateCheckLoginForm(this);">
               <html:hidden property="loginType" value="0"/>
               <td>
                       <table>
                               <tr>
                                      <td>用戶名:</td>
                                      <td><html:text property="name" /></td>
                               </tr>
                               <tr>
                                      <td>密碼:</td>
                                      <td><html:password property="password" /></td>
                               </tr>
                               <tr colspan="2">
                                      <td><html:submit property="login" value="登錄" /></td>
                               </tr>
                       </table>
               </td>
               </html:form>
        </tr>
         <html:javascript formName="checkLoginForm"   dynamicJavascript="true"    staticJavascript="true"/>
</table>
 
                      
當用戶名爲空時點擊登錄,將出現如下提示:
圖 2.2. 演示效果
第 3 章 springFramework
3.1. 概述
對spring的描述莫過於作者本人之言
 
Developing software applications is hard enough even with good tools and technologies. Implementing applications using platforms which promise everything but turn out to be heavy-weight, hard to control and not very efficient during the development cycle makes it even harder. Spring provides a light-weight solution for building enterprise-ready applications, while still supporting the possibility of using declarative transaction management, remote access to your logic using RMI or webservices, mailing facilities and various options in persisting your data to a database. Spring provides an MVC framework, transparent ways of integrating AOP into your software and a well-structured exception hierarchy including automatic mapping from proprietary exception hierarchies. 即使擁有良好的工具和優秀技術,應用軟件開發也是困難重重。如果使用了超重量級,難於控制,不能有效控制開 發週期的平臺 那麼就讓應用開發變得更爲困難。Spring爲已建立的企業級應用提供了一個輕量級的解決方案,這個方案包括聲明 性事務管理, 通過RMI或webservices遠程訪問業務邏輯,mail支持工具以及對於數據和數據庫之間持久層的各種配置的支持。 Spring還提供了 一個MVC應用框架,可以通過集成AOP透明的嵌入你的軟件和一個優秀的異常處理體系,這個異常體系可以自動 從屬性異常體系 進行映射。
 
 
--springFramework reference
springFramework是種非侵入式輕量級框架,允許自由選擇和組裝各部分功能,還提供和其他軟件集成的接口,如與Hibernate、Struts 的集成(後面的章節中會提到)。它提供的功能有Spring IOC、spring AOP、Spring ORM、Spring DAO、Spring MVC.筆者在項目 中用到的主要是IOC和AOP功能,ORM用hibernate取代,MVC用Struts取代。本文講述springFramework在web環境下的使用。
3.2. 爲什麼使用Spring
1、利用延時注入思想組裝代碼,提高系統擴展性,靈活性,實現插件式編程。
2、利用AOP思想,集中處理業務邏輯,減少重複代碼,構建優雅的解決方案。
3、利用其對Hibernate的SessionFactory、事務管理的封裝,更簡潔的應用Hibernate。
3.3. 快速入門
要使用Spring非常簡單,來體驗下:
例 3.1. MyClass.java
public interface MyClass
{
        public void execute();
}
                      
例 3.2. MyClassImpl.java
public class MyClassImpl implements MyClass
{
        public void execute()
        {
               ...
        }
}
                      
通過Spring注入MyClassImpl的實例,需在配置文件中做如下配置:
例 3.3. SpringConfig.xml
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-lazy-init="false" default-dependency-check="none" default-autowire="no">
        <bean id="myClass" class="MyClassImpl" />
</beans>
 
                      
這樣在代碼中就可以通過Spring體驗到什麼叫延時裝載了
例 3.4. 
        ApplicationContext ac = new FileSystemXmlApplicationContext("SpringConfig.xml"); (1)
        MyClass cls = (MyClass)ac.getBean("myClass");                                       (2)    
        cls.execute();                                                                                              
 
                              
載入Spring配置文檔,上例中SpringConfig.xml放置在工作路徑根目錄中。這種引用方式其配製文件只能相對於工作路徑 的引用。
實例化配置的對象,以配置文件的bean節點ID值作爲實例引用的關鍵字。
上面的例子中得到了實現類的實例,但是代碼中並沒有硬編碼具體實現類,而是將這種依賴轉移到配置文件中。
3.4. 搭建Web應用環境
2、在Web.xml中配置spring的啓動方式。
springFramework提供兩種形式的web context,基於Listener接口的實現和基於Servlet接口的實現。
例 3.5. web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
<display-name>Test</display-name>
<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring_bean.xml</param-value>
</context-param>
<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
<servlet>
        <servlet-name>context</servlet-name>
        <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
</servlet>
-->
<servlet>
        <servlet-name>Init</servlet-name>
        <servlet-class>com.m5.Initializtion</servlet-class>
        <load-on-startup>3</load-on-startup>
</servlet>
</web-app>
                      
tomcat啓動時會裝載/WEB-INF/spring_bean.xml文件,如果不指定contextConfigLocation參數,默認裝載/WEB-INF/applicationContext.xml文件。然後在tomcat啓動時初始化一個自定義servlet, 在這裏實現springFramework的裝載。
例 3.6. Initializtion.java
public class Initializtion extends HttpServlet
{
        public void init(ServletConfig config) throws ServletException
        {
               WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext
               (config.getServletContext());
               super.init(config);   
               InitSpring.Init((AbstractApplicationContext)wac);     (1)
        }
}
                      
InitSpring是一個符合單例模式的類。
例 3.7. InitSpring.java
public class InitSpring
{
        AbstractApplicationContext wac = null;
        private static InitSpring instance = new InitSpring();
        private InitSpring()
        {
              
        }
       
        public static void Init(AbstractApplicationContext wac)
        {
               instance.wac = wac;
        }
       
        public static Object getInstance(String objName)
        {
               return instance.wac.getBean(objName);
        }
       
        public static Object getInstance(Class objClass)
        {
               return getInstance(objClass.getName());
        }
}
              
經過以上處理,在項目任何一個類中如需得到Spring配置文件中的一個類實例,如下即可:
InitSpring.getObject(beanId);
              
3.5. Spring IOC
IOC(Inversion of Control),譯作反轉控制,其功能是將類之間的依賴轉移到外部的配置文件中, 避免在調用類中硬編碼實現類,因此也被稱作依賴注入(Dependency Injection)。在以往的開發中, 通常利用工廠模式(Factory)來解決此類問題,其實不管是工廠模式還是依賴注入,調用類與實現類不可能沒有任何依賴,工廠模式中工廠類通常根據參數來判斷該實例化哪個實現類,Spring IOC將需要實例的 類在配置文件文件中配置。使用Spring IOC能得到工廠模式同樣的效果,而且編碼更加簡潔。看段代碼比較 一下:
1、用工廠模式來實現
例 3.8. Product.java
public interface Product
{
        public void execute();
}
                      
例 3.9. ConcreteProductA.java
public class ConcreteProductA implements Product
{
        public void execute()
        {
               ...
        }
}
                      
例 3.10. ConcreteProductB.java
public class ConcreteProductB implements Product
{
        public void execute()
        {
               ...
        }
}
                      
例 3.11. Factory.java
public class Factory
{
       
        public Product CreateProduct(object param)
        {
               return ConstructObjects(param);
        }
       
        private Product ConstructObjects(object param)
        {
               ...
        }
}
                      
例 3.12. Client.java(調用類)
public class Client
{
        public Client()
        {
               //實例化ConcreteProductA
               Product product = Factory.CreateProduct(paramA);
              
               //實例化ConcreteProductB
               Product product = Factory.CreateProduct(paramB);
               ...
        }
}
                      
在ConstructObjects方法中設定實例化實現類的邏輯,這樣對於調用類來說,不直接實例化實現類,縱然實現類發生變化,調用代碼仍然可以不作修改,給維護與擴展帶來便利。
2、Spring IOC實現
例 3.13. SpringConfig.xml
<bean id="productA" class="ConcreteProductA" />
<bean id="productB" class="ConcreteProductB" />
 
                      
例 3.14. Client.java(調用類)
public class Client
{
        public Client()
        {
               //實例化ConcreteProductA
               Product product = (Product)InitSpring.getObject("productA");
              
               //實例化ConcreteProductB
               Product product = (Product)InitSpring.getObject("productB");
               ...
        }
}
                      
調用代碼中沒有硬編碼實現類,比較工廠模式,少了Factory類。
Spring爲依賴注入提供三種實現方式:接口注入、設值注入、構造注入。利用這些可以靈活的解決類之間的依賴關係,讓你爲所欲爲的組裝代碼。與其說Spring IOC是一個工具,還不如說搭建了一 個思想的舞臺。繼續看代碼:
來實現一個操作多個數據源的切換
例 3.15. DataSource.java
public class DataSource
{
         private String driverClassName;
         private String url;
         private String username;
         private String password;
 
         public String getDriverClassName()
         {
               return this.driverClassName;
         }
 
         public void setDriverClassName(String driverClassName)
         {
               this.driverClassName = driverClassName;
         }
 
         public String getUrl()
         {
               return this.url;
         }
 
         public void setUrl(String url)
         {
               this.url = url;
         }
 
         public String getUsername()
         {
               return this.Username;
         }
 
         public void setUsername(String username)
         {
               this.username = username;
         }
 
         public String getPassword()
         {
               return this.password;
         }
 
         public void setPassword(String password)
         {
               this.password = password;
         }
}
                      
例 3.16. DataAccessor.java
public class DataAccessor
{
        private DataSource dataSource;
        public void setDriver(DataSource dataSource)
        {
               this.dataSource = dataSource;
        }
 
        public void save(String sql)
        {
               Statement s = getStatement();
               try
        {
            s.getConnection().setAutoCommit(false);
                       int rows = s.executeUpdate(sql);
               s.getConnection().commit();
 
               }
        catch(Exception e)
        {
                       s.getConnection().rollback();
                       ...    
        }
               finally
               {
                       ...
               }
        }
 
        private Statement getStatement()
        {
               Statement s;
               try
               {
                       Class.forName(dataSource.getDriverClassName()).newInstance();
            java.sql.Connection conn =
                       java.sql.DriverManager.getConnection(dataSource.getUrl(),dataSource.getUser(),dataSource.getPassword());
            try
            {
                s = c.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
            }
               }
               catch(Exception e)
               {
                       ...
               }
               return s;
        }
}
                      
例 3.17. BussinessA.java
public class BussinessA
{
        private DataAccessor dao;
        public void setDao(DataAccessor dao)
        {
               this.dao = dao;
        }
 
        public void execute()
        {
               dao.save("insert into tb1 ...");
        }
}
                      
例 3.18. BussinessB.java
public class BussinessB
{
        private DataAccessor dao;
        public void setDao(DataAccessor dao)
        {
               this.dao = dao;
        }
 
        public void execute()
        {
               dao.save("insert into tb2 ...");
        }
 
}
                      
全部代碼就這樣了,執行BussinessA.java、BussinessB.java代碼即可完成數據插入操作,從代碼中看,這兩個類具體操作的是什麼數據庫?什麼樣的操作細節?讓你失望了,代碼中找不到這樣的關聯,看配置文件吧:
例 3.19. SpringConfig.xml
<bean id="dataSourceA" class="DataSource" destroy-method="close">
        <property name="driverClassName"><value>org.gjt.mm.mysql.Driver</value></property>
        <property name="url">
                       <value>jdbc:mysql://localhost:3306/test1?useUnicode=true&amp;characterEncoding=GBK</value>
        </property>
        <property name="username"><value>root</value></property>
        <property name="password"><value></value></property>
 </bean>
 
 <bean id="dataSourceB" class="DataSource" destroy-method="close">
        <property name="driverClassName"><value>org.gjt.mm.mysql.Driver</value></property>
        <property name="url">
                       <value>jdbc:mysql://localhost:3306/test2?useUnicode=true&amp;characterEncoding=GBK</value>
        </property>
        <property name="username"><value>root</value></property>
        <property name="password"><value></value></property>
 </bean>
 
<bean id="daoA" class="DataAccessor">
               <property name="dataSource">
                       <ref local="dataSourceA"/>
               </property>
</bean>
 
<bean id="daoB" class="DataAccessor">
               <property name="dataSource">
                       <ref local="dataSourceB"/>
               </property>
</bean>
 
<bean id="bussinessA" class="BussinessA">
               <property name="dao">
                       <ref local="daoA"/>
               </property>
</bean>
 
<bean id="bussinessB" class="BussinessB">
               <property name="dao">
                       <ref local="daoB"/>
               </property>
</bean>
 
                      
看完配置文件應該明白了,這裏承擔了所有的依賴關係。
  • 首先,我們通過設值注入方法設置數據源相關參數
  • 然後,我們將數據源實例注入給數據訪問類
  • 最後,我們爲每個具體業務類注入相應訪問器
是不是感覺想玩積木似的,在組裝你的代碼?
例 3.20. DaoTest.java
public void testSave()
{
        BussinessA bussinessA = (BussinessA)InitSpring.getObject("bussinessA");
        bussinessA.execute();
 
        bussinessB bussinessB = (BussinessB)InitSpring.getObject("bussinessB");
        bussinessB.execute();
}
                      
執行這段測試代碼,數據庫Test1、Test2中tb1、tb2表將分別插入對應的數據,從實現代碼來看操作多個數據庫和 操作一個數據庫完全一樣,即使當數據源,數據訪問類不斷變化,應用代碼也可以做到不用任何修改。
希望看完本章節的內容能讓讀者與我共鳴,Spring IOC是一種優雅的思想,藉助它發揮你無窮的想象吧。
3.6. Spring AOP
AOP的全稱爲Aspect Oriented Programming,譯爲面向方面編程,針對具體業務領域、業務邏輯。AOP目的是將複雜邏輯進行分離,抽取共性,讓各部分實現實現的功能更爲專一。如,Spring AOP思想實現的經典案例“事務管理”,每個訪問數據庫的業務類都有可能用到事務控制,將其事務管理代碼從具體類中抽取出來,放到一個單獨的 地方處理,這樣大大簡化了具體業務類的代碼。如圖:
圖 3.1. AOP分離前
圖 3.2. AOP分離後
從圖中能清楚地看出分離的效果,下面繼續借助圖例來闡述AOP的一些觀念
圖 3.3. 事務管理之AOP實現序列圖
  • Aspect(方面):我們所關注的,可以被抽取的業務邏輯,如圖中的“事務管理”。
  • JoinPoint(連接點):程序在執行過程中明確的點,如圖中execute()方法。
  • Advice(通知):在特定的連接點執行的動作。如在執行execute()方法前的預處理, 在執行完execute() 方法後的後處理。
  • PointCut(切入點):如圖中在客戶調用execute()時才產生圖中所示動作,還也可以 設定產生同樣動作的方法,如save(),update(),甚至申明成“save.*”,這些申明的 集合就稱之爲切入點。
  • TargetObject(目標對象):包含連接點的對象,也稱爲被代理的對象。如圖中的“業務組件”
理解完概念,接下來看實際例子:
例 3.21. DataAccessor.java
        public void save(String sql)
        {
                       Statement s = getStatement();
                       int rows = s.executeUpdate(sql);
       }
                      
多麼美妙的想法,看實現方法:
1、AOP中的“方面”已經明確,即“事務管理”,通常用攔截器來實現其功能。我們可以通過實現 AOP聯盟提供的通用AOP接口MethodInterceptor自定義攔截器。本例藉助Spring中提供的 org.springframework.jdbc.datasource.DataSourceTransactionManager事務管理 類實現。
2、”目標類“我們也已經明確,即DataAccessor.java。
3、定義切入點,該例中我們期望在執行save方法時做事務管理控制。
4、在SpringConfig.xml文件中進行相關配置
例 3.22. SpringConfig.xml
<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:3306/test1?useUnicode=true&amp;characterEncoding=GBK</value>
        </property>
        <property name="username"><value>root</value></property>
        <property name="password"><value></value></property>
 </bean>
 
<bean id="dataAccessor" class="DataAccessor">
               <property name="dataSource">
                       <ref local="dataSource"/>
               </property>
</bean>
 
<bean id="transactionManager"
               class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> (1)
               <property name="dataSource">
                       <ref local="dataSource" />
               </property>
</bean>
 
<bean id="dataAccessorProxy"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">        (2)
               <property name="transactionManager">                                         (3)
                       <ref bean="transactionManager" />
               </property>
               <property name="target">                                                              (4)
                       <ref local="dataAccessor" />
               </property>
               <property name="transactionAttributes">                                             (5)
                       <props>
                               <prop key="save">PROPAGATION_REQUIRED</prop>
                       </props>
               </property>
        </bean>
                      
申明事務管理的實現類。
通過Spring提供的代理工廠類,注入AOP相關實現信息。
注入事務實現類。
注入目標類。
定義切點(save)。
經過以上的配置,改動下DataAccessor.java類的實現,因爲這裏我們用的dataSource類爲Spring提供的 org.apache.commons.dbcp.BasicDataSource,且我們可以通過JdbcTemplate執行相關數據庫訪問操作。
例 3.23. DataAccessor.java
public class DataAccessor
{
        private DataSource dataSource;
       
        public void setDataSource(DataSource dataSource)
        {
               this.dataSource = dataSource;
        }
       
        public void save()
        {
               JdbcTemplate template = new JdbcTemplate(this.dataSource);
               template.update("insert into game (name) values ('test')");
        }
}
              
save()中代碼完全是我們所期望完全樣子,不用關係異常處理,不用關係事務管理,設想一下,如果項目中有100百個這樣的業務類,那能節省你不少敲這種重複代碼的時間了,最重要的是我們讓某個業務邏輯得 到集中處理,便於日後維護與擴展。這就是藉助AOP的好處。以後面對一個複雜的業務邏輯處理時,別忘了 想想是否能借助面向方面的思想去解決問題。
第 4 章 hibernate
4.1. 概述
對象到關係型數據映射(ORM)是架構中熱門的話題,hibernate是諸多優秀的ORM工具之一,使用和受推崇程度較高。 國內也有專門的hibernate網站與論壇,其中人氣最高當屬 java感謝他們的無私與辛勤,以致hibernate官方發行包中多了一份中文指南。 視線論壇,
4.2. 爲什麼使用Hibernate
1、減輕了編寫Sql語句工作量
例 4.1. 傳統的數據庫訪問代碼
insert into table (field_1,field_2,field_n) values('"+ field_value1 +"','" + field_value2 + "','" + field_value3 + "'")
                      
例 4.2. 藉助Hibernate後數據庫訪問代碼
session.save(table_Object)
                      
由代碼比較可以看出,數據表可以跟對象一樣被操作,這樣代碼顯得更加簡潔,可讀性也增強。在實際開發中,這裏是業務變動頻繁的地方,保證代碼的可讀性和易維護,很有價值。
2、Hibernate封裝了數據庫訪問、事務管理、數據緩存等工作。省去了自己去編寫這些代碼。
3、將數據表數據映射到對象中,以對象作爲傳輸媒介,能更好的在系統各層傳輸數據。
4.3. 快速入門
1、建立表:
表 4.1. book
id
name
price
1
《Basic》
12.00
2
《Pasic》
15.00
2、建立映射文件:
例 4.3. book.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>
<class
    name="com.m5.Book"         (1)
    table="book"                      (2)
 
    <id
        name="id"
        type="java.lang.Integer"
        column="id"
    >
        <generator class="native" />
    </id>
 
    <property
        name="name"
        type="java.lang.String"
        column="name"
        not-null="true"
        length="100"
    />
    <property
        name="price"
        type="long"
        column="price"
        not-null="true"
        length="10"
    />
</class>
</hibernate-mapping>
                      
對應的映射類.
對應的表。
通常增一個表就需要增加類似上面的映射文件。(別緊張,這樣的工作可以藉助工具生成,後續的內容會介紹)。
3、建立影射對象:
例 4.4. book.java
package com.m5;
public class Book
{
        private int id;
        private String name;
        private long price;
       
        public Book()
        {
              
        }
       
        public int getId()
        {
               return id;
        }
       
        public void setId(int id)
        {
               this.id = id;
        }
       
        public String getName()
        {
               return name;
        }
       
        public void setName(String name)
        {
               this.name = name;
        }
       
        public long getPrice()
        {
               return price; 
        }
       
        public void setPrice(long price)
        {
               this.price = price;   
        }
}
 
                      
4、建立hibernate的配置文件
例 4.5. hibernate.cfg.xml
<?xml version='1.0' encoding='gb2312'?>
<!DOCTYPE hibernate-configuration
    PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
<hibernate-configuration>
    <session-factory >
        <property name="hibernate.connection.driver_class">org.gjt.mm.mysql.Driver</property> (1)
        <property name="hibernate.connection.url">(2)
                       jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=GBK
               </property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password"></property>
        <property name="show_sql">true</property>
        <property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.cglib.use_reflection_optimizer">true</property>
        <property name="hibernate.query.substitutions">true 1, false 0</property>
        <property name="hibernate.query.substitutions">male 1, female 0</property>
       <mapping resource="book.hbm.xml"/>     (3)
    </session-factory>
</hibernate-configuration>
                      
指定數據庫驅動類,該文件配置的數據庫驅動爲mysql,hibernate還支持其它的數據庫,更改爲對應的驅動類即可。
數據庫的連接字符串
指定映射文件,該文件配置的book.hbm.xml表明放在類引用路徑的根目錄。如web項目的classes目錄。
5、數據庫訪問代碼
例 4.6. MyHibernate.java
import java.io.File;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
public class MyHibernate {
 
        public static void main(String[] args)
        {
               String name = "java";
               long price = 10;
               try
               {
                       File file = new File("d://hibernate.cfg.xml");       
                       Configuration config = new Configuration().configure(file);         (1)
                       SessionFactory sessionFactory = config.buildSessionFactory();
                       Session session = sessionFactory.openSession();
                       Book book = new Book();                                             
                       book.setName(name);
                       book.setPrice(price);
                       session.save(book);                           (2)
                       session.flush();
               }
               catch(Exception ex)
                {
                       System.out.println(ex.toString());
               }
        }
}
                      
導入配置文件。
將book對象進行數據庫持久,即完成insert的功能。
執行上面的代碼,book數據表將插入一條數據,這就是ORM。hibernate的使用是不是也沒有想象中的複雜?當然 實際開發中不僅僅只會有如此簡單的數據表操作,後續章節將逐步深入講述hibernate的使用。希望該例子能讓你 對hibernate有了較直觀的理解,至少明白hibernate的映射規則:
  • 首先,通過影射文件設定映射類與數據表之間的結構關聯。
  • 然後,裝載配置文件
  • 最後執行操作,將數據表數據映射到對象,或將對象數據持久到數據表中。
4.4. 工具的配合使用
在寫本節前首先感謝夏昕提供的《hibernate開發指南》,該文中詳細的介紹了怎樣生成映射文件 和值對象,以及開發中藉助工具常用的方法。
4.4.1生成影射文件
前面的例子已經介紹了,hibernate完成持久操作需要一個映射文件和影射類。從映射文件可以看出其結構類似 對數據表的描述,我們只要根據數據表就能夠找出生成映射文件的規律。於是我們有了工具來替代手動書寫,從而減少工作量,提高開發效率。下面介紹hibernate官方提供的Middlegen-Hibernate工具。
1、下載Middlegen-Hibernate,www.hibernate.org,解壓縮到c:/Middlegen目錄。
2、修改Middlegen-Hibernate的配置文件,Middlegen-Hibernate通過ant來構建運行(如果對ant不熟悉請先閱讀 ant章節的內容),打開根目錄的build.xml文件:
例 4.7. build.xml
<?xml version="1.0"?>
<!DOCTYPE project [
    <!ENTITY database SYSTEM "file:./config/database/mysql.xml">            (1)
]>
<project name="Middlegen Hibernate" default="all" basedir=".">
 
 
 
   <property file="${basedir}/build.properties"/>
   <property name="name" value="MyTest"/>                                           (2)
       
        ...
 
   <property name="build.dir"    value="c:/sample"/>                        (3)
 
        ...
 
         <hibernate
               destination="${build.gen-src.dir}"
               package="com.m5"                                                     (4)                            
               genXDocletTags="true"                                        (5)           
               genIntergratedCompositeKeys="false"
               javaTypeMapper="middlegen.plugins.hibernate.HibernateJavaTypeMapper"/>
       
        ...    
 
</project>
              
改成對應數據庫的映射文件,本例以mysql爲例,因此引用mysql.xml。
項目名稱。
映射文件的輸出路徑。
對應的包名。
是否生成XDoclet標籤,這裏設置成true,以便利用映射文件生成值對象。
3、找到/config/database目錄下對應的數據庫配置文件,本文用的是mysql,因此打開mysql.xml
例 4.8. mysql.xml
   <property name="database.script.file"           value="${src.dir}/sql/${name}-mysql.sql"/>
   <property name="database.driver.file"           value="${lib.dir}/mysql-connector-java-3.0.14-production-bin.jar"/>
   <property name="database.driver.classpath"      value="${database.driver.file}"/>
   <property name="database.driver"                value="org.gjt.mm.mysql.Driver"/>
 
   <property name="database.url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&
   characterEncoding=GBK"/>           (1)                                  
   <property name="database.userid" value="root"/>          (2)
   <property name="database.password" value=""/>                     (3)
  
   <property name="database.schema"                value=""/>
   <property name="database.catalog"               value=""/>
   <property name="jboss.datasource.mapping"       value="mySQL"/>
                      
                              
修改數據庫連接字符串。
配置數據庫用戶名。
配置數據庫密碼。
4、進入Middlegen-Hibernate,運行ant,將會看到如下圖的界面:
根據界面的標題進行各項對應的操作,最後單擊“Generate”按鈕即可生成映射文件。如,本文以book表爲例,生成 book表的映射文件:
例 4.9. book.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>
<!--
    Created by the Middlegen Hibernate plugin 2.1
 
    http://boss.bekk.no/boss/middlegen/
    http://www.hibernate.org/
-->
 
<class
    name="com.m5.Book"
    table="book"
    <meta attribute="class-description" inherit="false">
       @hibernate.class
        table="book"
    </meta>
 
    <id
        name="id"
        type="java.lang.Integer"
        column="id"
    >
        <meta attribute="field-description">
           @hibernate.id
            generator-class="assigned"
            type="java.lang.Integer"
            column="id"
 
 
        </meta>
        <generator class="assigned" />
    </id>
 
    <property
        name="name"
        type="java.lang.String"
        column="name"
        not-null="true"
        length="100"
    >
        <meta attribute="field-description">
           @hibernate.property
            column="name"
            length="100"
            not-null="true"
        </meta>   
    </property>
    <property
        name="price"
        type="long"
        column="price"
        not-null="true"
        length="10"
    >
        <meta attribute="field-description">
           @hibernate.property
            column="price"
            length="10"
           not-null="true"
        </meta>   
    </property>
 
    <!-- Associations -->
 
</class>
</hibernate-mapping>
 
                              
4.4.2生成映射對象
從book.hbm.xml文件中可以發現有類似這樣的代碼“ @hibernate.class table=‘book’”,這就是XDoclet 標籤。可以通過Hibernate Extension工具包中的hbm2java工具來轉換這些標籤生稱對應的值對象。
1、下載Hibernate Extension工具包,http://www.hibernate.org
2、hbm2java對本地類庫引用的配置文件爲:tools/bin/setenv.bat
例 4.10. setenv.bat
@echo off
rem -------------------------------------------------------------------
rem Setup environment for hibernate tools
rem -------------------------------------------------------------------
 
set JDBC_DRIVER=C:/jars/mysql-connector-java-3.0.14-production-bin.jar      (1)
set HIBERNATE_HOME=C:/hibernate-2.1                          (2)
 
set HIBERNATETOOLS_HOME=%~dp0..
echo HIBERNATETOOLS_HOME set to %HIBERNATETOOLS_HOME%
 
if "%HIBERNATE_HOME%" == "" goto noHIBERNATEHome
 
set CORELIB=%HIBERNATE_HOME%/lib
set LIB=%HIBERNATETOOLS_HOME%/lib
set CP=%CLASSPATH%;%JDBC_DRIVER%;%HIBERNATE_HOME%/hibernate2.jar;%CORELIB%/commons-logging-1.0.4.jar;
%CORELIB%/commons-lang-1.0.1.jar;%CORELIB%/cglib-full-2.0.2.jar;%CORELIB%/dom4j-1.4.jar;
%CORELIB%/odmg-3.0.jar;%CORELIB%/xml-apis.jar;%CORELIB%/xerces-2.4.0.jar;%CORELIB%/xalan-2.4.0.jar;
%LIB%/jdom.jar;%CORELIB%/commons-collections-2.1.1.jar;%LIB%/../hibernate-tools.jar         (3)           
 
if not "%HIBERNATE_HOME%" == "" goto end
 
:noHIBERNATEHome
echo HIBERNATE_HOME is not set. Please set HIBERNATE_HOME.
goto end
 
:end
                              
對應到本地JDBC_DRIVER jar包路徑。
hibernate根目錄。
對hibernate中jar包的引用,hibernate因版本不同而jar包的名字也會有可能不同,因此 請覈對。
3、進入hbm2java.bat所在目錄,運行 hbm2java 影射文件路徑 --output=輸出值對象路徑。如: hbm2java C:/sample/gen-src/com/m5/Book.hbm.xml --output=C:/sample/classes。在 C:/sample/classes目錄下生成以包名組織的文件/com/m5/Book.java
例 4.11. Book
package com.m5;
 
import java.io.Serializable;
import org.apache.commons.lang.builder.ToStringBuilder;
 
 
/**
 
*        @hibernate.class
 
*         table="book"
 
 
*/
public class Book implements Serializable {
 
    /** identifier field */
    private Integer id;
 
    /** persistent field */
    private String name;
 
    /** persistent field */
    private long price;
 
    /** full constructor */
    public Book(Integer id, String name, long price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
 
    /** default constructor */
    public Book() {
    }
 
    /**
   
        *            @hibernate.id
   
        *             generator-class="native"
   
        *             type="java.lang.Integer"
    
        *             column="id"
    
        */
    public Integer getId() {
        return this.id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
 
    /**
        *            @hibernate.property
    
        *             column="name"
    
        *             length="100"
    
        *             not-null="true"
    
        */
    public String getName() {
        return this.name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    /**
 
        *            @hibernate.property
    
        *             column="price"
    
        *             length="10"
    
        *             not-null="true"
    
        */
    public long getPrice() {
        return this.price;
    }
 
    public void setPrice(long price) {
        this.price = price;
    }
 
    public String toString() {
        return new ToStringBuilder(this)
            .append("id", getId())
            .toString();
    }
 
}
                              
4.4.3根據映射對象生成映射文件
上節提到了用hbm2java將影射文件生成映射對象,依靠xdoclet標籤完成。xdoclet也是依靠此標籤完成與影射文件的 同步。這樣實際開發中會帶來很大的便利,我們只要維護代碼,而不需要手動維護與影射文件的同步。xdoclet標籤 可以由上節講的方法去轉化得來,當然如果熟悉了xdoclet標籤,手動完成即可。xdoclet的使用很方便,可以加入我 們已有的ant任務中(如果尚未了解Ant,請參看相關章節)。
1、下載xdoclet
2、建立構建文件
例 4.12. build.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="XDoclet Examples" default="hibernate" basedir=".">
    <property name="xdoclet.root.dir" value="c:/xdoclet-1.2.2"/>
    <property name="xdoclet.lib.dir" value="${xdoclet.root.dir}/lib"/>
        <property name="samples.gen-src.dir" value="./gen-src"/>
 
    <path id="classpath">
        <fileset dir="${xdoclet.lib.dir}">
            <include name="*.jar"/>
        </fileset>
    </path>
     <taskdef
        name="hibernatedoclet"
        classname="xdoclet.modules.hibernate.HibernateDocletTask"
        classpathref="classpath"
        />
    <target name="hibernate" description="Generate mapping documents">
 
        <echo>+---------------------------------------------------+</echo>
        <echo>|                                                   |</echo>
        <echo>| R U N N I N G   H I B E R N A T E D O C L E T     |</echo>
        <echo>|                                                   |</echo>
        <echo>+---------------------------------------------------+</echo>
 
        <hibernatedoclet
            destdir="${samples.gen-src.dir}"
            excludedtags="@version,@author,@todo,@see"
            addedtags="@xdoclet-generated at ${TODAY},@copyright The XDoclet Team,@author XDoclet,@version ${version}"
            force="false"
            verbose="true">
 
            <fileset dir="${samples.gen-src.dir}">
                <include name="com/m5/Book.java"/>
            </fileset>
                <hibernate version="2.1"/>
 
        </hibernatedoclet>
    </target>
</project>
 
                              
build.xml中的目錄結構均爲筆者環境的,使用時請修改成對應的目錄。
3、運行ant,在輸出目錄生成對應的影射文件。
建議:如果你覺得hibernate的映射文件放在一個xml文件更爲方便,可以通過修改xdoclet的源碼, 使其生成的映射文件全部放置在制定的xml文件中,這樣生成新的映射文件時不需要去維護hibernate 的配置文件中對影射文件的引用,當然也有弊端,多人開發時,關於版本控制衝突,以及可讀性降低。 以上建議僅供參考。
4.5. 專用詞
在講述關聯關係之前,有必要解釋下一些專用詞的含義
4.5.1. cascade(級聯)
級聯在編程中經常接觸,寫過觸發器來修改或刪除關聯表相記錄的一定會知道,觸發器的作用是當主控表信息改變時,用來保證其關聯表中數據同步更新。比如一個employee存放職員信息,一個 timecard存放職員的考勤信息,當從職員表中刪除一個職員時,timecard表中對應的考勤信息已經沒有意義,因爲其所屬的職員已不存在,如果繼續留在timecard表中就成了沒用的也稱髒數據。 理想的做法是在刪除職員信息的同時將該職員信息對應的考勤信息也刪除。在hibernate中如果要達到這個 效果只需要設置cascade屬性值即可。當然是否進行級聯關係要根據實際情況慎重考慮。
4.5.2. inverse(反轉)
表與表之間的關聯,我們通常將主動發起關聯請求的表稱爲主動表,被關聯的表成爲被動表,hibernate中 將此概念冠以在表所對應的對象上,因此將主動發起關聯請求的對象稱爲主動對象或主控對象,被關聯的對象 稱爲被動對象或被控對象。hibernate由主動對象維護關聯關係,在實際中經常碰到一個對象的關聯角色並不 那麼明確,如雙向關聯,這時inverse值用來標明由誰來維護關聯關係。設爲true時反轉控制角色,即由該屬性關聯的對象維護關聯關係。
4.5.3. Lazy Loading(延時裝載)
延時裝載主要是從性能方面的考慮,對於 “select coulmn1 from table”和“select * from table”語句 的性能比較,相信大家不會有異議,第一條的執行性能要高於第二條,當然這個表中字段存儲的信息應該能充分 體現出優越性爲前提,比如說一個employee表中存放有,職員姓名、年齡、照片等,如果只需要查看姓名和年齡, 那麼照片信息就不應該附帶出來。表與表之間的關聯也應如此,如果不需要用到關聯表中的數據就不應該去進行關聯操作,或在需要的時候才啓動關聯操作。讓數據在最恰當的時候纔出現,這就是延時裝載。
4.6. 一對一表關聯操作
前面章節的例子是單表的操作,實際開發中表之間的關聯操作是必不可少的。 本章以書籍與出版社之間的關聯爲例,來講述一對一的關聯操作。
一對一關係在hibernate中以one-to-one表示,本例中以Book類爲主動連接方,因此在Book.java中加入 關聯Publish的屬性。一對一關聯在hibernate中有兩種方式:
  • 主鍵關聯:不需藉助外部字段,直接通過兩個表的主鍵進行關聯,因此必須保證兩個表的主鍵值一 致,這通常通常藉助foreign標識符生成器策略來完成。簡單來說,這種情況就是兩個表的主鍵 相等的內連接。
  • 唯一外鍵關聯:在主動方加入外鍵進行關聯,這樣主動方與被動方的影射關係實際上就成了多對一的關聯。
爲方便查詢,在此描述one-to-one節點的屬性含義(也可參考hibernate的官方指導手冊,有中英文對照很方便)
<one-to-one
        name="propertyName"                   (1)                          
        class="ClassName"                     (2)
        cascade="all|none|save-update|delete" (3)
        constrained="true|false"              (4)
        outer-join="true|false|auto"          (5)
        property-ref="propertyNameFromAssociatedClass"               (6)
        access="field|property|ClassName"            (7)
/>
              
name:映射屬性的名稱。
class(可選):被關聯的類的名稱,如果省略此屬性,則通過反射機制得到與此屬性名稱一致的類。
cascade(可選):表明操作是否從父對象級聯到被關聯的對象,all,爲所有變更動作都進行級聯操作;none,爲 從來不作級聯操作;save-update,爲insert,update動作時作級聯操作。delete,爲delete動作時作級聯操作。
constrained(可選):表明該類對應的表對應的數據庫表,和被關聯的對象所對應的數據庫表之間,通過一個外鍵 引用對主鍵進行約束。這個選項影響save()和delete()在級聯執行時的先後順序。
outer-join(可選):是否允許外連接抓取;默認是auto,關聯對象沒有采用proxy機制時使用外聯接。
property-ref(可選):指定關聯類的一個屬性,這個屬性將會和本外鍵相對應。默認爲關聯類的主鍵。
access(可選):Hibernate用來訪問屬性的策略,默認是property.
首先來看通過主鍵進行一對一的關聯操作:
表 4.2. book
id
name
price
1
《Basic》
12.00
2
《Pasic》
15.00
表 4.3. Publish
id
name
address
1
機械出版社
北京朝陽區
2
教育出版社
北京海底區
1、建立映射文件
例 4.13. hibernate_map.xml
<hibernate-mapping>
        <!--one to one-->
     <class name="hibernate.relation.oneToOne.Book" table="Book">
               <id name="id" column="id" type="java.lang.Integer">
                       <generator class="foreign">
                               <param name="property">publish</param>
                       </generator>
               </id>
               <property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
               <property name="price" type="long" column="price" length="100" not-null="true" />
               <one-to-one name="publish" class="hibernate.relation.oneToOne.Publish" cascade="none" outer-join="auto" constrained="false" />
         </class>
       
         <class name="hibernate.relation.oneToOne.Publish" table="Publish">
               <id name="id" column="id" type="java.lang.Integer">
                       <generator class="native"></generator>
               </id>
               <property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
               <property name="address" type="java.lang.String" column="address" length="100" not-null="true" />
         </class>
</hibernate-mapping>
 
                      
2、建立映射類
例 4.14. Book.java
package hibernate.relation.oneToOne;
import java.io.Serializable;
import org.apache.commons.lang.builder.ToStringBuilder;
public class Book implements Serializable {
 
 
    private Integer id;
    private String name;
    private long price;
    private Publish publish = null;
 
    public Book(Integer id, String name, long price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
 
    public Book() {
    }
 
    public Integer getId() {
        return this.id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
 
 
    public String getName() {
        return this.name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
  
    public long getPrice() {
        return this.price;
    }
 
    public void setPrice(long price) {
        this.price = price;
    }
   
 
    public Publish getPublish()
    {
        return this.publish;
    }
   
    public void setPublish(Publish publish)
    {
        this.publish = publish;
    }
 
    public String toString() {
        return new ToStringBuilder(this)
            .append("id", getId())
            .toString();
    }
 
}
                      
例 4.15. Publish.java
package hibernate.relation.oneToOne;
 
import java.io.Serializable;
import org.apache.commons.lang.builder.ToStringBuilder;
public class Publish implements Serializable {
    private Integer id;
    private String name;
    private String address;
    public Publish(Integer id, String name, String address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }
    public Publish() {
    }
 
    public Integer getId() {
        return this.id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return this.name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
    public String getAddress() {
        return this.address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String toString() {
        return new ToStringBuilder(this)
            .append("id", getId())
            .toString();
    }
}
                      
3、演示代碼
例 4.16. BookTest.java
public class BookTest extends TestCase
{
        private String hql;
        private SessionFactory sessionFactory;
        private Session session;
        private List list;
       
        protected void setUp() throws Exception
        {
               File file = new File("d://hibernate.cfg.xml");
               Configuration config = new Configuration().configure(file);
               sessionFactory = config.buildSessionFactory();
               session = sessionFactory.openSession();
        }
       
        public void testSave() throws HibernateException                    
        {
               Book book = new Book();
               book.setName("《Basic》");
               book.setPrice(Long.parseLong("12"));
               Publish publish = new Publish();
               publish.setName("機械出版社");
               publish.setAddress("北京朝陽");
               book.setPublish(publish);
               session.save(book);
               session.flush();
        }
               
        public void tesQuery() throws HibernateException                    
        {
               String publishName = null;
               hql = "from hibernate.relation.oneToOne.Book as book where book.id = 1";
               List books = session.find(hql);
               for ( int i=0; i < books.size(); i++ )
               {
                       Book book = (Book)books.get(i);
                       publishName = book.getPublish().getName();
                       System.out.println("對應的出版社爲:" + publishName);
               }
        }
}
                      
插入時將執行如下語句:
Hibernate: insert into Publish (name, address) values (?, ?)
Hibernate: insert into Book (name, price, id) values (?, ?, ?)
這是因爲我們在Book映射文件中設置了其id值爲外鍵生成策略,所以Hibernate會先插入 Publish,然後用publish的主鍵值填充Book的主鍵值,以保證Publish與Book表的主鍵 值相等。
執行查詢語句將執行如下語句:
Hibernate: select book0_.id as id, book0_.name as name, book0_.price as price from Book book0_ where (book0_.id=39 )
Hibernate: select publish0_.id as id0_, publish0_.name as name0_, publish0_.address as address0_ from Publish publish0_ where publish0_.id=?
以上示例通過Book、publish兩個表的主鍵形成關聯,接下來看如何通過外鍵關聯完成該例子。
4.7. 多對一表關聯操作
我們在Book表中添加publishId的外鍵,用來與publish表形成關聯。
表 4.4. book
id
publishId
name
price
1
1
《Basic》
12.00
2
2
《Pasic》
15.00
1、建立映射文件
例 4.17. hibernate_map.xml
<hibernate-mapping>
        <!--one to one-->
     <class name="hibernate.relation.oneToOne.Book" table="Book">
               <id name="id" column="id" type="java.lang.Integer">
                       <generator class="native" />
               </id>
               <property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
               <property name="price" type="long" column="price" length="100" not-null="true" />
               <many-to-one name="publish" class="hibernate.relation.oneToOne.Publish" cascade="all" outer-join="auto"/>
         </class>
        ...
</hibernate-mapping>
 
                      
只將book映射文件的one-to-one修改成many-to-one,其他的保持不變,執行BookTest.java文件,將看到與one-to-one同樣的效果。(注意:cascade="all",這裏設置級聯是必須的,因爲在插入book時應該先得到 publishid的值。)
上面的例子都是以Book爲主動關聯方進行操作,如果需要在操作Publish時獲取關聯的Book對象,我們需要在 Publish中加入與Book的關聯映射,這樣Book與Publish之間就形成了雙向關聯,這裏假設Publish與Book是 一對多的關係,具體操作請看下一章節。
4.8. 一對多表關聯操作
1、在publish映射中加入一對多關係
例 4.18. hibernate_map.xml
...
<class name="hibernate.relation.oneToOne.Publish" table="Publish">
               <id name="id" column="id" type="java.lang.Integer">
                       <generator class="native"></generator>
               </id>
               <property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
               <property name="address" type="java.lang.String" column="address" length="100" not-null="true" />
               <set name="book" table="book" lazy="false" cascade="none" sort="unsorted" inverse="false">
                       <key column="publishId" />
                       <one-to-many class="hibernate.relation.manyToOne.Book" />
               </set>
</class>
 
                      
2、在Publish映射類中加入book屬性
例 4.19. Publish.java
private Set book = new HashSet();
public Set getBook()
{
        return book;
}
public void setBook(Set book)
{
        this.book = book;
}
                      
這樣就能在操作Publish時也能獲取到與之關聯的Book信息,看測試代碼:
例 4.20. Publish.java
Publish publish = (Publish)session.get(Publish.class,Integer.valueOf(1));
Set books = publish.getBook();
for (Iterator it = books.iterator(); it.hasNext();)
{
        Book book = (Book)it.next();
        System.out.println("對應的書籍爲:" + book.getName());
 }                            
                      
執行上面的代碼顯示的結果爲:
  • Hibernate: select publish0_.id as id0_, publish0_.name as name0_, publish0_.address as address0_ from Publish publish0_ where publish0_.id=?
  • Hibernate: select book0_.id as id__, book0_.publishId as publishId__, book0_.id as id0_, book0_.name as name0_, book0_.price as price0_, book0_.publishId as publishId0_ from Book book0_ where book0_.publishId=?
  • 對應的書籍爲:《Basic》
4.9. 多對多表關聯操作
多對多的關聯在實際的開發中也是經常被用到的,假設現有一個員工表來存放所有員工的信息,一個福利項目表存放福利明細,要記錄每個員工享有的福利明細,同一福利項,多個員工均可享有, 一個員工也可以享有多項福利,這就形成了多對多的關聯,在這裏我們加入一個福利明細表充當關聯其兩者的中間表(多對多的關聯一般都是通過中間表進行關聯的)。看具體實現:
1、表結構如下:
表 4.5. Welfare(福利項目表)
id(主鍵遞增)
name
money
1
飯補
250.00
2
交通補助
200.00
3
崗位補助
500.00
表 4.6. Empolyee(人員表)
id(主鍵遞增)
name
job
1
王一
部門經理
2
李二
程序員
表 4.7. Empolyee_Welfare(員工福利明細表)
id(主鍵遞增)
EmpolyeeID(員工ID)
WelfareID(福利項ID)
1
1(王一)
1(飯補)
2
1(王一)
2(交通補助)
3
1(王一)
3(崗位補助)
4
2(李二)
1(飯補)
5
2(李二)
2(交通補助)
2、編寫影射文件
例 4.21. Hibernate_map.xml
<class name="hibernate.relation.manyToMany.Employee" table="Employee">
               <id name="id" column="id" type="java.lang.Integer">
                       <generator class="native"></generator>
               </id>
               <property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
               <property name="job" type="java.lang.String" column="job" length="100" not-null="true" />
               <set name="welfare" table="Empolyee_Welfare" lazy="false">
                       <key column="EmployeeId" />
                       <many-to-many class="hibernate.relation.manyToMany.Welfare" column="WelfareId" />
               </set>
</class>
<class name="hibernate.relation.manyToMany.Welfare" table="Welfare">
               <id name="id" column="id" type="java.lang.Integer">
                       <generator class="native"></generator>
               </id>
               <property name="name" type="java.lang.String" column="name" length="100" not-null="true" />
               <property name="money" type="java.lang.Double" column="money" length="18" not-null="true" />
               <set name="employee" table="Empolyee_Welfare" lazy="false" inverse="true">
                       <key column="WelfareId" />
                       <many-to-many class="hibernate.relation.manyToMany.Employee" column="EmployeeId" />
               </set>
</class>
 
                      
3、編寫映射類
例 4.22. Employee.java
public class Employee
{
        private int id;
        private String name;
        private String job;
        private Set welfare = new HashSet();
 
        ...
 
        public Set getWelfare()
        {
               return this.welfare;
        }
              
        public void setWelfare(Set welfare)
        {
               this.welfare = welfare;
        }
}
                      
例 4.23. Welfare.java
public class Welfare
{
        private int id;
        private String name;
        private Double money;
        private Set employee = new HashSet();
       
        ...
 
        public Set getEmployee()
        {
               return this.employee;
        }
       
        public void setEmployee(Set employee)
        {
               this.employee = employee;
        }
}
                      
4、測試代碼
例 4.24. Employee.java
public void testSave() throws Exception
        {
              
               try
               {
                       tx = session.beginTransaction();
                       Employee employee = new Employee();
                       employee.setName("王一");
                       employee.setJob("程序員");
                      
                       Welfare welfare = new Welfare();
                       welfare.setMoney(Double.valueOf("250"));
                       welfare.setName("飯補");
                      
                       employee.getWelfare().add(welfare);
                       welfare.getEmployee().add(employee);
                      
                       session.save(employee);
                       session.save(welfare);
                       tx.commit();
               }
               catch(Exception ex)
               {
                       if ( tx !=null )
                       {
                               tx.rollback();
                       }
               }
               finally
               {
                       session.close();
               }
        }
                      
執行以上代碼Empolyee、Welfare、Empolyee_Welfare表中將各插入一條數據:
  • Hibernate: insert into Employee (name, job) values (?, ?)
  • Hibernate: insert into Welfare (name, money) values (?, ?)
  • Hibernate: insert into salary (EmployeeId, WelfareId) values (?, ?)
4.10. 與spring的結合使用
上面的例子中我的測試代碼都是通過如下代碼來建立hibernate的Session。
File file = new File("d://hibernate.cfg.xml");
Configuration config = new Configuration().configure(file);
sessionFactory = config.buildSessionFactory();
session = sessionFactory.openSession();
              
這樣做的目的是爲了更直觀的說明對hibernate的使用,本節將演示結合Spring的使用,使代碼更爲簡潔。
首先,修改spring的配置文件,如下:
<beans default-lazy-init="false" default-dependency-check="none" default-autowire="no">
    <description>
    </description>
     <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:3306/test?useUnicode=true&amp;characterEncoding=GBK</value>
        </property>
        <property name="username">
            <value>root</value>
        </property>
        <property name="password">
            <value></value>
        </property>
    </bean>
  
   <bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
        <property name="dataSource">
            <ref local="dataSource"/>
        </property>
        <property name="mappingResources">
           <list>
            <value>Hibernate_Map.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.query.substitutions">true 1, false 0</prop>
            </props>
        </property>
    </bean>
<beans>
 
              
上面增加的“dataSource”和“sessionFactory”節點,可以完全替換掉hibernate.cfg.xml的配置信息, 並且在org.springframework.orm.hibernate.LocalSessionFactoryBean類中封裝了對hibernate的 session調用。請看如下調用代碼:
SessionFactory sessionFactory = (SessionFactory)InitSpring.getInstance("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, false);
session.save(...);
              
上面的代碼通過spring配置文件,實例sessionFactory,然後通過SessionFactoryUtils的getSession方法得到session實例。 關於在程序啓動時怎樣初始化Spring,請參考???
我們還可以通過Spring來實現Hibernate DAO,得到更簡潔的調用:
 <bean id="DAOTarget" class="hibernate.dao.test.MyDAO"
    singleton="true"
    lazy-init="default"
    dependency-check="default"
    autowire="default"
    >
        <property name="sessionFactory">
                       <ref local="sessionFactory" />
        </property>
    </bean>
 
              
例 4.25. MyDAO.java
public class MyDAO extends HibernateDaoSupport
{
         getHibernateTemplate().save(...);   
}
              
                      
上面的代碼如果看的不是很明白,沒有關係,在DAO章節將詳細講述其實現,這裏代碼只是想說明, 藉助spring能讓hibernate的應用更加簡潔。
4.11. Hibernate DAO
在開始本章之前,先介紹DAO模式的概念,DAO是Data Access Object的縮寫,DAO模式思想是將業務邏輯代碼與 數據庫交互代碼分離,降低兩者耦合。通過DAO模式可以使結構變得更爲清晰,代碼更爲簡潔,本節示例將結合Spring,演練Hibernate Dao所帶來的優越性。
爲什麼藉助Spring實現Hibernate DAO:
  • Spring幫我們封裝了針對Hibernate DAO的常用操作。
  • 將業務對象與DAO對象的依賴轉移到Spring配置文件中。
  • 藉助Spring AOP功能實現DAO對象中數據庫訪問的統一事務管理。
在開始演練Spring DAO之前,請看一段不借助DAO模式的代碼:
例 4.26. MyApp.java
public void AddUser()
{
        SessionFactory sessionFactory = (SessionFactory)InitSpring.getInstance("sessionFactory");
        Session session = SessionFactoryUtils.getSession(sessionFactory, false);
        User user = new User();
        user.setName("王一");
        String hql = " from User as user where user.name = '"+ user.name +"' "
        List list = session.find(hql);
 
        //進行邏輯判斷,如果已經存在相同的用戶則不允許添加
        if ( list.size() > 0 )
        {
               ...
        }
        else
        {
               try
               {
                       Transaction tx = session.beginTransaction();
                       session.save(user);
                       tx.commit();
               }
               catch(Exception ex)
               {
                       if ( tx !=null )
                       {
                               tx.rollback();
                               ...
                       }
               }
               finally
               {
                       ...
                       session.close();
               }
       
        }
 
}
                      
以上的代碼實現的功能是添加一個用戶,且在添加用戶之前判斷數據庫是否有同名用戶,應該說邏輯不算複雜,我們將這種業務邏輯與數據庫訪問代碼寫在一塊有如下不足之處:
  • 降低重用性:該例子中實現了查找用戶和添加用戶的代碼,這種功能很肯能在項目的很多地方都需要用到,但是 我們又不能直接調用AddUser()中實現的類似代碼,因爲該方法與具體的業務邏輯邦定在一塊。
  • 有可能降低代碼可讀性:當業務邏輯變得複雜時,在該例中既要實現業務邏輯,又要實現數據庫訪問代碼。
  • 造成重複編碼:從上面例子的代碼中我們可以看到附加了業務邏輯以外的代碼,如事務管理、異常捕獲。 最理想的業務類完成的功能應該是隻實現其自身的業務邏輯。
我們期望的代碼是:
例 4.27. MyApp.java
public void AddUser()
{
        User user = new User();
        user.setName("王一");
        if ( findUser(user).size() >0 )
        {
               ...
        }
        else
        {
               saveUser(user);
        }
}
                      
下面藉助Spring來實現Hibernate Dao,來看看是否能達到我們期望的效果:
1、將訪問數據庫的代碼從業務類MyApp.java中分離出來,放在DAO對象UserDao.java中
例 4.28. UserDao.java
public class UserDao extends HibernateDaoSupport
{
        public List findUser(User user)
        {
               String hql = " from User as user where user.name = '"+ user.name +"' ";
               return getHibernateTemplate().find(hql);
        }
 
        public void saveUser(User user)
        {
               getHibernateTemplate().save(user);
        }
}
                      
2、將業務類MyApp.java與DAO對象關聯(在MyApp.java類中增加DAO的屬性,以便通過Spring 注入DAO實例)。
例 4.29. Spring_Config.xml
<bean id="userDao" class="hibernate.dao.test.UserDao"
    singleton="true"
   lazy-init="default"
    dependency-check="default"
    autowire="default"
    >
        <property name="sessionFactory">
                       <ref local="sessionFactory" />
        </property>
</bean>
<bean id="myApp" class="hibernate.dao.test.MyApp"
    singleton="true"
    lazy-init="default"
    dependency-check="default"
    autowire="default">
        <property name="dao">
                       <ref local="userDao" />
        </property>
</bean>
 
                      
3、業務類MyApp.java的實現
例 4.30. MyApp.java
public class MyApp
{
        private UserDao dao;
        public void setDao(UserDao dao)
        {
               this.dao = dao;
        }
       
        public void AddUser()
        {
               User user = new User();
               user.setName("王一");
               if ( dao.findUser(user).size() >0 )
               {
                       ...
               }
               else
               {
                       dao.saveUser(user);   
               }
        }
}
                      
從MyApp.java可以看到與我們期望的效果幾乎一樣,在實際開發中我們應該讓業務類與DAO類分別針對其接口 做實現,這樣在代碼中可以只針對於接口做引用,從而降低調用類與具體實現類的耦合,這裏爲更簡潔的說明問題省略對其各自接口的定義。
第 5 章 log4j
5.1. 概述
log4j是用於java語言的日誌記錄工具,一個完整的商業軟件,日誌是必不可少的。現實開發中日誌記錄多種多樣,有打印在控制檯中,有記錄成文本文件,有保存到數據庫中等。日誌信息也許需要 分爲調試日誌,運行日誌,異常日誌等。這些雖然實現簡單,但是也繁瑣。本章將介紹用log4j來實現日誌 記錄的種種情況。
5.2. 快速入門
2、建立log4j的配置文件,本文中命名爲log4j.xml,內容如下:
例 5.1. log4j.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">    (1)                   
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        <appender name="file" class="org.apache.log4j.FileAppender"> (2)
               <param name="File"   value="D:/mytest.log" />                        (3)
               <layout class="org.apache.log4j.PatternLayout">                              (4)
                       <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c:(%F:%L) %n - %m%n"/>
               </layout>         
        </appender>
        <root>
               <priority value ="INFO"/>             (5)
               <appender-ref ref="file" />           (6)
        </root>
</log4j:configuration>
                      
申明驗證該文檔的dtd文件,”SYSTEM“說明是從本地尋找,因此需將log4j.dtd文件放入申明的路徑中
該節點配置成日誌以文件形式輸出(org.apache.log4j.FileAppender)。log4j還提供打印日誌到控制檯 (org.apache.log4j.ConsoleAppender),以信息流格式傳送到任何地方(org.apache.log4j.WriterAppender)。
指定日誌文件的路徑和名稱。
指定記錄日誌的佈局格式。log4j提供以html格式的佈局(org.apache.log4j.HTMLLayout),自定義佈局格式 (org.apache.log4j.PatternLayout),包含一些簡單的日誌信息,級別和信息字符串(org.apache.log4j.SimpleLayout) 包含詳細的日誌信息,如時間、線程、類別等信息。
設置日誌輸出的級別。log4j的日誌常用級別如下,按優先級別從高到低分爲:
Fatal:顯示致命錯誤。
Error:顯示錯誤信息。
Warn:顯示警告信息。
Info:顯示程序運行日誌。
debug:顯示調試信息。
只有日誌級別大於或等於被設置級別,相應的日誌才被記錄。如本配置文件配置級別爲Info,程序中除了debug級別的日誌, 其它級別的日誌都會被輸出。
引用輸出日誌的方式。
3、演示使用log4j記錄日誌
例 5.2. MyLog.java
package com.m5;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
public class MyLog {
        public static void main(String[] args)
        {
               String log4j = "d://log4j.xml" ;
               DOMConfigurator.configure(log4j);
               Logger logger = Logger.getLogger(MyLogServlet.class.getName());
               try
               {
                       int i = 10;
                       int n = 0 ;
                       int m = 10/0;
               }
               catch(Exception ex)
               {
                       logger.info(ex.toString());
               }
        }
}      
              
上面的程序會將捕捉到的異常信息寫入D:/mytest.log文件。日誌的書寫格式輸出目的地均可以通過log4j進行配置。關於具體配置請參考log4j.xml及其說明,在此不再一一演示其效果。從上面的演示代碼中可以看出對log4j的引用 也非常簡潔,在實際運用中可以優化下導入log4j的配置文件部分。MyLog.java中的代碼是爲了最簡捷清晰的說明對 log4j的使用。
4、實際環境中的應用(以web服務爲tomcat的web項目爲例)
在每個類中記錄日誌之前都敲一次裝載log4j的配置文件的代碼,顯然是不合理的。通常我們在程序啓動之前完成這些初始化工作。 spring一文中描述了對spring配置文件的初始化方法,同樣,初始化log4j配置文件的裝載也可以通過這個自定義的啓動類完成。
例 5.3. web.xml
<?xml version="1.0" encoding="UTF-8"?>
        <web-app xmlns="http://java.sun.com/xml/ns/j2ee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
        <display-name>Test</display-name>
        <context-param>
               <param-name>contextConfigLocation</param-name>
               <param-value>/WEB-INF/spring_bean.xml</param-value>
        </context-param>
        <listener>
                <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <!--
        <servlet>
               <servlet-name>context</servlet-name>
                <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>
               <load-on-startup>2</load-on-startup>
        </servlet>
        -->
        <servlet>
               <servlet-name>Init</servlet-name>
               <servlet-class>com.m5.Base</servlet-class>
               <init-param>
 
               <param-name>log4jConfigLocation</param-name>                 (1)
 
               <param-value>/WEB-INF/log4j.xml</param-value>
        </init-param>
        <load-on-startup>3</load-on-startup>
</servlet>
</web-app>
       
       
配置log4j的配置文件路徑。
例 5.4. Base.java
public class Base extends HttpServlet
{
        WebApplicationContext wac = null;
        public void init(ServletConfig config) throws ServletException
        {
               //ApplicationContext ac = new FileSystemXmlApplicationContext("bean.xml");
               wac = WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext());
               super.init(config);   
               InitSpring.Init((AbstractApplicationContext)wac);    
               String root = getServletConfig().getServletContext().getRealPath("/");
                String log4j = getInitParameter("log4jConfigLocation");              (1)
               DOMConfigurator.configure(root + log4j);                                     (2)
        }
}
              
得到配置文件路徑。
裝載配置文件,DOMConfigurator符合sigle模式,配置文件只需裝載一次,全局即可調用。
通過以上配置,項目中可以通過如下引用:
例 5.5. MyLog.java
package com.m5;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
public class MyLog {
        public static void main(String[] args)
        {
               //String log4j = "d://log4j.xml" ;
               //DOMConfigurator.configure(log4j);
               Logger logger = Logger.getLogger(MyLogServlet.class.getName());
               try
               {
                       int i = 10;
                       int n = 2 ;
                       int m = i/n;
               }
               catch(Exception ex)
               {
                       logger.info(ex.toString());
               }
        }
}      
       
規範合理的日誌記錄能讓開發人員和維護人員事半功倍,在記錄日誌時還應該考慮不同的角色對日誌內容可能會有不同的需求。比如,軟件正常情況下提供給用戶的日誌應該簡潔明瞭,調試時提供給程序員的日誌應該詳細明確。 請看如下代碼:
package com.m5;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
public class MyLog {
 
        public static void main(String[] args)
        {
               String log4j = "d://log4j.xml" ;
               DOMConfigurator.configure(log4j);
               Logger logger = Logger.getLogger(MyLogServlet.class.getName());
               try
               {
                       int i = 10;
                       int n = 2 ;
                       int m = i/n;
                       logger.debug("以下信息爲除法器運算跟蹤:");
                       logger.warn("請注意被除數不能爲0");
                       logger.info("除數爲" + Integer.toString(i));
                       logger.info("被除數爲" + Integer.toString(n));
                       logger.info("運算結果爲:" + Integer.toString(m) );
               }
               catch(Exception ex)
               {
                       logger.error(ex.toString());
               }
        }
}
              
調試的時候我們可以在log4j.xml配置文中指定級別爲debug,輸出所有日誌。正式運行時將級別設定爲error,只輸出 錯誤日誌。這樣就不用每次在軟件正式使用前註釋或者刪除調試的信息了。可以想象一下,如果要註釋成百上千個段調試代碼,也是項繁瑣的工作,再說在正式運行的時候如果出錯,想看詳細信息又得修改原代碼,然後再編譯。特別是異地非 遠程控制的情況下如果要得到詳細的調試日誌那是件苦不堪言的事情,因爲用戶不會幫你去改代碼。
log4j的配置文件還可以是屬性文件,在此不再另述。
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章