2014年8月6日 星期三

AXIS2-利用WSDL產生Client 端Stub 物件

1.先建立一個Dynamic Web Project,我的名稱為TestClientStub
2.在工具列上的Windows->Preference->Web Services-> Axis2 Preferences內,選擇自己Axis2的儲存位置

3.在專案名稱上選右鍵->New->Other 出現如下畫面,選擇 Web Service Client 後按下Next

3.在Service definition中選擇或輸入一個有WSDL的URL,我這邊輸入一個我已經架設好的Web Service 的URL: http://192.168.1.xxx/Test/services/APServiceService?wsdl ,後面的wsdl一定要有,否則無法讀取到WSDL定義檔; 選完後點選 Server runtime: Tomcat v6.0 Server,準備設定 Server runtime及Web service runtime



4.此步驟是要設定Client端的環境,因此基本上就是按照各位環境所安裝的Web Server及Web Service Runtime選擇就可以,我這邊client端的環境:Web Server是Tomcat 6.0 ,service runtime當然就是選Apache Axis2,確定選擇沒錯就可以按下OK->Next

5.接下來這個畫面顯示的項目基本上都不需要改變,Service Name是自動產生出來的stub名稱,Customer package name 為stub產生的目的package,而底下的Client mode是指定產生出來的stub在呼叫Web Service時所提供的method類型,按順序為:
1.產生一個支援同步與非同步的client code
2.僅產生同步的client code
3.僅產生非同步的client code
同步(Block)與非同步(Non-Block)的主要差別就在於: 同步是一個client端請求Web Service的服務後,必須等到目前執行的請求完成後,client才能再繼續請求下一個Web Service服務,非同步則不需要像同步等待前一個請求執行完成,即可以再發出下一個請求。


6.按下Finish後,自動產生的Client Stub如下,同時大家也可以看到Eclipse也自動幫我們把需要的jar檔都產生在WEB-INF/lib下了

7.這樣就可以利用自動產生出來的APServiceServiceStub來調用Web Service了

參考資料
http://blog.csdn.net/lifetragedy/article/details/7776907
http://axis.apache.org/axis2/java/core/api/org/apache/axis2/client/async/AxisCallback.html

2014年6月12日 星期四

Hibernate Tool安裝、自動產生hibernate.cfg.xml檔及對應的Entity

1.請到Help->Eclipse Marketplace,在Find內打 Hibernate tool後-> Go,出現如下畫面:


這裡我是安裝Hibernate Tools(Indigo),按下install,Next到底就會開始安裝。
2.安裝完成後請重新啟動Eclipse再開始使用
3.這裡要示範的是新增一個連線到MySQL的hibernate.cfg.xml,請從選單的
File > New > 選取 Other ,出現如下畫面 後,選擇第一個,按Next

4.在下面的畫面只要選擇你要存放hibernate.cfg.xml的路徑即可,基本上檔名是不用改的,按下Next

5.填入基本的連線資訊,依序填入如下圖,若DB的Driver Class若找不到適合的driver,表示你還沒有將適合的DB driver套件裝至你的專案中,請先下載,我使用的MySQL Driver套件是mysql-connector-java,填完直接按下Finish,你會看到你的src目錄底下已經有個hibernate.cfg.xml檔案


6.連線DB的基本設定檔已經有了,接下來要告訴hibernate工具有哪些table要轉成Entity,這要依靠 hibernate.reveng.xml 這個檔案,產生它的方式跟產生hibernate.cfg.xml的方式其實很像,一樣從File > New > 選取Other後,出現如下圖的選單選擇 Hibernate Reverse Engineering File (reveng.xml),按下Next

7.同樣地,指定Hibernate reverse檔要存在哪裡,這裡我就先指定在src底下,按下Next

8.接下來這步驟比較稍微繁瑣一點,條列如下:
  1. 選定Console configuration,記得我們在Step.5有特別用紅框框起來的地方嗎,那邊勾選的create console configuration就是為了在這邊使用,若Step.5 沒有勾選create console configuration ,在這邊的Console configuration 就沒得選。
  2. 按下左下方的Refresh,正常情況下會顯示出你連線DB的DB Name以及裡面的table在Data Schema這個區塊,若出現錯誤,則可能造成錯誤的情況很多,可能是DB 的使用者帳號或密碼輸入錯誤,或是任何連線至DB的基本資訊錯誤,檢查一下hibernate.cfg.xml檔案內容,是否有填錯,然後請記得別使用sa帳號作為連線登入帳號這是不被允許若使用sa帳號只會得到無法連線的錯誤訊息!
  3. 假設一切順利,那麼大家可以從Database schema那邊看到你的DB 名稱,以及底下的Table(這邊就容許小弟打個馬賽克,那對大家來說不是重點XD)。選定要轉成Entity的table後,按下中間的Include即可,若要移除請按Exclude,最後都確認就按下Finish吧!

9.最後準備產生Entity,從選單的Run >  Hibernate Code Generation > 選取 Hiberate Code Generation Configurations 選項

10. 基本上到這邊已經不太需要做甚麼變動,真的比較會變動的,可能是Package那個欄位,此欄位可以設定自動產生的entity 要存放到哪個package目錄下,改完直接按下Run,趕快去你指定的目錄看看自動產生出來的Entity檔案吧!

2014年5月26日 星期一

Quartz工作排程工具介紹(一)-簡單整合進Tomcat

因為工作上的需要,第一次接觸Quartz,這裡就先來簡單介紹一下我對Quartz近期的學習使用心得,以下內容若有任何錯誤,也請不吝指教,畢竟是第一次接觸,可能還有許多地方學習不足。
Quartz是一個Open Source 的工作排程(job schedule) library或稱框架,它讓我們更容易的去安排、管理多個job(可以是幾個而已,也可能成千上萬個)的執行時間,以及怎麼執行(多久執行一次)等。名詞解釋不用說太多,趕快進入正題,該怎麼開始使用它來進行工作排程,接下來的說明是以整合進Web Application為主,我使用的Web Server是Tomcat 7.0。
  1. 先至Quartz的官網 下載最新的Jar檔,目前最新的版本為Quartz 2.2.1,下載下來請將lib底下的jar檔複製到WEB-INF/lib底下,我的2.2.1版分別有以下這些jar檔: 
    1. quartz-2.2.1
    2. quartz-jobs-2.2.1
    3. slf4j-api-1.6.6
    4. slf4j-log4j12-1.6.6
    5. c3p0-0.9.1.1
  2. 在Web.xml內加入如下的listener設定:
    <listener>
       <listener-class>     
         org.quartz.ee.servlet.QuartzInitializerListener     
       </listener-class>
    </listener>
    
  3. 在src根目錄下新增一個quartz.properties檔案,用來設定排程器(scheduler)的一些參數,我的設定檔內容簡單如下(這邊盡量先以最簡單的方式完成設定):
    <!--當程式中有多個scheduler時,用來區別不同scheduler  -->
    org.quartz.scheduler.instanceName = MyScheduler
    <!--同一時間能夠並行執行的job個數,規定是只要正的整數值,但一般是限定在1~100  -->
    org.quartz.threadPool.threadCount = 5
    <!--設定儲存scheduling資訊(job,triggers,calendars)的位置,這邊就如一般設定儲存在記憶體中  -->
    org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
    # ----------------------------- Plugins --------------------------- #
    <!--Quartz並沒有內建的logging實作,所以務必指定幫我們執行log的class -->
    org.quartz.plugin.triggerHistory.class=org.quartz.plugins.history.LoggingTriggerHistoryPlugin
    <!-- Trigger在fire(發起)job執行時,要顯示的log資訊內容及格式 -->
    org.quartz.plugin.triggerHistory.triggerFiredMessage=Trigger [{1}.{0}] fired job [{6}.{5}] scheduled at: {2, date, dd-MM-yyyy HH:mm:ss.SSS}, next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}
    <!-- Trigger在job完成執行時,要顯示的log資訊內容及格式 -->
    org.quartz.plugin.triggerHistory.triggerCompleteMessage=Trigger {1}.{0} completed firing job {6}.{5} at {4, date, yyyy-MM-dd HH:mm:ss.SSS} with resulting trigger instruction code: {9}. Next scheduled at: {3, date, dd-MM-yyyy HH:mm:ss.SSS}  
    <!-- Trigger在fire(發起)job執行卻misFire時,要顯示的log資訊內容及格式 -->
    org.quartz.plugin.triggerHistory.triggerMisfiredMessage=Trigger {1}.{0} misfired job {6}.{5} at: {4, date, yyyy/MM/dd HH:mm:ss }. Should have fired at: {3, date,yyyy/MM/dd HH:mm:ss} 
    <!--此plugin 會從預設檔名為quartz_data.xml的設定檔讀取jobs和triggers設定並加至scheduler中 -->
    org.quartz.plugin.jobInitializer.class=org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
  4. 寫一個自己的job class,此class必須實作org.quartz.Job 這個interface,我的code如下:
    package com.xxx.util.Quartz.job;
    
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    
    import org.quartz.DisallowConcurrentExecution;
    import org.quartz.Job;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    
    @DisallowConcurrentExecution
    public class TestJob2 implements Job{
    
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
      Calendar expectedDateTime=Calendar.getInstance();
      expectedDateTime.add(Calendar.MILLISECOND, 5000);
      Long expectedExecuteTime= expectedDateTime.getTimeInMillis();
      Date date=new Date();
      
      while (expectedExecuteTime!=date.getTime() && expectedExecuteTime > date.getTime())
      {
       
       long leftMilliSecond = expectedExecuteTime-date.getTime();
       System.out.println("Thread name:"+Thread.currentThread().getName()+ "\n There is still have "+leftMilliSecond/1000.0 +" seconds");
       
       try {
        Thread.sleep(1000);
       } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
       }
       date=new Date();
      }
      SimpleDateFormat sdf=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
      System.out.println("I am job2 ,It's "+sdf.format(new Date()));
     }
    }
  5. 最後,新增一個quartz_data.xml(預設檔名)放在src根目錄下,內容如下:
    <job-scheduling-data version="1.8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData" xsi:schemalocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd">
      
       <schedule>
            <job>
                <name>TestJob</name>
                <job-class>com.xxx.util.Quartz.job.TestJob2</job-class>
            </job>
            <trigger>
                <cron>
                    <name>CronTestJob</name>
                    <job-name>TestJob</job-name>
                    <cron-expression>0/5 * * * * ?</cron-expression>
                </cron>
            </trigger>
        </schedule>
    
    </job-scheduling-data>
    
    此設定是安排job每五秒執行一次,cron-expression請參考 cron-expression1cron-expression詳細說明
完成以上五個步驟後,即可以啟動你的tomcat,看看job是否按照排程設定在正確的時間執行!

PS:下面列出兩個對大家可能有用的參考網址
1.Quartz 官網內的Configuration Reference,裏頭按照不同主題將configuration做詳細的分類說明
2.Quartz 2.2.1 API

2014年3月24日 星期一

Html跳脫字元處理(Escape Html)&XSS攻擊

特殊字元在Html的顯示處理會衍生出的相關問題常有:
1.Html頁面能不能正常的運作:此種情況通常是因為要在html中呈現的屬性值中含有特殊字元像是單引號(')、雙引號(")等,以Jsp的程式範例來說,我們可能會寫一段javascript如下:

var memo='${memo}'
假設${memo}的內容為'test,則整串javascript會變成var memo =''test' ,變成一句無效的javascript敘述,因前面兩個單引號已經表示字串的內容結尾,後面的test'即成為無效的敘述 ; 因此一定要養成良好的寫作習慣,要將Server端資料輸出到前端時,無論資料內容為何,還是盡量使用JSTL(<c:out value='${memo}' /> 或<bean:write ..>等有針對特殊字元的輸出做處理的 Tag函式庫,避免直接使用<%= %>或是${}等方式直接輸出。


2.Cross-Site Scripting(XSS) 跨網站指令稿攻擊:此種攻擊方式是利用網站未對
使用者輸入的內容加以過濾驗證處理,便將可能有攻擊性的Scripting在其他使用者的瀏覽器上
顯示執行,造成危害。例如將此串Scripting <script>alert('attack')</script>輸入在某個可供查詢輸入的文字方塊,一般來說,網站都會將使用者的查詢條件紀錄下來並隨同查詢結果一併再次顯示在回應的網頁上,若顯示查詢結果時,同時也執行了上面那段script,那麼即表示該頁面的輸出沒有針對特殊字元做輸出的處理。
如左圖即為在我自行製作的網頁的文字方塊內輸入上面那段script,再傳到server端後並沒有做任何特殊字元的過濾處理,而在顯示時也不使用任何有跳脫特殊字元的tag(如<c:out value='xxx' />),直接將那段script又原封不動的輸出在原html的文字方塊內,即產生被scripting 攻擊的效果。




我的前端頁面很單純,只是將該查詢的輸入內容再次從server端取得後並呈現在頁面上如下:
$(document).ready(function() {
         $(".searchWrapper").find("#keyword").val('${keyword}');
}
比較正確的做法應該是將'${keyword}'此段輸出改為<c:out value="${keyword}">,即可避免此種XSS的潛在危險。

當然以這個例子來說,實際上是危害不到任何人,只是提供一個會產生潛在XSS風險的寫法來做示範,若真的要有可能危害到其他瀏覽同一頁面的使用者,一般還是得將有風險的scripting輸入存入DB後,當其他使用者又瀏覽相同頁面時,再將該scripting輸出並執行,此種風險通常就存在可讓人輸入意見的論壇,若沒有針對特殊字元做過濾,則很容易在瀏覽其他人所留下的意見資訊時,被做XSS攻擊。


PS:
1.順帶一提,若要使用org.apache.commons.lang3的StringEscapeUtils來自行在後端escape特殊字元
,個人遇到的情況是當使用StringEscapeUtils.escapeHtml4及StringEscapeUtils.escapeHtml3都沒有針對單引號做escape,因此就自行寫了一個EscapeUtil並繼承StringEscapeUtils,並針對escapeHtml4未對單引號做escape的部分做補強,程式碼如下:
public class EscapeUtil extends StringEscapeUtils {
  /***
   * this method is to enhance  StringEscapeUtils.escapeHtml4' method,
   * StringEscapeUtils.escapeHtml4 doesn't escape "'" .
   * @param input
   * @return
   */
  public static String escapeHtml(String input){
   String escapeText= StringEscapeUtils.escapeHtml4(input);
   escapeText=escapeText.replaceAll("\'", "&#039;");
   return escapeText;
   
  }
}
附上StringEscapeUtils API參考網頁
2.XSS參考網站

2014年2月24日 星期一

Eclipse 改變背景色與文字顏色的plugin-color theme(手動設定方式)

1.在工具列的 Help -> Eclipse Marketplace 下,Find欄位鍵入 'color theme' 搜尋,看到
Eclipse Color Theme 點install ,若順利安裝完成直接按照下圖選取喜歡的Color Theme

點選工具列下Windows->Preference
2.若發生安裝錯誤,錯誤訊息如:
Cannot complete the install because one or more required items could not be found.
Software being installed: Eclipse Color Theme 0.13.0.201311210852 (com.github.eclipsecolortheme.feature.feature.group 0.13.0.201311210852)
Missing requirement: Eclipse Color Theme 0.13.0.201311210852 (com.github.eclipsecolortheme 0.13.0.201311210852) requires 'bundle org.eclipse.e4.ui.workbench.addons.swt 0.0.0' but it could not be found
Cannot satisfy dependency:
From: Eclipse Color Theme 0.13.0.201311210852 (com.github.eclipsecolortheme.feature.feature.group 0.13.0.201311210852)
To: com.github.eclipsecolortheme [0.13.0.201311210852]
則恭喜你跟我遇到同樣的安裝問題,經Google一下,可利用手動的安裝方式下載安裝稍舊的版本,安裝擋在這裡,下載後手動解壓縮,內容有兩個資料夾一個為features,直接將features內的整個資料夾複製到你的Eclipse所在目錄下features底下,例如我的是在C:\eclipse\features,就複製到此路徑下; 另一個資料夾plugins放的是jar檔,直接將此jar檔複製到你的Eclipse所在目錄下plugins底下 ,以我的例子來說是放在C:\eclipse\plugins下,完成後重新啟動Eclipse就可以如上圖方便的切換 theme囉!

2013年12月17日 星期二

Jquery 常用方法紀錄

  $("form#selector).find()形式
  選取特定form底下的特定元素
   $("form#Form的id").find("#元素的id")

 $(selector).find()形式:
  選取特定class下的特定元素: $(".ClassName").find("#元素的id")
  選取特定id下的特定元素:$("#id").find("#元素的id")

 $(selector).each()形式:
 取得符合過濾器的集合後,按照自己的需求對集合中每一個元素做處理,此方法使用頻率很高,而且在做動態處理元素集合時很好用。
下面一個範例程式是要取出一個div中的所有分隔線,並加總所有分隔線高度
$("#divChannel .DIV_Spliter1").each(function(index){
   splitHeight+=$(this).outerHeight();
   }); 

$(selector).animate({CSS屬性},duration, function(){ });  
CSS屬性:
對選出的元素進行設定的動畫效果,由css去指定如height、width或opacity等屬性,讓元素有變高、變寬或是變透明等效果。
例如想讓一個Div元素向左移動如下設定,表示每次往左移動50px
$("#div1").animate({
             left : "+=50px"
})
duration屬性:
此屬性是指要用多少時間去完成一個動作,單位是毫秒milliseconds。例如上面的例子可以再接著設定duration=200,來指定動畫要在200毫秒內完成。
$("#div1").animate({
             left : "+=50px"
},200)
同時,duration也可以以字串代替,"slow"=200毫秒,"fast"=600毫秒。
function(){}:
為animate指定一個callback function,也就是當動畫執行完畢後,最後會去執行的方法,例如:
$("#div1").animate({
             left : "+=50px"
},200, function(){ alert("done")} )
在向右移動完50px後,顯示"done"訊息。

.stop([clearQueue][,jumpToEnd])
clerarQueue參數:當一個元素有一個以上的動畫在queue中等待執行,當clearQueue設為true時,除了當前的動畫行為立刻停止之外,在queue中等待執行的動畫也會被清空,
clearQueue預設為false
jumpToEnd參數:設為true時,會立刻將目前正在執行的動畫執行完畢,若是在執行物件的移動時,就有如瞬間移動的效果,jumpToEnd預設為false

實務上,animate會配合.stop來搭配使用,以讓動畫看起來不會有延遲的情況,例如要做一個有下拉及收回的Div選單,若不加stop,則連續多按幾次,即可看出延遲效果
$("#div1").click(function()
  $( this ).slideToggle();
});
加入stop,因為會先立刻停止當前正在執行的動做,並執行下一個動作,因此就算連續按好幾下,也不會有延遲效果的問題。
$("#div1").click(function()
  $( this ).stop().slideToggle();
});

ps:在測試slideToggle時,同時發現疑似 jquery-1.4在slideToggle的bug,當執行上述的jquery指令時,若連續觸發數次click,則隨著div1連續執行.stop().slideTogge(),會發現div的高度會一次比一次還要低,最後幾乎看不到,但當我將jquery套件換成 jquery-1.8.3後,此問題即不存在。
經過我的追查,此問題在官網上有公布已經修正的版本為1.7,原文如下:

Toggling Animations Work Intuitively

In previous versions of jQuery, toggling animations such as .slideToggle() or .fadeToggle() would not work properly when animations were stacked on each other and a previous animation was terminated with .stop(). This has been fixed in 1.7 so that the animation system remembers the elements’ initial values and resets them in the case where a toggled animation is terminated prematurely.
文中即說明slideToggle()與fadeToggle配上stop()時皆會有類似的問題發生,會發生我所說的:當連續觸發
$(selector).stop().slideToggle()時,展開的高度一次比一次還低,是因為 jquery沒有將元素的初始高度正確的紀錄下來並在每次觸發slideToggle()時重設高度為原始高度。


好用的Jquery 功能查詢與線上測試網站
W3C_Jquery_原始碼測試:除了有中文(簡體)功能解說外,還可直接線上編輯jquery代碼並測試
Jquery 官網:有最新版Jquery與 API可供下載與查詢
Jquery UI套件:可下載最新的Jquery UI套件與線上測試

















2013年11月25日 星期一

DetachedCriteria 範例程式介紹(一) - 對照SQL Query解說

隔了半年,才又拿出時間記錄點學習歷程,希望這次能夠持之以恆阿...話說有時候真的是很懶惰,有些東西想記下來,卻又老是有一堆不寫的理由XD...

廢話不多說,今天主要想記錄的是 DetachedCriteria的應用。相信許多人在使用Hibernate時,都會比較習慣使用HQL來對資料庫下查詢指令,因為它跟Sql Query非常相似,基本上會Sql Query,幾乎就會使用HQL。但問題是code不總是自己寫,還要維護前人或其他人的code,也因此總還是會有機會去碰觸使用Criteria寫的資料庫查詢程式,所以索性還是耐著性子,上網爬了點文,將自己試跑過的DetachedCriteria的一些相關功能做個整理與紀錄。

在接下來的介紹中,打算以 DetachedCrititeria的實例程式對照轉譯成SQL後的樣子來進行解說,對我個人來說,這樣是學最快也是最好記憶的方式,若想知道一些個別介面的詳細使用說明,網路上有許多文可以爬,例如這篇有很詳細的DetachedCriteria的完整說明或是去查詢Hibernate API
 第一個detachedCriteria實例:
  Property versionNumberPro=Property.forName("versionNumber");
  Property apIdPro=Property.forName("apId");
  DetachedCriteria mainQuery= DetachedCriteria.forClass(AgentAp.class)
  .addOrder(Order.desc("apId"));
  DetachedCriteria subQuery=DetachedCriteria.forClass(AgentAp.class);
  subQuery.setProjection(Property.forName("apId"))
  .add(Restrictions.disjunction().add(versionNumberPro.eq(new String("1.0.1")))
    .add(versionNumberPro.eq(new String("1.0.2"))))
  .add(Restrictions.conjunction().add(apIdPro.ge(new Integer(114)))
          .add(apIdPro.le(new Integer(128))));

List listAps= mainQuery.add(Property.forName("apId").in(subQuery))
  .getExecutableCriteria(this.getSession()).list();//
Hibernate 翻譯出來的 sql長的樣子如下:因此我們可以從下面sql開始對照DetachedCriteria指令。
 
    select
        this_.AP_ID as AP1_84_0_,
        this_.IS_UPDATE as IS2_84_0_,
        this_.VERSION_DATE as VERSION3_84_0_,
        this_.VERSION_NUMBER as VERSION4_84_0_ 
    from
        AGENT_AP this_ 
    where
        this_.AP_ID in (
            select
                max(this_.AP_ID) as y0_ 
            from
                AGENT_AP this_ 
            where
                (
                    this_.VERSION_NUMBER=? 
                    or this_.VERSION_NUMBER=?
                ) 
                and (
                    this_.AP_ID>=? 
                    and this_.AP_ID<=?
                )
        ) 
    order by
        this_.AP_ID desc
首先 DetachedCriteria mainQuery= DetachedCriteria.forClass(AgentAp.class) ,
就是sql中的
    select
    this_.AP_ID as AP1_84_0_,
        this_.IS_UPDATE as IS2_84_0_,
        this_.VERSION_DATE as VERSION3_84_0_,
        this_.VERSION_NUMBER as VERSION4_84_0_ 
    from
        AGENT_AP this_

接下來,由於我的這個主查詢需要根據子查詢的結果當作主查詢的條件,因此在Criteria程式中,我會建立一個相當於子查詢功能(在sql中)的subQuery(一樣是DetachedCriteria類別): DetachedCriteria subQuery=DetachedCriteria.forClass(AgentAp.class);
建立好子查詢的DetachedCriteria後,接下來我們要考慮的是,主查詢是要以這個子查詢的哪個欄位來當作條件依據,在本例子中,是要以"apId"這個entity的property(與實際在db中的field name不一定相等,看各位自己名字的取法),因此會有這句的設定: subQuery.setProjection(Property.forName("apId").max()) 剛開始看這些東西,真的會覺得很不習慣,為什麼要這樣取,不過其實也沒太多為什麼,寫多看久,自然就知道要這樣用,而透過subQuery.setProjection(Property.forName("apId")設定好要select出來的欄位後,大家有注意到後面還有個max()方法,它其實就是取前面那個欄位名稱的最大值,到目前為止,所看到的Criteria程式,剛好就可以翻譯成
 select max(this_.AP_ID) as y0_ from AGENT_AP this_ 這樣的一段sql。

在這段子查詢中,我們也必須根據一些條件去過濾它,因此會有後面這段
.add(Restrictions.disjunction().add(versionNumberPro.eq(new String("1.0.1")))
    .add(versionNumberPro.eq(new String("1.0.2"))))
.add(Restrictions.conjunction().add(apIdPro.ge(new Integer(114))).add(apIdPro.le(new Integer(128))));
看起來好像有點小複雜,其實轉成sql就是下面這段:
where ( this_.VERSION_NUMBER=?
           or
            this_.VERSION_NUMBER=? )
 and (   this_.AP_ID>=?
           and
           this_.AP_ID<=? )
其中,Restrictions.disjunction()就是指我們接下來add的條件都是or起來的,如上面對VERSION_NUMBER 條件的or操作,
而Restrictions.conjunction()就是指接下來的條件都是and起來的,如上面對AP_ID的操作。
到現在為止,子查詢的部分已經解釋完畢,接下來就是將主查詢與子查詢給串起來,相信看到這邊在回顧一下最上面的Criteria程式,也能猜到是這句
mainQuery.add(Property.forName("apId").in(subQuery)
其實這部分還算滿直覺的,就是將主查詢加入一個子查詢,其中查詢的依據就是依據"apId"這個欄位,產生的sql如下這,紅色標記的部分當然就是子查詢了。
                where  this_.AP_ID in ( select max(this_.AP_ID) as y0_ 
                                                              from AGENT_AP this_ 
                                                              where ( this_.VERSION_NUMBER=? 
                                                                           or this_.VERSION_NUMBER=? )
                                                              and     ( this_.AP_ID>=? 
                                                                          and this_.AP_ID<=? ) 
                                                     )              
                 order by this_.AP_ID desc
奇怪,這個 order by 是怎麼來的,好像漏說了,恩對,確實沒說,這個order by 是一開始在建立主要的mainQuery時就指定,
DetachedCriteria mainQuery= DetachedCriteria.forClass(AgentAp.class) .addOrder(Order.desc("apId"))。

至此透過一個簡單的DetachedCriteria範例程式與SQL Query的對照,大致可以掌握了解DetachedCriteria的基本常用的一些方法,當然其實還有非常多的東西沒有講到,希望能在近期陸續將更多實用的相關方法補齊。