1. 程式人生 > >java面試小結——框架(hibernate、MyBatis、spring、Spring MVC)

java面試小結——框架(hibernate、MyBatis、spring、Spring MVC)

Hibernate

什麼是ORM

物件關係對映(Object-Relational Mapping,簡稱ORM)是一種為了解決程式的面向物件模型與資料庫的關係模型互不匹配問題的技術;簡單的說,ORM是通過使用描述物件和資料庫之間對映的元資料(在Java中可以用XML或者是註解),將程式中的物件自動持久化到關係資料庫中或者將關係資料庫表中的行轉換成Java物件,其本質上就是將資料從一種形式轉換到另外一種形式。

持久層設計要考慮的問題有哪些?你用過的持久層框架有哪些?

所謂”持久”就是將資料儲存到可掉電式儲存裝置中以便今後使用,簡單的說,就是將記憶體中的資料儲存到關係型資料庫、檔案系統、訊息佇列等提供持久化支援的裝置中。持久層就是系統中專注於實現資料持久化的相對獨立的層面。

持久層設計的目標包括:
- 資料儲存邏輯的分離,提供抽象化的資料訪問介面。
- 資料訪問底層實現的分離,可以在不修改程式碼的情況下切換底層實現。
- 資源管理和排程的分離,在資料訪問層實現統一的資源排程(如快取機制)。
- 資料抽象,提供更面向物件的資料操作。

持久層框架有:
- Hibernate
- MyBatis
- TopLink
- Guzz
- jOOQ
- Spring Data
- ActiveJDBC

Hibernate中SessionFactory是執行緒安全的嗎?Session是執行緒安全的嗎(兩個執行緒能夠共享同一個Session嗎)?

SessionFactory對應Hibernate的一個數據儲存的概念,它是執行緒安全的,可以被多個執行緒併發訪問。SessionFactory一般只會在啟動的時候構建。對於應用程式,最好將SessionFactory通過單例模式進行封裝以便於訪問。Session是一個輕量級非執行緒安全的物件(執行緒間不能共享session),它表示與資料庫進行互動的一個工作單元。Session是由SessionFactory建立的,在任務完成之後它會被關閉。Session是持久層服務對外提供的主要介面。Session會延遲獲取資料庫連線(也就是在需要的時候才會獲取)。為了避免建立太多的session,可以使用ThreadLocal將session和當前執行緒繫結在一起,這樣可以讓同一個執行緒獲得的總是同一個session。Hibernate 3中SessionFactory的getCurrentSession()方法就可以做到。

Hibernate中Session的load和get方法的區別是什麼?

主要有以下三項區別:
① 如果沒有找到符合條件的記錄,get方法返回null,load方法丟擲異常。
② get方法直接返回實體類物件,load方法返回實體類物件的代理。
③ 在Hibernate 3之前,get方法只在一級快取中進行資料查詢,如果沒有找到對應的資料則越過二級快取,直接發出SQL語句完成資料讀取;load方法則可以從二級快取中獲取資料;從Hibernate 3開始,get方法不再是對二級快取只寫不讀,它也是可以訪問二級快取的。

說明:對於load()方法Hibernate認為該資料在資料庫中一定存在可以放心的使用代理來實現延遲載入,如果沒有資料就丟擲異常,而通過get()方法獲取的資料可以不存在。

Session的save()、update()、merge()、lock()、saveOrUpdate()和persist()方法分別是做什麼的?有什麼區別?

Hibernate的物件有三種狀態:瞬時態(transient)、持久態(persistent)和遊離態(detached),如第下題中的圖所示。瞬時態的例項可以通過呼叫save()、persist()或者saveOrUpdate()方法變成持久態;遊離態的例項可以通過呼叫 update()、saveOrUpdate()、lock()或者replicate()變成持久態。save()和persist()將會引發SQL的INSERT語句,而update()或merge()會引發UPDATE語句。save()和update()的區別在於一個是將瞬時態物件變成持久態,一個是將遊離態物件變為持久態。merge()方法可以完成save()和update()方法的功能,它的意圖是將新的狀態合併到已有的持久化物件上或建立新的持久化物件。對於persist()方法,按照官方文件的說明:① persist()方法把一個瞬時態的例項持久化,但是並不保證識別符號被立刻填入到持久化例項中,識別符號的填入可能被推遲到flush的時間;② persist()方法保證當它在一個事務外部被呼叫的時候並不觸發一個INSERT語句,當需要封裝一個長會話流程的時候,persist()方法是很有必要的;③ save()方法不保證第②條,它要返回識別符號,所以它會立即執行INSERT語句,不管是在事務內部還是外部。至於lock()方法和update()方法的區別,update()方法是把一個已經更改過的脫管狀態的物件變成持久狀態;lock()方法是把一個沒有更改過的脫管狀態的物件變成持久狀態。

闡述實體物件的三種狀態以及轉換關係。

最新的Hibernate文件中為Hibernate物件定義了四種狀態(原來是三種狀態,面試的時候基本上問的也是三種狀態),分別是:瞬時態(new, or transient)、持久態(managed, or persistent)、遊狀態(detached)和移除態(removed,以前Hibernate文件中定義的三種狀態中沒有移除態),如下圖所示,就以前的Hibernate文件中移除態被視為是瞬時態。
這裡寫圖片描述

  • 瞬時態:當new一個實體物件後,這個物件處於瞬時態,即這個物件只是一個儲存臨時資料的記憶體區域,如果沒有變數引用這個物件,則會被JVM的垃圾回收機制回收。這個物件所儲存的資料與資料庫沒有任何關係,除非通過Session的save()、saveOrUpdate()、persist()、merge()方法把瞬時態物件與資料庫關聯,並把資料插入或者更新到資料庫,這個物件才轉換為持久態物件。
  • 持久態:持久態物件的例項在資料庫中有對應的記錄,並擁有一個持久化標識(ID)。對持久態物件進行delete操作後,資料庫中對應的記錄將被刪除,那麼持久態物件與資料庫記錄不再存在對應關係,持久態物件變成移除態(可以視為瞬時態)。持久態物件被修改變更後,不會馬上同步到資料庫,直到資料庫事務提交。
  • 遊離態:當Session進行了close()、clear()、evict()或flush()後,實體物件從持久態變成遊離態,物件雖然擁有持久和與資料庫對應記錄一致的標識值,但是因為物件已經從會話中清除掉,物件不在持久化管理之內,所以處於遊離態(也叫脫管態)。遊離態的物件與臨時狀態物件是十分相似的,只是它還含有持久化標識。

Query介面的list方法和iterate方法有什麼區別?

① list()方法無法利用一級快取和二級快取(對快取只寫不讀),它只能在開啟查詢快取的前提下使用查詢快取;iterate()方法可以充分利用快取,如果目標資料只讀或者讀取頻繁,使用iterate()方法可以減少效能開銷。
② list()方法不會引起N+1查詢問題,而iterate()方法可能引起N+1查詢問題

Hibernate如何實現分頁查詢?

通過Hibernate實現分頁查詢,開發人員只需要提供HQL語句(呼叫Session的createQuery()方法)或查詢條件(呼叫Session的createCriteria()方法)、設定查詢起始行數(呼叫Query或Criteria介面的setFirstResult()方法)和最大查詢行數(呼叫Query或Criteria介面的setMaxResults()方法),並呼叫Query或Criteria介面的list()方法,Hibernate會自動生成分頁查詢的SQL語句。

鎖機制有什麼用?簡述Hibernate的悲觀鎖和樂觀鎖機制。

有些業務邏輯在執行過程中要求對資料進行排他性的訪問,於是需要通過一些機制保證在此過程中資料被鎖住不會被外界修改,這就是所謂的鎖機制。
Hibernate支援悲觀鎖和樂觀鎖兩種鎖機制。悲觀鎖,顧名思義悲觀的認為在資料處理過程中極有可能存在修改資料的併發事務(包括本系統的其他事務或來自外部系統的事務),於是將處理的資料設定為鎖定狀態。悲觀鎖必須依賴資料庫本身的鎖機制才能真正保證資料訪問的排他性,關於資料庫的鎖機制和事務隔離級別在《Java面試題大全(上)》(http://www.importnew.com/22083.html)中已經討論過了。樂觀鎖,顧名思義,對併發事務持樂觀態度(認為對資料的併發操作不會經常性的發生),通過更加寬鬆的鎖機制來解決由於悲觀鎖排他性的資料訪問對系統性能造成的嚴重影響。最常見的樂觀鎖是通過資料版本標識來實現的,讀取資料時獲得資料的版本號,更新資料時將此版本號加1,然後和資料庫表對應記錄的當前版本號進行比較,如果提交的資料版本號大於資料庫中此記錄的當前版本號則更新資料,否則認為是過期資料無法更新。Hibernate中通過Session的get()和load()方法從資料庫中載入物件時可以通過引數指定使用悲觀鎖;而樂觀鎖可以通過給實體類加整型的版本欄位再通過XML或@Version註解進行配置。

提示:使用樂觀鎖會增加了一個版本欄位,很明顯這需要額外的空間來儲存這個版本欄位,浪費了空間,但是樂觀鎖會讓系統具有更好的併發性,這是對時間的節省。因此樂觀鎖也是典型的空間換時間的策略。

如何理解Hibernate的延遲載入機制?在實際應用中,延遲載入與Session關閉的矛盾是如何處理的?

延遲載入就是並不是在讀取的時候就把資料載入進來,而是等到使用時再載入。Hibernate使用了虛擬代理機制實現延遲載入,我們使用Session的load()方法載入資料或者一對多關聯對映在使用延遲載入的情況下從一的一方載入多的一方,得到的都是虛擬代理,簡單的說返回給使用者的並不是實體本身,而是實體物件的代理。代理物件在使用者呼叫getter方法時才會去資料庫載入資料。但載入資料就需要資料庫連線。而當我們把會話關閉時,資料庫連線就同時關閉了。

延遲載入與session關閉的矛盾一般可以這樣處理:
① 關閉延遲載入特性。這種方式操作起來比較簡單,因為Hibernate的延遲載入特性是可以通過對映檔案或者註解進行配置的,但這種解決方案存在明顯的缺陷。首先,出現”no session or session was closed”通常說明系統中已經存在主外來鍵關聯,如果去掉延遲載入的話,每次查詢的開銷都會變得很大。
② 在session關閉之前先獲取需要查詢的資料,可以使用工具方法Hibernate.isInitialized()判斷物件是否被載入,如果沒有被載入則可以使用Hibernate.initialize()方法載入物件。
③ 使用攔截器或過濾器延長Session的生命週期直到檢視獲得資料。Spring整合Hibernate提供的OpenSessionInViewFilter和OpenSessionInViewInterceptor就是這種做法。

舉一個多對多關聯的例子,並說明如何實現多對多關聯對映。

例如:商品和訂單、學生和課程都是典型的多對多關係。可以在實體類上通過@ManyToMany註解配置多對多關聯或者通過對映檔案中的和標籤配置多對多關聯,但是實際專案開發中,很多時候都是將多對多關聯對映轉換成兩個多對一關聯對映來實現的。

談一下你對繼承對映的理解

繼承關係的對映策略有三種:
① 每個繼承結構一張表(table per class hierarchy),不管多少個子類都用一張表。
② 每個子類一張表(table per subclass),公共資訊放一張表,特有資訊放單獨的表。
③ 每個具體類一張表(table per concrete class),有多少個子類就有多少張表。
第一種方式屬於單表策略,其優點在於查詢子類物件的時候無需表連線,查詢速度快,適合多型查詢;缺點是可能導致表很大。後兩種方式屬於多表策略,其優點在於資料儲存緊湊,其缺點是需要進行連線查詢,不適合多型查詢。

簡述Hibernate常見優化策略

這個問題應當挑自己使用過的優化策略回答,常用的有:
① 制定合理的快取策略(二級快取、查詢快取)。
② 採用合理的Session管理機制。
③ 儘量使用延遲載入特性。
④ 設定合理的批處理引數。
⑤ 如果可以,選用UUID作為主鍵生成器。
⑥ 如果可以,選用基於版本號的樂觀鎖替代悲觀鎖。
⑦ 在開發過程中, 開啟hibernate.show_sql選項檢視生成的SQL,從而瞭解底層的狀況;開發完成後關閉此選項。
⑧ 考慮資料庫本身的優化,合理的索引、恰當的資料分割槽策略等都會對持久層的效能帶來可觀的提升,但這些需要專業的DBA(資料庫管理員)提供支援。

談一談Hibernate的一級快取、二級快取和查詢快取。

Hibernate的Session提供了一級快取的功能,預設總是有效的,當應用程式儲存持久化實體、修改持久化實體時,Session並不會立即把這種改變提交到資料庫,而是快取在當前的Session中,除非顯示呼叫了Session的flush()方法或通過close()方法關閉Session。通過一級快取,可以減少程式與資料庫的互動,從而提高資料庫訪問效能。
SessionFactory級別的二級快取是全域性性的,所有的Session可以共享這個二級快取。不過二級快取預設是關閉的,需要顯示開啟並指定需要使用哪種二級快取實現類(可以使用第三方提供的實現)。一旦開啟了二級快取並設定了需要使用二級快取的實體類,SessionFactory就會快取訪問過的該實體類的每個物件,除非快取的資料超出了指定的快取空間。
一級快取和二級快取都是對整個實體進行快取,不會快取普通屬性,如果希望對普通屬性進行快取,可以使用查詢快取。查詢快取是將HQL或SQL語句以及它們的查詢結果作為鍵值對進行快取,對於同樣的查詢可以直接從快取中獲取資料。查詢快取預設也是關閉的,需要顯示開啟。

Hibernate中DetachedCriteria類是做什麼的?

DetachedCriteria和Criteria的用法基本上是一致的,但Criteria是由Session的createCriteria()方法建立的,也就意味著離開建立它的Session,Criteria就無法使用了。DetachedCriteria不需要Session就可以建立(使用DetachedCriteria.forClass()方法建立),所以通常也稱其為離線的Criteria,在需要進行查詢操作的時候再和Session繫結(呼叫其getExecutableCriteria(Session)方法),這也就意味著一個DetachedCriteria可以在需要的時候和不同的Session進行繫結。

@OneToMany註解的mappedBy屬性有什麼作用?

@OneToMany用來配置一對多關聯對映,但通常情況下,一對多關聯對映都由多的一方來維護關聯關係,例如學生和班級,應該在學生類中新增班級屬性來維持學生和班級的關聯關係(在資料庫中是由學生表中的外來鍵班級編號來維護學生表和班級表的多對一關係),如果要使用雙向關聯,在班級類中新增一個容器屬性來存放學生,並使用@OneToMany註解進行對映,此時mappedBy屬性就非常重要。如果使用XML進行配置,可以用< set>標籤的inverse=”true”設定來達到同樣的效果。

MyBatis

MyBatis中使用#和$書寫佔位符有什麼區別?

將傳入的資料都當成一個字串,會對傳入的資料自動加上引號; $ 將傳入的資料直接顯示生成在SQL中。注意:使用$佔位符可能會導致SQL注射攻擊,能用#的地方就不要使用$,寫order by子句的時候應該用$而不是#。

解釋一下MyBatis中名稱空間(namespace)的作用

在大型專案中,可能存在大量的SQL語句,這時候為每個SQL語句起一個唯一的標識(ID)就變得並不容易了。為了解決這個問題,在MyBatis中,可以為每個對映檔案起一個唯一的名稱空間,這樣定義在這個對映檔案中的每個SQL語句就成了定義在這個名稱空間中的一個ID。只要我們能夠保證每個名稱空間中這個ID是唯一的,即使在不同對映檔案中的語句ID相同,也不會再產生衝突了。

MyBatis中的動態SQL是什麼意思?

對於一些複雜的查詢,我們可能會指定多個查詢條件,但是這些條件可能存在也可能不存在,例如在58同城上面找房子,我們可能會指定面積、樓層和所在位置來查詢房源,也可能會指定面積、價格、戶型和所在位置來查詢房源,此時就需要根據使用者指定的條件動態生成SQL語句。如果不使用持久層框架我們可能需要自己拼裝SQL語句,還好MyBatis提供了動態SQL的功能來解決這個問題。MyBatis中用於實現動態SQL的元素主要有:
- if
- choose / when / otherwise
- trim
- where
- set
- foreach
下面是對映檔案的片段。

<select id="foo" parameterType="Blog" resultType="Blog">
     select * from t_blog where 1 = 1
     <if test="title != null">
         and title = #{title}
     </if>
     <if test="content != null">
         and content = #{content}
     </if>
     <if test="owner != null">
         and owner = #{owner}
     </if>
</select>

當然也可以像下面這些書寫。

<select id="foo" parameterType="Blog" resultType="Blog">
    select * from t_blog where 1 = 1
    <choose>
        <when test="title != null">
            and title = #{title}
        </when>
        <when test="content != null">
            and content = #{content}
        </when>
        <otherwise>
            and owner = "owner1"
        </otherwise>
    </choose>
</select>

再看看下面這個例子。

<select id="bar" resultType="Blog">
    select * from t_blog where id in
    <foreach collection="array" index="index"
        item="item" open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

Hibernate與Mybatis比較

第一方面:開發速度的對比

就開發速度而言,Hibernate的真正掌握要比Mybatis來得難些。Mybatis框架相對簡單很容易上手,但也相對簡陋些。個人覺得要用好Mybatis還是首先要先理解好Hibernate。
·
比起兩者的開發速度,不僅僅要考慮到兩者的特性及效能,更要根據專案需求去考慮究竟哪一個更適合專案開發,比如:一個專案中用到的複雜查詢基本沒有,就是簡單的增刪改查,這樣選擇hibernate效率就很快了,因為基本的sql語句已經被封裝好了,根本不需要你去寫sql語句,這就節省了大量的時間,但是對於一個大型專案,複雜語句較多,這樣再去選擇hibernate就不是一個太好的選擇,選擇mybatis就會加快許多,而且語句的管理也比較方便。

第二方面:開發工作量的對比

Hibernate和MyBatis都有相應的程式碼生成工具。可以生成簡單基本的DAO層方法。針對高階查詢,Mybatis需要手動編寫SQL語句,以及ResultMap。而Hibernate有良好的對映機制,開發者無需關心SQL的生成與結果對映,可以更專注於業務流程。

第三方面:sql優化方面

Hibernate的查詢會將表中的所有欄位查詢出來,這一點會有效能消耗。Hibernate也可以自己寫SQL來指定需要查詢的欄位,但這樣就破壞了Hibernate開發的簡潔性。而Mybatis的SQL是手動編寫的,所以可以按需求指定查詢的欄位。
·
Hibernate HQL語句的調優需要將SQL打印出來,而Hibernate的SQL被很多人嫌棄因為太醜了。MyBatis的SQL是自己手動寫的所以調整方便。但Hibernate具有自己的日誌統計。Mybatis本身不帶日誌統計,使用Log4j進行日誌記錄。

第四方面:物件管理的對比

Hibernate 是完整的物件/關係對映解決方案,它提供了物件狀態管理(state management)的功能,使開發者不再需要理會底層資料庫系統的細節。也就是說,相對於常見的 JDBC/SQL 持久層方案中需要管理 SQL 語句,Hibernate採用了更自然的面向物件的視角來持久化 Java 應用中的資料。
·
換句話說,使用 Hibernate 的開發者應該總是關注物件的狀態(state),不必考慮 SQL 語句的執行。這部分細節已經由Hibernate掌管妥當,只有開發者在進行系統性能調優的時候才需要進行了解。而MyBatis在這一塊沒有文件說明,使用者需要對物件自己進行詳細的管理。

第五方面:快取機制

Hibernate快取

Hibernate一級快取是Session快取,利用好一級快取就需要對Session的生命週期進行管理好。建議在一個Action操作中使用一個Session。一級快取需要對Session進行嚴格管理。
·
Hibernate二級快取是SessionFactory級的快取。
·
SessionFactory的快取分為內建快取和外接快取。內建快取中存放的是SessionFactory物件的一些集合屬性包含的資料(對映元素據及預定SQL語句等),對於應用程式來說,它是隻讀的。外接快取中存放的是資料庫資料的副本,其作用和一級快取類似.二級快取除了以記憶體作為儲存介質外,還可以選用硬碟等外部儲存裝置。二級快取稱為程序級快取或SessionFactory級快取,它可以被所有session共享,它的生命週期伴隨著SessionFactory的生命週期存在和消亡。

MyBatis快取

MyBatis 包含一個非常強大的查詢快取特性,它可以非常方便地配置和定製。MyBatis 3 中的快取實現的很多改進都已經實現了,使得它更加強大而且易於配置。
·
預設情況下是沒有開啟快取的,除了區域性的 session 快取,可以增強變現而且處理迴圈 依賴也是必須的。要開啟二級快取,你需要在你的 SQL 對映檔案中新增一行: < cache/>

字面上看就是這樣。這個簡單語句的效果如下:

  1. 對映語句檔案中的所有 select 語句將會被快取。
  2. 對映語句檔案中的所有 insert,update 和 delete 語句會重新整理快取。
  3. 快取會使用 Least Recently Used(LRU,最近最少使用的)演算法來收回。
  4. 根據時間表(比如 no Flush Interval,沒有重新整理間隔), 快取不會以任何時間順序 來重新整理。
  5. 快取會儲存列表集合或物件(無論查詢方法返回什麼)的 1024 個引用。
  6. 快取會被視為是 read/write(可讀/可寫)的快取,意味著物件檢索不是共享的,而且可以安全地被呼叫者修改,而不干擾其他呼叫者或執行緒所做的潛在修改。 所有的這些屬性都可以通過快取元素的屬性來修改。

比如:

<cache  eviction=”FIFO”  flushInterval=”60000″  size=”512″  readOnly=”true”/>

這個更高階的配置建立了一個 FIFO 快取,並每隔 60 秒重新整理,存數結果物件或列表的 512 個引用,而且返回的物件被認為是隻讀的,因此在不同執行緒中的呼叫者之間修改它們會導致衝突。可用的收回策略有, 預設的是 LRU:

  1. LRU – 最近最少使用的:移除最長時間不被使用的物件。
  2. FIFO – 先進先出:按物件進入快取的順序來移除它們。
  3. SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的物件。
  4. WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的物件。

flushInterval(重新整理間隔)可以被設定為任意的正整數,而且它們代表一個合理的毫秒形式的時間段。預設情況是不設定,也就是沒有重新整理間隔,快取僅僅呼叫語句時重新整理。
·
size(引用數目)可以被設定為任意正整數,要記住你快取的物件數目和你執行環境的 可用記憶體資源數目。預設值是1024。
·
readOnly(只讀)屬性可以被設定為 true 或 false。只讀的快取會給所有呼叫者返回快取物件的相同例項。因此這些物件不能被修改。這提供了很重要的效能優勢。可讀寫的快取 會返回快取物件的拷貝(通過序列化)。這會慢一些,但是安全,因此預設是 false。

相同點:Hibernate和Mybatis的二級快取除了採用系統預設的快取機制外,都可以通過實現你自己的快取或為其他第三方快取方案,建立介面卡來完全覆蓋快取行為。

不同點:Hibernate的二級快取配置在SessionFactory生成的配置檔案中進行詳細配置,然後再在具體的表-物件對映中配置是那種快取。

MyBatis的二級快取配置都是在每個具體的表-物件對映中進行詳細配置,這樣針對不同的表可以自定義不同的快取機制。並且Mybatis可以在名稱空間中共享相同的快取配置和例項,通過Cache-ref來實現。

兩者比較:因為Hibernate對查詢物件有著良好的管理機制,使用者無需關心SQL。所以在使用二級快取時如果出現髒資料,系統會報出錯誤並提示。

而MyBatis在這一方面,使用二級快取時需要特別小心。如果不能完全確定資料更新操作的波及範圍,避免Cache的盲目使用。否則,髒資料的出現會給系統的正常執行帶來很大的隱患。

第六方面:總結

對於總結,大家可以到各大java論壇去看一看

相同點:Hibernate與MyBatis都可以是通過SessionFactoryBuider由XML配置檔案生成SessionFactory,然後由SessionFactory 生成Session,最後由Session來開啟執行事務和SQL語句。其中SessionFactoryBuider,SessionFactory,Session的生命週期都是差不多的。
·
Hibernate和MyBatis都支援JDBC和JTA事務處理。

Mybatis優勢

MyBatis可以進行更為細緻的SQL優化,可以減少查詢欄位。 MyBatis容易掌握,而Hibernate門檻較高。

Hibernate優勢

  • Hibernate的DAO層開發比MyBatis簡單,Mybatis需要維護SQL和結果對映。
  • Hibernate對物件的維護和快取要比MyBatis好,對增刪改查的物件的維護要方便。
  • Hibernate資料庫移植性很好,MyBatis的資料庫移植性不好,不同的資料庫需要寫不同SQL。
  • Hibernate有更好的二級快取機制,可以使用第三方快取。MyBatis本身提供的快取機制不佳。

他人總結

  • Hibernate功能強大,資料庫無關性好,O/R對映能力強,如果你對Hibernate相當精通,而且對Hibernate進行了適當的封裝,那麼你的專案整個持久層程式碼會相當簡單,需要寫的程式碼很少,開發速度很快,非常爽。
  • Hibernate的缺點就是學習門檻不低,要精通門檻更高,而且怎麼設計O/R對映,在效能和物件模型之間如何權衡取得平衡,以及怎樣用好Hibernate方面需要你的經驗和能力都很強才行。
  • iBATIS入門簡單,即學即用,提供了資料庫查詢的自動物件繫結功能,而且延續了很好的SQL使用經驗,對於沒有那麼高的物件模型要求的專案來說,相當完美。
  • iBATIS的缺點就是框架還是比較簡陋,功能尚有缺失,雖然簡化了資料繫結程式碼,但是整個底層資料庫查詢實際還是要自己寫的,工作量也比較大,而且不太容易適應快速資料庫修改。

Spring

什麼是IoC和DI?DI是如何實現的?

IoC叫控制反轉,是Inversion of Control的縮寫,DI(Dependency Injection)叫依賴注入,是對IoC更簡單的詮釋。控制反轉是把傳統上由程式程式碼直接操控的物件的呼叫權交給容器,通過容器來實現物件元件的裝配和管理。所謂的”控制反轉”就是對元件物件控制權的轉移,從程式程式碼本身轉移到了外部容器,由容器來建立物件並管理物件之間的依賴關係。IoC體現了好萊塢原則 – “Don’t call me, we will call you”。依賴注入的基本原則是應用元件不應該負責查詢資源或者其他依賴的協作物件。配置物件的工作應該由容器負責,查詢資源的邏輯應該從應用元件的程式碼中抽取出來,交給容器來完成。DI是對IoC更準確的描述,即元件之間的依賴關係由容器在執行期決定,形象的來說,即由容器動態的將某種依賴關係注入到元件之中。

舉個例子:一個類A需要用到介面B中的方法,那麼就需要為類A和介面B建立關聯或依賴關係,最原始的方法是在類A中建立一個介面B的實現類C的例項,但這種方法需要開發人員自行維護二者的依賴關係,也就是說當依賴關係發生變動的時候需要修改程式碼並重新構建整個系統。如果通過一個容器來管理這些物件以及物件的依賴關係,則只需要在類A中定義好用於關聯介面B的方法(構造器或setter方法),將類A和介面B的實現類C放入容器中,通過對容器的配置來實現二者的關聯。

依賴注入可以通過setter方法注入(設值注入)、構造器注入和介面注入三種方式來實現,Spring支援setter注入和構造器注入,通常使用構造器注入來注入必須的依賴關係,對於可選的依賴關係,則setter注入是更好的選擇,setter注入需要類提供無參構造器或者無參的靜態工廠方法來建立物件。

Spring中Bean的作用域有哪些?

在Spring的早期版本中,僅有兩個作用域:singleton和prototype,前者表示Bean以單例的方式存在;後者表示每次從容器中呼叫Bean時,都會返回一個新的例項,prototype通常翻譯為原型。

補充:設計模式中的建立型模式中也有一個原型模式,原型模式也是一個常用的模式,例如做一個室內設計軟體,所有的素材都在工具箱中,而每次從工具箱中取出的都是素材物件的一個原型,可以通過物件克隆來實現原型模式。

Spring 2.x中針對WebApplicationContext新增了3個作用域,分別是:request(每次HTTP請求都會建立一個新的Bean)、session(同一個HttpSession共享同一個Bean,不同的HttpSession使用不同的Bean)和globalSession(同一個全域性Session共享一個Bean)。

說明:單例模式和原型模式都是重要的設計模式。一般情況下,無狀態或狀態不可變的類適合使用單例模式。在傳統開發中,由於DAO持有Connection這個非執行緒安全物件因而沒有使用單例模式;但在Spring環境下,所有DAO類對可以採用單例模式,因為Spring利用AOP和Java API中的ThreadLocal對非執行緒安全的物件進行了特殊處理。

ThreadLocal為解決多執行緒程式的併發問題提供了一種新的思路。ThreadLocal,顧名思義是執行緒的一個本地化物件,當工作於多執行緒中的物件使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒分配一個獨立的變數副本,所以每一個執行緒都可以獨立的改變自己的副本,而不影響其他執行緒所對應的副本。從執行緒的角度看,這個變數就像是執行緒的本地變數。

ThreadLocal類非常簡單好用,只有四個方法,能用上的也就是下面三個方法:
- void set(T value):設定當前執行緒的執行緒區域性變數的值。
- T get():獲得當前執行緒所對應的執行緒區域性變數的值。
- void remove():刪除當前執行緒中執行緒區域性變數的值。

ThreadLocal是如何做到為每一個執行緒維護一份獨立的變數副本的呢?在ThreadLocal類中有一個Map,鍵為執行緒物件,值是其執行緒對應的變數的副本,自己要模擬實現一個ThreadLocal類其實並不困難,程式碼如下所示:

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class MyThreadLocal<T> {
    private Map<Thread, T> map = Collections.synchronizedMap(new HashMap<Thread, T>());

    public void set(T newValue) {
        map.put(Thread.currentThread(), newValue);
    }

    public T get() {
        return map.get(Thread.currentThread());
    }

    public void remove() {
        map.remove(Thread.currentThread());
    }
}

解釋一下什麼叫AOP(面向切面程式設計)?

AOP(Aspect-Oriented Programming)指一種程式設計範型,該範型以一種稱為切面(aspect)的語言構造為基礎,切面是一種新的模組化機制,用來描述分散在物件、類或方法中的橫切關注點(crosscutting concern)。

你是如何理解”橫切關注”這個概念的?

”橫切關注”是會影響到整個應用程式的關注功能,它跟正常的業務邏輯是正交的,沒有必然的聯絡,但是幾乎所有的業務邏輯都會涉及到這些關注功能。通常,事務、日誌、安全性等關注就是應用中的橫切關注功能。

你如何理解AOP中的連線點(Joinpoint)、切點(Pointcut)、增強(Advice)、引介(Introduction)、織入(Weaving)、切面(Aspect)這些概念?

a. 連線點(Joinpoint):程式執行的某個特定位置(如:某個方法呼叫前、呼叫後,方法丟擲異常後)。一個類或一段程式程式碼擁有一些具有邊界性質的特定點,這些程式碼中的特定點就是連線點。Spring僅支援方法的連線點。
b. 切點(Pointcut):如果連線點相當於資料中的記錄,那麼切點相當於查詢條件,一個切點可以匹配多個連線點。Spring AOP的規則解析引擎負責解析切點所設定的查詢條件,找到對應的連線點。
c. 增強(Advice):增強是織入到目標類連線點上的一段程式程式碼。Spring提供的增強介面都是帶方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。很多資料上將增強譯為“通知”,這明顯是個詞不達意的翻譯,讓很多程式設計師困惑了許久。

說明: Advice在國內的很多書面資料中都被翻譯成”通知”,但是很顯然這個翻譯無法表達其本質,有少量的讀物上將這個詞翻譯為”增強”,這個翻譯是對Advice較為準確的詮釋,我們通過AOP將橫切關注功能加到原有的業務邏輯上,這就是對原有業務邏輯的一種增強,這種增強可以是前置增強、後置增強、返回後增強、拋異常時增強和包圍型增強。

d. 引介(Introduction):引介是一種特殊的增強,它為類新增一些屬性和方法。這樣,即使一個業務類原本沒有實現某個介面,通過引介功能,可以動態的未該業務類新增介面的實現邏輯,讓業務類成為這個介面的實現類。
e. 織入(Weaving):織入是將增強新增到目標類具體連線點上的過程,AOP有三種織入方式:①編譯期織入:需要特殊的Java編譯期(例如AspectJ的ajc);②裝載期織入:要求使用特殊的類載入器,在裝載類的時候對類進行增強;③執行時織入:在執行時為目標類生成代理實現增強。Spring採用了動態代理的方式實現了執行時織入,而AspectJ採用了編譯期織入和裝載期織入的方式。
f. 切面(Aspect):切面是由切點和增強(引介)組成的,它包括了對橫切關注功能的定義,也包括了對連線點的定義。

補充:代理模式是GoF提出的23種設計模式中最為經典的模式之一,代理模式是物件的結構模式,它給某一個物件提供一個代理物件,並由代理物件控制對原物件的引用。簡單的說,代理物件可以完成比原物件更多的職責,當需要為原物件新增橫切關注功能時,就可以使用原物件的代理物件。我們在開啟Office系列的Word文件時,如果文件中有插圖,當文件剛載入時,文件中的插圖都只是一個虛框佔位符,等使用者真正翻到某頁要檢視該圖片時,才會真正載入這張圖,這其實就是對代理模式的使用,代替真正圖片的虛框就是一個虛擬代理;Hibernate的load方法也是返回一個虛擬代理物件,等使用者真正需要訪問物件的屬性時,才向資料庫發出SQL語句獲得真實物件。

下面用一個找槍手代考的例子演示代理模式的使用:

/**
 * 參考人員介面
 * @author 駱昊
 *
 */
public interface Candidate {

    /**
     * 答題
     */
    public void answerTheQuestions();
}

/**
 * 懶學生
 * @author 駱昊
 *
 */
public class LazyStudent implements Candidate {
    private String name;        // 姓名

    public LazyStudent(String name) {
        this.name = name;
    }

    @Override
    public void answerTheQuestions() {
        // 懶學生只能寫出自己的名字不會答題
        System.out.println("姓名: " + name);
    }

}

/**
 * 槍手
 * @author 駱昊
 *
 */
public class Gunman implements Candidate {
    private Candidate target;   // 被代理物件

    public Gunman(Candidate target) {
        this.target = target;
    }

    @Override
    public void answerTheQuestions() {
        // 槍手要寫上代考的學生的姓名
        target.answerTheQuestions();
        // 槍手要幫助懶學生答題並交卷
        System.out.println("奮筆疾書正確答案");
        System.out.println("交卷");
    }

}

public class ProxyTest1 {

    public static void main(String[] args) {
        Candidate c = new Gunman(new LazyStudent("王小二"));
        c.answerTheQuestions();
    }
}

說明:從JDK 1.3開始,Java提供了動態代理技術,允許開發者在執行時建立介面的代理例項,主要包括Proxy類和InvocationHandler介面。下面的例子使用動態代理為ArrayList編寫一個代理,在新增和刪除元素時,在控制檯列印新增或刪除的元素以及ArrayList的大小:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;

public class ListProxy<T> implements InvocationHandler {
    private List<T> target;

    public ListProxy(List<T> target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object retVal = null;
        System.out.println("[" + method.getName() + ": " + args[0] + "]");
        retVal = method.invoke(target, args);
        System.out.println("[size=" + target.size() + "]");
        return retVal;
    }

}
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

public class ProxyTest2 {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        Class<?> clazz = list.getClass();
        ListProxy<String> myProxy = new ListProxy<String>(list);
        List<String> newList = (List<String>) 
                Proxy.newProxyInstance(clazz.getClassLoader(), 
                clazz.getInterfaces(), myProxy);
        newList.add("apple");
        newList.add("banana");
        newList.add("orange");
        newList.remove("banana");
    }
}

說明:使用Java的動態代理有一個侷限性就是代理的類必須要實現介面,雖然面向介面程式設計是每個優秀的Java程式都知道的規則,但現實往往不盡如人意,對於沒有實現介面的類如何為其生成代理呢?繼承!繼承是最經典的擴充套件已有程式碼能力的手段,雖然繼承常常被初學者濫用,但繼承也常常被進階的程式設計師忽視。CGLib採用非常底層的位元組碼生成技術,通過為一個類建立子類來生成代理,它彌補了Java動態代理的不足,因此Spring中動態代理和CGLib都是建立代理的重要手段,對於實現了介面的類就用動態代理為其生成代理類,而沒有實現介面的類就用CGLib通過繼承的方式為其建立代理。

Spring中自動裝配的方式有哪些?

  • no:不進行自動裝配,手動設定Bean的依賴關係。
  • byName:根據Bean的名字進行自動裝配。
  • byType:根據Bean的型別進行自動裝配。
  • constructor:類似於byType,不過是應用於構造器的引數,如果正好有一個Bean與構造器的引數型別相同則可以自動裝配,否則會導致錯誤。
  • autodetect:如果有預設的構造器,則通過constructor的方式進行自動裝配,否則使用byType的方式進行自動裝配。

說明:自動裝配沒有自定義裝配方式那麼精確,而且不能自動裝配簡單屬性(基本型別、字串等),在使用時應注意。

Spring中如何使用註解來配置Bean?有哪些相關的註解?

首先需要在Spring配置檔案中增加如下配置:

<context:component-scan base-package="org.example"/>

然後可以用@Component、@Controller、@Service、@Repository註解來標註需要由Spring IoC容器進行物件託管的類。這幾個註解沒有本質區別,只不過@Controller通常用於控制器,@Service通常用於業務邏輯類,@Repository通常用於倉儲類(例如我們的DAO實現類),普通的類用@Component來標註。

Spring支援的事務管理型別有哪些?你在專案中使用哪種方式?

Spring支援程式設計式事務管理和宣告式事務管理。許多Spring框架的使用者選擇宣告式事務管理,因為這種方式和應用程式的關聯較少,因此更加符合輕量級容器的概念。宣告式事務管理要優於程式設計式事務管理,儘管在靈活性方面它弱於程式設計式事務管理,因為程式設計式事務允許你通過程式碼控制業務。

事務分為全域性事務和區域性事務。全域性事務由應用伺服器管理,需要底層伺服器JTA支援(如WebLogic、WildFly等)。區域性事務和底層採用的持久化方案有關,例如使用JDBC進行持久化時,需要使用Connetion物件來操作事務;而採用Hibernate進行持久化時,需要使用Session物件來操作事務。

Spring提供瞭如下所示的事務管理器。

事務管理器實現類 目標物件
DataSourceTransactionManager 注入DataSource
HibernateTransactionManager 注入SessionFactory
JdoTransactionManager 管理JDO事務
JtaTransactionManager 使用JTA管理事務
PersistenceBrokerTransactionManager 管理Apache的OJB事務

這些事務的父介面都是PlatformTransactionManager。Spring的事務管理機制是一種典型的策略模式,PlatformTransactionManager代表事務管理介面,該介面定義了三個方法,該介面並不知道底層如何管理事務,但是它的實現類必須提供getTransaction()方法(開啟事務)、commit()方法(提交事務)、rollback()方法(回滾事務)的多型實現,這樣就可以用不同的實現類代表不同的事務管理策略。使用JTA全域性事務策略時,需要底層應用伺服器支援,而不同的應用伺服器所提供的JTA全域性事務可能存在細節上的差異,因此實際配置全域性事務管理器是可能需要使用JtaTransactionManager的子類,如:WebLogicJtaTransactionManager(Oracle的WebLogic伺服器提供)、UowJtaTransactionManager(IBM的WebSphere伺服器提供)等。

程式設計式事務管理如下所示。

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:p="http://www.springframework.org/schema/p"
    xmlns:p="http://www.springframework.org/schema/context"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

     <context:component-scan base-package="com.jackfrued"/>

     <bean id="propertyConfig"
         class="org.springframework.beans.factory.config.
  PropertyPlaceholderConfigurer">
         <property name="location">
             <value>jdbc.properties</value>
         </property>
     </bean>

     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
         <property name="driverClassName">
             <value>${db.driver}</value>
         </property>
         <property name="url">
             <value>${db.url}</value>
         </property>
         <property name="username">
             <value>${db.username}</value>
         </property>
         <property name="password">
             <value>${db.password}</value>
         </property>
     </bean>

     <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
         <property name="dataSource">
             <ref bean="dataSource" />
         </property>
     </bean>

     <!-- JDBC事務管理器 -->
     <bean id="transactionManager"
         class="org.springframework.jdbc.datasource.
       DataSourceTransactionManager" scope="singleton">
         <property name="dataSource">
             <ref bean="dataSource" />
         </property>
     </bean>

     <!-- 宣告事務模板 -->
     <bean id="transactionTemplate"
         class="org.springframework.transaction.support.
   TransactionTemplate">
         <property name="transactionManager">
             <ref bean="transactionManager" />
         </property>
     </bean>

</beans>
package com.jackfrued.dao.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;

import com.jackfrued.dao.EmpDao;
import com.jackfrued.entity.Emp;

@Repository
public class EmpDaoImpl implements EmpDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public boolean save(Emp emp) {
        String sql = "insert into emp values (?,?,?)";
        return jdbcTemplate.update(sql, emp.getId(), emp.getName(), emp.getBirthday()) == 1;
    }

}
package com.jackfrued.biz.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import com.jackfrued.biz.EmpService;
import com.jackfrued.dao.EmpDao;
import com.jackfrued.entity.Emp;


            
           

相關推薦

java面試小結——框架hibernateMyBatisspringSpring MVC

Hibernate 什麼是ORM 物件關係對映(Object-Relational Mapping,簡稱ORM)是一種為了解決程式的面向物件模型與資料庫的關係模型互不匹配問題的技術;簡單的說,ORM是通過使用描述物件和資料庫之間對映的元資料(在Ja

java利用myeclipse自帶三大框架搭建三大框架Hibernate+Struts2+Spring過程詳解

sun 過程 9.png att alt 分享圖片 struts apach sch 搭建過程因人而異,我的搭建過程大致是這樣的:   1.創建一個javaweb項目;   2.導入Spring框架,上圖:     2.1:     2.2:     2.3:   3.

持久層框架Hibernate

true 技術分享 mysql 分享圖片 pac transacti cat tid localhost 一、Hibernate處理關系 關系主要有三種:1、多對一  2、一對多  3、多對多 1、多對一 一個Product對應一個Category,一個Category對應

Java類集框架:Stack及Properties子類Collections工具類

Stack子類 在java.util包內可以利用stack類實現棧的功能。此類定義如下: public class Stack<E> extends Vector<E> Stack類常用方法: 方法 型別

java面試--基礎6框架

SpringMVC Spring MVC處理請求流程 使用者發起請求--進入前端控制器(進入springmvc的入口也是一個Servlet) 前端控制器會根據使用者請求的不同調用對應的頁面控制器(Servlet) 頁面控制器呼叫biz,dao之後會得到一個數據

java基礎--集合框架強弱

字符 收集 erro sts 軟引用 每一個 框架 bject 缺陷 (1) 隊列:單向和雙向 一、單向:一端操作 1、一般:FIFO 2、優先和堆棧: LIFO 二、雙向:兩端操作,頭或尾操作 package com.zwj.que; import

Java 面試基礎總結

tor rac 時有 線程安全 lec getclass ron 接口 add 1、九種基本數據類型的大小以及它們的封裝類 java提供的九種基本數據類型:boolean、byte(1)、char(2)、short(2)、int(4)、long(8)、float(4)、do

java 面試基礎總結---多線程

future sync 一個 副本 void all call ava task 1、實現多線程的三種方法 1.繼成Thread 類,覆蓋run()方法即可 2.implements Runnable接口 3.implements Callale接口,執行時通過Future

Java面試總結——如何處理項目的高並發大數據

Java Java開發 Java面試 鎮樓×××姐1.HTML靜態化如果網站的請求量過大,我們可以將頁面靜態化提供訪問來緩解服務器壓力,能夠緩解服務器壓力加大以及降低數據庫數據的頻繁交換。適合於某些訪問了過大,但是內容不經常改變的頁面,如首頁、新聞頁等2.文件服務器顧名思義,文件服務器就是將文件系

java 自學簡單框架反射+註解

spa 最終 reflect lec 分享 image c99 inf png 1、先定義一個學生類 2、再定義一個teacher類(這個是為了練習多個註解,自己練習可以 不寫這個) 3、再定義個一個學生老師類(這個是為了最終調用上面的那個學生類做準備) 4、下面開始

Java類集框架:偶物件儲存:Map介面

Map集合可以儲存一對關聯資料(按照“key = value”的形式)。 Map介面的常用方法 方法 型別 描述 public V put(K key, V value) 普通

Java類集框架:集合輸出

集合輸出的4種形式:Iterator輸出、ListIterator輸出、foreach(加強型for迴圈)輸出、Enumeration輸出。 迭代輸出:Iterator Iterator(迭代器)是集合輸出操作的一個介面,Collection介面中提供了直接為Iterator介面例項

Java類集框架:Set子介面

Set子介面只是簡單地繼承了Collection介面,並沒有擴充其他的方法。Set集合中不允許儲存重複的資料。在Set介面下有兩個常用的子類:HashSet、TreeSet。HashSet是雜湊存放資料,而TreeSet是有序存放的子類,預設按照字母的升序排列。在實際開發中如果沒有排序要求,

Java類集框架:List子介面

List子介面最大的功能是裡面儲存的資料可以存在重複的內容。List介面在對Collection介面擴充的方法如下: - public E get(int index):普通,取得索引編號的內容 - public E set(int index, E element):普通,修改指定索引

Java類集框架:簡介及Collection介面

簡介 在實際專案中,涉及到儲存多個物件的操作往往會用到陣列。然而傳統的陣列存在一個問題:長度是固定的。Java 2提供了一個專門實現資料結構的開發框架——類集框架,框架的程式介面和類都儲存在java.util包中,其最為核心的用處就在於實現了動態物件陣列的操作,定義了大量的操作標準,核心

Java Fork Join 框架實現

作者:Doug Lea  譯者:Alex  校對:方騰飛 這個框架是由大約800行純Java程式碼組成,主要的類是FJTaskRunner,它是java.lang.Thread的子類。FJTasks 自己僅僅維持一個關於結束狀態的布林值,所有其他的操作都是通過當前的工作執行緒來代理完成的。J

Java Fork Join 框架效能

4效能 如今,隨著編譯器與Java虛擬機器效能的不斷提升,效能測試結果也僅僅只能適用一時。但是,本節中所提到的測試結果資料卻能揭示Fork/join框架的基本特性。 下面表格中簡單介紹了在下文將會用到的一組fork/join測試程式。這些程式是從util.concurrent包裡的示例程式碼

JAVA面試700問

原文地址 譯者:葉文海([email protected]) 1、Java環境中的位元組碼是什麼? 由Java 編譯器生成的一種程式碼。 由JVM生成的一種程式碼。 Java原始檔(Java Source File)的別名。 一種寫在類的例項方法中的程式碼。 答案:由Java 編

Java併發——鎖框架讀寫鎖

1. 讀寫鎖機制——ReadWriteLock介面 讀寫鎖適用於對資料結構頻繁讀而較少修改的場景。舉個栗子,你可以建立一個線上詞典供多條讀執行緒併發讀取,然而單條寫執行緒可能會不時新增新的定義或更新已有的定義。一個資源可以被多個執行緒同時讀,或者被一個執行緒寫,但是不能同時存在讀和寫執行緒。&n

Java併發——鎖框架重入鎖ReentrantLock

1. 重入鎖 重入鎖,顧名思義,就是支援重進入的鎖,它表示該鎖能夠支援一個執行緒對資源的重複加鎖。重進入是指任意執行緒在獲取到鎖之後能夠再次獲取該鎖而不會被鎖阻塞。兩個關鍵問題: (1)執行緒再次獲取鎖。當一條執行緒持有這個鎖並且呼叫lock()、lockUninterruptibly()或