<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Vincent

    Vicent's blog
    隨筆 - 74, 文章 - 0, 評論 - 5, 引用 - 0
    數據加載中……

    For-Each 循環

    管中窺虎

    在學習 java 1.5 的過程中,我使用了 sun 公布的 tutorial ,這份文檔寫的比較詳盡易明,但是對于想快速了解 tiger 而且具有較好 java 基礎的人來說,大篇幅的英文文檔是比較耗時間和非必需的,所以我將會歸納這份文檔的主要內容,在保證理解的底線上,盡力減少閱讀者需要的時間。

    ?

    在以下地址可以進入各新增語言特色介紹以及下載相關文檔(若有)。

    http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html

    ?

    第二道虎紋: For-Each 循環

    目前在一個容器里做迭代訪問挺郁悶的,看看下面這個方法,方法的目的是把容器里的一系列計時任務取消。

    void ?cancelAll(Collection < TimerTask > ?c)? {

    ????
    for ?(Iterator < TimerTask > ?i? = ?c.iterator();?i.hasNext();?)

    ????????i.next().cancel();

    }

    關于

    Iterator 的部分真的很羅嗦,而且容易出錯。現在再看看 1.5 里帶來的 For-each 循環:

    void ?cancelAll(Collection < TimerTask > ?c)?{

    ????
    for ?(TimerTask?t?:?c)

    ????????t.cancel();

    }

    這個新的循環和泛型完美配合,既保持類型安全,又去掉了冗余。

    以下是一個在試圖嵌套迭代的時候經常會犯的錯誤。

    List?suits? = ?;

    List?ranks?
    = ?;

    List?sortedDeck?
    = ? new ?ArrayList();

    ?

    // ?BROKEN?-?throws?NoSuchElementException!

    for ?(Iterator?i? = ?suits.iterator();?i.hasNext();?)

    ????
    for ?(Iterator?j? = ?ranks.iterator();?j.hasNext();?)

    ????????sortedDeck.add(
    new ?Card(i.next(),?j.next()));

    ?

    原因是 i.next() 被過多的調用了。

    再看看新循環的表現,簡直是度身定造一樣的般配。

    for ?(Suit?suit?:?suits)

    ????
    for ?(Rank?rank?:?ranks)

    ????????sortedDeck.add(
    new ?Card(suit,?rank));

    ?

    for-each 循環也適用于數組,象隱藏迭代子一樣,這次它把數組下標藏起來了。

    // ?Returns?the?sum?of?the?elements?of?a

    int ?sum( int []?a)? {

    ????
    int ?result? = ? 0 ;

    ????
    for ?( int ?i?:?a)

    ????????result?
    += ?i;

    ????
    return ?result;

    }

    ?

    那么我們什么時候該用 for-each 循環呢?只要情況運行就應該用,它真的讓你的代碼好看了很多。不幸的是,它有不能發揮作用的情形,就是需要用 iterator remove 方法的時候,因為 iterator 被隱藏了,你也無法調用它的方法了,新的循環不適用于過濾元素。同樣的也不適用于需要把數組中的元素替換掉的情況。最后,它也不能在平行遍歷多個容器的情況里使用,這些缺點,設計者是知道的,但是最后他們明智地選擇這樣一個簡單的,能適用于多數情況的設計方案

    posted @ 2006-08-22 11:20 Binary 閱讀(188) | 評論 (0)編輯 收藏

    Hibernate Validator 簡介

    在項目的業務屬性中,你是不是要經常驗證屬性的取值范圍呢. 想要了解比較優美的解決方案嗎???????????

    看看Hibernate Validator 是怎么做的吧.一見到她,相信你就會說: Oh God, 這就是我需要的.

    任何獲得Matrix授權的網站,轉載請保留以下作者信息和鏈接:
    作者:icess(作者的blog:http://blog.matrix.org.cn/page/icess)
    關鍵字:Hibernate Validator

    用Annotations 給類或者類的屬性加上約束(constraint),在運行期檢查屬性值是很優雅的.Hibernate Validator就是這樣的一個框架.該框架是十分容易的(就像參考文檔中宣稱的那樣),幾乎沒有什么學習曲線,Validator 是一個驗證框架 不需要和Hibernate的其他部分綁定就可以使用,只要在你的項目中添加Hibernate-annotations.jar庫就可以了.那么下面就讓我們看看怎么使用吧.

    Person.java 類

    /*
    ? *?Created?on?2006-1-12 Person.java
    ? *?@author?
    ? */
    package? test.annotation.validator;

    import? org.hibernate.validator.Length;
    import? org.hibernate.validator.Min;
    import? org.hibernate.validator.Valid;
     

    //@Serializability? //測試自定義約束
    public?class? Person?{

    ?? private? String?name;
    ?? private?int? age;
    ?? private? Address?address;
    ??
    ?? public? Person()?{}
    ??
    ?? @Valid //注意此處
    ?? public? Address?getAddress()?{
    ???? return? address;
    ?? }
    ?? public?void? setAddress(Address?address)?{
    ???? this .address?=?address;
    ?? }
    ??
    ?? @Min(value?=? 1 )
    ?? public?int? getAge()?{
    ???? return? age;
    ?? }
    ?? public?void? setAge( int? age)?{
    ???? this .age?=?age;
    ?? }
    ??
    ?? @Length(min?=? 4 )
    ?? public? String?getName()?{
    ???? return? name;
    ?? }
    ?? public?void? setName(String?name)?{
    ???? this .name?=?name;
    ?? }
    }

     

    Address.java 類

    /*
    ? *?Created?on?2006-1-12 Address.java
    ? *?@author?
    ? */
    package? test.annotation.validator;

    import? org.hibernate.validator.Length;
    import? org.hibernate.validator.Max;
    import? org.hibernate.validator.Min;

    public?class? Address?{

    ?? private? String?street;
    ?? private?int? num;
    ??
    ?? public? Address()?{}
    ??
    ?? @Min(value?=? 1 )
    ?? @Max(value?=? 100 )
    ?? public?int? getNum()?{
    ???? return? num;
    ?? }
    ?? public?void? setNum( int? num)?{
    ???? this .num?=?num;
    ?? }
    ??
    ?? @Length(min?=? 3 ,max?=? 8 )
    ?? public? String?getStreet()?{
    ???? return? street;
    ?? }
    ?? public?void? setStreet(String?street)?{
    ???? this .street?=?street;
    ?? }
    }

    上面是兩個用 Validator Annotations 注釋的 類. 每個屬性都用 約束限制了.? 下面看看測試的類吧:

    TestValidator.java 類

    /*
    ? *?Created?on?2006-1-12
    ? *?@author?icerain
    ? */
    package? test.annotation.validator;

    import? org.hibernate.validator.ClassValidator;
    import? org.hibernate.validator.InvalidValue;


    public?class? TestValidator?{
    ?? public?void? test()?{
    ???? Address?add?=? new? Address();
    ???? add.setNum( 0 );
    ???? add.setStreet( "1" );
    ????
    ???? Person?p?=? new? Person();
    ???? p.setAddress(add);
    ???? p.setAge( 0 );
    ???? p.setName( "ice" );
    ????
    ???? /******************Test?validator?********/

    ??? // 注意該處只驗證了Person 為了說明 @Valid 注釋的使用
    ???? ClassValidator<Person>?classValidator?=? new? ClassValidator<Person>?(Person. class );
    ???? InvalidValue[]?validMessages?=?classValidator.getInvalidValues(p);
    ???? for? (InvalidValue?value?:?validMessages)?{
    ??????
    ???? System.out.println( "InvalidValue?的長度是:"? +?validMessages.length
    ???????? + "?.?驗證消息是:?"? +?value.getMessage()?
    ???????? +? "?.?PropertyPath?是:"? +?value.getPropertyPath()
    ???????? + "?.\n\t?PropertyName?是:?"? +value.getPropertyName()
    ???????? +? "Value?是:?"? +?value.getValue()
    ???????? + "?Bean?是:?" +?value.getBean()
    ???????? + "\n\t?BeanClass?是:"? +?value.getBeanClass());
    ???? }
    ?? }
    ??
    ?? public?static?void? main(String[]?args)?{
    ???? new? TestValidator().test();
    ?? }
    }

     

    程序的輸出如下

    InvalidValue 的長度是:4 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:age .

    PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@dd87b2

    BeanClass 是:class test.annotation.validator.Person

    InvalidValue 的長度是:4 . 驗證消息是: 長度必須介于 4 與 2147483647 之間 . PropertyPath 是:name .

    PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@dd87b2

    BeanClass 是:class test.annotation.validator.Person

    InvalidValue 的長度是:4 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:address.num .

    PropertyName 是: num. Value 是: 0 Bean 是: test.annotation.validator.Address@197d257

    BeanClass 是:class test.annotation.validator.Address

    InvalidValue 的長度是:4 . 驗證消息是: 長度必須介于 3 與 8 之間 . PropertyPath 是:address.street .

    PropertyName 是: street. Value 是: 1 Bean 是: test.annotation.validator.Address@197d257

    BeanClass 是:class test.annotation.validator.Address

    可以看出不滿足約束的值都被指出了.

    同時該句: ClassValidator<Person>?classValidator?=?new?ClassValidator<Person>?(Person.class);

    我們只驗證了 Person. 在Person里面的Address的屬性 由于有@Valid Annotations 所以 Address的相關屬性也被機聯驗證了 .

    如果 把 @Valid Annotations 去掉,結果如下:

    InvalidValue 的長度是:2 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:age .

    PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@18fef3d

    BeanClass 是:class test.annotation.validator.Person

    InvalidValue 的長度是:2 . 驗證消息是: 長度必須介于 4 與 2147483647 之間 . PropertyPath 是:name .

    PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@18fef3d

    BeanClass 是:class test.annotation.validator.Person

    可以看出 沒有驗證 Address.

    當然了 ,你還可以只驗證一個屬性 , 沒有必要驗證整個類.只需要在調用 classValidator.getInvalidValues(p,"age")方法時 加上你要驗證的屬性就可以了.如我們只想驗證age 屬性 把代碼改為如下所示:

    InvalidValue[] validMessages = classValidator.getInvalidValues(p,"age"); / /只驗證age 屬性

    運行結果如下:

    InvalidValue 的長度是:1 . 驗證消息是: 必須大于等于 1 . PropertyPath 是:age .

    PropertyName 是: age. Value 是: 0 Bean 是: test.annotation.validator.Person@1457cb

    BeanClass 是:class test.annotation.validator.Person

    只是驗證了 age 屬性.

    怎么樣 ,很簡單吧. 關于 Hibernate Validator 內建的驗證Annotations 大家可以看看 API 或者 參考文檔(中文版我正在翻譯中 請訪問我的 Blog 獲得最新信息).

    如果你要寫自己的約束呢 , 你不用擔心 ,這也是很容易的. 任何約束有兩部分組成: [約束描述符 即注釋]the constraint descriptor (the annotation) 和[約束validator 即 實現類] the constraint validator (the implementation class).下面我們擴展Hibernate Test suit 中的一個Test 來講解一下.

    首先: 要聲明一個 constraint descriptor .如下:

    package? test.annotation.validator;

    import? java.lang.annotation.Documented;
    import?static? java.lang.annotation.ElementType.TYPE;
    import?static? java.lang.annotation.ElementType.FIELD;
    import?static? java.lang.annotation.ElementType.METHOD;
    import? java.lang.annotation.Retention;
    import?static? java.lang.annotation.RetentionPolicy.RUNTIME;
    import? java.lang.annotation.Target;

    import? org.hibernate.validator.ValidatorClass;

    /**
    ? *?Dummy?sample?of?a?bean-level?validation?annotation
    ? *
    ? *? @author? Emmanuel?Bernard
    ? */
    @ValidatorClass(SerializabilityValidator. class )
    @Target({METHOD,FIELD,TYPE})
    @Retention(RUNTIME)
    @Documented
    public? @interface?Serializability?{
    ?? int? num()? default? 11 ;
    ?? String?message()? default? "bean?must?be?serialiable" ;
    }

    @ValidatorClass(SerializabilityValidator. class ) 指出了 constraint validator 類.

    @Target({METHOD,FIELD,TYPE})
    @Retention(RUNTIME)
    @Documented????????????????

    這幾個我就不用解釋了吧.

    Serializability?里面聲明了一個 message 顯示約束的提示信息. num 只是為了說明一個方面 在這里面沒有實際用途用 .

    然后就是 實現一個 constraint validator 類 該類要實現Validator<ConstraintAnnotation>.這里是SerializabilityValidator.java 如下:

    //$Id:?SerializabilityValidator.java,v?1.3?2005/11/17?18:12:11?epbernard?Exp?$
    package? test.annotation.validator;

    import? java.io.Serializable;

    import? org.hibernate.validator.Validator;

    /**
    ? *?Sample?of?a?bean-level?validator
    ? *
    ? *? @author? Emmanuel?Bernard
    ? */
    public?class? SerializabilityValidator? implements? Validator<Serializability>,?Serializable?{
    ?? public?boolean? isValid(Object?value)?{
    ??? //這里只是Validator 里面的 實現驗證規則的 方法. value 是要驗證的值.
    ???? System.out.println( "IN?SerializabilityValidator?isValid:" +value.getClass()+ ":?"? +value.toString());
    ???? return? value?instanceof?Serializable;
    ??}

    ??public?void?initialize(Serializability?parameters)?{
    ????//?在這里可以 取得
    constraint descriptor 里面的屬性 如上面我們聲明的 num
    ???? System.out.println( "IN?SerializabilityValidator:?parameters:" +?parameters.num()?);
    ?? }
    }

    然后在你的類中應用@Serializability? 就可以約束一個類實現Serializable 接口了. 如下:

    在我們的Person.java類 添加@Serializability? Annotations ,把Person.java 中的 //@Serializability //測試自定義約束 注釋去掉就ok了.

    運行結果如下:

    InvalidValue 的長度是:3 . 驗證消息是: bean must be serialiable . PropertyPath 是:null .

    PropertyName 是: null. Value 是: test.annotation.validator.Person@1a73d3c Bean 是: test.annotation.validator.Person@1a73d3c

    BeanClass 是:class test.annotation.validator.Person

    現在把Person類實現 java.io.Serializable 接口 則沒有出現 驗證錯誤消息.

    消息的國際化也是很簡單的,把 Serializability? 中的message 改為以{}擴住的 屬性文件的Key就可以了

    public? @interface?Serializability?{
    ?? int? num()? default? 11 ;
    ?? String?message()? default? "{Serializable}"; //"bean?must?be?serialiable"; //消息的國際化
    }

    然后編輯資料文件. 注意 該資源文件中要包括 Hibernate Validator 內建的資源. 可以在該org\hibernate\validator\resources 包里面的資源文件基礎上修改 ,在打包里面 這樣就可以了. 自己打包可能不太方便.你可以把該包里面的文件復制出來.然后放到你自己的項目包下在自己編輯, 該測試中 我是放在 test\resources 包下的.

    然后在 資源文件中添加 Serializable = '''''' 這么一行, 樣例如下:

    #DefaultValidatorMessages.properties (DefaultValidatorMessages_zh.properties 不再列出^_^)

     

    #下面是 Hibernate Validator 內建的國際化消息

    validator.assertFalse= assertion failed

    validator.assertTrue= assertion failed

    validator.future= must be a future date

    validator.length= length must be between {min} and {max}

    validator.max= must be less than or equal to {value}

    validator.min= must be greater than or equal to {value}

    validator.notNull= may not be null

    validator.past= must be a past date

    validator.pattern= must match "{regex}"

    validator.range= must be between {min} and {max}

    validator.size= size must be between {min} and {max}

    #下面是自定義的消息

    Serializable= Bean not Serializable? //加上自己定義的國際化消息.

    在構造 ClassValidator 時要添上 資源文件 如下:(在測試類中)

    ClassValidator<Person> classValidator = new ClassValidator<Person> (Person.class,ResourceBundle.getBundle("test.resources.DefaultValidatorMessages"));//加載資源

    這樣就可以了 .? 當然 你還可以 更改 Hibernate Validator 的消息(不是在上面的資源文件中直接修改 validator.length = ... 等等 ) , 還記得 Validator 注釋中有個 message 元素嗎? 你以前用的都是默認值,現在你可以該為你自己定義的了. 如:validator.length 我把他改為 "該字符串的長度不符合規定范圍范圍". 在資源文件中添加一行鍵值屬性對(key定義為 "myMsg")如下:

    myMsg=該字符串的長度不符合規定范圍范圍

    并且還要在 @Length 注釋中提供message的引用的key 如下 @Length(min = 4,message = "{ myMsg }")

    再一次運行測試 ,我們就可以看到上面兩條自定義綁定的消息了 .如下:

    InvalidValue 的長度是:3 . 驗證消息是: Bean 不是 可 Serializable . PropertyPath 是:null .
    PropertyName 是: null. Value 是: test.annotation.validator.Person@1bd4722 Bean 是: test.annotation.validator.Person@1bd4722
    BeanClass 是:class test.annotation.validator.Person


    InvalidValue 的長度是:3 . 驗證消息是: 該字符串的長度不符合規定范圍范圍 . PropertyPath 是:name .
    PropertyName 是: name. Value 是: ice Bean 是: test.annotation.validator.Person@1bd4722
    BeanClass 是:class test.annotation.validator.Person

    怎么樣,比你想象的簡單吧.

    OK 上面我們討論了 Hibernate Validator 的主要用法: 但是 該框架有什么用呢? ^_^

    看到這里其實不用我在多說了 大家都知道怎么用,什么時候用. 作為一篇介紹性文章我還是在此給出一個最常用的例子吧,更好的使用方式大家慢慢挖掘吧.

    比如 : 你現在在開發一個人力資源(HR)系統 (其實是我們ERP課程的一個作業 ^_^), 里面要處理大量的數據,尤其是在輸入各種資料時 如 登記員工信息. 如果你公司的員工的年齡要求是18 -- 60 那么你所輸入的年齡就不能超出這個范圍. 你可能會說這很容易啊 , 不用Validator就可以解決啊.這保持數據前驗證就可以啦 如if ( e.getAge() > 60 || e.getAge() < 18 ) ........ 給出錯誤信息 然后提示重新輸入不就OK啦 用得著 興師動眾的來個第三方框架嗎?

    是啊 當就驗證這一個屬性時, 沒有必要啊 ! 但是一個真正的HR 系統,會只有一個屬性要驗證嗎? 恐怕要有N多吧

    你要是每一個都那樣 寫一段驗證代碼 是不是很煩啊 ,況且也不方便代碼重用. 現在考慮一些 Validator 是不是更高效啊,攔截到 約束違例的 屬性 就可以直接得到 國際化的消息 可以把該消息顯示到一個彈出對話框上 提示更正? !

    Validator的用處不只這一種 ,你可以想到如何用呢 ! 歡迎發表你的高見!!

    OK 到此 我們的 Hibernate Validator 之旅就要先告一段落了 . 希望這是令你心曠神怡的一次寒冬之旅,

    把你學到的應用到你的項目中吧,一定會提高你的生產率的. 相信我 ,沒錯的? ^_^ !

    posted @ 2006-08-22 11:20 Binary 閱讀(239) | 評論 (0)編輯 收藏

    自動包裝機制

    ?
    管中窺虎

    在學習java 1.5的過程中,我使用了sun公布的tutorial,這份文檔寫的比較詳盡易明,但是對于想快速了解tiger而且具有較好java基礎的人來說,大篇幅的英文文檔是比較耗時間和非必需的,所以我將會歸納這份文檔的主要內容,在保證理解的底線上,盡力減少閱讀者需要的時間。

    ?

    在以下地址可以進入各新增語言特色介紹以及下載相關文檔(若有)。

    http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html

    ?

    ?

    2006815星期二

    第三道虎紋:自動包裝機制

    ?

    我們知道容器類不能放基本類型的,放進放出都要先包裝和解包,所有的這些工作都是繁瑣而無聊的,它早就該有自動機制了,終于在 1.5 里得到了實現。

    ?

    import ?java.util. * ;

    ?

    // ?Prints?a?frequency?table?of?the?words?on?the?command?line

    public ? class ?Frequency? {

    ???
    public ? static ? void ?main(String[]?args)? {

    ??????Map
    < String,?Integer > ?m? = ? new ?TreeMap < String,?Integer > ();

    ??????
    for ?(String?word?:?args)? {

    ??????????Integer?freq?
    = ?m.get(word);

    ??????????m.put(word,?(freq?
    == ? null ? ? ? 1 ?:?freq? + ? 1 ));

    ??????}


    ??????System.out.println(m);

    ???}


    }


    java Frequency if it is to be it is up to me to do the watusi

    {be=1, do=1, if=1, is=2, it=2, me=1, the=1, to=3, up=1, watusi=1}

    ?

    注意到 freq 如果為空,那么 put 的第二個參數就是 int 類型的 1 ,這個時候就出現了自動的包裝,而如果 freq 不為空,那么 freq 1 就是自動解包后運算,再自動包裝,放入 Map 中。

    現在你基本上可以忽略 Integer int (或者這一類的對應)之間的區別了,除了要注意幾點警告: Integer 是可以為 null 的,如果程序試圖自動解包一個 null ,會拋出 NullPointerException

    ==用于 Integer 時比的是引用,用于 int 的時候比的是值。最后,還有一點就是即使現在是自動解包打包,它們的運行損耗并沒消失,你依然為這些動作付出了 cpu 的計算時間。

    ?

    這里還有一個相關的例子:

    // ?List?adapter?for?primitive?int?array

    public ? static ?List < Integer > ?asList( final ? int []?a)? {

    ????
    return ? new ?AbstractList < Integer > ()? {

    ????????
    public ?Integer?get( int ?i)? {? return ?a[i];?}

    ????????
    // ?Throws?NullPointerException?if?val?==?null

    ????????
    public ?Integer?set( int ?i,?Integer?val)? {

    ????????????Integer?oldVal?
    = ?a[i];

    ????????????a[i]?
    = ?val;

    ????????????
    return ?oldVal;

    ????????}


    ????????
    public ? int ?size()? {? return ?a.length;?}

    ????}
    ;

    }


    ?

    通過自動的包裝機制,提供了數組與 List 的靈活轉換,但是它的運行效率是比較低的,每個 get set 操作,都進行了解包或者打包,偶爾使用這個方法還湊合,如果是用于核心代碼的循環里,那就是夠傻的了。

    ?

    那我們什么時候該用自動的包裝機制呢?僅僅是用于消除這類所謂的“阻抗不匹配”,就是基本類型與包裝類的差異,例如要把數值放入容器類的時候。如果在進行科學計算的代碼或者其他講究效率的代碼中使用,則是不恰當的。一個 Integer 不是一個 int ,自動包裝機制僅僅模糊了它們的區別,而沒有消除之。

    posted @ 2006-08-22 11:17 Binary 閱讀(790) | 評論 (0)編輯 收藏

    generic-泛型/類屬(三)

         摘要: 管中窺虎 在學習 java 1.5 的過程中,我使用了 sun 公布的 tutorial ,這份文檔寫的比較詳盡易明,但是對于想快速了解 tiger 而且具有較好 ...  閱讀全文

    posted @ 2006-08-22 11:17 Binary 閱讀(336) | 評論 (0)編輯 收藏

    generic-泛型/類屬(二)

         摘要: 管中窺虎 在學習 java 1.5 的過程中,我使用了 sun 公布的 tutorial ,這份文檔寫的比較詳盡易明,但是對于想快速了解 ...  閱讀全文

    posted @ 2006-08-22 11:14 Binary 閱讀(238) | 評論 (0)編輯 收藏

    generic-泛型/類屬

         摘要: 管中窺虎 在學習 java 1.5 的過程中,我使用了 sun 公布的 tutorial ,這份文檔寫的比較詳盡易明,但是對于想快速了解 ...  閱讀全文

    posted @ 2006-08-22 11:11 Binary 閱讀(546) | 評論 (1)編輯 收藏

    使用 Hibernate 和 Spring AOP 構建泛型類型安全的 DAO

    由于 Java? 5 泛型的采用,有關泛型類型安全 Data Access Object (DAO) 實現的想法變得切實可行。在本文中,系統架構師 Per Mellqvist 展示了基于 Hibernate 的泛型 DAO 實現類。然后展示如何使用 Spring AOP introductions 將類型安全接口添加到類中以便于查詢執行。

    對于大多數開發人員,為系統中的每個 DAO 編寫幾乎相同的代碼到目前為止已經成為一種習慣。雖然所有人都將這種重復標識為 “代碼味道”,但我們大多數都已經學會忍受它。其實有解決方案。可以使用許多 ORM 工具來避免代碼重復。例如,使用 Hibernate,您可以簡單地為所有的持久域對象直接使用會話操作。這種方法的缺點是損失了類型安全。

    為什么您要為數據訪問代碼提供類型安全接口?我會爭辯說,當它與現代 IDE 工具一起使用時,會減少編程錯誤并提高生產率。首先,類型安全接口清楚地指明哪些域對象具有可用的持久存儲。其次,它消除了易出錯的類型強制轉換的需要(這是一個在查詢操作中比在 CRUD 中更常見的問題)。最后,它有效利用了今天大多數 IDE 具備的自動完成特性。使用自動完成是記住什么查詢可用于特定域類的快捷方法。

    在本文中,我將為您展示如何避免再三地重復 DAO 代碼,而仍保留類型安全接口的優點。事實上,您需要為每個新 DAO 編寫的只是 Hibernate 映射文件、無格式舊 Java 接口以及 Spring 配置文件中的 10 行。

    DAO 實現

    DAO 模式對任何企業 Java 開發人員來說都應該很熟悉。但是模式的實現各不相同,所以我們來澄清一下本文提供的 DAO 實現背后的假設:

    • 系統中的所有數據庫訪問都通過 DAO 進行以實現封裝。
    • 每個 DAO 實例負責一個主要域對象或實體。如果域對象具有獨立生命周期,它應具有自己的 DAO。
    • DAO 負責域對象的創建、讀取(按主鍵)、更新和刪除(creations, reads, updates, and deletions,CRUD)。
    • DAO 可允許基于除主鍵之外的標準進行查詢。我將之稱為查找器方法查找器。查找器的返回值通常是 DAO 負責的域對象集合。
    • DAO 不負責處理事務、會話或連接。這些不由 DAO 處理是為了實現靈活性。





    泛型 DAO 接口

    泛型 DAO 的基礎是其 CRUD 操作。下面的接口定義泛型 DAO 的方法:


    清單 1. 泛型 DAO 接口
    public interface GenericDao <T, PK extends Serializable> {
    
        /** Persist the newInstance object into database */
        PK create(T newInstance);
    
        /** Retrieve an object that was previously persisted to the database using
         *   the indicated id as primary key
         */
        T read(PK id);
    
        /** Save changes made to a persistent object.  */
        void update(T transientObject);
    
        /** Remove an object from persistent storage in the database */
        void delete(T persistentObject);
    }
    
    

    實現接口

    用 Hibernate 實現清單 1 中的接口十分簡單,如清單 2 所示。它只需調用底層 Hibernate 方法和添加強制類型轉換。Spring 負責會話和事務管理。(當然,我假設這些函數已做了適當的設置,但該主題在 Hibernate 和 Springt 手冊中有詳細介紹。)


    清單 2. 第一個泛型 DAO 實現
    public class GenericDaoHibernateImpl <T, PK extends Serializable>
        implements GenericDao<T, PK>, FinderExecutor {
        private Class<T> type;
    
        public GenericDaoHibernateImpl(Class<T> type) {
            this.type = type;
        }
    
        public PK create(T o) {
            return (PK) getSession().save(o);
        }
    
        public T read(PK id) {
            return (T) getSession().get(type, id);
        }
    
        public void update(T o) {
            getSession().update(o);
        }
    
        public void delete(T o) {
            getSession().delete(o);
        }
    
        // Not showing implementations of getSession() and setSessionFactory()
                }
    

    Spring 配置

    最后,在 Spring 配置中,我創建了 GenericDaoHibernateImpl 的一個實例。必須告訴 GenericDaoHibernateImpl 的構造函數 DAO 實例將負責哪個域類。只有這樣,Hibernate 才能在運行時知道由 DAO 管理的對象類型。在清單 3 中,我將域類 Person 從示例應用程序傳遞給構造函數,并將先前配置的 Hibernate 會話工廠設置為已實例化的 DAO 的參數:


    清單 3. 配置 DAO
    <bean id="personDao" class="genericdao.impl.GenericDaoHibernateImpl">
            <constructor-arg>
                <value>genericdaotest.domain.Person</value>
            </constructor-arg>
            <property name="sessionFactory">
                <ref bean="sessionFactory"/>
            </property>
    </bean>
            







    可用的泛型 DAO

    我還沒有完成,但我所完成的確實已經可以使用了。在清單 4 中,可以看到原封不動使用該泛型 DAO 的示例:


    清單 4. 使用 DAO
    public void someMethodCreatingAPerson() {
        ...
        GenericDao dao = (GenericDao)
         beanFactory.getBean("personDao"); // This should normally be injected
    
        Person p = new Person("Per", 90);
        dao.create(p);
    }
            

    現在,我有一個能夠進行類型安全 CRUD 操作的泛型 DAO。讓子類 GenericDaoHibernateImpl 為每個域對象添加查詢能力將非常合理。因為本文的目的在于展示如何不為每個查詢編寫顯式的 Java 代碼來實現查詢,但是,我將使用其他兩個工具將查詢引入 DAO,也就是 Spring AOP 和 Hibernate 命名的查詢。







    Spring AOP introductions

    可以使用 Spring AOP 中的 introductions 將功能添加到現有對象,方法是將功能包裝在代理中,定義應實現的接口,并將所有先前未支持的方法指派到單個處理程序。在我的 DAO 實現中,我使用 introductions 將許多查找器方法添加到現有泛型 DAO 類中。因為查找器方法是特定于每個域對象的,因此將其應用于泛型 DAO 的類型化接口。

    Spring 配置如清單 5 所示:


    清單 5. FinderIntroductionAdvisor 的 Spring 配置
    <bean id="finderIntroductionAdvisor" class="genericdao.impl.FinderIntroductionAdvisor"/>
    
    <bean id="abstractDaoTarget"
            class="genericdao.impl.GenericDaoHibernateImpl" abstract="true">
            <property name="sessionFactory">
                <ref bean="sessionFactory"/>
            </property>
    </bean>
    
    <bean id="abstractDao"
            class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
            <property name="interceptorNames">
                <list>
                    <value>finderIntroductionAdvisor</value>
                </list>
            </property>
    </bean>
            

    在清單 5 的配置文件中,我定義了三個 Spring bean。第一個 bean 是 FinderIntroductionAdvisor,它處理引入到 DAO 的所有方法,這些方法在 GenericDaoHibernateImpl 類中不可用。我稍后將詳細介紹 Advisor bean。

    第二個 bean 是 “抽象的”。在 Spring 中,這意味著該 bean 可在其他 bean 定義中被重用,但不被實例化。除了抽象特性之外,該 bean 定義只指出我想要 GenericDaoHibernateImpl 的實例以及該實例需要對 SessionFactory 的引用。注意,GenericDaoHibernateImpl 類僅定義一個構造函數,該構造函數接受域類作為其參數。因為該 bean 定義是抽象的,所以我將來可以無數次地重用該定義,并將構造函數參數設置為合適的域類。

    最后,第三個也是最有趣的 bean 將 GenericDaoHibernateImpl 的 vanilla 實例包裝在代理中,賦予其執行查找器方法的能力。該 bean 定義也是抽象的,不指定希望引入到 vanilla DAO 的接口。該接口對于每個具體的實例是不同的。注意,清單 5 顯示的整個配置僅定義一次。







    擴展 GenericDAO

    當然,每個 DAO 的接口都基于 GenericDao 接口。我只需使該接口適應特定的域類并擴展該接口以包括查找器方法。在清單 6 中,可以看到為特定目的擴展的 GenericDao 接口示例:


    清單 6. PersonDao 接口
    public interface PersonDao extends GenericDao<Person, Long> {
        List<Person> findByName(String name);
    }
    
    

    很明顯,清單 6 中定義的方法旨在按名稱查找 Person。必需的 Java 實現代碼全部是泛型代碼,在添加更多 DAO 時不需要任何更新。

    配置 PersonDao

    因為 Spring 配置依賴于先前定義的 “抽象” bean,因此它變得相當簡潔。我需要指出 DAO 負責哪個域類,并且需要告訴 Springs 該 DAO 應實現哪個接口(一些方法是直接使用,一些方法則是通過使用 introductions 來使用)。清單 7 展示了 PersonDAO 的 Spring 配置文件:


    清單 7. PersonDao 的 Spring 配置
    <bean id="personDao" parent="abstractDao">
        <property name="proxyInterfaces">
            <value>genericdaotest.dao.PersonDao</value>
        </property>
        <property name="target">
            <bean parent="abstractDaoTarget">
                <constructor-arg>
                    <value>genericdaotest.domain.Person</value>
                </constructor-arg>
            </bean>
        </property>
    </bean>
            

    在清單 8 中,可以看到使用了這個更新后的 DAO 版本:


    清單 8. 使用類型安全接口
    public void someMethodCreatingAPerson() {
        ...
        PersonDao dao = (PersonDao)
         beanFactory.getBean("personDao"); // This should normally be injected
    
        Person p = new Person("Per", 90);
        dao.create(p);
    
        List<Person> result = dao.findByName("Per"); // Runtime exception
    }
            

    雖然清單 8 中的代碼是使用類型安全 PersonDao 接口的正確方法,但 DAO 的實現并不完整。調用 findByName() 會導致運行時異常。問題在于我還沒有實現調用 findByName() 所必需的查詢。剩下要做的就是指定查詢。為更正該問題,我使用了 Hibernate 命名查詢。







    Hibernate 命名查詢

    使用 Hibernate,可以在 Hibernate 映射文件 (hbm.xml) 中定義 HQL 查詢并為其命名。稍后可以通過簡單地引用給定名稱來在 Java 代碼中使用該查詢。該方法的優點之一是能夠在部署時優化查詢,而無需更改代碼。您一會將會看到,另一個優點是無需編寫任何新 Java 實現代碼,就可以實現 “完整的” DAO。清單 9 是帶有命名查詢的映射文件的示例:


    清單 9. 帶有命名查詢的映射文件
     <hibernate-mapping package="genericdaotest.domain">
         <class name="Person">
             <id name="id">
                 <generator class="native"/>
             </id>
             <property name="name" />
             <property name="weight" />
         </class>
    
         <query name="Person.findByName">
             <![CDATA[select p from Person p where p.name = ? ]]>
         </query>
     </hibernate-mapping>
            

    清單 9 定義了域類 Person 的 Hibernate 映射,該域類具有兩個屬性:nameweightPerson 是具有上述屬性的簡單 POJO。該文件還包含一個在數據庫中查找 Person 所有實例的查詢,其中 “name” 等于提供的參數。Hibernate 不為命名查詢提供任何真正的名稱空間功能。出于討論目的,我為所有查詢名稱都加了域類的短(非限定)名稱作為前綴。在現實世界中,使用包括包名稱的完全類名可能是更好的主意。







    逐步概述

    您已經看到了為任何域對象創建和配置新 DAO 所必需的全部步驟。三個簡單的步驟是:

    1. 定義一個接口,它擴展 GenericDao 并包含所需的任何查找器方法。
    2. 將每個查找器的命名查詢添加到域對象的 hbm.xml 映射文件。
    3. 為 DAO 添加 10 行 Spring 配置文件。

    查看執行查找器方法的代碼(只編寫了一次!)來結束我的討論。







    可重用的 DAO 類

    使用的 Spring advisor 和 interceptor 很簡單,事實上它們的工作是向后引用 GenericDaoHibernateImplClass。方法名以 “find” 打頭的所有調用都傳遞給 DAO 和單個方法 executeFinder()


    清單 10. FinderIntroductionAdvisor 的實現
    public class FinderIntroductionAdvisor extends DefaultIntroductionAdvisor {
        public FinderIntroductionAdvisor() {
            super(new FinderIntroductionInterceptor());
        }
    }
    
    public class FinderIntroductionInterceptor implements IntroductionInterceptor {
    
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    
            FinderExecutor genericDao = (FinderExecutor) methodInvocation.getThis();
    
            String methodName = methodInvocation.getMethod().getName();
            if (methodName.startsWith("find")) {
                Object[] arguments = methodInvocation.getArguments();
                return genericDao.executeFinder(methodInvocation.getMethod(), arguments);
            } else {
                return methodInvocation.proceed();
            }
        }
    
        public boolean implementsInterface(Class intf) {
            return intf.isInterface() && FinderExecutor.class.isAssignableFrom(intf);
        }
    }
    

    executeFinder() 方法

    清單 10 的實現中惟一缺少的是 executeFinder() 實現。該代碼查看調用的類和方法的名稱,并使用配置上的約定將其與 Hibernate 查詢的名稱相匹配。還可以使用 FinderNamingStrategy 來支持其他命名查詢的方法。默認實現查找叫做 “ClassName.methodName” 的查詢,其中 ClassName 是不帶包的短名稱。清單 11 完成了泛型類型安全 DAO 實現:


    清單 11. executeFinder() 的實現
    public List<T> executeFinder(Method method, final Object[] queryArgs) {
         final String queryName = queryNameFromMethod(method);
         final Query namedQuery = getSession().getNamedQuery(queryName);
         String[] namedParameters = namedQuery.getNamedParameters();
         for(int i = 0; i < queryArgs.length; i++) {
                 Object arg = queryArgs[i];
                 Type argType =  namedQuery.setParameter(i, arg);
          }
          return (List<T>) namedQuery.list();
     }
    
     public String queryNameFromMethod(Method finderMethod) {
         return type.getSimpleName() + "." + finderMethod.getName();
     }
    






    結束語

    在 Java 5 之前,該語言不支持編寫既類型安全 泛型的代碼,您必須只能選擇其中之一。在本文中,您已經看到一個結合使用 Java 5 泛型與 Spring 和 Hibernate(以及 AOP)等工具來提高生產率的示例。泛型類型安全 DAO 類相當容易編寫 —— 您只需要單個接口、一些命名查詢和為 Spring 配置添加的 10 行代碼 —— 而且可以極大地減少錯誤并節省時間。

    幾乎本文的所有代碼都是可重用的。盡管您的 DAO 類可能包含此處沒有實現的查詢和操作類型(比如,批操作),但使用我所展示的技術,您至少應該能夠實現其中的一部分。參閱 參考資料 了解其他泛型類型安全 DAO 類實現。

    致謝

    自 Java 語言中出現泛型以來,單個泛型類型安全 DAO 的概念已經成為主題。我曾在 JavaOne 2004 中與 Don Smith 簡要討論了泛型 DAO 的靈活性。本文使用的 DAO 實現類旨在作為示例實現,實際上還存在其他實現。例如,Christian Bauer 已經發布了帶有 CRUD 操作和標準搜索的實現,Eric Burke 也在該領域做出了工作。我確信將會有更多的實現出現。我要額外感謝 Christian,他目睹了我編寫泛型類型安全 DAO 的第一次嘗試并提出改進建議。最后,我要感謝 Ramnivas Laddad 的無價幫助,他審閱了本文。

    posted @ 2006-08-22 11:03 Binary 閱讀(315) | 評論 (0)編輯 收藏

    hibernate調用mysql5.0存儲過程小記

         摘要: 準備工作:1.hibernate3到這下載hibernate3: http://sourceforge.net/project/showfiles.phpgroup_id=40712&package_id=127784&release_id=403223 2.mysql (注意一定要用mysql5.0和最新驅動)?mysql官方網站http://www.mysql.co...  閱讀全文

    posted @ 2006-08-22 10:55 Binary 閱讀(274) | 評論 (0)編輯 收藏

    不要重復DAO!使用Hibernate 和Spring AOP 構建泛型類型安全的DAO

    由于 Java? 5 泛型的采用,有關泛型類型安全 Data Access Object (DAO) 實現的想法變得切實可行。在本文中,系統架構師 Per Mellqvist 展示了基于 Hibernate 的泛型 DAO 實現類。然后展示如何使用 Spring AOP introductions 將類型安全接口添加到類中以便于查詢執行。

    對于大多數開發人員,為系統中的每個 DAO 編寫幾乎相同的代碼到目前為止已經成為一種習慣。雖然所有人都將這種重復標識為 “代碼味道”,但我們大多數都已經學會忍受它。其實有解決方案。可以使用許多 ORM 工具來避免代碼重復。例如,使用 Hibernate,您可以簡單地為所有的持久域對象直接使用會話操作。這種方法的缺點是損失了類型安全。

    為什么您要為數據訪問代碼提供類型安全接口?我會爭辯說,當它與現代 IDE 工具一起使用時,會減少編程錯誤并提高生產率。首先,類型安全接口清楚地指明哪些域對象具有可用的持久存儲。其次,它消除了易出錯的類型強制轉換的需要(這是一個在查詢操作中比在 CRUD 中更常見的問題)。最后,它有效利用了今天大多數 IDE 具備的自動完成特性。使用自動完成是記住什么查詢可用于特定域類的快捷方法。

    在本文中,我將為您展示如何避免再三地重復 DAO 代碼,而仍保留類型安全接口的優點。事實上,您需要為每個新 DAO 編寫的只是 Hibernate 映射文件、無格式舊 Java 接口以及 Spring 配置文件中的 10 行。

    DAO 實現

    DAO 模式對任何企業 Java 開發人員來說都應該很熟悉。但是模式的實現各不相同,所以我們來澄清一下本文提供的 DAO 實現背后的假設:

    • 系統中的所有數據庫訪問都通過 DAO 進行以實現封裝。
    • 每個 DAO 實例負責一個主要域對象或實體。如果域對象具有獨立生命周期,它應具有自己的 DAO。
    • DAO 負責域對象的創建、讀取(按主鍵)、更新和刪除(creations, reads, updates, and deletions,CRUD)。
    • DAO 可允許基于除主鍵之外的標準進行查詢。我將之稱為查找器方法查找器。查找器的返回值通常是 DAO 負責的域對象集合。
    • DAO 不負責處理事務、會話或連接。這些不由 DAO 處理是為了實現靈活性。







    泛型 DAO 接口

    泛型 DAO 的基礎是其 CRUD 操作。下面的接口定義泛型 DAO 的方法:


    清單 1. 泛型 DAO 接口
    public interface GenericDao <T, PK extends Serializable> {

    /** Persist the newInstance object into database */
    PK create(T newInstance);

    /** Retrieve an object that was previously persisted to the database using
    * the indicated id as primary key
    */
    T read(PK id);

    /** Save changes made to a persistent object. */
    void update(T transientObject);

    /** Remove an object from persistent storage in the database */
    void delete(T persistentObject);
    }


    實現接口

    用 Hibernate 實現清單 1 中的接口十分簡單,如清單 2 所示。它只需調用底層 Hibernate 方法和添加強制類型轉換。Spring 負責會話和事務管理。(當然,我假設這些函數已做了適當的設置,但該主題在 Hibernate 和 Springt 手冊中有詳細介紹。)


    清單 2. 第一個泛型 DAO 實現
    public class GenericDaoHibernateImpl <T, PK extends Serializable>
    implements GenericDao<T, PK>, FinderExecutor {
    private Class<T> type;

    public GenericDaoHibernateImpl(Class<T> type) {
    this.type = type;
    }

    public PK create(T o) {
    return (PK) getSession().save(o);
    }

    public T read(PK id) {
    return (T) getSession().get(type, id);
    }

    public void update(T o) {
    getSession().update(o);
    }

    public void delete(T o) {
    getSession().delete(o);
    }

    // Not showing implementations of getSession() and setSessionFactory()
    }

    Spring 配置

    最后,在 Spring 配置中,我創建了 GenericDaoHibernateImpl 的一個實例。必須告訴 GenericDaoHibernateImpl 的構造函數 DAO 實例將負責哪個域類。只有這樣,Hibernate 才能在運行時知道由 DAO 管理的對象類型。在清單 3 中,我將域類 Person 從示例應用程序傳遞給構造函數,并將先前配置的 Hibernate 會話工廠設置為已實例化的 DAO 的參數:


    清單 3. 配置 DAO
    <bean id="personDao" class="genericdao.impl.GenericDaoHibernateImpl">
    <constructor-arg>
    <value>genericdaotest.domain.Person</value>
    </constructor-arg>
    <property name="sessionFactory">
    <ref bean="sessionFactory"/>
    </property>
    </bean>








    可用的泛型 DAO

    我還沒有完成,但我所完成的確實已經可以使用了。在清單 4 中,可以看到原封不動使用該泛型 DAO 的示例:


    清單 4. 使用 DAO
    public void someMethodCreatingAPerson() {
    ...
    GenericDao dao = (GenericDao)
    beanFactory.getBean("personDao"); // This should normally be injected

    Person p = new Person("Per", 90);
    dao.create(p);
    }

    現在,我有一個能夠進行類型安全 CRUD 操作的泛型 DAO。讓子類 GenericDaoHibernateImpl 為每個域對象添加查詢能力將非常合理。因為本文的目的在于展示如何不為每個查詢編寫顯式的 Java 代碼來實現查詢,但是,我將使用其他兩個工具將查詢引入 DAO,也就是 Spring AOP 和 Hibernate 命名的查詢。








    Spring AOP introductions

    可以使用 Spring AOP 中的 introductions 將功能添加到現有對象,方法是將功能包裝在代理中,定義應實現的接口,并將所有先前未支持的方法指派到單個處理程序。在我的 DAO 實現中,我使用 introductions 將許多查找器方法添加到現有泛型 DAO 類中。因為查找器方法是特定于每個域對象的,因此將其應用于泛型 DAO 的類型化接口。

    Spring 配置如清單 5 所示:


    清單 5. FinderIntroductionAdvisor 的 Spring 配置
    <bean id="finderIntroductionAdvisor" class="genericdao.impl.FinderIntroductionAdvisor"/>

    <bean id="abstractDaoTarget"
    class="genericdao.impl.GenericDaoHibernateImpl" abstract="true">
    <property name="sessionFactory">
    <ref bean="sessionFactory"/>
    </property>
    </bean>

    <bean id="abstractDao"
    class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
    <property name="interceptorNames">
    <list>
    <value>finderIntroductionAdvisor</value>
    </list>
    </property>
    </bean>

    在清單 5 的配置文件中,我定義了三個 Spring bean。第一個 bean 是 FinderIntroductionAdvisor,它處理引入到 DAO 的所有方法,這些方法在 GenericDaoHibernateImpl 類中不可用。我稍后將詳細介紹 Advisor bean。

    第二個 bean 是 “抽象的”。在 Spring 中,這意味著該 bean 可在其他 bean 定義中被重用,但不被實例化。除了抽象特性之外,該 bean 定義只指出我想要 GenericDaoHibernateImpl 的實例以及該實例需要對 SessionFactory 的引用。注意,GenericDaoHibernateImpl 類僅定義一個構造函數,該構造函數接受域類作為其參數。因為該 bean 定義是抽象的,所以我將來可以無數次地重用該定義,并將構造函數參數設置為合適的域類。

    最后,第三個也是最有趣的 bean 將 GenericDaoHibernateImpl 的 vanilla 實例包裝在代理中,賦予其執行查找器方法的能力。該 bean 定義也是抽象的,不指定希望引入到 vanilla DAO 的接口。該接口對于每個具體的實例是不同的。注意,清單 5 顯示的整個配置僅定義一次。








    擴展 GenericDAO

    當然,每個 DAO 的接口都基于 GenericDao 接口。我只需使該接口適應特定的域類并擴展該接口以包括查找器方法。在清單 6 中,可以看到為特定目的擴展的 GenericDao 接口示例:


    清單 6. PersonDao 接口
    public interface PersonDao extends GenericDao<Person, Long> {
    List<Person> findByName(String name);
    }


    很明顯,清單 6 中定義的方法旨在按名稱查找 Person。必需的 Java 實現代碼全部是泛型代碼,在添加更多 DAO 時不需要任何更新。

    配置 PersonDao

    因為 Spring 配置依賴于先前定義的 “抽象” bean,因此它變得相當簡潔。我需要指出 DAO 負責哪個域類,并且需要告訴 Springs 該 DAO 應實現哪個接口(一些方法是直接使用,一些方法則是通過使用 introductions 來使用)。清單 7 展示了 PersonDAO 的 Spring 配置文件:


    清單 7. PersonDao 的 Spring 配置
    <bean id="personDao" parent="abstractDao">
    <property name="proxyInterfaces">
    <value>genericdaotest.dao.PersonDao</value>
    </property>
    <property name="target">
    <bean parent="abstractDaoTarget">
    <constructor-arg>
    <value>genericdaotest.domain.Person</value>
    </constructor-arg>
    </bean>
    </property>
    </bean>

    在清單 8 中,可以看到使用了這個更新后的 DAO 版本:


    清單 8. 使用類型安全接口
    public void someMethodCreatingAPerson() {
    ...
    PersonDao dao = (PersonDao)
    beanFactory.getBean("personDao"); // This should normally be injected

    Person p = new Person("Per", 90);
    dao.create(p);

    List<Person> result = dao.findByName("Per"); // Runtime exception
    }

    雖然清單 8 中的代碼是使用類型安全 PersonDao 接口的正確方法,但 DAO 的實現并不完整。調用 findByName() 會導致運行時異常。問題在于我還沒有實現調用 findByName() 所必需的查詢。剩下要做的就是指定查詢。為更正該問題,我使用了 Hibernate 命名查詢。








    Hibernate 命名查詢

    使用 Hibernate,可以在 Hibernate 映射文件 (hbm.xml) 中定義 HQL 查詢并為其命名。稍后可以通過簡單地引用給定名稱來在 Java 代碼中使用該查詢。該方法的優點之一是能夠在部署時優化查詢,而無需更改代碼。您一會將會看到,另一個優點是無需編寫任何新 Java 實現代碼,就可以實現 “完整的” DAO。清單 9 是帶有命名查詢的映射文件的示例:


    清單 9. 帶有命名查詢的映射文件
     <hibernate-mapping package="genericdaotest.domain">
    <class name="Person">
    <id name="id">
    <generator class="native"/>
    </id>
    <property name="name" />
    <property name="weight" />
    </class>

    <query name="Person.findByName">
    <![CDATA[select p from Person p where p.name = ? ]]>
    </query>
    </hibernate-mapping>

    清單 9 定義了域類 Person 的 Hibernate 映射,該域類具有兩個屬性:nameweightPerson 是具有上述屬性的簡單 POJO。該文件還包含一個在數據庫中查找 Person 所有實例的查詢,其中 “name” 等于提供的參數。Hibernate 不為命名查詢提供任何真正的名稱空間功能。出于討論目的,我為所有查詢名稱都加了域類的短(非限定)名稱作為前綴。在現實世界中,使用包括包名稱的完全類名可能是更好的主意。








    逐步概述

    您已經看到了為任何域對象創建和配置新 DAO 所必需的全部步驟。三個簡單的步驟是:

    1. 定義一個接口,它擴展 GenericDao 并包含所需的任何查找器方法。
    2. 將每個查找器的命名查詢添加到域對象的 hbm.xml 映射文件。
    3. 為 DAO 添加 10 行 Spring 配置文件。

    查看執行查找器方法的代碼(只編寫了一次!)來結束我的討論。








    可重用的 DAO 類

    使用的 Spring advisor 和 interceptor 很簡單,事實上它們的工作是向后引用 GenericDaoHibernateImplClass。方法名以 “find” 打頭的所有調用都傳遞給 DAO 和單個方法 executeFinder()


    清單 10. FinderIntroductionAdvisor 的實現
    public class FinderIntroductionAdvisor extends DefaultIntroductionAdvisor {
    public FinderIntroductionAdvisor() {
    super(new FinderIntroductionInterceptor());
    }
    }

    public class FinderIntroductionInterceptor implements IntroductionInterceptor {

    public Object invoke(MethodInvocation methodInvocation) throws Throwable {

    FinderExecutor genericDao = (FinderExecutor) methodInvocation.getThis();

    String methodName = methodInvocation.getMethod().getName();
    if (methodName.startsWith("find")) {
    Object[] arguments = methodInvocation.getArguments();
    return genericDao.executeFinder(methodInvocation.getMethod(), arguments);
    } else {
    return methodInvocation.proceed();
    }
    }

    public boolean implementsInterface(Class intf) {
    return intf.isInterface() && FinderExecutor.class.isAssignableFrom(intf);
    }
    }

    executeFinder() 方法

    清單 10 的實現中惟一缺少的是 executeFinder() 實現。該代碼查看調用的類和方法的名稱,并使用配置上的約定將其與 Hibernate 查詢的名稱相匹配。還可以使用 FinderNamingStrategy 來支持其他命名查詢的方法。默認實現查找叫做 “ClassName.methodName” 的查詢,其中 ClassName 是不帶包的短名稱。清單 11 完成了泛型類型安全 DAO 實現:


    清單 11. executeFinder() 的實現
    public List<T> executeFinder(Method method, final Object[] queryArgs) {
    final String queryName = queryNameFromMethod(method);
    final Query namedQuery = getSession().getNamedQuery(queryName);
    String[] namedParameters = namedQuery.getNamedParameters();
    for(int i = 0; i < queryArgs.length; i++) {
    Object arg = queryArgs[i];
    Type argType = namedQuery.setParameter(i, arg);
    }
    return (List<T>) namedQuery.list();
    }

    public String queryNameFromMethod(Method finderMethod) {
    return type.getSimpleName() + "." + finderMethod.getName();
    }








    結束語

    在 Java 5 之前,該語言不支持編寫既類型安全 泛型的代碼,您必須只能選擇其中之一。在本文中,您已經看到一個結合使用 Java 5 泛型與 Spring 和 Hibernate(以及 AOP)等工具來提高生產率的示例。泛型類型安全 DAO 類相當容易編寫 —— 您只需要單個接口、一些命名查詢和為 Spring 配置添加的 10 行代碼 —— 而且可以極大地減少錯誤并節省時間。

    幾乎本文的所有代碼都是可重用的。盡管您的 DAO 類可能包含此處沒有實現的查詢和操作類型(比如,批操作),但使用我所展示的技術,您至少應該能夠實現其中的一部分。參閱 參考資料 了解其他泛型類型安全 DAO 類實現。

    致謝

    自 Java 語言中出現泛型以來,單個泛型類型安全 DAO 的概念已經成為主題。我曾在 JavaOne 2004 中與 Don Smith 簡要討論了泛型 DAO 的靈活性。本文使用的 DAO 實現類旨在作為示例實現,實際上還存在其他實現。例如,Christian Bauer 已經發布了帶有 CRUD 操作和標準搜索的實現,Eric Burke 也在該領域做出了工作。我確信將會有更多的實現出現。我要額外感謝 Christian,他目睹了我編寫泛型類型安全 DAO 的第一次嘗試并提出改進建議。最后,我要感謝 Ramnivas Laddad 的無價幫助,他審閱了本文。


    posted @ 2006-08-22 10:53 Binary 閱讀(827) | 評論 (0)編輯 收藏

    Hibernate的緩存機制介紹

      緩存是介于應用程序和物理數據源之間,其作用是為了降低應用程序對物理數據源訪問的頻次,從而提高了應用的運行性能。緩存內的數據是對物理數據源中的數據的復制,應用程序在運行時從緩存讀寫數據,在特定的時刻或事件會同步緩存和物理數據源的數據。

       緩存的介質一般是內存,所以讀寫速度很快。但如果緩存中存放的數據量非常大時,也會用硬盤作為緩存介質。緩存的實現不僅僅要考慮存儲的介質,還要考慮到管理緩存的并發訪問和緩存數據的生命周期。

      Hibernate 的緩存包括 Session 的緩存和 SessionFactory 的緩存,其中 SessionFactory 的緩存又可以分為兩類:內置緩存和外置緩存。 Session 的緩存是內置的,不能被卸載,也被稱為 Hibernate 的第一級緩存。 SessionFactory 的內置緩存和 Session 的緩存在實現方式上比較相似,前者是 SessionFactory 對象的一些集合屬性包含的數據,后者是指 Session 的一些集合屬性包含的數據。 SessionFactory 的內置緩存中存放了映射元數據和預定義 SQL 語句,映射元數據是映射文件中數據的拷貝,而預定義 SQL 語句是在 Hibernate 初始化階段根據映射元數據推導出來, SessionFactory 的內置緩存是只讀的,應用程序不能修改緩存中的映射元數據和預定義 SQL 語句,因此 SessionFactory 不需要進行內置緩存與映射文件的同步。 SessionFactory 的外置緩存是一個可配置的插件。在默認情況下, SessionFactory 不會啟用這個插件。外置緩存的數據是數據庫數據的拷貝,外置緩存的介質可以是內存或者硬盤。 SessionFactory 的外置緩存也被稱為 Hibernate 的第二級緩存。

      Hibernate 的這兩級緩存都位于持久化層,存放的都是數據庫數據的拷貝,那么它們之間的區別是什么呢?為了理解二者的區別,需要深入理解持久化層的緩存的兩個特性:緩存的范圍和緩存的并發訪問策略。

    持久化層的緩存的范圍

       緩存的范圍決定了緩存的生命周期以及可以被誰訪問。緩存的范圍分為三類。

      1 事務范圍:緩存只能被當前事務訪問。緩存的生命周期依賴于事務的生命周期,當事務結束時,緩存也就結束生命周期。在此范圍下,緩存的介質是內存。事務可以是數據庫事務或者應用事務,每個事務都有獨自的緩存,緩存內的數據通常采用相互關聯的的對象形式。

      2 進程范圍:緩存被進程內的所有事務共享。這些事務有可能是并發訪問緩存,因此必須對緩存采取必要的事務隔離機制。緩存的生命周期依賴于進程的生命周期,進程結束時,緩存也就結束了生命周期。進程范圍的緩存可能會存放大量的數據,所以存放的介質可以是內存或硬盤。緩存內的數據既可以是相互關聯的對象形式也可以是對象的松散數據形式。松散的對象數據形式有點類似于對象的序列化數據,但是對象分解為松散的算法比對象序列化的算法要求更快。

      3 集群范圍:在集群環境中,緩存被一個機器或者多個機器的進程共享。緩存中的數據被復制到集群環境中的每個進程節點,進程間通過遠程通信來保證緩存中的數據的一致性,緩存中的數據通常采用對象的松散數據形式。

       對大多數應用來說,應該慎重地考慮是否需要使用集群范圍的緩存,因為訪問的速度不一定會比直接訪問數據庫數據的速度快多少。

       持久化層可以提供多種范圍的緩存。如果在事務范圍的緩存中沒有查到相應的數據,還可以到進程范圍或集群范圍的緩存內查詢,如果還是沒有查到,那么只有到數據庫中查詢。事務范圍的緩存是持久化層的第一級緩存,通常它是必需的;進程范圍或集群范圍的緩存是持久化層的第二級緩存,通常是可選的。

    持久化層的緩存的并發訪問策略

       當多個并發的事務同時訪問持久化層的緩存的相同數據時,會引起并發問題,必須采用必要的事務隔離措施。

       在進程范圍或集群范圍的緩存,即第二級緩存,會出現并發問題。因此可以設定以下四種類型的并發訪問策略,每一種策略對應一種事務隔離級別。

       事務型:僅僅在受管理環境中適用。它提供了 Repeatable Read 事務隔離級別。對于經常被讀但很少修改的數據,可以采用這種隔離類型,因為它可以防止臟讀和不可重復讀這類的并發問題。

       讀寫型:提供了 Read Committed 事務隔離級別。僅僅在非集群的環境中適用。對于經常被讀但很少修改的數據,可以采用這種隔離類型,因為它可以防止臟讀這類的并發問題。

       非嚴格讀寫型:不保證緩存與數據庫中數據的一致性。如果存在兩個事務同時訪問緩存中相同數據的可能,必須為該數據配置一個很短的數據過期時間,從而盡量避免臟讀。對于極少被修改,并且允許偶爾臟讀的數據,可以采用這種并發訪問策略。

       只讀型:對于從來不會修改的數據,如參考數據,可以使用這種并發訪問策略。

       事務型并發訪問策略是事務隔離級別最高,只讀型的隔離級別最低。事務隔離級別越高,并發性能就越低。

    什么樣的數據適合存放到第二級緩存中?

    1 很少被修改的數據

    2 不是很重要的數據,允許出現偶爾并發的數據

    3 不會被并發訪問的數據

    4 參考數據

    不適合存放到第二級緩存的數據?

    1 經常被修改的數據

    2 財務數據,絕對不允許出現并發

    3 與其他應用共享的數據。

    Hibernate 的二級緩存

       如前所述, Hibernate 提供了兩級緩存,第一級是 Session 的緩存。由于 Session 對象的生命周期通常對應一個數據庫事務或者一個應用事務,因此它的緩存是事務范圍的緩存。第一級緩存是必需的,不允許而且事實上也無法比卸除。在第一級緩存中,持久化類的每個實例都具有唯一的 OID

       第二級緩存是一個可插拔的的緩存插件,它是由 SessionFactory 負責管理。由于 SessionFactory 對象的生命周期和應用程序的整個過程對應,因此第二級緩存是進程范圍或者集群范圍的緩存。這個緩存中存放的對象的松散數據。第二級對象有可能出現并發問題,因此需要采用適當的并發訪問策略,該策略為被緩存的數據提供了事務隔離級別。緩存適配器用于把具體的緩存實現軟件與 Hibernate 集成。第二級緩存是可選的,可以在每個類或每個集合的粒度上配置第二級緩存。

    Hibernate 的二級緩存策略的一般過程如下:

    1) 條件查詢的時候,總是發出一條 select * from table_name where …. (選擇所有字段)這樣的 SQL 語句查詢數據庫,一次獲得所有的數據對象。

    2) 把獲得的所有數據對象根據 ID 放入到第二級緩存中。

    3) Hibernate 根據 ID 訪問數據對象的時候,首先從 Session 一級緩存中查;查不到,如果配置了二級緩存,那么從二級緩存中查;查不到,再查詢數據庫,把結果按照 ID 放入到緩存。

    4) 刪除、更新、增加數據的時候,同時更新緩存。

      Hibernate 的二級緩存策略,是針對于 ID 查詢的緩存策略,對于條件查詢則毫無作用。為此, Hibernate 提供了針對條件查詢的 Query 緩存。

    Hibernate Query 緩存策略的過程如下:

    1) Hibernate 首先根據這些信息組成一個 Query Key Query Key 包括條件查詢的請求一般信息: SQL, SQL 需要的參數,記錄范圍(起始位置 rowStart ,最大記錄個數 maxRows) ,等。

    2) Hibernate 根據這個 Query Key Query 緩存中查找對應的結果列表。如果存在,那么返回這個結果列表;如果不存在,查詢數據庫,獲取結果列表,把整個結果列表根據 Query Key 放入到 Query 緩存中。

    3) Query Key 中的 SQL 涉及到一些表名,如果這些表的任何數據發生修改、刪除、增加等操作,這些相關的 Query Key 都要從緩存中清空。

    posted @ 2006-08-22 10:52 Binary 閱讀(168) | 評論 (0)編輯 收藏

    僅列出標題
    共8頁: 上一頁 1 2 3 4 5 6 7 8 下一頁 
    主站蜘蛛池模板: 久久夜色精品国产亚洲av| 13一14周岁毛片免费| 亚洲欧洲另类春色校园小说| 精品久久免费视频| 极品色天使在线婷婷天堂亚洲| 亚洲一区二区电影| 亚洲人色婷婷成人网站在线观看| 日韩成人免费在线| 99久久国产热无码精品免费| 精品亚洲成a人在线观看| 亚洲日本在线播放| 久久青草亚洲AV无码麻豆| 久久久久亚洲精品无码网址 | 日韩在线视频免费看| 最近免费2019中文字幕大全| 成人免费区一区二区三区| 日日摸夜夜添夜夜免费视频| 亚洲国产精品无码久久| 久久综合久久综合亚洲| 亚洲人成电影网站| 亚洲JIZZJIZZ中国少妇中文| 日韩精品在线免费观看| 国产免费一区二区三区免费视频| 免费手机在线看片| 猫咪免费人成在线网站| 亚洲AV无码专区在线厂| 青青青亚洲精品国产| 亚洲国产精品自在自线观看| 亚洲精品无码久久久久秋霞| 亚洲kkk4444在线观看| 亚洲1234区乱码| 亚洲乱码在线卡一卡二卡新区| 亚洲制服在线观看| 亚洲卡一卡二卡乱码新区| 香蕉大伊亚洲人在线观看| 久久狠狠爱亚洲综合影院 | 在线a级毛片免费视频| 57PAO成人国产永久免费视频| 国产成人精品久久免费动漫| 日本亚洲免费无线码| 成人片黄网站色大片免费|