在JavaSE中使用Hibernate框架
目前人們很容易發現Hibernate正迅速的成為流行的J2EE的O/R映射工具和數據集成框架(如果不是最流行的)。Hibernate為企業應用開發者操作關系數據庫的提供了清晰明了而又強大的工具。然而如果你需要在外部訪問那些包裝在J2EE web應用里的實體,情況又怎樣呢?你的開發跟應用獨立分開,卻又相同的實體以訪問你的數據嗎?又或者你得編寫附加的web組件來管理對數據的內部訪問嗎?
在很多情況下,這些問題都會出現。我的情況是我的公司需要將來自多個供應商,有著多種文件格式的記錄導入到數據庫里。我想起我以前經常使用的方法,那就是編寫Shell和SQL教本(甚至是存儲過程)來導入數據。但是由于我們的數據模型太過復雜,我決定在web應用之外盡可能的利用現有的實體,Spring DAO以及服務并且開發一個自定義的J2SE命令行數據加載工具。
大問題:你該怎樣呢?
現在很多Hibernate的文檔和范例都是綁定在容器上。不管是web應用還是內部的大型應用,總會使用到容器的。人們有很好的理由去使用它。容器是設計來提供對各種特性的支持,例如事務處理,線程以及安全。現今,這些特性都是開發中等規模和企業應用所必需的工具。然而當你需要在容器之外訪問實體時,你該怎樣呢?你是使用現有的架構和代碼呢,還是會從一個不同的角度來解決問題,比如說完全采用另一種開發語言?當然,我們沒有正確答案。在本文的余下部分,我將說明我的方法:就是在Spring容器之外重用現有的實體/POJO。
起初,腳本語言,例如Perl,Python,Ruby甚至Tcl(是的,我以前也做過這個)看起來有很多優勢。它們能省下很多時間,可以輕易得到初始結果,還能規避許多Hibernate潛在的復雜度。人們完全可能只用幾行代碼就可以連接數據庫,查詢結果,已經打印輸出到終端屏幕或者日志文件。然而,取決于你的數據模型,事情也(總是)會變得相當復雜。譬如說你有一個表 person, 其中有一個外鍵屬于表 address。當我們添加數據的時候,表address沒有正確的插入數據,就會導致表person 也不能插入了。這是個很典型的事務處理方面的問題。也許有人會說在腳本語言中這個問題不難解決,就像在你的主程序里一樣。可是問題仍然存在,為什么要這樣做呢?業務邏輯不是已經在你的應用里面了嗎?為什么要在寫一遍代碼呢?而且這并不是唯一的情況,你必須重復你的工作和業務邏輯,這樣就會帶來出錯的可能。
然而,有些人會覺得這樣也行,他們使用自己覺得最適合的工具。也許你已經因為程序之外的原因而有了某種獨立的架構;也許你會在獨立的數據庫里加載和測試數據,然后在通過各種測試后再遷移到產品的數據庫里;又也許你把數據庫維護外包出去,你只需要把相關文件發給合作伙伴讓他們去處理那些問題。總之,總會有很多理由不使用現有的Hibernate數據層。沒有誰對誰錯,只是如果你可以也愿意在你的應用之外使用現有的代碼,請往下看。我會告訴你一些方法,這能解決你不少的煩惱噢。
配置
如果你覺得可以在容器之外使用現有的Hibernate對象的話,那你首先要做的事就是得自己手工管理所有的配置項,在本文余下部分我所采用的方法是使用一個基于命令行的JAVA程序。既然你已經配置了Hibernate XML配置文件,你應該知道需要提供的參數,例如JNDI DataSource名,實體映射文件,還有其他一些處理SQL日志的屬性。如果你想使用命令行程序的話,你就得解決如何解析XML文件和把它添加到配置項中的這些問題。雖然解析XML文件也不難,但這本身并不是我們的重點。因此,我建議使用propetries文件,properties文件比較直觀而且容易加載并從中讀取數據。下面是配置Hibernate所需要的最小屬性集(不包括任何實體映射)。
清單1:
正如你所看到的,上面的屬性值指定了數據庫方言,JDBC驅動,數據庫url,用戶名,用戶密碼,以及是否使用查找替換。只要定義以上幾項數值并保存在文件hibernate.properties里(要放置在你的類路徑里面哦),就能很輕松的加載,填充到Hibernate Configuation類里面。
清單2:
這樣我們就得到了Hibernate Session類了。但我們也有必要解決如何利用現有的實體映射這個問題。在《Hibernate in Action》一書中,提到怎樣從實體映射XML文件中加載,如下所示:
清單3:
這段代碼清晰的說明了從hello包里加載Message實體定義的過程。對于這個例子來說還好,但對那些有多個實體的應用來說,就很單一而且容易出錯。不僅映射關系是硬編碼,還得手工管理每次添加一個新的實體就要更新實體加載的代碼。其實有跟簡單的方法去查找和加載映射關系以使其與最新的jar文件保持一致。
首先,在你的web服務器或者企業服務器里,映射文件需要放置在類路徑里,這樣Hibernate才能正常的運行。這樣做是很有好處的,因為你所需要做的就是使用同樣的jar包和查找相應的映射文件的名字。因為你可能會有多個jar文件在你的類路徑里,你需要指定哪個jar包包含了映射文件。以下就是一種查找映射關系的方法
清單4:
上面的代碼主要完成了以下幾件事情:獲取Java虛擬機初始化的classpath系統屬性;查找含有實體映射文件的jar包;解析映射文件的名字,然后添加到一個ArrayList對象中去。當我們的ArrayList對象裝滿了實體映射的名字后,就可以將其傳遞到Hibernate Configuration 對象,如下所示:
清單5:
只要我們在Hibernate Session 對象里配置好正確的映射關系,我們就可以將實體拿來使用了。
使用Session
關于這一點,你可以參考關于Hibernate和持久層的文章或者指南,也可以查詢各種對象和實例來了解怎么使用事務,所以我不打算詳細說這些內容。相反,我會更多考慮使用實體后我們需要做什么?這會對Hibernate Session 對象有怎樣的影響?是否可以使用現有的業務對象,甚至是數據訪問對象?當我建立數據層的時候,我使用了Spring及其提供的一些類來管理數據庫連接,事務和會話。這些對象都在XML配置文件里面定義了,并與很多規則和關系緊密集成在Spring里。首先,通過Spring的依賴注射,DAO對象會被引入到應用服務中(關于依賴注射,參見Bruce Tate的《Five Things I Love About Spring》一書)。然后,配置應用服務以捕獲DAO異常(通過XML配置文件),讓Spring去處理。可是因為我覺得把Spring集成到數據加載應用里會帶來相當大的工作量。我對DAO對象做了輕微的修改,使得可以在web應用之外使用他們。
例如說在PersonDAO類里面有一個保存person對象的方法。如果Hibernate Session 是由容器建立的,那這個方法就不能在容器外使用,因為這需要一個配置好的Session對象。以下是一個典型的PersonDAO,它由Spring 容器提供了對Session的支持。
清單6:
上面的類繼承的是Spring HibernateTemplate 類,它提供了各種不錯的使用Hibernate的基類方法。而且由于HibernateTemplate類維護了大多數的常見操作,你就只需要專注特定的持久層需求。當然也應該有相應的異常處理,但在本次范例當中,只要以上的就夠了。
現在,要在容器外加上對Session的支持,我們只需要做一些小小的改動:
清單7:
因為HibernateTemplate類繼承于HibernateAccessor類,我們就可以從任何Session對象中建立SessionFactory。這是Spring小組的一個高靈活性的設計,使得重用現有代碼更加容易。
也許你并沒有使用Spring而是采用完全不同的方法。如果你不喜歡Spring的一來注射,你也可以通過JNDI查找Session對象:
清單8:
以上的例子依賴于應用服務器來使得Hibernate Session對象可用。在容器之外使用的最簡單方法就是添加一個帶Session參數的構造函數,如下所示:
清單9:
當然我們并沒有處理太多的異常,事務問題。甚至我們在多個方法內共用一個Session對象,這會導致一些并發事務問題(取決于容器或框架如何處理對象實例)。不過我想很顯然以上的例子演示了如何重用大量的現有數據層代碼。只需要一點點有創造力的想法。要弄清楚你是否想在應用服務器之外使用現有的實體和DAO,接下來就不斷嘗試把。
結論
正如你所見,要在web容器外使用Hibernate 實體和DAO是需要技巧的,但這肯定能做到的。最大的困難在于如何查找實體映射關系和如何重設置(或者說修改)現有的數據訪問對象(即DAO)。處理后者時,要小心處理事務問題,因為沒有應用服務可以依賴了。不過最后,我們還是可以訪問所有的實體和進行持久化的對象,這樣能省下大量的重新開發代碼的時間。祝您好運!!
在很多情況下,這些問題都會出現。我的情況是我的公司需要將來自多個供應商,有著多種文件格式的記錄導入到數據庫里。我想起我以前經常使用的方法,那就是編寫Shell和SQL教本(甚至是存儲過程)來導入數據。但是由于我們的數據模型太過復雜,我決定在web應用之外盡可能的利用現有的實體,Spring DAO以及服務并且開發一個自定義的J2SE命令行數據加載工具。
大問題:你該怎樣呢?
現在很多Hibernate的文檔和范例都是綁定在容器上。不管是web應用還是內部的大型應用,總會使用到容器的。人們有很好的理由去使用它。容器是設計來提供對各種特性的支持,例如事務處理,線程以及安全。現今,這些特性都是開發中等規模和企業應用所必需的工具。然而當你需要在容器之外訪問實體時,你該怎樣呢?你是使用現有的架構和代碼呢,還是會從一個不同的角度來解決問題,比如說完全采用另一種開發語言?當然,我們沒有正確答案。在本文的余下部分,我將說明我的方法:就是在Spring容器之外重用現有的實體/POJO。
起初,腳本語言,例如Perl,Python,Ruby甚至Tcl(是的,我以前也做過這個)看起來有很多優勢。它們能省下很多時間,可以輕易得到初始結果,還能規避許多Hibernate潛在的復雜度。人們完全可能只用幾行代碼就可以連接數據庫,查詢結果,已經打印輸出到終端屏幕或者日志文件。然而,取決于你的數據模型,事情也(總是)會變得相當復雜。譬如說你有一個表 person, 其中有一個外鍵屬于表 address。當我們添加數據的時候,表address沒有正確的插入數據,就會導致表person 也不能插入了。這是個很典型的事務處理方面的問題。也許有人會說在腳本語言中這個問題不難解決,就像在你的主程序里一樣。可是問題仍然存在,為什么要這樣做呢?業務邏輯不是已經在你的應用里面了嗎?為什么要在寫一遍代碼呢?而且這并不是唯一的情況,你必須重復你的工作和業務邏輯,這樣就會帶來出錯的可能。
然而,有些人會覺得這樣也行,他們使用自己覺得最適合的工具。也許你已經因為程序之外的原因而有了某種獨立的架構;也許你會在獨立的數據庫里加載和測試數據,然后在通過各種測試后再遷移到產品的數據庫里;又也許你把數據庫維護外包出去,你只需要把相關文件發給合作伙伴讓他們去處理那些問題。總之,總會有很多理由不使用現有的Hibernate數據層。沒有誰對誰錯,只是如果你可以也愿意在你的應用之外使用現有的代碼,請往下看。我會告訴你一些方法,這能解決你不少的煩惱噢。
配置
如果你覺得可以在容器之外使用現有的Hibernate對象的話,那你首先要做的事就是得自己手工管理所有的配置項,在本文余下部分我所采用的方法是使用一個基于命令行的JAVA程序。既然你已經配置了Hibernate XML配置文件,你應該知道需要提供的參數,例如JNDI DataSource名,實體映射文件,還有其他一些處理SQL日志的屬性。如果你想使用命令行程序的話,你就得解決如何解析XML文件和把它添加到配置項中的這些問題。雖然解析XML文件也不難,但這本身并不是我們的重點。因此,我建議使用propetries文件,properties文件比較直觀而且容易加載并從中讀取數據。下面是配置Hibernate所需要的最小屬性集(不包括任何實體映射)。
清單1:
hibernate.dialect=net.sf.hibernate.dialect.PostgreSQLDialect hibernate.connection.driver_class=org.postgresql.Driver hibernate.connection.url=jdbc:postgresql://devserver/devdb hibernate.connection.username=dbuserhibernate.connection.password=dbpassword hibernate.query.substitutions yes 'Y' |
正如你所看到的,上面的屬性值指定了數據庫方言,JDBC驅動,數據庫url,用戶名,用戶密碼,以及是否使用查找替換。只要定義以上幾項數值并保存在文件hibernate.properties里(要放置在你的類路徑里面哦),就能很輕松的加載,填充到Hibernate Configuation類里面。
清單2:
Properties props = new Properties(); try { props.load(props.getClass().getResourceAsStream("hibernate.properties")); }catch(Exception e){ System.out.println("Error loading hibernate properties."); e.printStackTrace(); System.exit(0); } String driver = props.getProperty("hibernate.connection.driver_class"); String connUrl = props.getProperty("hibernate.connection.url"); String username = props.getProperty("hibernate.connection.username"); String password = props.getProperty("hibernate.connection.password"); // In my examples, I use Postgres, but Hibernate // supports virtually every popular dbms out there.Class.forName("org.postgresql.Driver"); Connection conn = DriverManager.getConnection(connUrl, username, password); Configuration cfg = new Configuration(); cfg.setProperties( props ); SessionFactory sessions = cfg.buildSessionFactory(); Session session = sessions.openSession(conn); |
這樣我們就得到了Hibernate Session類了。但我們也有必要解決如何利用現有的實體映射這個問題。在《Hibernate in Action》一書中,提到怎樣從實體映射XML文件中加載,如下所示:
清單3:
Configuration cfg = new Configuration(); cfg.addResource("hello/Message.hbm.xml"); cfg.setProperties( System.getProperties() ); SessionFactory sessions = cfg.buildSessionFactory(); |
這段代碼清晰的說明了從hello包里加載Message實體定義的過程。對于這個例子來說還好,但對那些有多個實體的應用來說,就很單一而且容易出錯。不僅映射關系是硬編碼,還得手工管理每次添加一個新的實體就要更新實體加載的代碼。其實有跟簡單的方法去查找和加載映射關系以使其與最新的jar文件保持一致。
首先,在你的web服務器或者企業服務器里,映射文件需要放置在類路徑里,這樣Hibernate才能正常的運行。這樣做是很有好處的,因為你所需要做的就是使用同樣的jar包和查找相應的映射文件的名字。因為你可能會有多個jar文件在你的類路徑里,你需要指定哪個jar包包含了映射文件。以下就是一種查找映射關系的方法
清單4:
String cp = System.getProperty("java.class.path"); String jarFile = null; List hbmList = null;String[] cparr = cp.split("\:"); for(int j=0;j<cparr.length;j++){ // The following assumes our entities // are wrapped up in a jar file // called 'dbobjs.jar' if(cparr[j].indexOf("dbobjs.jar") != -1) jarFile=(cparr[j]); } if(jarFile != null){ JarFile jar = new JarFile(new File(jarFile)); Enumeration e = jar.entries(); if(e.hasMoreElements()) { hbmList = new ArrayList(); while(e.hasMoreElements()){ // Object comes back as JarFile$JarFileEntry JarEntry entry = (JarEntry)e.nextElement(); if(entry.getName().indexOf(".hbm.xml") != -1) { hbmList.add(entry.getName()); } } }else { System.out.println("Error: The entity jar dbobjs.jar was not found in " + "classpath: " + cp); } } |
上面的代碼主要完成了以下幾件事情:獲取Java虛擬機初始化的classpath系統屬性;查找含有實體映射文件的jar包;解析映射文件的名字,然后添加到一個ArrayList對象中去。當我們的ArrayList對象裝滿了實體映射的名字后,就可以將其傳遞到Hibernate Configuration 對象,如下所示:
清單5:
Configuration cfg = new Configuration(); Iterator iterator = hbmFileNames.iterator(); while(iterator.hasNext()){ cfg.addResource((String)iterator.next()); } |
只要我們在Hibernate Session 對象里配置好正確的映射關系,我們就可以將實體拿來使用了。
使用Session
關于這一點,你可以參考關于Hibernate和持久層的文章或者指南,也可以查詢各種對象和實例來了解怎么使用事務,所以我不打算詳細說這些內容。相反,我會更多考慮使用實體后我們需要做什么?這會對Hibernate Session 對象有怎樣的影響?是否可以使用現有的業務對象,甚至是數據訪問對象?當我建立數據層的時候,我使用了Spring及其提供的一些類來管理數據庫連接,事務和會話。這些對象都在XML配置文件里面定義了,并與很多規則和關系緊密集成在Spring里。首先,通過Spring的依賴注射,DAO對象會被引入到應用服務中(關于依賴注射,參見Bruce Tate的《Five Things I Love About Spring》一書)。然后,配置應用服務以捕獲DAO異常(通過XML配置文件),讓Spring去處理。可是因為我覺得把Spring集成到數據加載應用里會帶來相當大的工作量。我對DAO對象做了輕微的修改,使得可以在web應用之外使用他們。
例如說在PersonDAO類里面有一個保存person對象的方法。如果Hibernate Session 是由容器建立的,那這個方法就不能在容器外使用,因為這需要一個配置好的Session對象。以下是一個典型的PersonDAO,它由Spring 容器提供了對Session的支持。
清單6:
import org.springframework.orm.hibernate.HibernateTemplate; import test.pojos.Person; public class PersonDAO extends HibernateTemplate { public PersonDAO(){} public Person save(Person aPerson) { if(aPerson != null) super.save(person); return person; } } |
上面的類繼承的是Spring HibernateTemplate 類,它提供了各種不錯的使用Hibernate的基類方法。而且由于HibernateTemplate類維護了大多數的常見操作,你就只需要專注特定的持久層需求。當然也應該有相應的異常處理,但在本次范例當中,只要以上的就夠了。
現在,要在容器外加上對Session的支持,我們只需要做一些小小的改動:
清單7:
import org.springframework.orm.hibernate.HibernateTemplate; import net.sf.hibernate.Session; import test.pojos.Person; public class PersonDAO extends HibernateTemplate { public PersonDAO(){} public void setExternalSessionFactory(Session aSession){ setSessionFactory(session.getSessionFactory()); } public Person save(Person aPerson) { if(aPerson != null) super.save(person); return person; } } |
因為HibernateTemplate類繼承于HibernateAccessor類,我們就可以從任何Session對象中建立SessionFactory。這是Spring小組的一個高靈活性的設計,使得重用現有代碼更加容易。
也許你并沒有使用Spring而是采用完全不同的方法。如果你不喜歡Spring的一來注射,你也可以通過JNDI查找Session對象:
清單8:
import net.sf.hibernate.Session;public class PersonDAO { // This example assumes that there is a Hibernate // Session object at the following JNDI location // on a Tomcat 5.5 server: // java:/comp/env/obj/hibernateSession private Session session; public PersonDAO(){ try { Context initCtx = new InitialContext(); Context envCtx = (Context) initCtx.lookup("java:comp/env"); session = (Session)envCtx.lookup("obj/hibernateSession"); }catch(Exception e) { e.printStackTrace(); } } public Person save(Person aPerson) { if(aPerson != null) session.save(person); return person; } } |
以上的例子依賴于應用服務器來使得Hibernate Session對象可用。在容器之外使用的最簡單方法就是添加一個帶Session參數的構造函數,如下所示:
清單9:
import net.sf.hibernate.Session; public class PersonDAO { // This example assumes that there is a Hibernate // Session object at the following JNDI location // on a Tomcat 5.5 server: // java:/comp/env/obj/hibernateSession private Session session; public PersonDAO(){ try { Context initCtx = new InitialContext(); Context envCtx = (Context) initCtx.lookup("java:comp/env"); session = (Session)envCtx. lookup("obj/hibernateSession"); }catch(Exception e) { e.printStackTrace(); } } public PersonDAO(Session aSession){ session = aSession; } public Person save(Person aPerson) { if(aPerson != null) session.save(person); return person; } } |
當然我們并沒有處理太多的異常,事務問題。甚至我們在多個方法內共用一個Session對象,這會導致一些并發事務問題(取決于容器或框架如何處理對象實例)。不過我想很顯然以上的例子演示了如何重用大量的現有數據層代碼。只需要一點點有創造力的想法。要弄清楚你是否想在應用服務器之外使用現有的實體和DAO,接下來就不斷嘗試把。
結論
正如你所見,要在web容器外使用Hibernate 實體和DAO是需要技巧的,但這肯定能做到的。最大的困難在于如何查找實體映射關系和如何重設置(或者說修改)現有的數據訪問對象(即DAO)。處理后者時,要小心處理事務問題,因為沒有應用服務可以依賴了。不過最后,我們還是可以訪問所有的實體和進行持久化的對象,這樣能省下大量的重新開發代碼的時間。祝您好運!!