1. 程式人生 > >【Spring】【筆記】《Spring In Action》第5章 資料庫處理

【Spring】【筆記】《Spring In Action》第5章 資料庫處理

5.1 Spring資料訪問原理      DAO 資料訪問物件(data access object)。      DAO提供了資料讀取和寫入到資料庫中的一種方式。他們應該以介面的方式釋出功能,而應用程式的其他部分就可以通過介面來進行訪問了。      實現了鬆耦合程式碼 5.1.1 Spring資料訪問異常體系      不與特定的持久化方式相關聯。這意味著可以使用Spring丟擲一致的異常,而不用關心所選擇的持久化方案。 Spring的資料訪問異常:
  • CannotAcquireLockException
  • CannotSerializeTransactionException
  • CleanupFailureDataAccessException
  • ConcurrencyFailureException
  • DataAccessException
  • DataAccessResourceFailureException
  • DataIntegrityViolationException
  • DataRetrievalFailureException
  • DeadlockLoserDataAccessException
  • EmptyResultDataAccessException
  • IncorrectResultSizeDataAceessException
  • InvalidDataAccessApiUsageException
  • InvalidDataAccessResourceUsageException
  • OptimisticLockingFailureException
  • PermissionDeniedDataAccessException
  • PessimisticLockingFailureException
  • PessimisticLockingFailureException
  • TypeMismatchDataAccessException
  • UncategorizedDataAccessException
     ...      這些異常都繼承自DataAccessException,DataAccessException是一個非檢查型異常,沒有必要去捕獲Spring所丟擲的資料訪問異常,這是因為Spring認為觸發異常的很多問題是不能在catch程式碼塊中修復的。因此開發人員可以選擇是否編寫catch程式碼塊。 5.1.2 資料訪問模板化      模板方法模式:定義了過程的主要框架,將過程中與特點實現相關的部分委託給介面,而這個介面的不同實現定義了過程中的具體行為。      Spring在資料訪問中使用的就是模板方法模式。      Spring將資料訪問過程中固定的和可變的部分明確劃分為兩個不同的類:模板 template 、回撥 callback,模板管理過程中固定的部分,回撥處理自定義的資料訪問程式碼。      Spring的處理邏輯
DAO模組 DAO回撥
1.準備資源 2.開始事務 3.在事務中執行
5.提交/回滾事務 6.關閉資源和處理錯誤 4.返回資料
     Spring的模板類處理資料訪問的固定部分——事務控制、管理資源、異常處理。應用程式相關的資料訪問(建立語句、繫結引數以及整理結果集)在回撥的實現中處理      針對不同的持久化平臺,Spring提供了多個可選的模板
模板類(org.springframework.*) 用途
jca.cci.core.CciTemplate JCA CCI連線
jdbc.core.JdbcTemplate JDBC連線
jdbc.core.namedparam.NamedParameterJdbcTemplate 支援命名引數的JDBC連線
jdbc.core.simple.SimpleJdbcTemplate 通過Java 5簡化後的JDBC連線
orm.hibernate.HibernateTemplate Hibernate 2.x的Session
orm.hibernate3.HibernateTemplate Hibernate 3.x的Session
orm.ibatis.SqlMapClientTemplate iBATIS SqlMap客戶端
orm.jdo.JdoTemplate Java資料物件(Java Data Object)實現
orm.jpa.JpaTemplate Java持久化API的實體管理器
     使用資料訪問模板只需將其配置為Spring上下文中的Bean並將其織入到應用程式的DAO中。或者使用Spring的DAO支援類進一步簡化應用程式的DAO配置。 5.1.3 DAO支援類      基於模板-回撥設計,Spring提供了DAO支援類,而將業務自己的DAO類作為它的子類。當編寫應用程式自己的DAO實現時,可以繼承自DAO支援類並呼叫模板獲取方法來直接訪問底層的資料訪問模板。Spring不僅提供了多個數據模板實現類,還為每種模板提供了對應的DAO支援類。
DAO支援類(org.springframework.*) 為誰提供DAO支援
jca.cci.support.CcciDaoSupport JCA CCI連線
jdbc.core.support.JdbcDaoSupport JDBC連線
jdbc.core.namedparam.NamedParameterJdbcDaoSupport 帶有命名引數的JDBC連線
jdbc.core.simple.SimpleJdbcDaoSupport 用Java5進行簡化的JDBC連線
orm.hibernate.support.HibernateDaoSupport Hibernate2.x的Session
orm.hibernate3.support.HibernateDaoSupport Hibernate3.x的Session
orm.ibatis.support.SqlMapClientDaoSupport iBaTIS SqlMap客戶端
orm.jdo.support.JdoDaoSupport Java資料物件(Java Data Object)實現
orm.jpa.support.JpaDaoiSupport Java持久化API的試題管理器
5.2 配置資料來源      無論選擇哪一種SpringDAO的支援方式,都需要配置一個數據源的引用。Spring提供了在Spring上下文中配置資料來源Bean的多種方式,包括:
  • 通過JDBC驅動程式定義的資料來源
  • 通過JNDI查詢的資料來源
  • 連線池的資料來源
5.2.1 使用JNDI資料來源   
      JNDI(Java Naming and Directory Interface,Java命名和目錄介面)是SUN公司提供的一種標準的Java命名系統介面,JNDI提供統一的客戶端API,通過不同的訪問提供者介面JNDI服務供應介面(SPI)的實現,由管理者將JNDI API對映為特定的命名服務和目錄系統,使得Java應用程式可以和這些命名服務和目錄服務之間進行互動
     完全可以在應用程式之外對資料來源進行管理,應用程式只需在訪問資料庫的時候查詢資料來源就可以了。      利用Spring,可以像使用SpringBean那樣配置JNDI中資料來源的引用,並將其裝配到需要的類中。jee名稱空間下的<jee:jndi-lookup>元素可以用於檢索JNDI中的任何物件包括資料來源,並將其用於SpringBean中。 eg:  <jee:jndi-lookup id="dataSource"         jndi-name="/jdbc/SpitterDS"         resource-ref="true"/>     jndi-name指定JNDI中資源的名稱。如果只設置了jndi-name屬性,那麼就會根據指定的名稱查詢資料來源。但是如果應用程式執行在Java應用程式伺服器中,則需要將resource-ref屬性設定為true,這樣給定的jndi-name將會自動新增java:comp/env字首 5.2.2 使用資料來源連線池      Spring並沒有提供資料來源連線池實現      Spring推薦使用DBCP(Jakarta Commons Database Connection Pooling)
所需jar:commons-dbcp.jar、commons-pool.jar
     DBCP包含了多個提供連線池功能的資料來源,其中BasicDataSource是最常用的,因為它易於在Spring中配置,而且類似於Spring自帶的DriverManagerDataSource。      例如: <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">      <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>      <property name="url" value="jdbc:hsqldb:hsql://localhost/spitter/spitter"/>      <property name="username" value="sa"/>      <property name="password" value=""/>      <property name="initialSize" value="5"/>      <property name="maxActive" value="10"/> </bean>      前4個屬性是配置BasicDataSource所必須的。屬性driverClassName制定了JDBC驅動類的全限定類名。此外還有多個屬性用來配置資料來源連線池。
池配置屬性 所指定的內容
initialSize 池啟動時建立的連線數量
maxActive 同一時間可從池中分配的最多連線數,設定為0表示無限制
maxIdle 池裡不會被釋放的最多控線連線,設定為0表示無限制
maxOpenPreparedStatements 在同一時間能夠從語句池中分配的預處理語句的最大數量,設定為0表示無限制
maxWait 在丟擲 異常之前,池等待連接回收的最大時間(當沒有可用連線時)。如果設定為-1,表示無限等待
minEvictableIdleTimeMillis 連線在池中保持空閒而不被回收的最大時間
minIdle 在不建立新連線的情況下,池中保持空閒的最小連線數
poolPreparedStatements 是否對預處理語句進行池管理(布林值)
5.2.3基於JDBC驅動的資料來源      Spring提供了兩種資料來源物件,均位於org.springframework.jdbc.datasource
  • DriverManagerDataSource:在每個連線請求時都會返回一個新建的連線。與DBCP的BasicDataSource不同,由DriverManagerDataSource提供的連線並沒有進行池化管理
  • SingleConnectionDataSource:在每個連線請求時都會返回同一個連線。可以視為只有一個連線的池,但不是嚴格意義上的連線池資料來源
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">      <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>      <property name="url" value="jdbc:hsqldb:hsql://localhost/spitter/spitter"/>      <property name="username" value="sa"/>      <property name="password" value=""/> </bean>      SingleConnectionDataSource有且只有一個數據庫連線,所以不適用於多執行緒的應用程式。儘管DriverManagerDataSource支援多執行緒,但是在每次請求連線時都會建立新連線,這是以效能為代價的。因此推薦使用資料來源連線池。 5.3 在Spring中使用JDBC 5.3.1 應對失控的JDBC程式碼      大致就是JDBC程式碼太長了、很麻煩、但很重要等等等等…… 5.3.2 使用JDBC模板      Spring為JDBC提供了3個模板類供使用
  • JDBCTemplate:最基本的Spring JDBC模板,這個模板支援最簡單的JDBC資料庫訪問功能以及簡單的索引引數查詢
  • NamedParameterJdbcTemplate:使用該模板類執行查詢時,可以將查詢值以命名引數的形式繫結到SQL中,而不是使用簡單的索引引數
  • SimpleJdbctemplate:該模板類利用Java 5的一些特性,如自動裝箱、泛型以及可變引數列表來簡化JDBC模板的使用。
     Spring3.0之後SimpleJdbcTemplate是最好的選擇 使用SimpleJdbcTemplate訪問資料      在Spring4.2中,已經沒有SimpleJdbcTemplate了。      只需要設定DataSource就能夠讓SimpleJdbcTemplate正常工作。      Spring中配置SimpleJdbcTemplate < bean id= "jdbcTemplate" class= "org.springframework.jdbc.core.simple.SimpleJdbcTemplate" >      <constructor-arg ref = "dataSource"/> </bean >      屬性dataSource所引用的dataSource可以是javax.sql.DataSource的任意實現 使用命名引數      命名引數可以賦予SQL中的每個引數一個明確的名字,在繫結值到查詢語句的時候就通過該名字來引用引數。 使用Spring的JDBC DAO支援類      在Spring4.2中,已經沒有SimpleJdbcTemplateDaoSupport      DaoSupport類就是把每一個Dao類需要完成的相同工作抽離出來,形成一個父類,讓所有的dao類去繼承它,減少重複程式碼。 5.4 在Spring中整合Hibernate
  • 延遲載入(Lazy loading):藉助於延遲載入,可以只抓取需要的資料。
  • 預先抓取(Eager fetching):與延遲載入相對。藉助預先抓取,可以使用一個查詢獲取完整的關聯物件。
  • 級聯(Cascading):更改資料庫中的表會同時修改其他表
     ORM框架(物件/關係對映 object-relational mapping,ORM)提供了這些服務。在持久層使用ORM工具可以節省程式碼數量和開發時間。      Spring對對多個持久化框架都提供了支援,包括Hibernate、iBATIS、JDO、Java持久化API。      與Spring對JDBC的支援那樣,Spring對ORM框架的支援提供了與這些框架的整合店以及一些附加的服務:
  • Spring宣告式事務的繼承支援
  • 透明的異常處理
  • 執行緒安全的、輕量級的模板類
  • DAO支援類
  • 資源管理
5.4.1 Hibernate概覽      以前,在Spring應用程式中使用Hibernate是通過HibernateTemplate進行的,其職責之一是管理Hibernate的Session。HibernateTemplate的不足之處在於存在一定程度的侵入性。在DAO中使用HibernateTemplate(無論是直接使用還是通過HibernateDaoSupport)時,DAO類就會與SpringAPI產生了耦合。      Hibernate3中引入了上下文Session(Contextual session),這是Hibernate本身所提供的保證每個事務使用同一Session的方案,因此沒有必要再使用HibernateTemplate來保證這一行為。這種方式可以讓自定義的DAO類不包含特定的Spring程式碼。 5.4.2 宣告Hibernate的Session工廠      使用Hibernate的主要介面是org.hibernate.Session,Session介面提供了基本的資料訪問功能,通過Hibernate的Session介面,應用程式的DAO能夠滿足所有的持久化需求。      獲取Hibernate Session物件的標準方式是藉助於Hibernate的SessionFactory介面的實現類。除了一些其他的任務,SessionFactory主要負責HibernateSession的開啟、關閉以及管理。      在Spring中,通過Spring的某一個Hibernate Session 工廠Bean來獲取Hibernate的SessionFactory。可以在Spring上下文中,像配置其他Bean那樣來配置Hibernate Session工廠。配置是要確定持久化域物件時通過XML檔案還是通過註解來進行配置的。如果選擇在XML中定義物件與資料庫之間的對映,那麼需要在Spring中配置LocalSessionFactoryBean <bean id="sessionFactory"      class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">      <property name="dataSource" ref="dataSource"/>      <property name="mappingResources">           <list>                <value>Spitter.hbm.xml</value>           </list>      </property>      <property name="hibernateProperties">           <props>                <prop key="dialect">org.hibernate.dialect.HSQLDialect</prop>           </props>      </property> </bean>      dataSource裝配了一個DataSource Bean的引用。      mappingResources裝配了一個或多個的Hibernate對映檔案,在這些檔案中定義了應用程式的持久化策略。          hibernateProperties屬性配置了Hibernate如何進行操作的細節。      如果使用註解的方式來定義持久化,那需要使用AnnotationSessionFactoryBean來代替LocalSessionFactoryBean      < bean id= "sessionFactory"           class= "org.springframework.orm.hibernate3.annotaion.AnnotationSessionFactoryBean" >             <property name = "dataSource"      ref= "dataSource" />             <property name = "packagesToScan" value= "com.habuma.spitter.domain" />             <property name = "hibernateProperties">                  <props >                       <prop key= "dialect" >org.hibernate.dialect.HSQLDialect </prop >                  </props >             </property >       </bean >      dataSource和hibernateProperties屬性生命了從哪裡獲取資料庫連線以及要使用哪一種資料庫。package說ToScan告訴Spring掃描一個或多個包以查詢域類,這些類通過註解方式表明要使用Hibernate進行持久化。使用JPA的@Entity或@MappedSuperclass註解以及Hibernate的@Entity註解進行標註的類都會包含在內。      AnnotationSessionFactoryBean的package說ToScan屬性使用一個String所組成的陣列來表明要在那些包中查詢域類。一般情況下會使用列表(<property name="pcakagesToScan"> <list> <value>).還可以通過使用annotatedClasses屬性來將應用程式中所有的持久化類以許可權定名的方式明確列出。 5.4.3 構建不依賴於Spring的Hibernate程式碼      沒有上下文Session之時,Spring的Hibernate模板用於保證每個事務使用同一個Session。既然Hibernate自己能夠對其進行管理,那就沒有必要使用模板類了。這意味著,可以直接將HibernateSession裝配到DAO類中。 import java.util.List; import org.hibernate.SessionFactory; import org.hibernate.classic.Session; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import com.habuma.spitter.domain.Spitter; import com.habuma.spitter.domain.Spittle; @Repository public class HibernateSpitterDao implements SpitterDao {     private SessionFactory sessionFacotory;     @Autowired     public HibernateSpitterDao(SessionFacoty sessionFactory) {         this.sessionFactory = sessionFactory;     }     private Session currentSession() {         return sessionFactory.getCurrentSession();     }     public void addSpitter(Spitter spitter) {         currentSession().save(spitter);     }     public Spitter getSpitterById(long id) {         return (Spitter) currentSession().get(Spitter.class,id);     }     public void saveSpitter(Spitter spitter) {         currentSession().update(spitter);     }     ... }      使用@Autowire的可以讓Spring自動將一個SessionFactory注入到HibernateSpi忐忑日Dao的sessionFactory屬性。在currentSession()方法中,使用這個SessionF擦頭人員來獲取當前事務的Session。     @Repository註解:首先,@Repository是Spring的一種構造型註解,可以被Spring的<context:component-scan>掃描到,因此可不用去宣告這個Bean,只需配置<context:component-scan>的base-package屬性為相應的包就可以了。      為了給不適用模板的HibernateDAO新增異常轉換功能,需要在Spring應用上下文新增一個PersistenceExceptionTranslationPostProcessor  Bean。 <bean class="org.springframework.dao.annotaion.PersistenceExceptionTranslationPostProcessor"/>      它是一個Bean的後置處理程式,會在所有擁有@Repository註解的類上新增一個通知器(advisor),這樣就會補貨任何平臺相關的異常,並以Spring的非檢查型資料訪問異常的形式重新丟擲。 5.5 Spring與Java持久化API JPA是基於POJO的持久化機制,從Hibernate和Java資料物件上借鑑了很多理念並加入了Java5註解的特性。 5.5.1 配置實體管理器工廠      基於JPA的應用程式使用EntityManagerfactory的實現類來獲取EntityManager例項。JPA定義了兩種型別的實體管理器:
  • 應用程式管理型別(Application-managed):當應用程式向實體管理器工廠直接請求實體管理器時,工廠會建立一個實體管理器。在這種模式下,程式要負責開啟或關閉試題管理器並在事務中對其進行控制。這種方式的試題管理器適合於不執行在Java EE容器中的獨立應用程式。
  • 容器管理型別(Container-managed):試題管理器由JavaEE建立和管理。應用程式根本不予試題管理器工廠打交道。相反試題管理器直接通過注入或JNDI來獲取。容器負責配置實體管理器工廠。這種型別的實體管理器最適合用於JavaEE容器,在這種情況下會希望在persistence.xml指定的JPA配置之外保持一些自己對JPA的控制。
     兩者都實現了EntityManager介面。區別在於EntityManager的建立和管理方式。無論使用哪一種EntityManagerFactory,Spring都會負責管理Entity-Manager。如果使用應用程式管理型別的實體管理器,Spring承擔了應用程式的角色並以透明的方式處理EntityManager,在容器管理的場景下,Spring會擔當容器的角色。      建立這兩種試題管理器工廠的Spring工廠Bean:
  • LocalEntityManagerFactoryBean生成應用程式管理型別的EntityManagerFactory;
  • LocalContainerEntityManagerFactoryBean生成容器管理型別的EntityManagerFactory
使用應用程式管理型別的JPA      絕大部分配置檔案來源於一個名為persistence.xml的配置檔案。這個檔案必須位於類路徑下的META-INF目錄下。persistence.xml的作用在於定義一個或多個持久化單元。持久化單元是同一個資料來源下的一個或多個持久化類 <persistence xmlns="http://java.sun.com/xml/ns/persistence" version=1.0>     <persistence-unit name="spitterPU">         <class>com.habuma.spitter.domain.Spitter</class>         <class>com.habuma.spitter.domain.Spittle</class>         <properties>             <property name="toplink.jdbc.driver" value="org.hsqldb.jdbcDriver"/>             <property name="toplink.jdbc.url" value="jdbc:hsqldb:hsql://localhost/spitter/spitter"/>             <property name="toplink.jdbc.user" value="sa"/>             <property name="toplink.jdbc.password" value=""/>         </properties>     </persistence-unit> </persistence>      Spring中要配置的內容很少 <bean id="emf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">     <property name="persistenceUnitName" value="spitterPU"/> </bean>      persistenceUnitName的值就是在Persistence.xml中配置的持久化單元的名字 使用容器管理型別的JPA(相對較好)      當在容器中執行時,使用容器提供的資訊來生成EntityManagerFactory。當我們使用Spring時,就可以在Spring的應用上下文中配置JPA了。 <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">     <property name="dataSource" ref="dataSource"/>     <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/> </bean>      datasource可以是任何javax.sql.DataSource的實現      jpaVendorAdapter屬性用於致命所使用的是哪一個廠商的JPA實現。Spring提供了多個JPA廠商的介面卡:
  • EclipseLinkJpaVendorAdapter
  • HibernateJpaVendorAdapter
  • OpenJpaVendorAdapter
  • TopLinkJpaVendorAdapter
     使用Hibernate的例子: <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">      <property name="database" value="HSQL"/>      <property name="showSql" value="true"/>      <property name="generateDdl" value="false"/>      <property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect"/> </bean> Hibernate的JPA介面卡的資料庫
資料庫平臺 屬性database的值
IBM DB2 DB2
Apache Derby DERBY
H2 H2
Hypersonic HSQL
Informix INFORMIX
MySQL MYSQL
Oracle ORACLE
PostgresQL POSTGRESQL
Microsoft SQL Server SQLSERVER
Sybase SYBASE
從JNDI獲取實體管理器工廠      如果將Spring應用程式部署在應用伺服器中,Spring可能已經建立了EntityManagerFactory並將其置於JNDI中等待查詢使用。這種情況下,可以使用Spring的jee名稱空間下的<jee:jndi-lookup>元素來獲取對EntityManagerFactory 的引用: <jee:jndi-lookup id="emf" jndi-name="persistence/spitterPU"/> 5.5.2 編寫基於JPA的DAO      Spring對JPA繼承也提供了JpaTemplate模板以及對應的支援類JpaDaoSupport。但是基於模板的JPA已經被棄用了(類似Hibernate)。 import java.util.List; import javax.persistence.EntityManager; import javax.persistence.Persistencecontext; import org.springframework.dao.DataaccessException; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotaion.Transactional; @Repository("spitterDao") @Transactional public class JpaspitterDao implements SpitterDao {      private static final String RECENT_SPITTLES = "SELECT s FROM Spittle s";      private static final String ALL_SPITTERS = "SELECT s FROM Spitter s";      private static final String SPITTER_FOR_USERNAME = "SELECT s FROM Spitter s WHERE s.username = :username";      private static final String SPITTLES_BY_USERNAME = "SELECT S FROM Spittle s WHERE s.spitter.username = :username";      @PersistenceContext      private EntityManager em;      public void addSpitter(Spitter spitter) {           em.persist(spitter);      }      public Spitter getSpitterById(long id) {           return em.find(Spitter.class,id);      }      public void saveSpitter(Spitter spitter) {           em.merge(spitter);      } }      @PersistenceContext註解表明需要將一個EntityManager注入到所標註的成員上。為了在Spring中實現EntityManager注入,需要在Spring應用上下文中配置一個PersistenceAnotationBeanPostProcessor <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>      @Repository與開發Hibernate上下文Session的DAO是一隻的。由於沒有模板類來處理異常,所以需要為DAO新增@Repository註解,這樣PersistenceExceptionTranslationPostProcessor就會知道要將這個Bean產生的異常轉換成Spring的同一資料訪問。要使用PersistenceExceptionTranslationPostProcessor,需要將其作為一個Bean裝配到spring中 <bean class="org.springframework.dao.annotaion.PersistenceExceptionTranslationPostProcessor"/> 第五章小結      Spring對JDBC和ORM的框架的支援簡化了各種持久化機制都存在的樣板程式碼,這是我們只需關注與應用程式相關的資料訪問即可。      Spring簡化資料訪問方式之一就是管理資料庫連線的生命週期和ORM框架的Session。通過這種方式,持久化機制的管理對應用程式程式碼時完全透明的。      Spring能夠捕獲框架的特定異常並將其轉換成異常體系中的非檢查型異常。包括將JDBC丟擲的SQLException轉換為含義更豐富的異常