天天看點

Spring JDBC-Spring對DAO的支援

  • ​​概述​​
  • ​​Spring的DAO理念​​
  • ​​統一的異常體系​​
  • ​​統一的資料通路模闆​​
  • ​​使用模闆和回調機制​​
  • ​​模闆類​​
  • ​​資料源​​
  • ​​配置資料源​​
  • ​​DBCP資料源​​
  • ​​C3P0資料源​​
  • ​​擷取JNDI資料源​​
  • ​​Spring的資料源實作類​​
  • ​​總結​​

概述

Spring對多個持久化技術提供了內建支援,包括Hibernate、MyBatis、JPA、JDO。 此外Spring還提供了一個簡化JDBC API操作的Spring JDBC架構。

Spring面向DAO制定了一個通用的異常體系,屏蔽了持久化技術的異常,使業務層和具體的持久化技術實作解耦。

另外,Spring提供了模闆類簡化各種持久化技術的使用。

通用的異常體系和模闆類是Spring整合各種持久化技術的不二法門。

Spring的DAO理念

DAO(DATA Acces Object)是用于通路資料的對象,雖然大多數情況下存儲在資料庫中,但是也可以存放在檔案或者LDAP(輕量目錄通路協定,Lightweight Directory Access Protocol)中。 DAO不但屏蔽了資料存儲最重媒體的不同,也屏蔽了具體的實作技術的不同。

早起,JDBC是主流選擇,近些年,資料庫持久化技術得到了長足的發展。 隻要為資料通路定義好DAO接口,并使用具體的實作技術實作DAO接口的功能,就可以在不同的實作技術之間平滑的切換。

我們來舉個例子 : 如下是一個典型的DAO應用執行個體,在USerDAO中定義通路User資料對象的接口方法,業務層通過UserDao操作資料,并使用具體的持久化技術實作USerDao接口方法,這樣就實作了業務層和具體的持久化技術之間的解耦

Spring JDBC-Spring對DAO的支援

提供DAO抽象層的好處:

  • 首先可以很容易的構造模拟對象,友善單元測試的開展
  • 其次在使用切面會有更多的選擇,可以使用JDK動态代理,又可以使用CGLib動态代理

Spring本質上希望以統一的方式整合底層的持久化技術,即以統一的方式進行調用及事務管理,避免讓具體的實作侵入到業務層的代碼中。 由于每種持久化技術都有各自的異常體系,是以Spring提供了統一的異常體系,使不同異常體系的阻抗得以消弭,友善定義出和具體實作技術無關的DAO接口,以及整合到相同的事務管理體系中。

統一的異常體系

統一的異常體系是整合不同的持久化技術的關鍵。 Spring提供了一套和實作技術無關的、面向DAO層語義的異常體系,并通過轉換器将不同持久化技術的異常轉換成Spring的異常

很多正統API或者架構中,檢查型異常被過多的使用,以緻在使用API時,代碼中充斥了大量的try/cath樣闆式的代碼。這樣就導緻一堆異常處理的代碼喧賓奪主侵入到業務代碼中,破壞了代碼的整潔和優雅。

Spring在org.springframework.dao包中提供了一套完備優雅的DAO異常體系,

這些異常都繼承于DataAccessException , 而DataAccessException本身又繼承于NestedRuntimeException, NestedRuntimeException異常以嵌套的方式封裝了源異常。 是以,雖然不同的持久化技術的特定異常被轉換到Spring的DAO異常體系中,但原始的異常資訊并不會丢失,我們依然可以通過getCaucse()方法擷取原始的異常資訊。

JDBC/Mybatis的異常轉換器為SQLErrorCodeSQLExceptionTranslator

Spring JDBC-Spring對DAO的支援
Spring JDBC-Spring對DAO的支援

Spring以分類手法建立了異常分類目錄 ,如下所示(為第一層次的異常類,每個異常類下都可能有衆多子異常類),一方面可以使開發者從底層細如針麻的技術細節中脫離出來,另一方面可以選擇感興趣的異常加以處理。

Spring JDBC-Spring對DAO的支援

統一的資料通路模闆

Spring為支援持久化技術分别提供了模闆通路的方式,降低了使用各種持久化技術的難度,是以可以大幅度的提供開發效率。

使用模闆和回調機制

我們先看一段使用JDBC進行資料操作的簡單代碼,已經盡可能的簡化了整個處理的過程

public void saveUser(User user) {
        Connection connection = null;
        PreparedStatement stmt = null;

        try {
            // 擷取連接配接
            connection = DriverManager.getConnection(url, user, password);

            // 啟動事物
            connection.setAutoCommit(false);

            // 業務操作
            stmt = connection
                    .prepareStatement("insert into user(id ,name) values(?,?)");
            stmt.setLong(1, user.getUserId());
            stmt.setString(2, user.getUserName());

            // 執行送出
            stmt.execute();

            // 送出事務
            connection.commit();

        } catch (Exception e) {
            // 復原
            connection.rollback();
        } finally {
            // 釋放資源
            stmt.close();
            connection.close();
        }
    }      

如上代碼所述,JDBC通路資料按照如下流程

  • 擷取連接配接
  • 開啟事務(如果有需要)
  • 執行具體的資料通路操作
  • 送出/復原事務
  • 關閉資源

我們可以看到隻有具體的業務操作才是我們關心的, Spring将這些相同的資料通路流程固化到模闆中,并将資料通路中固定和變化的部分分開,同時保證模闆類是線程安全的,以便多個資料通路線程共享同一個模闆執行個體。變化的部分通過回調接口開放出來,用于定義資料通路和結果傳回的操作。 (如下圖)

這樣我們隻需要編寫回調接口,并調用模闆類進行資料通路,就可以得到我們期待的結果:資料通路成功執行,前置和後置的樣闆化工作也按照順序正确執行,在提供開發效率的同時保證了資源使用的正确性,徹底消除了因為忘記進行資源釋放而引起的資源洩漏問題。

Spring JDBC-Spring對DAO的支援

模闆類

Spring為各種支援的持久化技術都提供了簡化操作的模闆和回調,在回調中編寫具體的資料操作邏輯,使用模闆執行資料操作,在Spring中這是典型的資料操作模式。

我們來了解下Spring為不同的持久化技術所提供的模闆類

ORM持久化技術 模闆類
JDBC/Mybatis org.springframework.jdbc.core.JdbcTemplate
Hibernate org.springframework.orm.hibernate3/4/5.HibernateTemplate
JPA org.springframework.orm.jpa.JpaTemplate
JDO org.springframework.orm.jdo.JdoTemplate

如果直接使用模闆類,則演需要在DAP中定義一個模闆對象并提供資料資源。 Spring為每種持久化技術都提供了支援列,支援類中已完成了這樣的功能。 這樣我們隻需要擴充這些支援類,就可以直接編寫實際的資料通路邏輯,是以更加友善。

ORM持久化技術 支援類
JDBC/Mybatis org.springframework.jdbc.core.JdbcDaoSupport
Hibernate org.springframework.orm.hibernate3/4/5.HibernateDaoSupport
JPA org.springframework.orm.jpa.JpaDaoSupport
JDO org.springframework.orm.jdo.JdoDaoSupport

這些類都是繼承于dao.support.DaoSupport類, DaoSupport實作了InitializingBean接口,在afterPropertiesSet()接口中檢查模闆對象和資料源是否被正确設定,否則将抛出異常。

所有的支援類都是abstract,其目的是希望被繼承使用,而非直接使用

資料源

在Spring中,不但可以通過JNDI擷取應用伺服器的資料源,也可以在Spring容器中配置資料源。 此外還可以通過代碼的方式建立一個資料源,以便進行無容器依賴的單元測試。

配置資料源

Spring在第三方依賴包中包含了2個資料源的實作類包

  • Apache的DBCP
  • C2P0

我們可以在Spring配置檔案中利用二者中的任何一個配置資料源。

DBCP資料源

因篇幅原因,另開一篇 ​​Apache-DBCP資料庫連接配接池解讀​​

簡略配置如下:

<bean id="dataSourcePR" class="org.apache.commons.dbcp.BasicDataSource" 
        destroy-method="close"
        p:driverClassName="${jdbc.driverClassNamePR}"
        p:url="${jdbc.urlPR}"
        p:username="${jdbc.usernamePR}"
        p:password="${jdbc.passwordPR}" />      

BasicDataSource提供了close()方法關閉資料源,是以必須設定destroy-method=”close”屬性,以便Spring容器關閉時,資料源能夠正常關閉。

假設資料庫為MySQL,如果配置不當,會發生經典的“8小時為” 。

原因是MySQL在預設情況下發現一個連接配接空閑時間超過8小時,則會在資料庫端自動關閉這個連接配接。 而資料源并不知道這個連接配接已經被資料庫關閉了,當它将這個無用的連接配接傳回個某個DAO時,DAO就會抛出無法擷取Connection的異常。

如果采用DBCP預設配置,由于testOnBorrow預設為true,資料源在将連接配接交給DAO之前,會事先檢查這個連接配接是否良好,如果連接配接有問題(在資料庫端被關閉),則回取一個其他的連接配接給DAP,并不會有“8小時問題”。 但是這樣每次都檢查有效性,在高并發的應用中會帶來性能問題。

一種推薦的高效方式是: 将testOnBorrow設定為false,而将testWhielIdel設定為true,再設定好 timeBetweenEvictionRunsMillis的值。 這樣DBCP将通過一個背景線程定時的對空閑連接配接進行檢測,當發現無用的空閑連接配接(那些被資料庫關閉的連接配接)時,就會将它們清掉,隻要将timeBetweenEvictionRunsMillis設定為小于8小時,那些被MySQL關閉的空閑連接配接就可以被清除出去,進而避免“8小時問題”

當然,MySQL本身可以通過調整interactive-timeout(以秒為機關)配置參數,更改空閑連接配接的過期時間, 是以在設定timeBetweenEvictionRunsMillis值時,必須首先獲知MySQL空閑連接配接的最大過期時間。

C3P0資料源

因篇幅原因,另開一篇 ​​C3P0-資料庫連接配接池解讀​​

一個簡單的配置

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">     
    <property name="driverClass">     
        <value>oracle.jdbc.driver.OracleDriver</value>     
    </property>     
    <property name="jdbcUrl">                
        <value>jdbc:oracle:thin:@ip:port:instanceName</value>     
     </property>     
    <property name="user">     
        <value>artisan</value>     
    </property>     
    <property name="password">     
        <value>artisan</value>     
    </property>     
</bean>      

擷取JNDI資料源

Java Naming and Directory Interface (Java命名和目錄服務接口)

如果應用配置在高性能的應用伺服器比如weblogic/websphere等,則可能希望使用應用本身提供的資料源。 應用伺服器的資料源使用JNDI開放調用者使用,Spring為此專門提供了引用JNDI資料源的JndiObjectFactoryBean,我們來看一個簡單的配置

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/自定義name"/>
</bean>      

通過jndiName指定引用的JNDI資料源名稱

Spring為JavaEE資源提供了一個jee命名空間,通過jee命名空間,可以有效的簡化Java EE資源的引用, 如下所示:

<beans xmlns=http://www.springframework.org/schema/beans       
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance       
xmlns:jee=http://www.springframework.org/schema/jee       
xsi:schemaLocation="http://www.springframework.org/schema/beans        
http://www.springframework.org/schema/beans/spring-beans.xsd        
http://www.springframework.org/schema/jee       
http://www.springframework.org/schema/jee/spring-jee.xsd">         
<jee:jndi-lookup id="dataSource" jndi-name=" java:comp/env/jdbc/自定義name"/>         
</beans>      

Spring的資料源實作類

Spring本身也提供了一個簡單的資料源實作類org.springframework.jdbc.datasource.DriverManagerDataSource

這個類實作了javax.sql.DataSource接口, 但 它并沒有提供池化連接配接的機制,每次調用getConnection()擷取新連接配接時,隻是簡單地建立一個新的連接配接。

是以,這個資料源類比較适合在單元測試 或簡單的獨立應用中使用,因為它不需要額外的依賴類。

下面,我們來看一下DriverManagerDataSource的簡單使用。

DriverManagerDataSource ds = new DriverManagerDataSource ();         
ds.setDriverClassName("com.mysql.jdbc.Driver");         
ds.setUrl("jdbc:mysql://localhost:3309/sampledb");         
ds.setUsername("artisan");         
ds.setPassword("123456");         
Connection actualCon = ds.getConnection();      

當然,我們也可以通過配置的方式直接使用DriverManagerDataSource。 如下

<!-- Initialization for data source -->
   <bean id = "dataSource" 
      class = "org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name = "driverClassName" value = "com.mysql.jdbc.Driver"/>
      <property name = "url" value = "jdbc:mysql://localhost:3309/sampledb"/>
      <property name = "username" value = "artisan"/>
      <property name = "password" value = "123456"/>
   </bean>      

總結

繼續閱讀