02.25 Hibernate 這麼硬核,為什麼用的人少?

關於SQL和ORM的爭論,永遠都不會終止,我也一直在思考這個問題。最近溫習了一遍SSH框架,發了動彈,和廣大猿友進行了深刻的探討,被噴的五體投地,感慨萬千,於是就有了今天這篇文章。

一、hibernate優勢

hibernate讓你不用寫sql了,這不單可以讓你的應用更好移植其它數據庫,更主要的是讓程序員更專注業務邏輯、數據關係、對象關係等。hibernate對一對多,多對多關係實現是非常好的。很關鍵一點,它支持lazy,可以讓你的數據只在需要的時候被加載,聽起來很完美。hibernate還有一個更牛的就是HQL,這是完全可以把查詢映射到你OO模型的查詢語言,和mybatis的映射比起來,還是更方便和更強大的。

1、@Lazy註解是什麼?

@Lazy註解用於標識bean是否需要延遲加載,源碼如下:

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
/**
* Whether lazy initialization should occur.
*/
boolean value() default true;
}

只有一個參數,默認是true,也就是說只要加了這個註解就會延遲加載。

2、@Lazy註解怎麼使用

沒加註解之前主要容器啟動就會實例化bean,如下:

AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(MainConfig.class);

創建user實例

而加上@Lazy註解則必須在第一次調用的時候才會加載如下:

@Scope
@Lazy
@Bean(value="user0",name="user0",initMethod="initUser",destroyMethod="destroyUser")
public User getUser(){
System.out.println("創建user實例");
return new User("張三",26);
}AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(MainConfig.class);
User bean2 = applicationContext2.getBean(User.class);創建user實例
實例1 === User [userName=張三, age=26]

@Lazy註解註解的作用主要是減少springIOC容器啟動的加載時間


二、hibernate劣勢

看完優勢之後,感覺hibernate無所不能了,無敵是多麼的寂寞。處理大量數據或者大併發情況的網絡服務感覺不是很好用,那麼現在開始說說hibernate的問題。

1、難以使用數據庫的一些功能

hibernate將數據庫與開發者隔離了,開發者不需要關注數據庫是Oracle還是MySQL,hibernate來幫你生成查詢的sql語句,但問題來了,如果你想用某種數據庫特有的功能,或者要讓查詢的sql完全符合你的心意,這就難了。如果使用hibernate,雖然它能對生成的查詢進行一定程序的定製,但就有點隔靴撓癢的感覺了,而且你開發起來付出的代價更大。至於hibernate對native sql的支持,還是挺完善的,這種native sql還能返回non-managed entity,不走hibernate的cache,優化是搞定了,但如果整個項目都這麼整,那還是用mybatis吧。

很多時候,我們都有一個需求:得到數據庫服務器的當前時間。這是因為本機時間和服務器時間是有差別的。各種數據庫都提供了函數來獲得,比如,mysql,可以用“select now()”。hibernate也提供了一個函數current_timestamp(說起timestamp,個人認為數據庫的timestamp做的很差,它居然和datetime是一個數量級的(精確度),這怎麼可以用來表示真正的stamp啊!)。可是,你卻無法用直接使用“select current_timestamp()”來獲得服務器的當前時間,你還必須加上一個查詢的表!比如,“select current_timestamp() from tbl_Good”。個人十分鬱悶,我只是想用這個簡單功能而已,為什麼我一定要知道數據庫裡面的表格呢????更何況還必須建立映射。。。。。。不是我不明白,這世界太複雜了 。每樣產品都是拼命的複雜化,其實,它們實在是忽略了一般的用戶只需要一小部分功能而已。默認的功能應該是能夠滿足普通用戶的常見需求的,那樣才算是一個好的產品。我不認為hibernate做到了這點。

2、滿足不了程序對cache的需求

很多web服務,對cache的依賴是非常大的,hibernate自帶的cache按理說也是很強大的,但還是滿足不了很多需求。

3、耦合度高

hibernate的確是在你項目開發的時候節約了很多時間,但是它對你的業務邏輯模型和數據庫模型互相依賴的程度太高了。短期沒啥問題,但隨著項目的變遷,這些都會改變,在維持這種僅僅耦合的關係的時候,你會發現你的代碼特別脆弱,隨便改一處數據庫的schema,整個java項目可能要改幾十次。而且現在mybatis的自動mapping做的也很好,開發起來也沒花多長時間,等項目進入中後期,你需要大量定製和優化查詢的時候,mybatis的開發效率就更明顯了。

4、debug難

作為一個後端程序員,我比較喜歡每一行代碼我都精確知道它到底在幹什麼。尤其是數據庫訪問的代碼,往往系統的瓶頸就在這些地方產生,每一行都不能小看。我看過hibernate早期版本的部分代碼,比我想象的複雜和強大很多。的確很多地方Hibernate可以強大的只用一行代碼解決很多問題,但比如說一個update()或者save()到底做了什麼,這裡既有hibernate本身的邏輯,也有你應用的邏輯,如果這一行產生了問題,你該如何去做?我個人覺得這是很難搞的,還不如一開始費點事,用mybatis這種。

作為一個程序員,我始終堅持認為改代碼比改配置文件容易。

5、hibernate更新大批量數據

(1)hibernate批量更新customers表中大於零的所有記錄的age字段:

<code>Transaction transaction = session.beginTransaction();Iterator customers=session.find("from Customer c where c.age>0").iterator();while(customers.hasNext()){    Customer customer=(Customer)customers.next();    customer.setAge(customer.getAge()+1);}transaction.commit();session.close();/<code>

如果customers表中有一萬條年齡大於零的記錄,那麼session的find()方法會一下子加載一萬個customer對象到內存中。當執行tx.commit()方法時,會清理緩存,hibernate執行一萬條更新customers表的update語句:

<code>update CUSTOMERS set AGE=? …. where ID=i;/<code>

(2)以上hibernate批量更新方式有兩個缺點

  • 佔用大量內存空間,必須把一萬個customer對象先加載到內存,然後一一更新他們。
  • 執行的update語句的數目太多,每個update語句只能更新一個Customer對象,必須通過1萬條update語句才能更新一萬個Customer對象,頻繁的訪問數據庫,會大大降低應用的性能。

(3)為了迅速釋放1萬個Customer對象佔用的內存,可以在更新每個Customer對象後,就調用Session的evict()方法立即釋放它的內存:

<code>Transaction transaction = session.beginTransaction();Iterator customers=session.find("from Customer c where c.age>0").iterator();while(customers.hasNext()){    Customer customer=(Customer)customers.next();    customer.setAge(customer.getAge()+1);    session.flush();    session.evict(customer);}transaction.commit();session.close();/<code>

在以上程序中,修改了一個Customer對象的age屬性後,就立即調用Session的flush()方法和evict()方法,flush()方法使hibernate立刻根據這個Customer對象的狀態變化同步更新數據庫,從而立即執行相關的update()語句;evict()方法用於把這個Customer對象從緩存中清除出去,從而及時釋放它佔用的內存。

但evict()方法只能稍微提高批量操作的性能,因為不管有沒有使用evict()方法,Hibernate都必須執行1萬條update語句,才能更新1萬個Customer對象,這是影響批量操作性能的重要因素。假如Hibernate能直接執行如下SQL語句:

<code>update CUSTOMERS set AGEAGE=AGE+1 where AGE>0;/<code>

那麼以上一條update語句就能更新CUSTOMERS表中的1萬條記錄。但是Hibernate並沒有直接提供執行這種update語句的接口。應用程序必須繞過Hibernate API,直接通過JDBC API來執行該SQL語句:

<code>Transaction transaction = session.beginTransaction();Connection con=session.connection();PreparedStatement stmt=con.prepareStatement("update CUSTOMERS set AGEAGE=AGE+1 where AGE>0 ");stmt.executeUpdate();     transaction.commit();  /<code>

以上程序演示了繞過Hibernate API,直接通過JDBC API訪問數據庫的過程。應用程序通過Session的connection()方法獲得該Session使用的數據庫連接,然後通過它創建 PreparedStatement對象並執行SQL語句。值得注意的是,應用程序仍然通過Hibernate的Transaction接口來聲明事務邊 界。 如果底層數據庫(如Oracle)支持存儲過程,也可以通過存儲過程來執行Hibernate批量更新。存儲過程直接在數據庫中運行,速度更加快。在Oracle數據庫中可以定義一個名為batchUpdateCustomer()的存儲過程,代碼如下:

<code>create or replace procedure batchUpdateCustomer(p_age in number) as     begin     update CUSTOMERS set AGEAGE=AGE+1 where AGE>p_age;end;/<code>

以上存儲過程有一個參數p_age,代表客戶的年齡,應用程序可按照以下方式調用存儲過程:

<code>Transaction transaction = session.beginTransaction();Connection con=session.connection();String procedure = "{call batchUpdateCustomer(?) }";CallableStatement cstmt = con.prepareCall(procedure);cstmt.setInt(1,0); //把年齡參數設為0cstmt.executeUpdate();     transaction.commit();  /<code>

從上面程序看出,應用程序也必須繞過Hibernate API,直接通過JDBC API來調用存儲過程。

6、hibernate刪除大批量數據

Session的各種重載形式的update()方法都一次只能更新一個對象,而delete()方法的有些重載形式允許以HQL語句作為參數,例如:

<code>session.delete("from Customer c where c.age>0");/<code>

如果CUSTOMERS表中有1萬條年齡大於零的記錄,那麼以上代碼能刪除一萬條記錄。但是Session的delete()方法並沒有執行以下delete語句

<code>delete from CUSTOMERS where AGE>0;/<code>

Session的delete()方法先通過以下select語句把1萬個Customer對象加載到內存中:

<code>select * from CUSTOMERS where AGE>0;/<code>

接下來執行一萬條delete語句,逐個刪除Customer對象:

<code>delete from CUSTOMERS where ID=i;delete from CUSTOMERS where ID=j;delete from CUSTOMERS where ID=k;/<code>

由 此可見,直接通過Hibernate API進行Hibernate批量更新和Hibernate批量刪除都不值得推薦。而直接通過JDBC API執行相關的SQL語句或調用存儲過程,是hibernate批量更新和批量刪除的最佳方式。


三、群眾的眼光的雪亮的,千萬不要逆天而行

Hibernate 這麼硬核,為什麼用的人少?

Hibernate 這麼硬核,為什麼用的人少?

Hibernate 這麼硬核,為什麼用的人少?

Hibernate 這麼硬核,為什麼用的人少?

Hibernate 這麼硬核,為什麼用的人少?


四、被噴之後的一些感悟

感覺就是一場批鬥大會,我深深的感覺到才疏學淺的無奈,我真的只是想好好學習而已,希望若干年後,我還能初心不改。

作為一個初級程序員而言,沒有必要花費過多的時間去證明技術的無用論,我並沒有對hibernate這個框架進行深入的研究,只是在膚淺的層面的一些個人感悟。

框架本身並沒有對錯一說,只有適合不適合,任何框架都有其自身的能力範圍,hibernate封裝了很多有用的API給我們,降低了操作數據庫的難度和複雜度,同時也減少了模板代碼的數量,但hibernate留給開發者的可操作空間相對mybatis少了很多;mybatis框架使用起來更加靈活,開發者可以自定義查詢語句,但增加了模板代碼量,看起來並沒有hibernate邊界。兩種框架在便捷與靈活兩個指標上做出了取捨和妥協,這不能說是框架的錯。對於一個框架而言,需要有自身專注的領域和設計願景,不可能面面俱到,就如這位大哥所言:

Hibernate 這麼硬核,為什麼用的人少?

使用任何一種技術框架,都需要貼合現實的業務需求以及自身的技術能力。當你還沒有深入的去了解一門技術或者當前業務需求無法與框架契合時,不要盲目的批判框架的好壞。


分享到:


相關文章: