使用Spring 3.1和Hibernate做持久層

Liszt發表於2012-01-05

原文連結如下: http://www.baeldung.com/2011/12/02/the-persistence-layer-with-spring-3-1-and-hibernate/

特別說明:感謝大家積極參與【iTran樂譯】第2期

這是關於Spring持久化系列文章的第一篇。這篇文章將關注使用Spring 3.1和Hibernate來配置和實現持久化層。關於如何一步一步建立基於java配置的Spring上下文和專案的基礎Maven pom,參看這裡

Spring持久化系列:

**Part 2** – [Simplifying the Data Access Layer with Spring and Java Generics][2]
**Part 3** – [The Persistence Layer with Spring 3.1 and JPA][3]
**Part 4** – [The Persistence Layer with Spring Data JPA][4]
**Part 5** – [Transaction configuration with JPA and Spring 3.1][5]

沒有Spring Templates

從Spring 3.0 and Hibernate 3.0.1開始,我們不再需要使用Spring的*HibernateTemplat*e來管理Hibernate Session,而我們或許可以利用contextual sessions(直接被Hibernate管理的session,他們在一個事務範圍是保持活動的)。

因此,現在的最佳時間就是直接使用Hibernate API而不是HibernateTemplate,這樣便可以有效地將DAO層的實現與Spring完全解耦。

不用template做異常翻譯

HibernateTemplate的職責之一就是異常翻譯,即將低層次的Hibernate異常(它們與Hibernate API耦合緊密)翻譯成高層次,泛化的Spring異常。(譯註:HibernateTemplate即將具體的ORM框架的異常泛化,達到具體ORM與ORM呼叫程式碼的解耦)。

現在即使不用template去幹這些事,異常翻譯仍可以通過為DAO加上@Repository註釋來實現。因為,所有每個被@Repository註釋標記的bean都會被Spring上下午委派一個PersistenceExceptionTranslator作為這個bean的postprocessor。(譯註:postprocessor是Spring bean的一個機制,即呼叫每次呼叫完方法後,都會執行一次postprocessor,此處當你的DAO執行完Hibernate API之後,postprocessor會自動翻譯Hiberante產生的異常)

異常翻譯是通過代理實現的,為了讓Spring能夠建立一個包裹DAO類的代理,DAO類一定不能被宣告為final

不用template管理Hibernate Session

當Hiberante支援contextual session後,HibernateTemplate基本已經過時了;實際上,這個類的javadoc已經被更新如下提示:

注意: 從Hibernate 3.0.1後, Hibernate事務處理(transactional Hibernate access)程式碼也可
以以簡單的Hiberante方式書寫。因此,對於新開始的專案來說,可以考慮採用標準的Hibernate3
風格訪問資料物件而不是基於{@link org.hibernate.SessionFactory#getCurrentSession()}

基於JAVA註釋的Spring

通過在配置檔案中建立一個Spring factory bean(AnnotationSessionFactoryBean),我們可以建立和管理Hibernate SessionFactory。這會啟用通過classpath掃描自動偵測實體類(entity classes)的功能。注意:使用這個功能需要 Hibernate 3.2+ 和JDK 1.5+。

一個可選的方案是為session factory bean手動指定所有被註釋標記的實體類(entity classes),通過使用setAnnotatedClasses方法。

@Configuration
@EnableTransactionManagement
public class PersistenceHibernateConfig{

   @Bean
   public AnnotationSessionFactoryBean alertsSessionFactory(){
      AnnotationSessionFactoryBean sessionFactory = new AnnotationSessionFactoryBean();
      sessionFactory.setDataSource( this.restDataSource() );
      sessionFactory.setPackagesToScan( new String[ ] { "org.rest" } );
      sessionFactory.setHibernateProperties( this.hibernateProperties() );

      return sessionFactory;
   }
   @Bean
   public DataSource restDataSource(){
      DriverManagerDataSource dataSource = new DriverManagerDataSource();
      dataSource.setDriverClassName( this.driverClassName );
      dataSource.setUrl( this.url );
      dataSource.setUsername( "restUser" );
      dataSource.setPassword( "restmy5ql" );

      return dataSource;
   }
   @Bean
   public HibernateTransactionManager transactionManager(){
      HibernateTransactionManager txManager = new HibernateTransactionManager();
      txManager.setSessionFactory( this.alertsSessionFactory().getObject() );

      return txManager;
   }
   @Bean
   public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
      return new PersistenceExceptionTranslationPostProcessor();
   }

}

Also, note that cglib must be on the classpath for Java @Configuration classes to work; to better understand the need for cglib as a dependency, see this article. 同樣要注意,為了能使@Configuration類能工作,cglib一定要再classpath裡找得到。想更好地理解依賴cglib的需求,參看這篇文章

The Spring XML configuration Spring XML配置

同樣的配置(使用XML方式) enter image description here

基於XML的配置和基於JAVA註釋的配置有一些小的區別。在XML中,一個指向其他bean的引用既可以指向bean或者指向一個用於生成這個bean的工廠。然而在java中,編譯器不允許那樣做,所以SessionFactory首先會從它的工廠裡取出,然後傳遞給transaction manager:

transactionManager.setSessionFactory( this.alertsSessionFactory().getObject() );

Hibernate屬性檔案

通過如下Hibernate屬性配置,實現與Spring的協作:

hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.hbm2ddl.auto=update
hibernate.show_sql=false

注意:MySQL方言只是配置在這裡只是做一個參考,任何Hiberate支援的方言都可以使用。

潛在異常

事務工廠

Hibernate建立事務的協議是通過TransactionFactory介面指定的。為了讓Spring能夠完全管理事務,這個協議的預設實現(JDBCTransactionFactory )會被其相對的預設的Spring實現(SpringTransactionFactory)替換。

它可以通過手動在Hibernate屬性檔案中實現。(這樣做有點多餘): *transaction.factory_class=org.springframework.orm.hibernate3.SpringTransactionFactory*

當前的Session Context

當Hibernate SessionFactory在Spring的上下文裡被它的factory bean建立後,它會建立CurrentSessionContext。這個協定是為了支援current session概念,它的實現通過分析“*hibernate.current_session_context_class*” Hibernate屬性來決定。

將這個屬性設定為“managed”意味著使用為contextual sessions使用受管理的實現(ManagedSessionContext )。這樣就假設了current session正被一個外部實體(external entity)所管理。在我們的Spring上下文裡,那樣會失敗:

org.springframework.orm.hibernate3.HibernateSystemException: 
No session currently bound to execution context

在Hibernate配置裡,將屬性設定為“thread”將允許thread-bound strategy;它同樣會與Spring事務管理衝突並造成如下結果:

org.springframework.orm.hibernate3.HibernateSystemException: 
persist is not valid without active transaction

為了讓Spring管理事務,這個屬性是必須的“org.springframework.orm.hibernate3.SpringSessionContext”;因為這個設定同樣是預設的,所以顯示的屬性定義將被刪除。

DAO

每一個DAO都給予一個引數化的,抽象的,支援常用泛化操作的DAO類:

public abstract class AbstractHibernateDAO< T extends Serializable >{
   private final Class< T > clazz;

   @Autowired
   SessionFactory sessionFactory;

   public void setClazz( final Class< T > clazzToSet ){
      this.clazz = clazzToSet;
   }

   public T findOne( final Long id ){
      Preconditions.checkArgument( id != null );
      return (T) this.getCurrentSession().get( this.clazz, id );
   }
   public List< T > findAll(){
      return this.getCurrentSession()
       .createQuery( "from " + this.clazz.getName() ).list();
   }

   public void save( final T entity ){
      Preconditions.checkNotNull( entity );
      this.getCurrentSession().persist( entity );
   }

   public void update( final T entity ){
      Preconditions.checkNotNull( entity );
      this.getCurrentSession().merge( entity );
   }

   public void delete( final T entity ){
      Preconditions.checkNotNull( entity );
      this.getCurrentSession().delete( entity );
   }
   public void deleteById( final Long entityId ){
      final T entity = this.getById( entityId );
      Preconditions.checkState( entity != null );
      this.delete( entity );
   }

   protected final Session getCurrentSession(){
      return this.sessionFactory.getCurrentSession();
   }
}

這裡有些有趣的事情 - 在之前的討論,抽象DAO不繼承任何Spring template(比如HibernateTemplate)。然而,Hibernate SessionFactory被直接注入到DAO中,然後它將會扮演主要Hibernate API的角色,通過contextual Session可以看到:
this.sessionFactory.getCurrentSession();

同樣注意:為用作泛化操作,實體類在建構函式裡被傳遞:

@Repository
public class FooDAO extends AbstractHibernateDAO< Foo > implements IFooDAO{

   public FooDAO(){
      setClazz(Foo.class );
   }

}

Maven配置

除了之前文章定義的Maven配置以外,還需要加入以下依賴:spring-ormhibernate-core: enter image description here

注意對於MySQL被放在這裡作為一個參考( 一個驅動需要配置一個資料來源),任何Hibernate支援的資料庫都可以使用。

總結

這篇文章涵蓋了使用Hibernate和Spring 3.1做持久層的配置和實現(包含使用XML方式和java註釋方式)。討論了停止依賴Spring template做DAO層的理由和配置Spring管理事務和Hibernate Session的陷阱。最後給出了一個輕量級的,乾淨的DAO實現(在編譯時幾乎不依賴Spring)。你可以從github上下載完整的專案

相關文章