---
# System prepended metadata

title: Spring5筆記
tags: [backend]

---

# Spring5筆記

```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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
        
</beans>
```
### Spring開發步驟
1. 導入座標(maven dependency)
```xml=
<?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">
    <parent>
        <artifactId>test</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-01-ioc</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
    </dependencies>

</project>
```
2. 創建Bean
![](https://i.imgur.com/cF4l6Zj.png)
3. 創建applecationContext.xml(配置文件)
4. 在配置文件中進行配置
```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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="com.ithema.dao.impl.UserDaoImpl"></bean>

</beans>
```
5. 創建applicationContext物件,getBean
# 1. IOC容器
* 控制反轉:反轉bean的控制權
:star:舊式做法:Service -> DAO, 必須在Service new一個DAO的實例出來, 但是使用Spring的話, 當Service層呼叫DAO時, 並不需要創建一個新的實例, 因為我們把創建的動作交給了Spring負責(IOC), 並且達到了解耦的效果
(DI的概念)
* 使用IOC的目的: <font color="red">降低耦合度</font>
* IOC底層原理: xml解析, 工廠模式, 映射
![](https://i.imgur.com/e8nVPYw.png)

## 1.2 IOC操作Bean管理
* Bean管理指的是兩個操作
1. Spring 創建對象
2. Spring 屬性注入 

:star:Bean管理操作有2種方式:star:
1. 基於xml配置文件實現
2. 基於註解方式實現

##### 建構子創建對象的方式
* 共<font color="red">三種方式</font>
1. 第一種 index賦值
```xml=
<bean id="user" class="com.liz.pojo.User">
    <constructor-arg index="0" value="狂神說java">
</bean>
```
2. 第二種 通過型別創建(<font color="red">不建議使用!!</font>)
:point_right:<font color="#33BDFF">如果兩個參數的型別都一樣, 將無法指定!</font>
```xml=
<bean id="user" class="com.liz.pojo.User">
    <constructor-arg type="java.lang.String" value="狂神說">
    <constructor-arg type="int" value="0">
</bean>
```
3. 第三種 直接通過參數名來設置
```xml=
<bean id="user" class="com.liz.pojo.User">
    <constructor-arg name="name" value="老師">
</bean>
```
總結: 在配置文件(bean.xml)加載的時候, 容器就已經創建出初始化的instance了!

---

# 2. Spring的配置
### 1. 別名
```xml=
<!-- 如果增加了別名, 我們也可以使用這個別名來取到這個對象 -->
<alias id="bean id的值" alias="別名" />

//name=""也可以作為bean的標示, 但是在Struct1較常用
```
### 2. Bean的配置
```xml=
<!-- 
    id : bean的唯一識別名, 也相當於我們知道的instance name
    class : bean對象的全域名稱 package + class
    name : 也是別名, 而且name可以同時取多個別名
    scope
    autowire
-->
<bean id="userT" class="com.liz.pojo.UseT" name="user2 u2,u3;user4">
    <constructor-arg name="name" value="學習">
</bean>

```
user2 u2,u3;user4都會呼叫同一個bean對象
### 3. import
:star:一般用於<font color="red">團隊開發</font>
import可以將多個配置檔導入合併為一個
假設現在專案有多人開發, 大家負責不同的類別開發, 則不同的類別需要在不同的bean中, 我們可以利用import將所有人的bean.xml合併成一個總和的檔案!
```xml=
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
```
使用的時候, 用まとめた配置檔就可以了!
:bulb:如果宣告的內容重覆, 則內容依樣會被合併

---

# 3. IOC 依賴注入(DI)
### 1. 建構子注入
上面提過了
### 2. Set方式注入:bulb:<font color="red">!重點!</font>:bulb:
* 依賴注入的本質 = set注入
    * 依賴 : bean對象的創建依賴於容器
    * 注入 : bean對象中的所有屬性, 由容器來注入!
    2.1 普通值注入(經由value="")
    `<property name="name" value="大衛"/>`
    2.2 Bean注入(使用ref="")
    `<property name="address" ref="address"/>`
    2.3 陣列注入
    ```xml=
    <property name="books">
        <array>
            <value>紅樓夢</value>
            <value>西遊記</value>
            <value>水滸傳</value>
            <value>三國演義</value>
        </array>
    </property>
    ```
    2.4 List
    ```xml=
    <property name="hobbies">
        <list>
            <value>聽歌</value>
            <value>看電影</value>
        </list>
    </property>
    ```
    2.5 Set
    ```xml=
    <property name="games">
        <set>
            <value>LOL</value>
            <value>WOW</value>
        </set>
    </property>
    ```
    2.6 Map
    ```xml=
    <property name="cards">
        <map>
            <entry key="身分證" value="B00221"/>
            <entry key="提款卡" value="005-888-166"/>
            <entry key="學生證" value="100123456"/>
        </map>
    </property>
    ```
    2.7 <font color="red">空字串注入</font>
    `<property name="wife" value=""/>`
    2.8 <font color="red">null值注入</font>
    ```xml=
    <property name="wife">
        <null/>
    </property>
    ```
    2.9 Properties注入(鍵值對, 但value要寫角括號外面)
    ```xml=
    <property name="info">
        <props>
            <prop key="學號">20210103</prop>
            <prop key="性別">男</prop>
            <prop key="name">大衛海鮮</prop>
            <prop key="userName">DS</prop>
            <prop key="password">123456</prop>
        </props>
    </property>
    ```
    2.10 注入特殊符號
    
    ```xml=
        // 把<>進行轉譯(&lt;和&gt;)
        // 把特殊符號內容寫到CDATA

        <bean id="orderDao" class="com.ithema.dao.OrderDao">
             <constructor-arg name="oname" value="電腦"></constructor-arg>
             <constructor-arg name="address" value="TW"></constructor-arg>
            
             <property name="label" value="&lt;城市&gt;"></property>

             <property name="place">
                 <value><![CDATA[<<南京>>]]></value>
             </property>
        </bean>
    ```
    2.11 外部Bean注入
    ```xml=
    //xml
    <bean id="userDaoImpl" class="com.ithema.dao.impl.UserDaoImpl"></bean>
    <bean id="userService" class="com.ithema.service.UserService">
            <property name="userDaoA" ref="userDaoImpl"/>
    </bean>
    ```
    ```java=
    //java code
    public class UserService {

        private UserDao userDaoA;

        public void updateService(){
            userDaoA.save();
            System.out.println("user service....");
        }

        public void setUserDaoA(UserDao userDaoA) {
            this.userDaoA = userDaoA;
        }

    }
    ```
    2.12 內部Bean
    ```xml=
    <!--  內部bean-->
    <bean id="emp" class="com.ithema.bean.Emp">
        <property name="ename" value="Lucy"/>
        <property name="gender" value="女"/>

        <property name="dept">
            <bean id="dept" class="com.ithema.bean.Dept">
                <property name="dname" value="會計部"/>
            </bean>
        </property>
    </bean>
    ```
    2.13 注入屬性-集聯賦值
    第一種寫法
    ```xml=
        <!--  集聯賦值-->
    <bean id="emp" class="com.ithema.bean.Emp">
        <property name="ename" value="Lucy"/>
        <property name="gender" value="女"/>
        <property name="dept" ref="dept"></property>
    </bean>

    <bean id="dept" class="com.ithema.bean.Dept">
        <property name="dname" value="財務部"/>
    </bean>
    ```
    第二種寫法
    此種寫法是先在emp instance裡生成dept, 再從dept裡面拿取dname,
    所以java file裡面必須有dept的getter, 不然會報錯
    ```xml=
     <!--  集聯賦值-->
    <bean id="emp" class="com.ithema.bean.Emp">
        <property name="ename" value="Lucy"/>
        <property name="gender" value="女"/>

        <property name="dept" ref="dept"></property>
        <property name="dept.dname" value="技術部"></property>
    </bean>

    <bean id="dept" class="com.ithema.bean.Dept">
        <property name="dname" value="財務部"/>
    </bean>
    ```
    2.14 將集合注入部分提取出來供其他bean使用
        1. 在spring配置文件中引入名稱空間util
    ```xml=
        <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           https://www.springframework.org/schema/beans/spring-beans.xsd 
                           http://www.springframework.org/schema/util
                           https://www.springframework.org/schema/util/spring-util.xsd">
    ```
    2. 使用util標籤完成list集合注入提取

```xml=
    <util:list id="bookList">
        <value>易筋經</value>
        <value>九陰真經</value>
        <value>九陽神功</value>
    </util:list>

    <bean id="books" class="com.atguigu.spring5.collectiontype.Book">
        <property name="list" ref="bookList"></property>
    </bean>

```

### 3. 拓展方式注入(第三方)
我們可以使用p命名空間和c命名空間來進行注入
:bulb:注意點 : c命名空間和p命名空間不可直接使用, 需要導入xml約束
*p命名空間的底層還是使用setter來實現

3.1    p 注入方式(p命名空間) 
* 註冊檔開頭需要導入Xml約束
`xmlns:p="http://www.springframework.org/schema/p"`
* 類似property宣告, 對應到set方式注入
```xml=
<bean name="user" class="com.spring.pojo.User" p:userName="Peter" p:age="45"/>
```
3.2    c 注入方式(c命名空間)
* 註冊檔開頭需要導入Xml約束
`xmlns:c="http://www.springframework.org/schema/c"`
* 通過建構子注入, 對應到建構子注入
```xml=
<bean name="user2" class="com.spring.pojo.User" c:userName="Tom" c:age="18"/>
```
---
# 4. IOC容器Bean管理
## Factory Bean
#### Spring有兩種類型的Bean
1. 普通Bean: 在配置文件中定義的Bean類型就是返回類型
2. Factory Bean: 配置文件中定義類型的類型可以跟返回類型不一樣

Howto:
1. 創建一個Class, 然後實現FactoryBean介面
2. Override介面的方法, 在實現的方法中定義返回的Bean類型

```java=
public class MyBean implements FactoryBean<Course> {

    //定義return的bean
    //我們設定的Bean是"MyBean", 但是返回的對象是"Course"這就是工廠Bean
    //配置文件中定義類型的類型可以跟返回類型不一樣
    @Override
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setCname("工廠bean");
        return course;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
```
```xml=
<bean id="myBean" class="com.atguigu.spring5.factoryBean.MyBean"></bean>
```
```java=
 @Test
    public void testCollection3(){

        //以下寫法會報錯, 因為返回的類型不適MyBean而是Course

//        ApplicationContext app = new ClassPathXmlApplicationContext("bean3.xml");
//        MyBean myBean = app.getBean("myBean", MyBean.class);
//
//        System.out.println(myBean);

        //========================================

        ApplicationContext app = new ClassPathXmlApplicationContext("bean3.xml");
        Course myBean = app.getBean("myBean", Course.class);

        System.out.println(myBean); //輸出Course{cname='工廠bean'}
    }
```
---
  
## Bean Scope

#### 如何配置
```xml=
<bean id="books" class="com.atguigu.spring5.collectiontype.Book">
        <property name="list" ref="bookList" scope="singleton"></property>
    </bean>
```

1. Singleton (Default)單實例
無論用哪個DAO去拿, 都用同一個instance去回應
![](https://i.imgur.com/ymMZSAc.png)
記憶體位址相同
![](https://i.imgur.com/W9xfjzQ.png)

2. Prototype(原型模式)多實例
如果scope設定為prototype, 則上圖程式碼執行結果為<font color="red">false</font>
每次從容器中get的時候, 都會產生一個新的instance
```xml=
   <bean id="books" class="com.atguigu.spring5.collectiontype.Book" scope="prototype">
        <property name="list" ref="bookList"></property>
    </bean>
```
![](https://i.imgur.com/0wlBwB6.png)

3. request/session/application/websocket
這些只能在web開發裡使用到!
 
##### :bulb:scope="songleton"的時候, 加載配置文件時spring就會創建instance
##### :bulb:scope="prototypr"則不是在加載配置文件是創建instance, 而是在調用getBean方法時創建多實例對象

---
## Bean的生命週期
1. 通過無參數建構子創建bean
2. 設置屬性(setter)
3. 調用bean初始化方法(需要在配置文件裡面配置)
4. bean可以使用了
5. 當容器關閉的時候, 調用的bean銷毀方法(需要在配置文件裡面配置)

```xml=
<bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
        <property name="oname" value="手機"></property>
</bean>
```
```java=
public class Orders {

    private String oname;
    
    public Orders() {
        System.out.println("第一步, 調用無參數建構子");
    }

    //執行的初始化方法
    public void initMethod() {
        System.out.println("第三步, 執行init 方法");
    }

    //執行的銷毀方法
    public void destroyMethod(){
        System.out.println("第五步, 銷毀時的方法");
    }


    public void setOname(String oname) {
        this.oname = oname;
        System.out.println("第二步, 調用setter設定屬性值");
    }
}
```

```java=
    @Test
    public void testBeanInit(){
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("bean4.xml");
        Orders orders = app.getBean("orders", Orders.class);

        System.out.println("第四步, 獲取創建bean實例對象");
        System.out.println(orders);

        //手動讓bean實例銷毀
       app.close();

    }
```

![](https://i.imgur.com/PsWgrty.png)

### Bean的後置處理器(before/after)
Runtime的順序如下 :arrow_down: 
###### `postProcessBeforeInitialization`
3. 把實例bean傳給後置處理器的方法(需要在配置文件裡面配置)
    3.1 實現interface `BeanPostProcessor`
###### `postProcessAfterInitialization`
:warning: <font color='red'>後置處理器會被適用於當前配置文件中的所有bean實例</font>
```xml=
<bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
        <property name="oname" value="手機"></property>
    </bean>

    <!--    配置後置處理器-->
    <!--    此後置處理器會被適用於當前配置文件中的所有bean實例-->
    <bean id="myBean" class="com.atguigu.spring5.bean.MyBeanPost">

    </bean>
```
---
## Bean的自動裝配
* 自動裝配是Spring滿足bean依賴的一種方式
* Spring會在上下文中自動尋找, 並自動為bean裝配屬性
* IOC操作Bean管理(xml/annotaion)參照之前筆記

在Spring中有三種裝配方式
1. 在xml中配置 <font color='red'>(手動裝配)</font>
2. 在java中顯示配置
3. 隱式的自動裝配bean:bulb:<font color="red">!重點!</font>:bulb:

### 基於XML方式進行自動裝配(少用)
一般進行自動裝配時, 還是使用annotation來進行
### bean標籤屬性autowire來配置自動裝配
        autowire屬性常用的兩個值:
        1. byName: 根據屬性名稱(attribute name)注入
        2. byType: 根據屬性的類型(Class)注入

### autowire="byName"
:bulb: 和class裡面的attribute一樣名稱
會自動在容器上下文中尋找, 和自己set方法後面的值對應的<font color="red">bean id!</font>

```xml=
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="people" class="com.kuang.pojo.People" autowire="byName">
    <property name="name" value="大衛"/>
</bean>
```

### autowire="byType"
會自動在容器上下文中尋找, 和自己對象屬性<font color="red">class相同</font>的bean!

```xml=
<bean class="com.kuang.pojo.Dog"/>
<bean class="com.kuang.pojo.Cat"/>
<bean id="people" class="com.kuang.pojo.People" autowire="byType">
    <property name="name" value="大衛"/>
</bean>
```
小結: 
* byName的時候, 需要保證所有bean的id是唯一的! 並且這個bean需要和自動注入的屬性的set方法的值一致!
* byType的時候, 需要保證所有bean的class唯一! 並且這個bean需要和自動注入的屬性的類別一致!

```xml=
當使用"byType"時, 以下配置會出錯, 因為dept和dept1都是Dept class
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byType"></bean>

<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
<bean id="dept1" class="com.atguigu.spring5.autowire.Dept"></bean>
```
---
# 5. IOC操作Bean管理(外部屬性文件)

將經常使用的constant(ex. DB的帳密, enpoint)抽取出來寫在properties檔之後引用

例:
1. 直接配置database資訊
    1.1 配置德魯伊連接池
    1.2 引入德魯伊連接池的依賴 or jar包
```xml=
 <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
```
```xml=
 <!--直接配置連線池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/userDB"></property>
            <property name="username" value="root"></property>
            <property name="password" value="root"></property>
</bean>
```
2. 引入外部屬性文件配置database連接池
    2.1 創建外部屬性文件(properties檔), 創建資料庫資訊
    ![](https://i.imgur.com/Jj1rnMc.png)
    2.2 把外部properties文件引入到Spring配置文件中
    * 引入context名稱空間
    * 在spring配置文件中使用標籤引入外部屬性文件
```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:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           https://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util
                           https://www.springframework.org/schema/util/spring-util.xsd
                           https://www.springframework.org/schema/context/spring-context.xsd">

</beans>
```
```xml=
   <!--  引入外部屬性文件  -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置連線池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClass}"></property>
        <property name="url" value="${prop.url}"></property>
        <property name="username" value="${prop.username}"></property>
        <property name="password" value="${prop.password}"></property>
    </bean>
```
---
# 6. Bean管理-使用註解開發
* 何謂註解: 
1. 註解是程式碼的特殊標記, 格式:@註解名稱(屬性名稱=屬性值, 屬性名稱=屬性值...)
2. 註解可以使用於class上面, attribute上面, method上面
3. 使用註解目的: 簡化xml配置
* Spring針對Bean管理中創建對象提供註解
1. `@Component`
    * 相當於`<bean id="user" class="com.kuang.pojo.User" />`
3. `@Service`
4. `@Controller`
5. `@Repository` (DAO)
上面4個註解功能是一樣的, 都可以用來創建bean實例

## 6.1 使用註解實現自動裝配
要使用註解(Spring4之後), 必須保證 :
1.  導入aop的依賴(jar檔)
![](https://i.imgur.com/VIdUOht.png)
2. 開啟組件掃描
     * 在xml配置文件中導入context約束
![](https://i.imgur.com/e7fvmBS.png)
```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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!--    開啟組件掃描, 可以用","隔開多個package或是掃描上層package目錄-->
<!--    <context:component-scan base-package="com.atguigu.spring5.dao,com.atguigu.spring5.service"></context:component-scan>-->
    
    <!-- 掃描"com.atguigu"底下的所有類別 -->
    <context:component-scan base-package="com.atguigu"></context:component-scan>
    
    <!--    例1
            use-default-filters="false" 表示現在不使用預設                 filter, 自己配置filter
            context:include-filter 設置掃描那些內容
            只掃描package底下帶有"@Controller"註解的class
    -->
    <context:component-scan base-package="com.atguigu" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
     <!--   例2 
            下面配置掃描"com.atguigu"所有內容 
            context:exclude-filter, 設置哪些內容不進行掃描 -->
    <context:component-scan base-package="com.atguigu">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>
```
 3. 配置support註解 :bulb:<font color="red">!重點!</font>:bulb:
 ` <context:annotation-config/>` 或 
 ` <context:component-scan base-package="com.atguigu"></context:component-scan>`
 4. 創建class, 在class上面添加創建對象註解
```java=
@Component(value = "userService")
public class UserService {

    public void add() {
        System.out.println("Service add...");
    }
}
```

## 6.2 基於註解實現屬性注入
### 1. `@Autowired`註解
第一步 把service跟dao對象創建, 在service跟dao添加創建對象註釋
第二步 在service注入dao對象, 在service類添加dao類型屬性, 在屬性上面使用註釋

* 根據屬性類型進行自動裝配(類似byType)
使用autowire annotation的話, 可以不寫set方法!(但getter還是需要!)
```java=
  @Autowired
    private Dog dog;
```
注意事項:
 1. 直接在屬性上使用, 也可在setter上使用
 2. 使用Autowired, 我們可以不用編寫set方法, 前提是這個自動配裝的屬性在IOC容器(Spring)容器中存在, 且符合名字("byName")!!


如果定義了Autowired的required屬性為false, 則表示這個對象可以為null, 否則不允許為空值`@Autowired(required = false)`

### 2. `@Qualifier`註解
* 根據屬性的名稱進行注入
* 必須和`@Autowired`一起使用

如果自動裝配的環境比較複雜, 我們可以使用`@Qualifier(value="xxx")`去配合`@Autowired`, 指定一個唯一的bean對象注入
```java=
@Service
public class UserService {

    //定義Dao類型屬性
    //不需要添加Set方法
    //添加注入屬性註解
    @Autowired
    @Qualifier(value = "userDaoImpl1")
    private UserDao userDao;

    public void add() {
        System.out.println("Service add...");
        userDao.addUser();
    }
}
```

### 3. `@Resource`註解
* 可以根據類型注入, 也可以根據名稱注入
先找上下文中id/name相同的對象, 如果都不相同, 在尋找相同class的對象
如果自動裝配的環境比較複雜, 我們可以使用`@Resource(name="xxx")`, 來指定一個唯一的bean對象注入
```java=
 @Resource(name = "userDaoImpl2")  //預設是根據名稱進行注入
    private UserDao userDao;

    public void add() {
        System.out.println("Service add...");
        userDao.addUser();
    }
```
:bulb: <font color='red'>建議使用Autowired和Qualifier</font>
![](https://i.imgur.com/lzSbJVJ.png)


### 4. `@Value`註解
* 注入普通類型屬性
```java=
 @Value(value = "王阿花")
 private String name;
```


小結:
 1. 都是用來自動裝配的, 都可以放在屬性上
 2. @Autoired通過byType的方式實現, 而且必須要求這個對象存在!
 3. @Resource預設通過byName的方式實現, 如果找不到對應的name, 則通過byType方式實現! 如果兩個都找不到的話就會報錯
 4. 區別: @Autoired通過byType的方式實現。@Resource預設通過byName實現。

重點:
1. bean
2. 屬性如何注入
```java=
@Component
public class User {

    public String name;
    
    @Value("老師")
    //相當於<property name="name" value="老師"/>
    public void setName(String name) {
        this.name = name;
    }
}
```
3. 衍生註解
***@Component***有幾個衍生註解, 我們在web開發中, 會按照mvc分層架構分成三層
* dao【@Repository】
* service 【@Service】
* controller 【@Controller】
:star: <font color="red">這四個註解功能都是一樣的, 都是代表將某個class註冊到Spring容器中裝配Bean</font>:star:
4. 自動裝配
上面寫過了
5. scope
```java=
@Component
@Scope("singleton")
```
6. 小結
xml與註解:
* xml更加萬能, 適用於任何場合, 維護簡單方便
* 註解不是自己的class使用不了, 維護相對複雜
xml與註解最佳實踐方法:
* xml用來管理bean
* 註解只負責屬性的注入
* 我們在使用的過程中, 只需要注意一個問題: 要讓註解生效, 就必須開啟支援註解!!!
```xml=
    <!--指定要掃描的package, 這個package底下的註解就會生效-->
    <context:component-scan base-package="com.kuang"/>
    <context:annotation-config/>
```
---
## 6.3 完全註解開發
1. 創建配置類, 取代xml配置文件
### 使用java的方式配置Spring
完全不使用Spring的xml配置, 全部使用java來達到Spring配置
JavaConfig是Spring的一個子項目, 在Spring4之後, 他成為了一個核心功能

### `@Configuration`
代表一個配置Class, 就和之前的beans.xml是一樣的
```java=
@Configuration
@ComponentScan(basePackages = {"com.atguigu"})
public class SpringConfig {
}
```
```java=
 @Test
    public void testService2(){

        //加載配置類
        ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService us = app.getBean("userService", UserService.class);

        us.add();

    }
```
---
# 7. AOP 面向導向程式設計

#### 不影響原本的業務類別下, 實現動態增強
* OOP利用AOP可以對業務邏輯的各部分進行隔離(耦合性低!)
* 不通過修改源碼方式, 在主幹功能裡面添加新功能

<font color="red">使用AOP時需要多導入一個aspect(織入)檔</font>
```xml=
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
```

bean.xml要導入aop的約束
```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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
```
![](https://i.imgur.com/cjClIJo.png)
![](https://i.imgur.com/1CnMp6R.png)

方法一: 使用spring提供的interface(主要是SpringAPI介面實現)
```java=
MethodBeforeAdvice
AfterReturningAdvice
```

```xml=
    <!--    使用spring原生的interface-->
    <!-- 配置aop: 需要導入AOP的約束-->
    <aop:config>
        <!--切入點(在哪個地方執行Spring): expression 表達式 execution(要執行的位置! * * * * *)-->
        <aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>

        <!--    執行環繞增加-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut" />
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut" />
    </aop:config>
```
方法二: 自定義類別來實現AOP(主要是切面定義)

方式三: 使用註解實現!

---
# 8. 代理模式

SpringAOP的底層
代理模式的分類:
* 靜態代理
* 動態代理

### 8.1 靜態代理
腳色分析
* 抽象角色: 一般會使用interface或抽象類別來解決(ex. 租房)
* 真實角色: 被代理的角色(ex. 房東)
* 代理角色:代理真實角色, 代理真實角色之後, 我們一般會做一些附屬操作
* 客戶: 訪問代理對象的人

代理模式的優點:
* 可以使真實角色的操作更加純粹! 不用去關注公共業務(提高重用性)
* 公共業務交給代理角色, 實現業務分工
* 公共業務發生擴展的時候, 方便集中管理

缺點:
* 一個真實角色就會產生一個代理角色, 程式碼量翻倍, 開發效率變低

:point_right: <font color="red">靜態代理撰寫步驟</font>
1. 介面
```java=
package com.kuang.demo01;

//租房--抽象角色
public interface Rent {

    public void rent();
}
```
2. 真實角色
```java=
package com.kuang.demo01;

//房東--真實角色
public class Host implements Rent{

    @Override
    public void rent() {
        System.out.println("房東要出租房子");
    }
}
```
3. 代理角色
```java=
package com.kuang.demo01;

//代理角色--房屋仲介
public class Proxy implements Rent{

    //房東
    private Host host;

    public Proxy() {
    }

    public Proxy(Host host) {
        this.host = host;
    }

    //代理的抽象角色
    @Override
    public void rent() {
        seeHouse();
        sign();
        takeFee();
        host.rent();
    }

    //只有代理能做的事(ex. 看房)
    public void seeHouse(){
        System.out.println("仲介帶你看房");
    }

    public void sign(){
        System.out.println("簽租賃合約");
    }

    public void takeFee(){
        System.out.println("收仲介費");
    }

}
```
4. 訪問代理角色的客戶端
```java=
package com.kuang.demo01;

public class Client {

  public static void main(String []args){

      //原來的模式, 沒有使用代理
      //房東要租房子
      Host host = new Host();
      //host.rent();

      //可以放入不同的房東代理!
      //代理, 仲介幫房東租房, 但是代理一般會有一些附屬操作
      Proxy proxy = new Proxy(host);

      //你不用面對房你不用面對房東, 直接找仲介租房即可
      proxy.rent();
  }
}
```

### 8.2 動態代理
* 動態代理跟靜態代理的角色一樣
* 動態代理的代理類別是動態生成的, 不是我們直接寫好的
* 動態代理分為兩大類
* 基於介面的動態代理, 基於class的動態代理
    * 基於介面---JDK動態代理 
    ![](https://i.imgur.com/0Uo95A8.png)
    * 基於class---CGLIB
    ![](https://i.imgur.com/ZQzcH4v.png)
    * java字節碼實現: javasist

需要了解兩個類別
* Proxy
    * 生成動態代理實例 
* InvocationHandler
    * 調用處理程序(InvocationHandler), 並返回結果
```java=
public class JDKProxy {

    public static void main(String[] args) {

        Class[] interfaces = {UserDao.class};
        UserDao userDao = new UserDaoImpl();
        //創建介面實現類的代理對象
        UserDao up = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserProxy(userDao));
//        System.out.println("result: "+ up.add(10,5));
        System.out.println("result: "+ up.update("測試"));

    }
}

//創建代理對象
class UserProxy implements InvocationHandler {

    //把欲代理的class傳遞過來
    //使用建構子傳遞
    private Object obj;

    public UserProxy(Object userDao) {
        this.obj = userDao;
    }

    // Override後方法理實現增強的邏輯
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //方法之前
        System.out.println("方法之前..." + method.getName() + ":傳遞的參數...:" + Arrays.toString(args));

        //執行被增強的方法
        Object res = method.invoke(obj, args);

        //方法之後
        System.out.println("方法之後執行..." + obj);

        return res;
    }
}
```
![](https://i.imgur.com/Ea0vNWU.png)

:point_right: <font color="red">動態代理的優點</font>
* 所有靜態代理有的優點動態代理都有
* 一個動態代理類, 代理的是一個介面, 一般就是對應相同類型的業務
* 一個動態代理類, 可以代理多個實現了同一個介面的類!
---
# 9. AOP操作
1. Spring框架一般都是基於AspectJ實現AOP操作
    1.1 什麼是AspectJ
    * AspectJ不是Spring組成的一部分, 是獨立的AOP框架, 一般把AspectJ跟Spring框架一起使用, 進行AOP操作
    
2. 基於AspectJ實現AOP操作
    2.1 基於xml配置文件
    2.2 基於註解方式(常用)
    
3. 引入AOP依賴
![](https://i.imgur.com/oGrl0lh.png)

4. 切入點表達式
    4.1 切入點表達式作用: 知道對哪個類裡面的哪個方法進行增強
    4.2 語法結構:
execution( \[權限修飾符\]\[返回類型\]\[類全路徑\]\[方法名稱\](\[參數列表\]) )
* 舉例1: 對com.atguigu.dao.BookDao類裡面的add進行增強
execution(* com.atguigu.dao.BookDao.add(..))

* 舉例2: 對com.atguigu.dao.BookDao類裡面的所有方法進行增強
execution(* com.atguigu.dao.BookDao.*(..))

* 舉例3: 對com.atguigu.dao包裡面的所有類的所有方法進行增強
execution(* com.atguigu.dao.*.*(..))

### AspectJ註解
1. 創建class, 在class裡面定義方法
2. 創建增強class(編寫增強邏輯)
    2.1 在增強類裡面創建方法, 讓不同方法代表不同通知類型
3. 進行通知的配置
    3.1 在spring配置文件中, 開啟註釋掃描
    ```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:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd  http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

            <!--開啟註釋掃描-->
        <context:component-scan base-package="com.atguigu.spring5.aopanno"/>

    </beans>
    ```
    3.2 使用註釋創建User跟UserProxy
    ```java=
    //被增強的類
    @Component
    public class User {

        public void add(){
            System.out.println("add....");
        }
    }
    ```
    ```java=
    //增強的類
    @Component
    @Aspect  //生成代理對象
    public class UserProxy {

        //前置通知
        public void before(){
            System.out.println("before...");
        }
    }
    ```
    3.3 在增強類上面添加註釋`@Aspect`
    3.4 在配置文件中開啟生成代理對象
    ```xml=
    <!--開啟AspectJ, 生成代理對象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    ```
4. 配置不同類型的通知
    4.1 在增強類的裡面, 在作為通知的方法上面添加通知類型註釋, 並且使用切入點表達式配置
```java=
package com.atguigu.spring5.aopanno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

//增強的類
@Component
@Aspect //生成代理對象
public class UserProxy {

    //前置通知    
    @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void before() {
        System.out.println("before...");
    }

    //最終通知
    //不管有沒有異常, 都會執行
    @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void after() {
        System.out.println("after...");
    }

    //後置通知(返回通知)
    @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void afterReturning() {
        System.out.println("afterReturning...");
    }

    //異常通知
    @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void afterThrowing() {
        System.out.println("AfterThrowing...");
    }

    //環繞通知
    @Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPointo) throws Throwable {

        System.out.println("環繞之前...");

        //被增強的方法執行
        proceedingJoinPointo.proceed();

        System.out.println("環繞之後...");
    }
}

```
* 正常執行:
![](https://i.imgur.com/Mnde508.png)
* 有例外:
![](https://i.imgur.com/FeeM7PJ.png)

5. 抽取相同的切入點
```java=
    //抽取相同的切入點
    @Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void pointDemo(){

    }

    //前置通知
    @Before(value = "pointDemo()")
    public void before() {
        System.out.println("before...");
    }
```
6. 有多個增強類, 對同一個方法進行增強, 我們可以設置增強類的優先度
    * 在增強類上面添加註釋`@Order(數字的值)`, 數字越小優先度越高
```java=
@Component
@Aspect
@Order(1)
public class PersonProxy {
}
```
7. 完全使用註解開發
   * 創建配置類, 不需要創建xml配置文件
```java=
@Configuration
@ComponentScan(basePackages = {"com.atguigu.spring5"})
@EnableAspectJAutoProxy(proxyTargetClass = true) //預設是false
public class ConfigAop {
}
```
### AspectJ XML配置文件
1. 創建2個class, 增強類與被增強類, 創建方法
2. 在spring配置文件中創造2個類的對象
```xml=
<!--創建對象-->
<bean id="book" class="com.atguigu.spring5.aopanno.aopxml.Book"/>
<bean id="bookProxy" class="com.atguigu.spring5.aopanno.aopxml.BookProxy"/>
```
3. 在spring文件中配置切入點
```xml=
<!--配置AOP增強-->
    <aop:config>
        <!--切入點-->
        <aop:pointcut id="p" expression="execution(* com.atguigu.spring5.aopanno.aopxml.Book.buy(..))"/>
        <!--配置切面-->
        <aop:aspect ref="bookProxy">
            <!--配置要增強的具體方法-->
            <aop:before method="before" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>
```
---
# 10. Jdbc Template
### 10.1 什麼是Jdbc Template?
* Spring框架對JDBC進行封裝, 方便實現對資料庫操作
1. 引入依賴
![](https://i.imgur.com/FT0z68g.png)
2. 在配置文件中配置連接池
(可以用properties配置文件設定)
```xml=
    <!--直接配置連線池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql:///user_db"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
```
3. 配置JdbcTemplate, 注入DataSource
```xml=
    <!--JDBC Template對象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入DataSource-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
```
4. 創建service, dao類, 在dao class注入JdbcTemplate
    * 配置文件
    ```xml=
     <!--組件掃描-->
        <context:component-scan base-package="com.atguigu.spring5"></context:component-scan>
    ```
    * DAO
    ```java=
    @Repository
    public class BookDaoImpl implements BookDao {

        //注入JDBC Template
        @Autowired
        private JdbcTemplate jdbcTemplate;
    }
    ```
    * Service
    ```java=
    @Service
    public class BookService {

        //注入dao
        @Autowired
        private BookDao bookDao;
    }
    ```
### 10.2 Jdbc Template操作database(Add)
1. 對應資料庫table, 創建實體類(一般來說, 一個table一個對應的實體類)
```java=
package com.atguigu.spring5.entity;

public class User {

    private String userId;
    private String userName;
    private String userStatus;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserStatus() {
        return userStatus;
    }

    public void setUserStatus(String userStatus) {
        this.userStatus = userStatus;
    }

}
```
2. 編寫dao和service
    2.1 在dao進行資料庫add操作
    2.2 調用JdbcTemplate的update方法實現添加操作
        * 第一個參數: SQL語法
        * 第二個參數: 可變參數(? 代表的屬性)
![](https://i.imgur.com/2vMWl0D.png)
```java=
@Repository
public class BookDaoImpl implements BookDao {

    //1. 創建SQL語句
    private String addBookSQL = "INSERT INTO t_book values(?,?,?)";

    //注入JDBC Template
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void add(Book book) {

        //2.調用方法實現
        int resultRow = jdbcTemplate.update(addBookSQL,book.getBookId(), book.getBookName(), book.getBookStatus());
        System.out.println(resultRow);
    }
}
```
### 10.2 Jdbc Template操作database(修改和刪除)
1. 修改
```java=
 @Override
public void updateBook(Book book) {
    String sql = "UPDATE t_book set bookname=?,bookstatus=? WHERE bookid=?";
    Object[] args = {book.getBookName(), book.getBookStatus(), book.getBookId()};
    int update = jdbcTemplate.update(sql, args);
    System.out.println(update);
}
```
2. 刪除
```java=
@Override
public void deleteBook(String id) {
    String sql = "DELETE FROM t_book WHERE bookid=?";
    int delete = jdbcTemplate.update(sql, id);
    System.out.println(delete);
}
```
### 10.2 Jdbc Template操作database(查詢)
1. 查詢返回值
```java=
@Override
public int selectCount() {
        
    String sql = "SELECT COUNT(*) FROM t_book";
    return jdbcTemplate.queryForObject(sql, Integer.class); //參數: 1. SQL語法, 2. 返回值的Class
}
```
2. 查詢返回Object
* 第一個參數:SQL語句, 第三個參數:參數值(?的參數)
* `.queryForObject的第二個參數`Row Mapper是一個interface, 針對返回不同類型數據, 使用這個介面實現類完成數據封裝
```java=
//查詢詳情(返回Object)
@Override
public Book findOne(String id) {

    String sql = "SELECT * WHERE bookid=?";
    Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
    return book;
}
```
3. 查詢返回Collection
```java=
  //查詢返回集合
    @Override
    public List<Book> findList() {
        String sql = "SELECT * FROM t_book";
        List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
        return bookList;
    }
```
### 10.3 批量添加
```java=

    //批量添加
    @Override
    public void batchAddBooks(List<Object[]> batchAddArgs) {

        String sql = "INSERT INTO t_book values(?,?,?)";
        int[] ints = jdbcTemplate.batchUpdate(sql, batchAddArgs);
        System.out.println(Arrays.toString(ints));
    }
```
### 10.4 批量修改
```java=
   //批量修改
    @Override
    public void batchUpdateBooks(List<Object[]> batchAddArgs) {

        int[] ints = jdbcTemplate.batchUpdate(updateBookSql, batchAddArgs);
        System.out.println(ints);
    }
```
### 10.5 批量刪除
```java=
 //批量刪除
    @Override
    public void batchDeleteBooks(List<Object[]> batchAddArgs) {

        int[] ints = jdbcTemplate.batchUpdate(deleteBooksql, batchAddArgs);
        System.out.println(Arrays.toString(ints));
    }
```
---
# 11. Spring的交易管理

### 維持交易的ACID!
1. 原子性: 過程中不可分割, 操作中如果有一個失敗, 所有操作都失敗
2. 一致性: 操作前和操作後總量不變
3. 隔離性: 多事務操作之間(各種事務可以併發執行), 彼此不會產生影響
4. 持久性: 狀態的改變是持久的，不會失效。一旦某個事務提交, 則它造成的狀態變更就是永久性的。

### 當出現異常時
#### 事務操作
1. 開啟交易管理
2. 進行業務操作
3. (沒有發生異常)提交事務(=交易 = transaction)
4. (出現異常)事務回滾(rollback)

##### Spring交易管理介紹
1. 交易的程式碼要加在service層(業務邏輯層)
2. 在Spring進行事務管理操作, 有兩種方式:
    1. 編程式交易管理
    * 在程式碼裡面進行交易管理
    * 需要不斷實現程式碼, 程式碼會變得攏長, 一般不使用
    ```java=
    //原理    
    public void changeMoneys(){
        try {

            //第一步: 開啟交易管理

            //第二步: 進行交易操作
            userDao.addMoney();

            //製造異常
            int i = 10/0;

            userDao.reduceMondey();
            //第三步: 沒有發生異常, 提交事務

        } catch (Exception e){
            //第四步: 出現異常, 事務rollback

        }

    }
    ```
    2.聲明式交易管理(使用)
    1. :bulb:基於註解方式(方便, 簡單)
    2. 基於xml配置文件方式
3. Spring的聲明式交易管理, 底層使用AOP
* 聲明式交易 : AOP (交由容器管理), 不改變原本的程式碼, 達到交易的效果
4.Spring事務管理API
    * 提供一個介面,代表事務管理器, 此介面針對不同的ORM框架, 提供不同的實現類
    ![](https://i.imgur.com/mC5vlcy.png)
#### 以註解方式實現聲明式事務管理
1. 在Spring配置文件配置事務管理器
```xml=
<!--配置事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<!--注入DataSource-->
    <property name="dataSource" ref="dataSource" />
</bean>
```
2. 在Spring配置文件開啟事務註解
    (1)在配置文件中引入名稱空間 tx
    ```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:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">
    ```
    (2)開啟事務註解
    ```xml
     <!--開啟事務註解-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    ```
3. 在Service類上, 或者Service類裡面的method上面, 添加事務註解
    (1) `@Transactional` 可以加上class上面, 也可以加上方法上面
    (2)如果把這個註解加在class上面, 表示這個class的所有方法都添加事務
    (3)如果把這個註解加在方法上面, 表示為這個方法添加事務
#### 聲明式事務管理參數配置
1. 在Service class上面添加註釋`@Transactional`, 在這個註釋裡面可以配置事務相關參數
![](https://i.imgur.com/tSBssCV.png)
 * propogation: 事務的傳播行為
     * 多事務方法直接進行調用, 這個過程中事務是如何進行管理的
     ![](https://i.imgur.com/0b5F5VX.png)
     ![](https://i.imgur.com/aFem9AX.png)
     (如果不指定傳播行為參數, 預設為REQUIRED)

 * isolation: 事務的隔離級別
     * 併發操作中產生的級別
     * 多事務操作之間, 彼此不會產生影響
     * 不考慮隔離性的話, 會產生三讀問題
         1. 髒讀: 一個未提交事務, 讀取到另一個未提交事務的數據![](https://i.imgur.com/Eq1qu11.png)

         2. 不可重複讀: 一個未提交事務, 讀取到另一個提交事務修改的數據![](https://i.imgur.com/jMhiQMN.png)

         3. 幻讀: 一個未提交事務, 讀取到另一個提交事務添加的數據
     * 解決方法: 通過設置事務隔離級別, 解決讀的問題 
     ![](https://i.imgur.com/ocltBKU.png)
     * Spring的預設隔離級別是根據使用的database而不同 ex. MySQL=REPEATABLE READ
     ![](https://i.imgur.com/vPrJGaF.png)

```java=
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
```
 * timeout: 超時時間
     * 事務需要在一定時間內提交, 否則進行rollback
     * Spring預設值是-1(不超時), 設置單位以秒來計算
```java=
@Transactional(timeout = 5,propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
```
 * readOnly: 是否只讀
     * 讀:查詢操作, 寫:添加修改刪除操作
     * readOnly預設值為false, 表可以查詢, 也可以新增刪除修改
     * readyOnly值設置為true之後, 只能進行查詢操作
```java=
@Transactional(readOnly = true,timeout = 5,propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
```
 * rollbackFor: 回滾
     * 設置出現那些異常, 進行交易rollback
     * [觸發時可能遇到的問題, 參考我的notion](https://www.notion.so/transactional-does-not-rollback-on-checked-exceptions-97bc2dc798df49a98d21d682c7d36e0f)
:::danger
:warning: 注意 :warning: 
spring的交易管理註解 @Transactional 預設只會被runtime exception觸發rollback, checked exception會因為可以使用try catch捕獲而不觸發rollback

:bulb: 解決方法: @Transactional(rollbackFor = Exception.class)
:::
 * noRollbackFor: 不回滾
     * 設置出現那些異常, 不進行交易rollback

#### XML方式實現聲明式事務管理
##### 在Spring配置文件中進行配置
1. 配置事務管理器
2. 配置通知
3. 配置切入點和切面

需要在xml檔裡面撰寫配置
```xml=
<!--配置聲明式交易-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg ref="datasource" />
</bean>

<!--結合AOP實現交易的織入(注意!!需導入XML約束文件!)-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
    <!--配置交易方法-->
    <!--配置交易傳播特性: new propagation(預設為REQUIRED)-->
        <tx:method name="add" propagation="REQUIRED"/>
        <tx:method name="delete" propagation="REQUIRED"/>
        <tx:method name="update" propagation="REQUIRED"/>
        <tx:method name="query" read-only="true"/>
        <tx:method name="*" propagation="REQUIRED"/>(可以只寫這個)
    </tx:attributes>
</tx:advice>

<!--配置交易切入點-->
<aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.kuang.mapper.*.* (..))" />
        <aop:advicor advice-ref="txAdvice" pointcut-ref="txPointCut" />
</aop:config>
```
#### 完全註解方式實現聲明式事務管理
```java=
package com.atguigu.spring5.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration  //表示此class為配置類
@ComponentScan(basePackages = "com.atguigu")  //開啟組件掃描
@EnableTransactionManagement//開啟事務管理
public class TxConfig {

//    創建連接池
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();

        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///user_db");
        dataSource.setUsername("root");
        dataSource.setPassword("root");

        return dataSource;
    }

//    創建JDBC template對象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        //()裡面的參數, spring會到ioc容器中, 根據類型找到dataSource
        JdbcTemplate jdbc = new JdbcTemplate();
        //注入dataSource
        jdbc.setDataSource(dataSource);

        return jdbc;
    }

//    創建事務管理器
    @Bean
    public DataSourceTransactionManager manager(DataSource dataSource){
        DataSourceTransactionManager manager = new DataSourceTransactionManager();
        manager.setDataSource(dataSource);

        return manager;
    }
}
```

### Q:為何需要交易管理
* 如無配置交易管理, 則可能存在數據提交不一致的情況(ex. 存提款餘額金額不一致)
* 如果我們不在SPRING中配置聲明式交易, 我們就需要在程式碼裡面手動配置交易管理!
* 交易管理在開發中相當重要, 涉及到數據的一致性和完整性問題, 不可馬虎!

---
# Spring5新功能

### 整合日誌框架log4j2

### Nullable註解
* Nullable註解可以用在方法上面, 屬性上面, 參數上面
    1. method上面: 表示方法返回值可以為空![](https://i.imgur.com/QsQoIHM.png)
    2. 參數前面: 參數值可以為空![](https://i.imgur.com/xwt7d5J.png)
    3. 屬性上面: 屬性值可以為空![](https://i.imgur.com/8PW4wkL.png)

### Spring5核心容器支援函數式風格表達式GenericApplicationContext(lambda風格)
```java=
//函數式風格創建對象, 交給Spring進行管理
@Test
public void testGenericApplicationContext(){
//第一步: 創建GenericApplicationContext對象
GenericApplicationContext context = new GenericApplicationContext();
//第二步: 調用方法進行對象註冊
context.refresh();
context.registerBean(User.class, () -> new User());
//第三步: 獲取在Spring裡面註冊的對象
// !!此處注意不是用小寫的bean name獲取bean, 因為我們並沒有用bean name註冊, ioc容器裡面沒有此實例!!
//需要用完整路徑獲取bean, 或註冊時指定bean name
User user = (User)context.getBean("com.atguigu.spring5.dao.User");
        System.out.println(user);
}
```

### Spring5支援整合JUnit5
* 整合JUnit4
    * 第一步: 引入Spring相關針對測試之依賴
![](https://i.imgur.com/fi7eJOT.png)
![](https://i.imgur.com/3jXvIFh.png)
    * 第二步: 創建測試類, 使用註解方式完成
```java=
@RunWith(SpringJUnit4ClassRunner.class) //單元測試框架
@ContextConfiguration("classpath:bean1.xml") //加載配置文件
public class JTest {

    //直接注入Service
    @Autowired
    private UserService userService;

    @Test
    public void test1(){
        userService.changeMoneys();
    }
}

```
* Spring5整合JUnit5
    * 第一步: 引入JUnit5的jar包
    * 第二步: 創建測試類, 使用註解完成
    * 也可使用複合註解替代上面兩行註解完成整合`@SpringJUnitConfig(locations = "classpath:bean1.xml")`
```java=
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean1.xml")
// @SpringJUnitConfig(locations = "classpath:bean1.xml")
public class JTest5 {

    //直接注入Service
    @Autowired
    private UserService userService;

    @Test
    public void test1() {
        userService.changeMoneys();
    }
}
```
