1. 程式人生 > 實用技巧 >畢業設計:文獻參考(三)

畢業設計:文獻參考(三)

Spring

一、Spring的概述

1.1 Spring是什麼

Spring 是分層的 Java SE/EE 應用 full-stack 輕量級開源框架,以 IoC(Inverse Of Control:
反轉控制)和 AOP(Aspect Oriented Programming:面向切面程式設計)為核心,提供了展現層 Spring
MVC 和持久層 Spring JDBC 以及業務層事務管理等眾多的企業級應用技術,還能整合開源世界眾多
著名的第三方框架和類庫,逐漸成為使用最多的 Java EE 企業應用開源框架。

1.2 Spring的發展歷程

  • 1997 年 IBM 提出了 EJB 的思想

  • 1998 年,SUN 制定開發標準規範 EJB1.0

  • 1999 年,EJB1.1 釋出

  • 2001 年,EJB2.0 釋出

  • 2003 年,EJB2.1 釋出

  • 2006 年,EJB3.0 釋出

  • Rod Johnson(spring 之父)

  • Expert One-to-One J2EE Design and Development(2002)
    闡述了 J2EE 使用 EJB 開發設計的優點及解決方案

  • Expert One-to-One J2EE Development without EJB(2004)
    闡述了 J2EE 開發不使用 EJB 的解決方式(Spring 雛形)

  • 2017 年 9 月份釋出了 spring 的最新版本 spring 5.0 通用版(GA)

1.3 spring的優勢

1.3.1 方便解耦,簡單開發

通過 Spring 提供的 IoC 容器,可以將物件間的依賴關係交由 Spring 進行控制,避免硬編碼所造
成的過度程式耦合。使用者也不必再為單例模式類、屬性檔案解析等這些很底層的需求編寫程式碼,可
以更專注於上層的應用。

1.3.2 AOP程式設計的支援

通過 Spring 的 AOP 功能,方便進行面向切面的程式設計,許多不容易用傳統 OOP 實現的功能可以通過 AOP 輕鬆應付。

1.3.3 宣告式事務的支援

可以將我們從單調煩悶的事務管理程式碼中解脫出來,通過宣告式方式靈活的進行事務的管理,提高開發效率和質量。

1.3.4 方便程式的測試

可以用非容器依賴的程式設計方式進行幾乎所有的測試工作,測試不再是昂貴的操作,而是隨手可做的事情。

1.3.5 方便整合各種優秀框架

Spring 可以降低各種框架的使用難度,提供了對各種優秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支援。

1.3.6 降低 JavaEE API 的使用難度

Spring 對 JavaEE API(如 JDBC、JavaMail、遠端呼叫等)進行了薄薄的封裝層,使這些 API 的使用難度大為降低。

1.3.7 Java 原始碼是經典學習範例

Spring 的原始碼設計精妙、結構清晰、匠心獨用,處處體現著大師對 Java 設計模式靈活運用以及對 Java 技術的高深造詣。它的原始碼無意是 Java 技術的最佳實踐的範例。

1.4 Spring的體系結構

二、程式的耦合

2.1 耦合

耦合性(Coupling),也叫耦合度,是對模組間關聯程度的度量。耦合的強弱取決於模組間介面的複雜性、調
用模組的方式以及通過介面傳送資料的多少。模組間的耦合度是指模組之間的依賴關係,包括控制關係、呼叫關
系、資料傳遞關係。模組間聯絡越多,其耦合性越強,同時表明其獨立性越差( 降低耦合性,可以提高其獨立
性)。耦合性存在於各個領域,而非軟體設計中獨有的,但是我們只討論軟體工程中的耦合。
在軟體工程中,耦合指的就是就是物件之間的依賴性。物件之間的耦合越高,維護成本越高。因此物件的設計
應使類和構件之間的耦合最小。軟體設計中通常用耦合度和內聚度作為衡量模組獨立程度的標準。劃分模組的一個
準則就是高內聚低耦合。
它有如下分類:
(1)內容耦合。當一個模組直接修改或操作另一個模組的資料時,或一個模組不通過正常入口而轉入另
一個模組時,這樣的耦合被稱為內容耦合。內容耦合是最高程度的耦合,應該避免使用之。
(2)公共耦合。兩個或兩個以上的模組共同引用一個全域性資料項,這種耦合被稱為公共耦合。在具有大
量公共耦合的結構中,確定究竟是哪個模組給全域性變數賦了一個特定的值是十分困難的。
(3) 外部耦合 。一組模組都訪問同一全域性簡單變數而不是同一全域性資料結構,而且不是通過引數表傳
遞該全域性變數的資訊,則稱之為外部耦合。
(4) 控制耦合 。一個模組通過介面向另一個模組傳遞一個控制訊號,接受訊號的模組根據訊號值而進
行適當的動作,這種耦合被稱為控制耦合。
(5)標記耦合 。若一個模組 A 通過介面向兩個模組 B 和 C 傳遞一個公共引數,那麼稱模組 B 和 C 之間
存在一個標記耦合。
(6) 資料耦合。模組之間通過引數來傳遞資料,那麼被稱為資料耦合。資料耦合是最低的一種耦合形
式,系統中一般都存在這種型別的耦合,因為為了完成一些有意義的功能,往往需要將某些模組的輸出資料作為另
一些模組的輸入資料。
(7) 非直接耦合 。兩個模組之間沒有直接關係,它們之間的聯絡完全是通過主模組的控制和呼叫來實
現的。
總結
耦合是影響軟體複雜程度和設計質量的一個重要因素,在設計上我們應採用以下原則:如果模組間必須
存在耦合,就儘量使用資料耦合,少用控制耦合,限制公共耦合的範圍,儘量避免使用內容耦合。
內聚與耦合
內聚標誌一個模組內各個元素彼此結合的緊密程度,它是資訊隱蔽和區域性化概念的自然擴充套件。內聚是從
功能角度來度量模組內的聯絡,一個好的內聚模組應當恰好做一件事。它描述的是模組內的功能聯絡。耦合是軟體結構中各模組之間相互連線的一種度量,耦合強弱取決於模組間介面的複雜程度、進入或訪問一個模組的點以及通過介面的資料。 程式講究的是低耦合,高內聚。就是同一個模組內的各個元素之間要高度緊密,但是各個模組之間的相互依存度卻要不那麼緊密。
內聚和耦合是密切相關的,同其他模組存在高耦合的模組意味著低內聚,而高內聚的模組意味著該模組同他
模組之間是低耦合。在進行軟體設計時,應力爭做到高內聚,低耦合。

2.2 解決耦合的思路

Class.forName("com.mysql.jdbc.Driver");//此處只是一個字串

此時的好處是,我們的類中不再依賴具體的驅動類,此時就算刪除 mysql 的驅動 jar 包,依然可以編譯(執行就不要想了,沒有驅動不可能執行成功的)。
同時,也產生了一個新的問題,mysql 驅動的全限定類名字串是在 java 類中寫死的,一旦要改還是要修改
原始碼。
解決這個問題也很簡單,使用配置檔案配置。

2.3 工廠模式解耦

在實際開發中我們可以把三層的物件都使用配置檔案配置起來,當啟動伺服器應用載入的時候,讓一個類中的方法通過讀取配置檔案,把這些物件創建出來並存起來。在接下來的使用的時候,直接拿過來用就好了。
那麼,這個讀取配置檔案,建立和獲取三層物件的類就是工廠。

工廠類:

public class BeanFactory {
    private static Properties properties;
    //建立單例的容器,用於儲存要建立的bean物件
    private static Map<String,Object> beans;
    static {
        try {
            properties = new Properties();
            //載入配置檔案
            properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"));
            //例項化map容器
            beans = new HashMap<String, Object>();
            //獲取properties的所有keys
            Set<Object> keys = properties.keySet();
            //遍歷key
            for (Object o : keys) {
                //強轉為String型別
                String key = (String)o;
                //獲取properties集合key對應的全限定類名
                String beanPath = properties.getProperty(key);
                //使用反射技術建立物件
                Object bean = Class.forName(beanPath).newInstance();
                //將建立的物件儲存到beans容器
                beans.put(key,bean);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 此方法是使用單利模式建立所對應的物件
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        return beans.get(beanName);
    }
    
    /**
     * 根據bean的名稱獲取物件  此方法建立的是多例模式
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName)  {
        Object bean = null;
        try {
            String beanPath = properties.getProperty(beanName);
            bean = Class.forName(beanPath).newInstance();
            return bean;
        }catch (Exception e){

        }
        return bean;
    }

}

配置檔案

	AccountServiceImpl=com.service.impl.AccountServiceImpl
AccountDaoImpl=com.dao.impl.AccountDaoImpl

建立物件

IAccountService accountService = (IAccountService) BeanFactory.getBean("AccountServiceImpl");
IAccountDao dao = (IAccountDao)BeanFactory.getBean("AccountDaoImpl");

2.4 IOC 控制反轉

2.4.1控制反轉

****(Inversion of Control,縮寫為IoC)是面向物件程式設計中的一種設計原則,可以用來減低計算機程式碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI**),還有一種方式叫“依賴查詢”(Dependency Lookup)。通過控制反轉,物件在被建立的時候,由一個調控系統內所有物件的外界實體將其所依賴的物件的引用傳遞給它。也可以說,依賴被注入到物件中。

2.4.2 IOC的作用

IOC是削減程式之間的耦合度,而不能完全消除。

我們普通的通過new的方式獲取物件

使用工廠模式建立物件

2.5 Bromon的blog上對IoC與DI淺顯的講解

IoC(控制反轉)

  首先想說說IoC(Inversion of Control,控制反轉)。這是spring的核心,貫穿始終。所謂IoC,對於spring框架來說,就是由spring來負責控制物件的生命週期和物件間的關係。這是什麼意思呢,舉個簡單的例子,我們是如何找女朋友的?常見的情況是,我們到處去看哪裡有長得漂亮身材又好的mm,然後打聽她們的興趣愛好、qq號、電話號、ip號、iq號………,想辦法認識她們,投其所好送其所要,然後嘿嘿……這個過程是複雜深奧的,我們必須自己設計和麵對每個環節。傳統的程式開發也是如此,在一個物件中,如果要使用另外的物件,就必須得到它(自己new一個,或者從JNDI中查詢一個),使用完之後還要將物件銷燬(比如Connection等),物件始終會和其他的介面或類藕合起來。

  那麼IoC是如何做的呢?有點像通過婚介找女朋友,在我和女朋友之間引入了一個第三者:婚姻介紹所。婚介管理了很多男男女女的資料,我可以向婚介提出一個列表,告訴它我想找個什麼樣的女朋友,比如長得像李嘉欣,身材像林熙雷,唱歌像周杰倫,速度像卡洛斯,技術像齊達內之類的,然後婚介就會按照我們的要求,提供一個mm,我們只需要去和她談戀愛、結婚就行了。簡單明瞭,如果婚介給我們的人選不符合要求,我們就會丟擲異常。整個過程不再由我自己控制,而是有婚介這樣一個類似容器的機構來控制。Spring所倡導的開發方式就是如此,所有的類都會在spring容器中登記,告訴spring你是個什麼東西,你需要什麼東西,然後spring會在系統執行到適當的時候,把你要的東西主動給你,同時也把你交給其他需要你的東西。所有的類的建立、銷燬都由 spring來控制,也就是說控制物件生存週期的不再是引用它的物件,而是spring。對於某個具體的物件而言,以前是它控制其他物件,現在是所有物件都被spring控制,所以這叫控制反轉。

DI(依賴注入)

IoC的一個重點是在系統執行中,動態的向某個物件提供它所需要的其他物件。這一點是通過DI(Dependency Injection,依賴注入)來實現的。比如物件A需要操作資料庫,以前我們總是要在A中自己編寫程式碼來獲得一個Connection物件,有了 spring我們就只需要告訴spring,A中需要一個Connection,至於這個Connection怎麼構造,何時構造,A不需要知道。在系統執行時,spring會在適當的時候製造一個Connection,然後像打針一樣,注射到A當中,這樣就完成了對各個物件之間關係的控制。A需要依賴 Connection才能正常執行,而這個Connection是由spring注入到A中的,依賴注入的名字就這麼來的。那麼DI是如何實現的呢? Java 1.3之後一個重要特徵是反射(reflection),它允許程式在執行的時候動態的生成物件、執行物件的方法、改變物件的屬性,spring就是通過反射來實現注入的。

  理解了IoC和DI的概念後,一切都將變得簡單明瞭,剩下的工作只是在spring的框架中堆積木而已。

三、使用Spring的IOC解決程式的耦合

3.1 Spring的入門案例

meaven的依賴配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.itheima</groupId>
    <artifactId>SpringDemo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
</project>

service業務層

public class UserServiceImpl implements IUserService {
    public void saveUser() {
        System.out.println("使用者已經儲存");
    }
}

配置bean檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--class是建立類的全限定類名-->
    <bean id="userService" class="service.impl.UserServiceImpl"></bean>
</beans>

測試

public class SpringTest01 {

    public static void main(String args[]){
		//獲取SpringIOC的核心容器,並根據id獲取物件
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        IUserService userService = (IUserService)ac.getBean("userService");
        userService.saveUser();

    }
}

3.2 Spring基於xml的IOC細節

3.2.1 Spring中工廠的類結構圖
3.2.2 BeanFactory和Application的區別

BeanFactory 才是 Spring 容器中的頂層介面。
ApplicationContext 是它的子介面。

  • BeanFactory 和 ApplicationContext 的區別:
    建立物件的時間點不一樣。
    ApplicationContext:只要一讀取配置檔案,預設情況下就會建立物件。用於建立單例模式
    BeanFactory:什麼使用什麼時候建立物件。用於建立多例模式
3.2.3 ApplicationContext的三個常用類

ApplicationContext的三個常用類:
ClassPathXmlApplicationContext(String configLocation):
他可以載入類路徑下的配置檔案,要求配置檔案是在類路徑下,不在的話載入不了(常用)
FileSystemXmlApplicationContext(String configLocation)
他可以載入磁碟上任意位置上面的配置檔案(要有訪問許可權)
AnnotationConfigApplicationContext:
他可以讀取註解建立容器

3.3 IOC中xml管理的細節

建立bean的方式有三種

3.3.1 bean標籤

作用
用於配置物件讓 spring 來建立的。
預設情況下它呼叫的是類中的無參建構函式。如果沒有無參建構函式則不能建立成功。
屬性
id:給物件在容器中提供一個唯一標識。用於獲取物件。
class:指定類的全限定類名。用於反射建立物件。預設情況下呼叫無參建構函式。
scope:指定物件的作用範圍。

  • singleton :預設值,單例的.
  • prototype :多例的.
  • request :WEB 專案中,Spring 建立一個 Bean 的物件,將物件存入到 request 域中.
  • session :WEB 專案中,Spring 建立一個 Bean 的物件,將物件存入到 session 域中.
  • global session(叢集) :WEB 專案中,應用在 Portlet 環境.如果沒有 Portlet 環境那麼globalSession 相當於 session
  • init-method:指定類中的初始化方法名稱。
  • destroy-method:指定類中銷燬方法名稱。
3.3.2 bean 的作用範圍和生命週期

單例物件:scope="singleton"
一個應用只有一個物件的例項。它的作用範圍就是整個引用。
生命週期:
物件出生:當應用載入,建立容器時,物件就被建立了。
物件活著:只要容器在,物件一直活著。
物件死亡:當應用解除安裝,銷燬容器時,物件就被銷燬了。
多例物件:scope="prototype"
每次訪問物件時,都會重新建立物件例項。
生命週期:
物件出生:當使用物件時,建立新的物件例項。
物件活著:只要物件在使用中,就一直活著。
物件死亡:當物件長時間不用時,被 java 的垃圾回收器回收了。

3.3.3 建立bean的三種方式
  • 方式一:使用預設建構函式進行建立

    在Spring的配置檔案中使用bean標籤,配置id和class之後,沒有其他屬性和標籤時,
    採用的就是預設建構函式建立bean物件,此時類中如果沒有空參構造方法,則無法建立物件

<bean id="accountService" class="com.service.impl.AccountServiceImpl"></bean>
  • 方式二:spring 管理例項工廠-使用例項工廠的方法建立物件

    使用某個類中的方法建立並存入Spring的IOC容器中,這種方式一般是存在於jar包中使用例項方法建立物件的方式

    /**
     *  模擬一個工廠類(該類可能存在於jar包中,我們無法通過修改原始碼的方式來提供建構函式)
     * @param
     * @return
     */
    public class InstanceFactory {
        public IAccountService getIAccountService(){
            return new AccountServiceImpl();
        }
    }
    
    <bean id="instanceFactory" class="com.factory.InstanceFactory"></bean>
        <bean id="accountService" factory-bean="instanceFactory" factory-method="getIAccountService"></bean>
    

    此種方式是先把工廠的建立交給 spring 來管理。然後再使用工廠的 bean 來呼叫裡面的方法
    factory-bean 屬性:用於指定例項工廠 bean 的 id。
    factory-method 屬性:用於指定例項工廠中建立物件的方法。方式三:

  • 方式三:spring 管理靜態工廠-使用靜態工廠的方法建立物件

    使用某個類中的方法建立並存入Spring的IOC容器中,這種方式一般是存在於jar包中使用靜態方法建立物件的方式

    /**
     *  模擬一個工廠類,使用靜態方法建立IAccountService物件(該類可能存在於jar包中,我們無法通過修改原始碼的方式來提供建構函式)
     * @param
     * @return
     */
    public class StaticInstanceFactory {
    
        public static IAccountService getIAccountService(){
            return new AccountServiceImpl();
        }
    }
    
    <bean id="accountService" class="com.factory.StaticInstanceFactory" factory-method="getIAccountService"></bean>
    

    使用 StaticFactory 類中的靜態方法 createAccountService 建立物件,並存入 spring 容器
    id 屬性:指定 bean 的 id,用於從容器中獲取
    class 屬性:指定靜態工廠的全限定類名
    factory-method 屬性:指定生產物件的靜態方法

3.4 Spring的依賴注入

3.4.1 概念

依賴注入:Dependency Injection。它是 spring 框架核心 ioc 的具體實現。我們的程式在編寫時,通過控制反轉,把物件的建立交給了 spring,但是程式碼中不可能出現沒有依賴的情況。ioc 解耦只是降低他們的依賴關係,但不會消除。例如:我們的業務層仍會呼叫持久層的方法。那這種業務層和持久層的依賴關係,在使用 spring 之後,就讓 spring 來維護了。簡單的說,就是坐等框架把持久層物件傳入業務層,而不用我們自己去獲取。

  • 依賴注入:Dependency Injection。它是 spring 框架核心 ioc 的具體實現。我們的程式在編寫時,通過控制反轉,把物件的建立交給了 spring,但是程式碼中不可能出現沒有依賴的情況。ioc 解耦只是降低他們的依賴關係,但不會消除。例如:我們的業務層仍會呼叫持久層的方法。那這種業務層和持久層的依賴關係,在使用 spring 之後,就讓 spring 來維護了。簡單的說,就是坐等框架把持久層物件傳入業務層,而不用我們自己去獲取。
3.4.2 依賴注入

依賴注入:
Dependency Injection
IOC的作用:
降低程式間的依賴關係即降低耦合
依賴關係的管理:
都交給了Spring來維護
在當前類需要用到其他類的物件,由spring為我們提供,我們只需要在配置檔案中說明
依賴關係的維護:
我們就稱之為依賴注入
依賴注入:
能注入的資料有三類:
基本資料型別和String
其他的bean型別(在配置檔案中或者註解配置過的bean)
複雜型別/集合型別
注入的方式有三種:
第一種:使用建構函式提供
第二種:使用set方法提供
第三種:使用註解提供

3.4.2.1 第一種注入:建構函式注入

使用標籤constructor-arg
使用出現在bean標籤的內部
標籤中的屬性:
type:用於指定要注入的資料的資料型別,該資料型別也是建構函式中某個或某些引數的型別
index:用於指定要注入的資料給建構函式中指定索引位置的引數賦值,索引的位置是從0開始的
name:用於指定個建構函式中指定名稱的引數賦值(常用)
以上三個用於指定給建構函式中哪個引數賦值===
value:用於提供基本型別和String型別的資料
ref:用於指定的其他bean型別資料,他指的就是在spring的IOC核心容器中出現過的bean物件

​ 優勢:在注入資料時,使用者必須要注入資料,否則物件無法建立成功,可以不讓使用者遺忘資料的注入
​ 弊端:改變了bean物件的例項化方式,使我們在建立物件時,即使用不到這些資料,也必須注入

public class AccountServiceImpl implements IAccountService {
    private String name;
    private Integer age;
    private Date birthday;
    public AccountServiceImpl(String name,Integer age,Date birthday){
        this.name=name;
        this.age=age;
        this.birthday=birthday;
    }
    /**
     * 儲存使用者
     */
    @Override
    public void saveAccount() throws Exception{
        System.out.println("service中的saveAccount方法執行了 ");
    }
    @Override
    public String toString() {
        return "AccountServiceImpl{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}';
    }
}
<bean id="accountService" class="com.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="test"></constructor-arg>
        <!--Integer型別spring能直接進行型別轉型為Integer型別-->
        <constructor-arg name="age" value="18"></constructor-arg>
        <!--java.util.Date型別不能直接進行spring能直接進行轉型-->
        <constructor-arg name="birthday" ref="date"></constructor-arg>
    </bean>
    <!--配置一個日期物件,將來用於引用-->
    <bean id="date" class="java.util.Date"></bean>
3.4.1.2 第二種注入:set方法注入

第二種:set方法注入 (更常用的方式 )
使用的標籤是property
使用在bean標籤的內部
標籤的屬性:
name:找用於指定注入時所呼叫的set方法名稱
value:用於提供基本型別和String型別的資料
ref:用於指定的其他bean型別資料,他指的就是在spring的IOC核心容器中出現過的bean物件
優勢:建立物件時,沒有明確限制
劣勢:如果有某成員必須有值,則容易造成遺忘

<bean id="accountService2" class="com.service.impl.AccountServiceImpl2">
        <property name="name" value="test02"></property>
        <property name="age" value="21"></property>
        <property name="birthday" ref="date2"></property>
    </bean>
    <!--配置一個日期物件,將來用於引用-->
    <bean id="date2" class="java.util.Date"></bean>
/**
 * 賬戶的業務層實現類
 * @param
 * @return
 */
public class AccountServiceImpl2 implements IAccountService {
    private String name;
    private Integer age;
    private Date birthday;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    /**
     * 儲存使用者
     */
    @Override
    public void saveAccount() throws Exception{
        System.out.println("service2中的saveAccount方法執行了 ");
    }

    @Override
    public String toString() {
        return "AccountServiceImpl{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}';
    }
}
3.4.1.3 第三種注入:注入集合屬性

用於給集合結構注入的標籤
list array set
用於給map結構注入的標籤
map property
結構相同的時候,可以任意使用,開發中一般都用set和map則這兩種結構

<bean id="accountService3" class="com.service.impl.AccountServiceImpl3">
        <!--注入陣列-->
        <property name="myStrs">
            <array>
                <!--陣列中的元素-->
                <value>a</value>
                <value>b</value>
                <value>c</value>
            </array>
        </property>

        <!--注入list集合-->
        <property name="myList">
            <list>
                <value>d</value>
                <value>e</value>
                <value>f</value>
            </list>
        </property>

        <!--注入set集合-->
        <property name="mySet">
            <set>
                <value>g</value>
                <value>h</value>
                <value>i</value>
            </set>
        </property>

        <!--注入map集合-->
        <property name="myMap">
            <map>
                <!--map集合的對映方式1-->
                <entry key="k1" value="張三"></entry>
                <!--map集合的對映方式2-->
                <entry key="k2">
                    <!--value表示值的型別-->
                    <value type="java.lang.String">李四</value>
                </entry>
            </map>
        </property>

        <property name="myPros">
            <props>
                <prop key="p1">這是p1的內容</prop>
                <prop key="p2">這裡p2的內容</prop>
            </props>
        </property>
    </bean>
/**
 * 賬戶的業務層實現類
 * @param
 * @return
 */
public class AccountServiceImpl3 implements IAccountService {
    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myPros;

    public String[] getMyStrs() {
        return myStrs;
    }

    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs;
    }

    public List<String> getMyList() {
        return myList;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public Set<String> getMySet() {
        return mySet;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public Map<String, String> getMyMap() {
        return myMap;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public Properties getMyPros() {
        return myPros;
    }

    public void setMyPros(Properties myPros) {
        this.myPros = myPros;
    }

    @Override
    public void saveAccount() throws Exception {
        System.out.println("save方法執行");
        System.out.println(Arrays.toString(myStrs));
        System.out.println(mySet);
        System.out.println(myMap);
        System.out.println(myPros);
    }

四、基於註解的IOC配置

4.1、xml的基本配置

使用註解配置需要在bean.xml 檔案引入xmlns依賴,並且需要告知Spring容器在建立的時候要掃描的包,配置所需的標籤不是bean的約束中,而是一個名為context名稱空間和約束中

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

4.2 常用註解

4.2.1 用於載入物件進入IOC容器的註釋

他們的作用和使用xml配置配置檔案的標籤功能是一致的

@Component

作用:用於把當前物件存入到Spring的IOC容器中

​ 屬性:

​ value:用於指定bean的id,預設為當前型別且首字母小寫

@Controlle

作用:和@Component標籤的作用一致,spring用於區分三層架構的表現層

@Service

作用:和@Component標籤的作用一致,spring用於區分三層架構的業務層

@Repositor

作用:和@Component標籤的作用一致,spring用於區分三層架構的持久層

以上三個註解他們的作用和屬性和Component是一致的

他們三個是Spring框架為我們提供明確的使用三層架構使用的註解,使我們的三層架構更加清晰

4.2.2 用於注入資料的註解

他們的作用和xml配置檔案中的bean標籤中的標籤的作用是一致的

@Autowried

一般出現在變數和方法中

作用:

自動按照型別注入,只要容器中有唯一的bean物件型別和要注入的變數型別匹配,就可以注入成功,

如果IOC容器沒有任何bean的型別和要注入的變數型別匹配則報錯

如果IOC容器中有多個 bean的型別相同時,先圈定出這些物件,再根據注入變數的變數名去和IOC容器中的 key進行匹配,如果都沒有匹配上,則報錯

細節:

使用註解方式注入set方法就不是必須的了

@Qualifier

作用:在按照型別注入的基礎之上再按照名稱注入,它在給類成員注入時不能單獨使用,在給方法引數注入時可以單獨使用

@Resource

作用:直接按照bean的id注入,他可以直接使用

屬性:

​ name:用於指定bean的id

以上三個註解只能注入其他型別bean型別的資料,而基本型別和String型別無法使用上述註解實現,另外,集合型別的注入只能通過xml配置的形式來實現

@value

作用:用於注入基本資料型別和String型別

屬性:

​ value:用於指定資料的值,它可以使用Spring中的SpEL(Spring的EL表示式)

​ SpEL的寫法:${表示式}

//    @Autowired
//    @Qualifier("accountDao1")
    @Resource(name = "accountDao2")
    private IAccountDao accountDao = null;
4.2.3 用於改變作用範圍的註釋

他們的作用和標籤中的scope屬性實現的功能一致

@Scope

作用:用於指定bean的作用範圍

屬性:

​ value:用於指定作用範圍,常取值為:singleton(單例,預設值) prototype(多例)

4.2.4 用於生命週期的註釋
@PreDestroy

作用:用於指定銷燬方法

細節:多例物件沒有銷燬方法,多例物件是長時間不使用後由java的垃圾回收機制回收

@PosConstruct

作用:用於指定初始化方法

@Component("accountService")
//@Scope("singleton")//建立的是單例物件,Spring預設的
//@Scope("prototype")//建立的是多例物件,
public class AccountServiceImpl implements IAccountService {
//    @Autowired
//    @Qualifier("accountDao1")
    @Resource(name = "accountDao2")
    private IAccountDao accountDao = null;

    /**
     * 儲存使用者
     */
    @Override
    public void saveAccount() {
        accountDao.saveAccount();
    }

    @PostConstruct//宣告是初始化方法
    public void init(){
        System.out.println("初始化方法執行");
    }

    @PreDestroy//宣告是銷燬方法
    public void destroy(){
        System.out.println("銷燬方法執行");
    }
}

4.3高階註解

@Configuration

作用:

​ 指定當前類是一個配置類

細節:當配置類作為AnnotationConfigApplicationContext物件建立的引數時,該註解可不寫

@ComponentScan

作用:

​ 通過註解指定Spring需要掃描的包

屬性:

​ value:和xml的context:component-scan basePackages的作用是一致的,都是用於指定建立容器時要掃描的包,我們使用此註解相當於用xml配置了<context:component-scan base-package="com.itheima"></context:component-scan>

@ComponentScan("com.itheima")
@Bean

作用:用於把當前方法的返回值作為bean物件存入Spring的ioc容器中

屬性:

​ name:用於指定bean的id,當不寫時,預設為該方法名

細節:當我們使用註解配置的方式,如果方法有引數,Spring會自動去ioc容器中查詢有沒有可用的bean物件,查詢的方式和Autowired註解的作用是一致的

/**
     * 用於建立一個QueryRunner物件
     *
     * @param ds
     * @return
     */
    @Bean(name = "queryRunner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource ds) {
        return new QueryRunner(ds);
    }
@Import

作用:用於匯入其他配置類

屬性:

​ value:用於指定其他配置類的位元組碼檔案,當我們使用Improt註解後,有Import註解的類就是父配置類,而匯入的是子配置類

@Configuration//標識為配置類
@ComponentScan("com.itheima")//spring需要掃描的包
@Import(JdbcConfig.class)//指定子配置類
@PropertySource("classpath:jdbc.properties")//指定jdbc.properties檔案位置
public class SpringConfiguration {
	
}

4.4 配置properties檔案註解

@PropertySource

作用:用於指定properties檔案的位置

屬性:

​ value:指定檔案的名稱和路徑

​ 關鍵字:classpath:表示類路徑下

當使用該配置後,properties檔案的內容可以通過SpEL表示式的形式獲取

獲取方式:

如:@Value("${jdbc.driver}")

@PropertySource("classpath:jdbc.properties")//指定jdbc.properties檔案位置
//配置檔案
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy_spring
jdbc.user=root
jdbc.password=123
@Value("${jdbc.driver}")
private String driver;

@Value("${jdbc.url}")
private String url;

@Value("${jdbc.user}")
private String user;

@Value("${jdbc.password}")
private String password;

4.5 細節

當使用純註解開發的方式時,獲取ioc容器不再使用new ClassPathXmlApplicationContext("bean.xml")的方式,而是採用new AnnotationConfigApplicationContext(SpringConfiguration.class)的方式

五、spring整合Junit

Spring整合junit的配置:

​ 1、匯入Spring整合junit的jar或maven的座標(Spring-test),同時junit的jar包必須是在4.12版本以上

<!--配置spring整合junit的jar包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>

<!--匯入junit的jar包,必須是在4.12版本以上-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

​ 2、使用Junit提供的@RunWit註解把junit原有的main方法替換,替換成Spring提供的

​ 3、使用@ContextConfigurtion告知spring的執行器,spring的ioc建立是基於配置檔案還是基於註解類,並且說明對應位置

​ @ContextConfiguration的屬性:

​ locations:指定xml 檔案的位置,注意要加上classpath關鍵字表示根目錄

​ classes:指定註解類所在的位置

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=SpringConfiguration.class)
public class AccountServiceTest {
    //自動注入
    @Autowired
    private IAccountService service;
    @Test
    public void testFindAll() {
        List<Account> accounts = service.findAll();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
}

六、基於註解開發的簡單賬務專案

實體類Account物件

package com.itheima.domain;
import java.io.Serializable;
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Integer money;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getMoney() {
        return money;
    }
    public void setMoney(Integer money) {
        this.money = money;
    }
    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

持久層實現類

package com.itheima.dao.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.sql.SQLException;
import java.util.List;

/**
 * @param
 * @return
 */
@Service("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Resource(name = "queryRunner")
    private QueryRunner runner;

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    @Override
    public List<Account> findAll() {
        try {
            return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findById(Integer accountId) {
        try {
            return runner.query("select * from account where id=?",new BeanHandler<Account>(Account.class),accountId);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void saveAccount(Account account) {
        try {
            runner.update("insert into account(name,money) value(?,?)",account.getName(),account.getMoney());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateAccount(Account account) {
        try {
            runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteAccount(Integer accountId) {
        try {
            runner.update("delete from account where id=?", accountId);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

業務層實現類

package com.itheima.service.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
import java.util.List;

/**
 * @param
 * @return
 */
@Controller("accountService")
public class AccountServiceImpl implements IAccountService {
    @Resource(name = "accountDao")
    private IAccountDao dao;

    @Override
    public List<Account> findAll() {
        return dao.findAll();
    }

    @Override
    public Account findById(Integer accountId) {
        return dao.findById(accountId);
    }

    @Override
    public void saveAccount(Account account) {
        dao.saveAccount(account);
    }

    @Override
    public void updateAccount(Account account) {
        dao.updateAccount(account);
    }

    @Override
    public void deleteAccount(Integer accountId) {
        dao.deleteAccount(accountId);
    }
}

主配置類SpringConfiguration類

package config;
import org.springframework.context.annotation.*;
@Configuration//標識為配置類
@ComponentScan("com.itheima")//需要掃描的包
@Import(JdbcConfig.class)//指定配置類
@PropertySource("classpath:jdbc.properties")//指定properties檔案位置
public class SpringConfiguration {

}

子配置JdbcConfig類

package config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import javax.sql.DataSource;

/**
 * 配置資料庫
 * @param
 * @return
 */
//@Configuration
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.user}")
    private String user;

    @Value("${jdbc.password}")
    private String password;

    /**
     * 用於建立一個QueryRunner物件
     *
     * @param ds
     * @return
     */
    @Bean(name = "queryRunner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource ds) {
        return new QueryRunner(ds);
    }

    /**
     * 建立資料來源物件
     *
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(user);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException("資料庫連線失敗!");
        }
    }
}

jdbc配置檔案

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy_spring
jdbc.user=root
jdbc.password=123

測試類

package com.itheima.test;
import config.SpringConfiguration;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

/**
 * @param
 * @return
 */
public class AccountServiceTest {
    @Test
    public void testFindAll() {
//        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //使用註解建立容器
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        IAccountService service = context.getBean("accountService", IAccountService.class);
        List<Account> accounts = service.findAll();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }

    @Test
    public void testFindById() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService service = (IAccountService)context.getBean("accountService");
        Account account = service.findById(2);
        System.out.println(account);
    }

    @Test
    public void testSave() {
//        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //使用註解建立容器
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        IAccountService service = (IAccountService)context.getBean("accountService");
        Account account = new Account();
        account.setName("test");
        account.setMoney(2000);
        service.saveAccount(account);
    }

    @Test
    public void testUpdate() {
//        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //使用註解建立容器
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        IAccountService service = (IAccountService)context.getBean("accountService");
        Account account = service.findById(4);
        account.setMoney(3000);
        service.updateAccount(account);

    }@Test
    public void testDelete() {
//        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //使用註解建立容器
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        IAccountService service = (IAccountService)context.getBean("accountService");
        service.deleteAccount(4);
    }
}

七、AOP的相關概念

7.1 、AOP概述

7.1.1 什麼是AOP

在軟體業,AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期間動態代理實現程式功能的統一維護的一種技術。AOP是OOP的延續,是軟體開發中的一個熱點,也是Spring框架中的一個重要內容,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

簡單的說他就是把我們重複的程式碼抽取出來,在需要執行的時候,使用動態代理的技術,在不修改原始碼的基礎上進行已有方法的增強

7.1.2 AOP的作用及優勢

作用:

​ 在程式執行期間,不修改原始碼對已有方法進行增強

優勢:

​ 減少程式碼重複率

​ 提高開發效率

​ 維護方便

7.1.3 AOP的實現方式

使用動態代理技術

7.2 動態代理

特點:位元組碼隨用隨呼叫

作用:在不修改原始碼的方式對方法進行增強

分類:基於介面的動態代理、基於子類的動態代理(cglib)

7.2.1 基於介面的動態代理

提供者:JDK官方

要求:被代理物件至少實現一個介面

使用方法請閱讀程式碼註釋

商品介面

/**
 * @param
 * @return
 */
public interface IProducer {

    /**
     * 銷售產品
     * @param money
     */
    void saleProduct(float money);

    /**
     * 產品售後
     * @param money
     */
    void afterProduct(float money);
}

商品介面實現類

public interface IProducer {

    /**
     * 銷售產品
     * @param money
     */
    void saleProduct(float money);

    /**
     * 產品售後
     * @param money
     */
    void afterProduct(float money);
}

消費類,進行方法增強

public class Client {
    public static void main(String args[]){
        /**
         * 動態代理:
         *      特點:位元組碼隨用隨建立,隨用隨載入
         *      作用:在不修改原始碼的方式對方法進行增強
         *      分類:
         *          基於介面的動態代理
         *          基於子類的動態代理
         *
         *       基於介面的動態代理:
         *             涉及的類:Proxy
         *             提供者:JDK官方
         *             如何建立代理物件:
         *                      使用Proxy的newProxyInstance方法
         *             建立代理物件的要求:
         *                  被代理物件最少實現一個介面,如果沒有則不能使用
         *        newProxyInstance方法的引數:
         *              ClassLoader:類載入器
         *                  它用於載入代理物件的位元組碼的,和被代理物件使用相同的類載入器,固定寫法
         *              Class[]:位元組碼陣列
         *                  它是用於讓代理物件和被代理物件有相同的方法,固定寫法
         *              InvocationHandler:用於提供增強的程式碼
         *                  它是讓我們寫代理方法的,我們一般都是寫介面的實現類,常寫為匿名內部類
         *
         *
         */
        IProducer producer = new Producer();

        //建立代理物件
        IProducer producerProxy = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {

            /**
             *   作用:執行被代理物件的任何介面方法都會經過該方法
             * @param proxy 代理物件的引用
             * @param method    當前執行的方法	
             * @param args      當前執行方法所需的引數
             * @return 和被代理物件有相同的返回值
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object value = null;
                //提供增強的程式碼
                //1、獲取方法執行的引數
                String money_str = args[0].toString();
                float money = Float.parseFloat(money_str);

                //2、判斷當前執行方法是不是銷售方法
                if (method.getName().equals("saleProduct")) {
                    value = method.invoke(producer, money * 0.8f);

                } else {
                    //不是銷售方法,直接執行,方法不增強
                    value = method.invoke(producer,args);
                }
                return value;
            }
        });

        producerProxy.saleProduct(2000f);
    }
}
7.2.2 基於子類實現動態代理

提供者:cglib庫

要求:被代理的類不是最終類,及被代理的類不能被final修飾

使用方法請閱讀程式碼註釋

商品實現類

	
/**
 * 一個生產者
 * @param
 * @return
 */
public class Producer implements IProducer{

    /**
     * 銷售產品
     * @param money
     */
    @Override
    public void saleProduct(float money){
        System.out.println("銷售產品,並拿到錢:"+money);
    }


    /**
     * 售後
     * @param money
     */
    @Override
    public void afterProduct(float money){
        System.out.println("提供售後服務,並拿到錢"+money);

    }

}

模擬一個消費者

/**
 * 模擬一個消費者
 * @param
 * @return
 */
public class Client {
    public static void main(String args[]) {
        /**
         * 動態代理:
         *      特點:位元組碼隨用隨建立,隨用隨載入
         *      作用:在不修改原始碼的方式對方法進行增強
         *      分類:
         *          基於子類的動態代理
         *          基於子類的動態代理
         *
         *       基於介面的動態代理:
         *             涉及的類:Enhancer
         *             提供者:第三方cglib庫
         *             如何建立代理物件:
         *                      使用Enhancer類的create方法
         *             建立代理物件的要求:
         *                  被代理類不能是最終類:最終類:被final修飾的類
         *        create方法的引數:
         *              Class:指定被代理物件的位元組碼
         *
         *              Callback:用於提供增強的程式碼
         *                  它是讓我們寫如何代理,我們一般都是些一些該介面的實現類,通常情況下都是使用匿		   *                  名內部類
         *                  此介面的實現類一般都是誰用誰寫
         *                  我們一般寫的都是該介面的子介面實現類:MethodInterceptor
         *
         *
         */

        Producer producer = new Producer();
        Producer productCglib  = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 執行被代理物件的方法都經過該方法,用於提供增強程式碼的
             * @param o  代理物件的引用
             * @param method  當前執行的方法
             * @param objects   當前執行方法所需要的引數
             * @param methodProxy 當前執行方法的代理物件
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //判斷是不是銷售方法
                if (method.getName().equals("saleProduct")) {
                    //獲取執行方法的引數
                    float money = (float) objects[0];
                    method.invoke(producer, money * 0.8f);
                    return null;
                }
                Object obj = method.invoke(producer, args);
                return obj;
            }
        });
        productCglib.saleProduct(20000f);

    }

}

7.2.3 使用動態代理解決事務問題

connection連線的工具類,用於從資料來源獲取同一個連線,並實現執行緒和連線的解綁功能

package com.itheima.utils;
import javax.sql.DataSource;
import java.sql.Connection;

/**
 *
 * connection連線的工具類,用於從資料來源中獲取一個連線,並且實現和執行緒的解綁
 * @param
 * @return
 */
public class ConnectionUtils {
    private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

    private DataSource dataSource;

    public void setDataSource(DataSource ds){
        this.dataSource = ds;
    }

    //判斷當前執行緒容器是否有connection連線

    /**
     * 獲取連線
     * @return
     */
    public Connection getThreadConnection(){
        Connection conn = null;
        try {
            //從執行緒容器中獲取connection
             conn = threadLocal.get();

            //判斷執行緒容器中是否有連線
            if(conn == null){
                //建立一個連線
                conn = dataSource.getConnection();
                //存入到執行緒容器
                threadLocal.set(conn);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 將執行緒和連線解綁:
     *  為什麼要解綁:
     *      執行緒一般儲存線上程池中,連線一般儲存在連線池中,
     *      當一個連線使用完後將close放到連線池中,此時還繫結在對應的執行緒中,當執行緒下次使用時拿到的是一個close的連線物件
     */
    public void removeConnection(){
        threadLocal.remove();
    }
}

執行事務的工具類 TransactionManager

package com.itheima.utils;
import java.sql.SQLException;

/**
 * 執行事務的工具類
 * @param
 * @return
 */
public class TransactionManager {

    private ConnectionUtils connectionUtils;
    public void setConnectionUtils(ConnectionUtils connectionUtils){
        this.connectionUtils = connectionUtils;
    }

    /**
     * 開啟事務方法
     */
    public void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 提交事務方法
     */
    public void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
            System.out.println("事務提交");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 事務回滾方法
     */
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
            System.out.println("事務回滾");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 釋放連線
     */
    public void release(){
        try {
            //將連線放回池中
            connectionUtils.getThreadConnection().close();
            //將連線和執行緒解綁
            connectionUtils.removeConnection();
            System.out.println("執行緒和連線解綁");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

建立service層的代理物件的工廠類

package com.itheima.factory;
import com.itheima.service.IAccountService;
import com.itheima.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 用於建立service的代理物件的工廠
 * @param
 * @return
 */
public class BeanFactory {

    private IAccountService accountService;
    private TransactionManager manager;

    public void setAccountService(IAccountService accountService){
        this.accountService = accountService;
    }

    public void setManager(TransactionManager manager){
        this.manager = manager;
    }

    /**
     * 獲取service的代理物件
     * @return
     */
    public IAccountService getAccountService(){
        return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
            /**
             * 新增事務的支援
             * @param proxy
             * @param method
             * @param args
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object rtValue = null;
                try {
                    System.out.println("使用service代理物件");
                    //開啟事務
                    manager.beginTransaction();
                    //執行操作
                    rtValue = method.invoke(accountService, args);
                    //提交事務
                    manager.commit();
                    return rtValue;

                }catch (Exception e){
                    //事務回滾
                    manager.rollback();
                    e.printStackTrace();
                }finally {
                    //解除繫結
                    manager.release();
                }
                return rtValue;

            }
        });
    }

}

八、Spring中的AOP

8.1 spring中的AOP的相關術語和代理選擇方式

Joinpoint(連線點)

​ 所謂連線點是指那些被攔截的點。在Spring中,這些點指的是方法,因為spring值支援方法型別的連線點

Pointcut(切入點):

​ 所謂切入點是指我們要對哪些Joinpoint進行攔截的定義

Advice(通知/增強)

​ 所謂通知是指攔截到Joinpoint之後所要做的事情就是通知

​ 通知的型別分為:

前置通知(before):在切入點方法執行之前執行

結果通知(affter-returning):在切入點正常執行之後執行

異常通知(after-throwing):在切入點異常執行之後執行

最終通知(after):無論切入點是否正常執行都會執行

Introduction(引介):

​ 引介是一種特殊的通知在不修改類程式碼的前提下,Introduction可以在執行期間為類動態新增一些方法或Field

Target(物件目標):代理的目標物件

Weaving(織入):是把增強引用到目標物件來建立新的代理物件的過程,Spring採用動態代理織入,而Aspectj採用編譯期織入和類裝載期織入

Proxy(代理):一個類被AOP織入增強後,就產生一個結果代理類

Aspect(切面):是切入點和通知(引介)的結合

在 spring 中,框架會根據目標類是否實現了介面來決定採用哪種動態代理的方式。

8.2 Spring中基於註解的AOP配置

spring基於xml配置AOP的步驟
1、把通知類Logger也交給spring來管理
2、使用aop:config標籤表示開始AOP的配置
3、使用aop:aspect標籤表明配置切面
4、在aop:aspect標籤內部使用對應標籤來配置通知的型別

8.2.1 aop:config標籤

作用:表示開始Spring的AOP配置

8.2.2 aop:aspect標籤

作用:在aop:aspect標籤內部使用對應標籤來配置通知的型別

屬性:
id:是給切面提供一個唯一標識
ref屬性:是指定通知類bean的id

下面是關於通知型別的標籤,出現在aop:aspect標籤的內部

8.2.3 aop:before 前置通知標籤

作用:表示前置通知, 在切入點方法執行之前執行

method屬性:用於指定切面類(Logger)中的哪個方法是前置通知

pointcut屬性:用於指定切入點表示式,該表示式的含義的是對業務層的哪些方法進行增強

pointcut-ref屬性:用於指定表示式配置的id

切入點表示式:

​ 關鍵字:execution(表示式)

​ 表示式的格式:
​ 修飾符名稱 返回值名稱 寶明名.方法名稱(引數列表)

​ 標準表示式寫法:

​ public void com.itheima.service.impl.AddressServiceImpl.saveAddress( )

​ 訪問修飾符可以省略:

​ void com.itheima.service.impl.AddressServiceImpl.saveAddress( )

​ 返回值可以使用萬用字元“ * ”配置來表示任意返回值:

   	\* com.itheima.service.impl.AddressServiceImpl.saveAddress( )

​ 包名可以使用萬用字元“ * ”來配置,表示任意包,但是有幾級包就要用幾個*來表示

​ * *.*.*.*.AddressServiceImpl.saveAddress( )

​ 包名可以使用..表示當前包及其子包

​ * *..AddressServiceImpl.saveAddress()

​ 類名和方法名都可以使用*來實現通配

​ * *..*.*( )

​ 引數列表

​ 可以直接寫資料型別

​ 基本型別直接寫名稱 如:int boolean

​ 引用資料型別寫包的全限定類名 如:java.lang.String

​ 可以使用*來表示任意型別,但是必須有引數

​ * *..*.*(* )

​ 可以使用..表示有無引數都可以,有引數可以是任意型別

​ 全萬用字元寫法:

​ * *..*.*(..)

​ 但是在實際開發中,我們值對service進行aop管理,所以常寫為:

​ * com.itheima.service.impl.*.*(..)

如:
<bean id="addressService" class="com.itheima.service.impl.AddressServiceImpl"></bean>
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<aop:config>
        <!--配置切入點-->
        <aop:aspect id="aop" ref="logger">
            <!--配置切面-->
            <aop:before method="printLog" pointcut=
                        "execution(* com.itheima.service.impl.*.*())"></aop:before>
        </aop:aspect>
    </aop:config>
8.2.4 aop:after-returning後置通知標籤

作用:表示後置通知,在切入點方法正常執行之後執行,後置通知和異常通知永遠只能執行一個

method屬性:用於指定切面類(Logger)中的哪個方法是前置通知

pointcut屬性:用於指定切入點表示式,該表示式的含義的是對業務層的哪些方法進行增強

pointcut-ref屬性:用於指定表示式配置的id

8.2.5 aop:after-throwing異常通知標籤

作用:表示異常通知,在切入方法執行產生異常後執行,異常通知和後置通知永遠只能執行一個

method屬性:用於指定切面類(Logger)中的哪個方法是前置通知

pointcut屬性:用於指定切入點表示式,該表示式的含義的是對業務層的哪些方法進行增強

pointcut-ref屬性:用於指定表示式配置的id

8.2.6 aop:after最終通知標籤

作用:表示最終通知,無論切入點是否正常執行都會在其後執行

method屬性:用於指定切面類(Logger)中的哪個方法是前置通知

pointcut屬性:用於指定切入點表示式,該表示式的含義的是對業務層的哪些方法進行增強

pointcut-ref屬性:用於指定表示式配置的id

8.2.7 aop:pointcut配置表示式 標籤

作用:配置切入點表示式

id屬性:用於指定表示式內部的唯一表達式的標識

expression屬性:用於指定表示式的內容

注:此標籤寫在aop:aspect標籤內部只能當前切面使用
它還可以寫在aop:aspect標籤外部,表示所有的切面都可以使用

8.3 Spring基於XMl配置的AOP案例

8.3.1 Address介面

地址介面類

package com.itheima.service;
public interface IAddressService {
    /**
     * 模擬儲存賬戶
     */
    void saveAddress();

    /**
     * 模擬更新賬戶
     * @param i
     */
    void updateAddress(int i);

    /**
     * 模擬刪除賬戶
     * @return
     */
    int deleteAddress();
}
8.3.2 Address的實現類

地址介面實現類

package com.itheima.service.impl;

import com.itheima.service.IAddressService;

/**
 * @param
 * @return
 */
public class AddressServiceImpl implements IAddressService {

    public void saveAddress() {
        System.out.println("儲存了地址");
    }

    public void updateAddress(int i) {
        System.out.println("更新了使用者"+i);
    }

    public int deleteAddress() {
        System.out.println("刪除了使用者");
        return 0;
    }
}

8.3.3 Looger工具類

工具模擬日誌類

package com.itheima.utils;

import org.aspectj.lang.ProceedingJoinPoint;
/**
 * 用於記錄日誌的工具類,裡面提供了公共的程式碼
 */
public class Logger {

    /**
     * 用於列印日誌:計劃讓其在切入點方法執行之前執行(切入點就是業務層方法)
     */
    public void beforePrintLog(){
        System.out.println("前置通知Logger類的beforePrintLog方法正在記錄日誌......");
    }

    /**
     * 用於列印日誌:計劃讓其在切入點方法執行之前執行(切入點就是業務層方法)
     */
    public void afterReturningPrintLog() {
        System.out.println("後置通知Logger類的afterReturningPrintLog方法正在記錄日誌......");
    }

    /**
     * 用於列印日誌:計劃讓其在切入點方法執行之前執行(切入點就是業務層方法)
     */
    public void afterThrowingPrintLog(){
        System.out.println("異常通知Logger類的afterThrowingPrintLog方法正在記錄日誌......");
    }

    /**
     * 用於列印日誌:計劃讓其在切入點方法執行之前執行(切入點就是業務層方法)
     */
    public void afterPrintLog(){
        System.out.println("最終通知Logger類的afterPrintLog方法正在記錄日誌......");
    }
    
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object retValue = null;
        try {
            Object[] args = pjp.getArgs();//獲取切入方法執行所需的引數

            System.out.println("(前置通知)Logger類中的aroundPrintLog方法開始執行記錄日誌");

            retValue = pjp.proceed(args);//執行方法(明確呼叫業務層方法(切入點))

            System.out.println("(後置通知)Logger類中的aroundPrintLog方法開始執行記錄日誌");
            return retValue;

        }catch (Throwable t){
            System.out.println("(異常通知)Logger類中的aroundPrintLog方法開始執行記錄日誌");;
            throw new RuntimeException(t);
        }finally {
            System.out.println("(最終通知)Logger類中的aroundPrintLog方法開始執行記錄日誌");
        }


    }

}

8.3.4 bean.xml配置檔案

引入標頭檔案

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置spring的ioc,把service物件配置進來-->
    <bean id="addressService" class="com.itheima.service.impl.AddressServiceImpl"></bean>
    <bean id="logger" class="com.itheima.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切入點表示式
            id屬性用於指定表示式內部的唯一表達式的標識
            expression屬性:用於指定表示式的內容
            注:此標籤寫在<aop:aspect>標籤內部只能當前切面使用
                它還可以寫在<aop:aspect>變遷外部,表示所有的切面都可以使用
        -->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面-->
        <aop:aspect id="aop" ref="logger">
            <!--配置前置通知:在切入點方法執行之前執行-->
            <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>

            <!--配置後置通知:在切入點方法正常執行之後執行,他和異常通知永遠只能執行一個-->
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>

            <!--配置異常通知:在切入點方法執行產生異常之後執行,他和後置通知永遠只能執行一個-->
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>

            <!--配置最終通知:無論切入點方法是否正常執行它都會在其後執行-->
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
        </aop:aspect>
    </aop:config>

</beans>

執行結果

8.4 Spring中的環繞通知

通知的型別和代理物件的參照

8.4.1 aop:around 環繞通知標籤

作用:表示環繞配置,它是Spring框架為我們提供的一種可以在程式碼中手動控制增強方法何時執行的方式

method屬性:用於指定切面類(Logger)中的哪個方法是前置通知

pointcut屬性:用於指定切入點表示式,該表示式的含義的是對業務層的哪些方法進行增強

pointcut-ref屬性:用於指定表示式配置的id

ProceedingJoinPoint介面:該介面有一個方法proceed(),此方法就相當於明確呼叫切入點方法

    <!--配置spring的ioc,把service物件配置進來-->
    <bean id="addressService" class="com.itheima.service.impl.AddressServiceImpl"></bean>
    <bean id="logger" class="com.itheima.utils.Logger"></bean>
    <!--配置AOP-->
    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--配置切面-->
        <aop:aspect id="aop" ref="logger">
            <!--配置環繞切面  切面詳情請參考Logger內部的aroundPrintLog方法-->
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
        </aop:aspect>
    </aop:config>

Looger的環繞方法

/**
     * 環繞通知
     *
     *  問題:
     *      當我們使用aop配置了環繞通知後,切入點方法aroundPrintLog方法沒有執行
     *  分析:
     *      當我們比較動態代理的程式碼的時候,發現使用動態代理時有明確的切入點方法value = method.invoke(Object,arg...[]);
     *      而我們程式碼中卻沒有
     */
//    public void aroundPrintLog(){
//        System.out.println("環繞通知Logger類的aroundPrintLog方法正在記錄日誌......");
//    }

    /**
     *  解決方法:
     *      Spring框架為我們提供了一個介面:ProceedingJoinPoint。該介面有一個方法proceed(),此方法就相當於明確呼叫切入點方法
     *      該介面可以作為環繞通知的方法引數,再執行程式時,Spring框架會為我們提供介面的實現類供我們使用
     *
     *  spring中的環繞通知
     *      它是Spring框架為我們提供的一種可以在程式碼中手動控制增強方法何時執行的方式
     *
     * @return
     */
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object retValue = null;
        try {
            Object[] args = pjp.getArgs();//獲取切入方法執行所需的引數

            System.out.println("(環繞——前置通知)Logger類中的aroundPrintLog方法開始執行記錄日誌");

            retValue = pjp.proceed(args);//執行方法(明確呼叫業務層方法(切入點))

            System.out.println("(環繞——後置通知)Logger類中的aroundPrintLog方法開始執行記錄日誌");
            return retValue;

        }catch (Throwable t){
            System.out.println("(環繞——異常通知)Logger類中的aroundPrintLog方法開始執行記錄日誌");;
            throw new RuntimeException(t);
        }finally {
            System.out.println("(環繞——最終通知)Logger類中的aroundPrintLog方法開始執行記錄日誌");
        }
    }
}

執行結果

九、Spring基於註解的AOP

9.1 Spring的AOP常用註解

@Aspect( )

作用:表示當前類是一個切面類

@Pointcut( )

作用:配置切點通用表示式

@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    public void pt1(){ 
    	
    }
@Before( )

作用:表示當前方法是一個前置通知方法

value屬性:切入點表示式的引用

 @Before(value = "pt1()")//前置通知
    public void beforePrintLog(){
        System.out.println("前置通知Logger類的beforePrintLog方法正在記錄日誌......");
    }

@AfterReturning( )

作用:表示當前方法是一個後置通知方法

value屬性:切入點表示式的引用

@AfterReturning("pt1()")//後置通知
    public void afterReturningPrintLog() {
        System.out.println("後置通知Logger類的afterReturningPrintLog方法正在記錄日誌......");
    }
@AfterThrowing( )

作用:表示當前方法是一個異常通知方法

value屬性:切入點表示式的引用

 @AfterThrowing("pt1()")//異常通知
    public void afterThrowingPrintLog(){
        System.out.println("異常通知Logger類的afterThrowingPrintLog方法正在記錄日誌......");
    }
@After( )

作用:表示當前方法是一個最終通知方法

value屬性:切入點表示式的引用

 @After(value = "pt1()")//最終通知
    public void afterPrintLog(){
        System.out.println("最終通知Logger類的afterPrintLog方法正在記錄日誌......");
    }

@Around( )

作用:表示當前方法是一個環繞通知方法

value屬性:切入點表示式的引用

@Around("pt1()")//環繞通知
    public void aroundPrintLog(){
        System.out.println("環繞通知Logger類的aroundPrintLog方法正在記錄日誌......");
    }
@EnableAspectJAutoProxy

作用:表示spring開啟了aop,配置在切面類

9.2 基於註解AOP的簡單演示

9.2.1 載入service到IOC容器
@Service("addressService")
public class AddressServiceImpl implements IAddressService {

    public void saveAddress() {
        System.out.println("儲存了地址");
    }

    public void updateAddress(int i) {
        System.out.println("更新了使用者"+i);
    }

    public int deleteAddress() {
        System.out.println("刪除了使用者");
        return 0;
    }
}
9.2.2 載入Logger物件進Spring的IOC容器
/**
 * 用於記錄日誌的工具類,裡面提供了公共的程式碼
 * @param
 * @return
 */
@Component("logger")//載入入springIOC容器
@Aspect//表示當前類是一個切面類
public class Logger {

    //配置切入點表示式
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    public void pt1(){ }

    @Before("pt1()")//前置通知
    public void beforePrintLog(){
        System.out.println("前置通知Logger類的beforePrintLog方法正在記錄日誌......");
    }

    @AfterReturning("pt1()")//後置通知
    public void afterReturningPrintLog() {
        System.out.println("後置通知Logger類的afterReturningPrintLog方法正在記錄日誌......");
    }

    @AfterThrowing("pt1()")//異常通知
    public void afterThrowingPrintLog(){
        System.out.println("異常通知Logger類的afterThrowingPrintLog方法正在記錄日誌......");
    }

    @After("pt1()")//最終通知
    public void afterPrintLog(){
        System.out.println("最終通知Logger類的afterPrintLog方法正在記錄日誌......");
    }

    @Around("pt1()")//環繞通知
    public void aroundPrintLog(){
        System.out.println("環繞通知Logger類的aroundPrintLog方法正在記錄日誌......");
    }
}

9.2.3 bean.xml配置檔案
 <!--配置Spring建立容器時要掃描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>
    <!--配置Spring開啟註解aop的支援-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
9.2.4 使用純註解的方式

新增配置類

package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * 這裡是Aop的配置類
 * @param
 * @return
 */
@Configuration//標識為配置類
@ComponentScan("com.itheima")//標識需要掃描的包
@EnableAspectJAutoProxy//標識spring開啟Aop
public class AopConfig {

}

十、JdbcTemplate

10.1 概念

它是 spring 框架中提供的一個物件,是對原始 Jdbc API 物件的簡單封裝。spring 框架為我們提供了很多

的操作模板類。

​ 操作關係型資料的:

​ JdbcTemplate

​ HibernateTemplate

操作 nosql 資料庫的:

​ RedisTemplate

操作訊息佇列的:

​ JmsTemplate

我們今天的主角在 spring-jdbc-5.0.2.RELEASE.jar 中,我們在導包的時候,除了要匯入這個 jar 包

外,還需要匯入一個 spring-tx-5.0.2.RELEASE.jar(它是和事務相關的)。

10.2 jdbcTemplate的建立

JdbcTemplate template = new JdbcTemplate(DBUtls.getDataSource());

10.2 查詢多個

 List<Account> accounts = template.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), name);

10.3 查詢單個

 List<Account> accounts =  template.query("select * from account where id = ?" ,new BeanPropertyRowMapper<Account>(Account.class),id);

10.4 增刪改

template.update("update account set name = ?, money = ? where     id=?",account.getName(),account.getMoney(),account.getId());

10.5 查詢單行單列(查詢記錄條數)

template.query("select count(*) from account",Integer.class);

10.6 在dao中使用JdbcTemplate

10.6.1 準備實體類
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id; }
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name; }
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money; }
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account [id=" + id + ", name=" + name + ", money=" + money + "]";
10.6.2 第一種方式:在dao中定義 JdbcTemplate

賬戶介面類

/**
* 賬戶的介面
*/
public interface IAccountDao {
    /**
    * 根據 id 查詢賬戶資訊
    * @param id
    * @return
    */
    Account findAccountById(Integer id);
    /**
    * 根據名稱查詢賬戶資訊
    * @return
    */
    Account findAccountByName(String name);
    /**
    * 更新賬戶資訊
    * @param account
    */
    void updateAccount(Account account);
}

介面實現類

public class AccountDaoImpl implements IAccountDao {
	private JdbcTemplate jdbcTemplate;
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}
    
	@Override
	public Account findAccountById(Integer id) {
		List<Account> list = jdbcTemplate.query("select * from account where id = ? 
		",new AccountRowMapper(),id);
		return list.isEmpty()?null:list.get(0);
	}
                                                
	@Override
	public Account findAccountByName(String name) {
		List<Account> list = jdbcTemplate.query("select * from account where name 
		= ? ",new AccountRowMapper(),name);
		if(list.isEmpty()){
			return null; 
        }
		if(list.size()>1){
			throw new RuntimeException("結果集不唯一,不是隻有一個賬戶物件");
		}
		return list.get(0);
	}
	@Override
	public void updateAccount(Account account) {
		jdbcTemplate.update("update account set money = ? where id = ? 
		",account.getMoney(),account.getId());
	} 
}

bean.xml配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- 配置一個 dao -->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
		<!-- 注入 jdbcTemplate --> 
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>
		<!-- 配置一個數據庫的操作模板:JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property 			name="dataSource" ref="dataSource"></property>
	</bean>
    <!-- 配置資料來源 -->
    <bean id="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 		<property name="driverClassName"value="com.mysql.jdbc.Driver"></property>
      <property name="url" value="jdbc:mysql:///spring_day04"></property> <property name="username" value="root"></property> <property name="password" value="1234">
        </property>
	</bean>
</beans>

有個小問題。就是我們的 dao 有很多時,每個 dao 都有一些重複性的程式碼。下面就是重複程式碼:

private JdbcTemplate jdbcTemplate;

public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {

this.jdbcTemplate = jdbcTemplate;

}

4.6.3 第二種方式:讓dao 繼承JdbcDaoSupport

JdbcDaoSupport 是 spring 框架為我們提供的一個類,該類中定義了一個 JdbcTemplate 物件,我們可以

直接獲取使用,但是要想建立該物件,需要為其提供一個數據源:具體原始碼如下:

public abstract class JdbcDaoSupport extends DaoSupport {
	//定義物件
	private JdbcTemplate jdbcTemplate;
	//set 方法注入資料來源,判斷是否注入了,注入了就建立 JdbcTemplate
	public final void setDataSource(DataSource dataSource) {
		if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) { 
            //如果提供了資料來源就建立 JdbcTemplate
			this.jdbcTemplate = createJdbcTemplate(dataSource);
			initTemplateConfig();
		}
    }
    
	//使用資料來源建立 JdcbTemplate
	protected JdbcTemplate createJdbcTemplate(DataSource dataSource) {
		return new JdbcTemplate(dataSource);
	}
	//當然,我們也可以通過注入 JdbcTemplate 物件
	public final void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
   		this.jdbcTemplate = jdbcTemplate;
		initTemplateConfig();
	}
    
	//使用 getJdbcTmeplate 方法獲取操作模板物件
	public final JdbcTemplate getJdbcTemplate() {
 		return this.jdbcTemplate;
    }

賬戶的介面

public interface IAccountDao {
    /**
    * 根據 id 查詢賬戶資訊
    * @param id
    * @return
    */
    Account findAccountById(Integer id);
    /**
    * 根據名稱查詢賬戶資訊
    * @return
    */
    Account findAccountByName(String name);
    /**
    * 更新賬戶資訊
    * @param account
    */
    void updateAccount(Account account);
}

賬戶的持久層實現類 (此版本 dao,只需要給它的父類注入一個數據源)

public class AccountDaoImpl2 extends JdbcDaoSupport implements IAccountDao {
    @Override
    public Account findAccountById(Integer id) {
        //getJdbcTemplate()方法是從父類上繼承下來的。
        List<Account> list = getJdbcTemplate().query("select * from account where 
        id = ? ",new AccountRowMapper(),id);
        return list.isEmpty()?null:list.get(0);
    }
                                                     
	@Override
	public Account findAccountByName(String name) {
    //getJdbcTemplate()方法是從父類上繼承下來的。
    List<Account> list = getJdbcTemplate().query("select * from account where 
    name = ? ",new AccountRowMapper(),name);
    if(list.isEmpty()){
        return null; 
    }
	if(list.size()>1){
		throw new RuntimeException("結果集不唯一,不是隻有一個賬戶物件");
	}
	return list.get(0);
	}
                                                 
	@Override
	public void updateAccount(Account account) {
		//getJdbcTemplate()方法是從父類上繼承下來的。
		getJdbcTemplate().update("update account set money = ? where id = ? 			",account.getMoney(),account.getId());
	}
}

bean.xml配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans.xsd">
	<!-- 配置 dao2 -->
    <bean id="accountDao2" class="com.itheima.dao.impl.AccountDaoImpl2">
	<!-- 注入 dataSource -->
        <property name="dataSource" ref="dataSource"></property>
	</bean>
	<!-- 配置資料來源 --> 
    <bean id="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource"		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property> 
        <property name="url" value="jdbc:mysql:///spring_day04"></property> 
        <property name="username" value="root"></property> 
        <property name="password" value="1234"></property>
	</bean>
</beans>

兩種實現的區別:

​ 第一種在dao類中定義JdbcTemplate的方式,適用於所有的配置方式(xml配置方式和註解配置的方式)

​ 第二種讓dao繼承JdbcDaoSupport的方式,只能基於xml配置的方式,基於註解的方式不能使用

十一、Spring中的事務控制

11.1 Spring事務控制需要明確的

第一:JavaEE 體系進行分層開發,事務處理位於業務層,Spring 提供了分層設計業務層的事務處理解決方

案。

第二:spring 框架為我們提供了一組事務控制的介面。具體在後面的第二小節介紹。這組介面是在

spring-tx-5.0.2.RELEASE.jar 中。

第三:spring 的事務控制都是基於 AOP 的,它既可以使用程式設計的方式實現,也可以使用配置的方式實現。我

們學習的重點是使用配置的方式實現。

11.2 Spring中事務控制的API的介紹

11.2.1 PlatformTransactionManager

此介面是spring中的事務管理器,它提供了我們常用的操作事務的方法

//獲取事務狀態

TransactionStatus getTransaction(TransactionDefinition definition)

//提交事務

void commit(TransactionStatus status)

//回滾事務

void rollback(TransactionStatus status)

在我們開發中都是使用它的實現類

真正管理事務的物件
	org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 SpringJDBC 或 iBatis 進行持久化資料時使用
	org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate 版本進行持久化資料時使用
11.2.2 TrnasactionDefinition

該介面是事務的定義資訊物件,裡面有如下方法

//獲取事務物件名稱

String getName( );

//獲取事務隔離級別

int getIsolationLeve( )

//獲取事務傳播行為

int getPropagationBehavior( )

//獲取事務超時時間

int getTimeout

//獲取事務是否只讀

//讀寫型事務:增加、刪除、修改事務開啟

//只讀型事務:執行查詢是時,也會開啟事務

boolean isReadOnly( )

11.2.2.1 事務的隔離級別

11.2.2.2 事務的傳播行為

REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務,加入到這個事務。是預設值

SUPPORTS:支援當前事務,如果當前沒有事務,就以非事務的方式執行(沒有事務)

MANDATORY:使用當前的事務,如果當前沒有,就丟擲異常

REQUERS_NEW:新建事務,如果當前在事務中,把當前事務掛起

NOT_SUPPORTED:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起

NEVER:以非事務方式執行,如果當前存在事務,丟擲異常

NESTED:如果當前存在事務,則在巢狀事務內執行,如果當前沒有事務,則執行REQUIRED 類似的操作。

11.2.2.3 超時時間

預設值為-1,表示沒有超時限制。如果有,以秒為單位。

11.2.2.4 是否是隻讀事務

建議查詢是設為只讀型事務

11.2.3 TransactionStatus

此介面是事務具體的執行狀態

TranactionStatus介面描述了某個時間點上事務物件的狀態資訊,包含6個具體的操作

//重新整理事務

void flush( )

//獲取是否存在儲存點

boolean hasSavepoint( )

//獲取事務是否完成

boolean isCompleted( )

//獲取事務是否為新的事務

boolean isNewTransaction( )

//獲取事務是否回滾

boolean isRollbackObly( )

//設定事務回滾

void setRollbackOnly( )

11.3 Spring中基於XML的宣告式事務控制

11.3.0 配置步驟

步驟一:配置事務管理器DataSourceTransactionManager

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

步驟二:配置事務通知

​ 需要匯入事務的約束到bean.xml的約束中,tx的名稱空間和約束,同時也需要aop

​ 使用tx:advice標籤配置事務通知

​ id屬性:給事務通知起一個唯一標識

​ transaction-manager:給事務提供一個事務管理器引用

<!--配置事務的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--
            配置事務的屬性:
				name:指定業務層的核心方法名稱
                isolation:"指定事務的隔離級別,預設值是DEFAULT,表示使用資料庫的預設隔離級別"
                propagation:"用於指定事務的傳播行為。預設值為REQUIRED,表示一定會有事務,增刪改的選擇,查詢的方法可以選擇SUPPORTS".
                read-only:"用於指定事務是否只讀,只有查詢方法才能設定為true,預設值為false,表示讀寫"
                timeout:"用於表示事務的超時時間,預設值為-1,表示永不超時,如果指定了時間,以秒為單位"
                rollback-for:"用於指定一個異常,當產生該異常時,事務回滾,產生其他異常時,事務不回滾,沒有預設值,表示任何異常都回滾。"
                no-rollback-for:"用於指定一個異常,當產生該異常時,事務不回滾,當產生其他異常時事務回滾,沒有預設值,表示任何異常都回滾"
        -->
        <tx:attributes>
            <!-- REQUIRED:如果當前沒有事務,就新建一個事務,如果已經存在一個事務,加入到這個事務。預設值
			     SUPPORTS:支援當前事務,如果當前沒有事務,就以非事務的方式執行(沒有事務)
			-->
            <tx:method name="transfer" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>

步驟三:建立AOP中的通用切入點表示式

步驟四:建立事務通知和切入點表示式的對應關係

<!--配置AOP-->
    <aop:config >
        <!--配置切入點表示式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--建立切入點表示式和事務通知的對應關係-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>

步驟五:配置事務的屬性,在事務的通知tx:advice標籤的內部配置

11.3.1 匯入依賴
<dependencies>
        <!--spring的IOC容器-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <!--spring的jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <!--spring支援事務的依賴-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <!--mysql資料庫-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <!--aop切點表示式的依賴-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

        <!--junit測試類-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!--spring整合junit測試的依賴-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
11.3.2 account持久層介面
package com.itheima.dao;

import com.itheima.domain.Account;

/**
 * 賬戶的持久層介面
 */
public interface IAccountDao {
    /**
     * 根據id查詢賬戶
     * @param id
     * @return
     */
    Account findAccountById(Integer id);

    /**
     * 根據使用者名稱查詢賬戶
     * @param name
     * @return
     */
    Account findAccountByName(String name);

    /**
     * 更新賬戶
     * @param account
     */
    void updateAccount(Account account);
}

11.3.3 account持久層實現類
package com.itheima.dao.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

import java.util.List;

/**賬戶的持久層實現類
 * @param
 * @return
 */
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {

    @Override
    public Account findAccountById(Integer id) {
        List<Account> accounts =  getJdbcTemplate().query("select * from account where id = ?" ,new BeanPropertyRowMapper<Account>(Account.class),id);
        return accounts.isEmpty() ? null : accounts.get(0);
    }

    @Override
    public Account findAccountByName(String name) {
        List<Account> accounts = getJdbcTemplate().query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), name);
        if (accounts.isEmpty()){
            return null;
        }
        if (accounts.size() > 1){
            throw new RuntimeException("結果集不唯一");
        }

        return accounts.get(0);
    }

    @Override
    public void updateAccount(Account account) {
        getJdbcTemplate().update("update account set name = ?, money = ? where id=?",account.getName(),account.getMoney(),account.getId());
    }
}

11.3.4 account業務層介面
package com.itheima.service;

import com.itheima.domain.Account;

/**
 * 業務層的介面類
 * @param
 * @return
 */
public interface IAccountService {

    /**
     * 根據使用者id查詢使用者
     * @param id
     * @return
     */
    Account findAccountById(Integer id);

    /**
     * 轉賬
     * @param sourceName  轉出賬戶名稱
     * @param targetName  轉入賬戶名稱
     * @param money       轉賬金額
     */
    void transfer(String sourceName,String targetName,Float money);

}
11.3.5 account業務層實現類
package com.itheima.service.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;

import java.util.List;

/**
 * @param
 * @return
 */
public class AccountServiceImpl implements IAccountService {
    private IAccountDao dao;

    public void setDao(IAccountDao dao) {
        this.dao = dao;
    }

    @Override
    public Account findAccountById(Integer accountId) {
        return dao.findAccountById(accountId);
    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer...");
        //1、根據名稱查詢轉出賬戶
        Account source = dao.findAccountByName(sourceName);
        //2、根據名稱查詢轉入賬戶
        Account target = dao.findAccountByName(targetName);
        //3、轉出賬戶減錢
        source.setMoney(source.getMoney() - money);
        //4、轉入賬戶加錢
        target.setMoney(target.getMoney() + money);
        //5、更新轉出賬戶
        dao.updateAccount(source);
        //模擬異常
//        int i = 1 / 0;
        //6、更新轉入賬戶
        dao.updateAccount(target);

    }
}
11.3.6 bean.xml配置檔案
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置賬戶的持久層-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dateSource"></property>
    </bean>
    
    <!--配置業務層-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="dao" ref="accountDao"></property>
    </bean>

    <!--配置資料來源檔案dataSource-->
    <bean id="dateSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/eesy_spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123"></property>
    </bean>
    <!--
        spring中基於XML宣告式事務控制配置步驟
        1、配置事務管理器
        2、配置事務通知
            需要匯入事務的約束到bean.xml的約束中 tx的名稱空間和約束,同時也需要aop的
            使用<tx:advice>標籤配置事務通知
                屬性:id:給事務通知起一個唯一的標誌
                      transaction-manager:給事務通知提供一個事務管理器引用
        3、配置AOP中的通用切入點表示式
        4、建立事務通知和切入點表示式的對應關係
        5、配置事務的屬性
            在事務的通知<tx:advice>標籤的內部配置
    -->
    <!--配置事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dateSource"></property>
    </bean>

    <!--配置事務的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--
            配置事務的屬性:
                isolation:"指定事務的隔離級別,預設值是DEFAULT,表示使用資料庫的預設隔離級別"
                propagation:"用於指定事務的傳播行為。預設值為REQUIRED,表示一定會有事務,增刪改的選擇,查詢的方法可以選擇SUPPORTS".
                read-only:"用於指定事務是否只讀,只有查詢方法才能設定為true,預設值為false,表示讀寫"
                timeout:"用於表示事務的超時時間,預設值為-1,表示永不超時,如果指定了時間,以秒為單位"
                rollback-for:"用於指定一個異常,當產生該異常時,事務回滾,產生其他異常時,事務不回滾,沒有預設值,表示任何異常都回滾。"
                no-rollback-for:"用於指定一個異常,當產生該異常時,事務不回滾,當產生其他異常時事務回滾,沒有預設值,表示任何異常都回滾"
        -->
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>

    <!--配置AOP-->
    <aop:config >
        <!--配置切入點表示式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--建立切入點表示式和事務通知的對應關係-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>
</beans>

11.4 Sring中基於註解的宣告式事務控制

@Transactional

當標於類前時, 標示類中所有方法都進行事務處理

propagation屬性:事務傳播行為,預設值為REQUIRED

redaOnly屬性:用於指定事務是否只讀,只有查詢方法一般設定為true,預設為false,表示讀寫

當出現在方法上時,表示配置這一個方法的事務屬性

@EnableTransactionManagement

表示開啟事務管理,用於使用純註解配置事務管理器的註釋

bean.xml配置檔案
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="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/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd>
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">


    <!--配置Spring建立容器要掃描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <!--配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dateSource"></property>
    </bean>

    <!--配置資料來源檔案dataSource-->
    <bean id="dateSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/eesy_spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123"></property>
    </bean>
    <!--
        spring中基於註解的宣告式事務控制配置步驟
        1、配置事務管理器
        2、開啟Spring對註解事務的支援
        3、在需要事務支援的地方使用@Transactional註解

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

    <!--開啟Spring對註解事務的支援-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

持久層實現類
package com.itheima.dao.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;

import java.util.List;

/**賬戶的持久層實現類
 * @param
 * @return
 */
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account findAccountById(Integer id) {
        List<Account> accounts =  jdbcTemplate.query("select * from account where id = ?" ,new BeanPropertyRowMapper<Account>(Account.class),id);
        return accounts.isEmpty() ? null : accounts.get(0);
    }

    @Override
    public Account findAccountByName(String name) {
        List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), name);
        if (accounts.isEmpty()){
            return null;
        }
        if (accounts.size() > 1){
            throw new RuntimeException("結果集不唯一");
        }

        return accounts.get(0);
    }

    @Override
    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set name = ?, money = ? where id=?",account.getName(),account.getMoney(),account.getId());
    }
}

業務層實現類
package com.itheima.service.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @param
 * @return
 */
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)//只讀型事務的配置
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao dao;

    @Override
    public Account findAccountById(Integer accountId) {
        return dao.findAccountById(accountId);
    }

    //配置此方法的事務傳播行為為REQUIRED讀寫型別
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer...");
        //1、根據名稱查詢轉出賬戶
        Account source = dao.findAccountByName(sourceName);
        //2、根據名稱查詢轉入賬戶
        Account target = dao.findAccountByName(targetName);
        //3、轉出賬戶減錢
        source.setMoney(source.getMoney() - money);
        //4、轉入賬戶加錢
        target.setMoney(target.getMoney() + money);
        //5、更新轉出賬戶
        dao.updateAccount(source);
        //模擬異常
        int i = 1 / 0;
        //6、更新轉入賬戶
        dao.updateAccount(target);

    }
}

十二、Spring的新特性

12.1 與JDK相關的升級

12.1.1 jdk版本要求

spring5.0 在 2017 年 9 月釋出了它的 GA(通用)版本。該版本是基於 jdk8 編寫的,所以 jdk8 以下版本

將無法使用。同時,可以相容 jdk9 版本。

tomcat 版本要求 8.5 及以上。

注:

我們使用 jdk8 構建工程,可以降版編譯。但是不能使用 jdk8 以下版本構建工程。

由於 jdk 和 tomcat 版本的更新,我們的 IDE 也需要同時更新。(目前使用的 eclipse 4.7.2)

12.1.2 利用jdk8版本更新的內容

第一:在jdk1.8後,spring對建立物件的效率上做了增強

第二:@NonNull 註解和@Nullable 註解的使用

用 @Nullable 和 @NotNull 註解來顯示錶明可為空的引數和以及返回值。這樣就夠在編譯的時候處

理空值而不是在執行時丟擲 NullPointerExceptions。

第三:日誌記錄方面

Spring Framework 5.0 帶來了 Commons Logging 橋接模組的封裝, 它被叫做 spring-jcl 而

不是標準的 Commons Logging。當然,無需任何額外的橋接,新版本也會對 Log4j 2.x, SLF4J, JUL

( java.util.logging) 進行自動檢測。

12.1.2 核心容器的更新

Spring Framework 5.0 現在支援候選元件索引作為類路徑掃描的替代方案。該功能已經在類路徑掃描器中

新增,以簡化新增候選元件標識的步驟。

應用程式構建任務可以定義當前專案自己的 META-INF/spring.components 檔案。在編譯時,源模型是

自包含的,JPA 實體和 Spring 元件是已被標記的。

從索引讀取實體而不是掃描類路徑對於小於 200 個類的小型專案是沒有明顯差異。但對大型專案影響較大。

載入元件索引開銷更低。因此,隨著類數的增加,索引讀取的啟動時間將保持不變。

載入元件索引的耗費是廉價的。因此當類的數量不斷增長,加上構建索引的啟動時間仍然可以維持一個常數,

不過對於元件掃描而言,啟動時間則會有明顯的增長。

這個對於我們處於大型 Spring 專案的開發者所意味著的,是應用程式的啟動時間將被大大縮減。雖然 20

或者 30 秒鐘看似沒什麼,但如果每天要這樣登上好幾百次,加起來就夠你受的了。使用了元件索引的話,就能幫

助你每天過的更加高效。

你可以在 Spring 的 Jira 上了解更多關於元件索引的相關資訊。

12.1.3 JetBrains Kotlin語言支援

Kolin概述:是一種支援函數語言程式設計程式設計風格的面嚮物件語言。Kotlin 執行在 JVM 之上,但執行環境並不

限於 JVM。

Kolin 的示例程式碼:

{

("/movie" and accept(TEXT_HTML)).nest {

GET("/", movieHandler::findAllView)

GET("/{card}", movieHandler::findOneView)

}

("/api/movie" and accept(APPLICATION_JSON)).nest {

GET("/", movieApiHandler::findAll)

GET("/{id}", movieApiHandler::findOne)

}

}

Kolin 註冊 bean 物件到 spring 容器:

val context = GenericApplicationContext {

registerBean()

registerBean { Cinema(it.getBean()) }

12.1.4 響應式程式設計風格

此次 Spring 發行版本的一個激動人心的特性就是新的響應式堆疊 WEB 框架。這個堆疊完全的響應式且非

阻塞,適合於事件迴圈風格的處理,可以進行少量執行緒的擴充套件。

Reactive Streams 是來自於 Netflix, Pivotal, Typesafe, Red Hat, Oracle, Twitter 以及

Spray.io 的工程師特地開發的一個 API。它為響應式程式設計實現的實現提供一個公共的 API,好實現

Hibernate 的 JPA。這裡 JPA 就是這個 API, 而 Hibernate 就是實現。

Reactive Streams API 是 Java 9 的官方版本的一部分。在 Java 8 中, 你會需要專門引入依賴來使

用 Reactive Streams API。

Spring Framework 5.0 對於流式處理的支援依賴於 Project Reactor 來構建, 其專門實現了

Reactive Streams API。

Spring Framework 5.0 擁有一個新的 spring-webflux 模組,支援響應式 HTTP 和 WebSocket 客

戶端。Spring Framework 5.0 還提供了對於運行於伺服器之上,包含了 REST, HTML, 以及 WebSocket 風

格互動的響應式網頁應用程式的支援。

在 spring-webflux 中包含了兩種獨立的服務端程式設計模型:

基於註解:使用到了@Controller 以及 Spring MVC 的其它一些註解;

使用 Java 8 lambda 表示式的函式式風格的路由和處理。

有 了 Spring Webflux, 你現在可以創建出 WebClient, 它是響應式且非阻塞的,可以作為

RestTemplate 的一個替代方案。

這裡有一個使用 Spring 5.0 的 REST 端點的 WebClient 實現:

WebClient webClient = WebClient.create();

Mono person = webClient.get()

.uri("http://localhost:8080/movie/42")

.accept(MediaType.APPLICATION_JSON)

.exchange()

.then(response -> response.bodyToMono(Movie.class));

12.1.5Junit5 支援

完全支援 JUnit 5 Jupiter,所以可以使用 JUnit 5 來編寫測試以及擴充套件。此外還提供了一個程式設計以及

擴充套件模型,Jupiter 子專案提供了一個測試引擎來在 Spring 上執行基於 Jupiter 的測試。

另外,Spring Framework 5 還提供了在 Spring TestContext Framework 中進行並行測試的擴充套件。

針對響應式程式設計模型, spring-test 現在還引入了支援 Spring WebFlux 的 WebTestClient 整合測

試的支援,類似於 MockMvc,並不需要一個執行著的服務端。使用一個模擬的請求或者響應, WebTestClient

就可以直接繫結到 WebFlux 服務端設施。

你可以在這裡找到這個激動人心的 TestContext 框架所帶來的增強功能的完整列表。

當然, Spring Framework 5.0 仍然支援我們的老朋友 JUnit! 在我寫這篇文章的時候, JUnit 5 還

只是發展到了 GA 版本。對於 JUnit4, Spring Framework 在未來還是要支援一段時間的。

12.1.6 依賴的類庫更新

終止的類庫

Portlet.
Velocity.
JasperReports.
XMLBeans.
JDO.
Guava.

支援的類庫

Jackson 2.6+
EhCache 2.10+ / 3.0 GA
Hibernate 5.0+
JDBC 4.0+
XmlUnit 2.x+
OkHttp 3.x+
Netty 4.1+