1. 程式人生 > >Spring 中的事務操作、註解、以及 XML 配置

Spring 中的事務操作、註解、以及 XML 配置

事務

事務全稱叫資料庫事務,是資料庫併發控制時的基本單位,它是一個操作集合,這些操作要麼不執行,要麼都執行,不可分割。例如我們的轉賬這個業務,就需要進行資料庫事務的處理。

 

轉賬中至少會涉及到兩條 SQL 語句:

 

1

2

update Acoount set balance = balance - money where id = 'A';

update Acoount set balance = balance + money where id = 'B'

 

上面這兩條 SQL 就可以要看成是一個事務,必須都執行,或都不執行。如何保證呢,一般這樣表示:

 

1

2

3

4

5

6

7

8

9

10

11

12

# 開啟事務

begin transaction

 

 

update Account set balance = balance - money where id = 'A';

update Account set balance = balance + money where id = 'B'

# 提交事務

commit transaction

 

Exception

    # 回滾事務

    rollback transaction

 

事務的特性(筆試的時候會有)

Atomic(原子性):事務中包含的操作被看做一個邏輯單元,這個邏輯單元中的操作要麼全部成功,要麼全部失敗。

 

Consistency(一致性):只有合法的資料可以被寫入資料庫,否則事務應該將其回滾到最初狀態。在轉賬的時候不會出現一當少錢了,另一方沒有增加的情況。

 

Isolation(隔離性):事務允許多個使用者對同一個資料進行併發訪問,而不破壞資料的正確性和完整性。同時,並行事務的修改必須與其他並行事務的修改相互獨立。

 

Durability(永續性):事務完成之後,它對於系統的影響是永久的,該修改即使出現系統故障也將一直保留,真實的修改了資料庫。

 

以上 4 個屬性常被簡稱為 acid(酸的)。

 

事務併發的問題

髒讀:事務二讀取到事務一中已經更新但是還沒有提交的資料,這就是髒讀。

 

不可重複讀:一個事務兩次讀取同一個行資料結果不同,因為有其它的事務對資料進行了更新。此時的資料即為不可重複讀資料。

 

幻讀:同一事務執行兩次查詢,結果不一致,因為中間有其它的事務對資料進行更改。

 

如何解決這些問題呢?資料庫系統為事務設定了 4 種不同的隔離級別。

 

事務隔離級別

讀未提交(read uncommitted):最低級別,可能會匯入髒讀。

 

讀已提交(read committed):可以避免髒讀,只能查詢到已經提交的資料。且具有良好的效能,但是不能避免不可重複讀和幻讀。

 

可重複讀(repeatable):解決了不可重複讀,可能會出現幻讀。

 

序列化(serializable):通過加鎖,使同一時間只能執行一個事務,不出現上述問題,但是可能會導致大量的超時現象和鎖競爭。

 

另外,MySQL 中預設的隔離級別是可重複讀。Oracle 中預設的事務隔離級別是讀已提交。

 

說完事務,想想我們曾經為了處理事務而寫過的那些程式碼。最後在說說 Spring 中是如何處理的,學完 Spring 再也不用擔心事務操作了。

 

在 JDBC 時代我們需要這樣手動的處理事務。

 

1

2

3

4

5

6

7

8

// 獲取連線 conn

conn.setAutoCommit(false); 設定提交方式為手工提交

// 業務程式碼

// 減錢

// 加錢

conn.commit(); 提交事務

// 出現異常

conn.rollback(); 回滾事務

 

我們說處理事務那是處理資料庫事務,所以肯定要先有資料庫連線才能說事務的事,而在 Java 中我們連線資料庫無非就是 JDBC,或是對 JDBC 進一步的封裝,比方說 Hibernate ,Mybatis 或是 Dbutils 這些框架,所以萬變不離其宗,就是這麼回事,就是看誰封裝的好罷了。你們有興趣可以看看它們都是如何封裝的。

 

Spring 中的如何管理事務呢

首先,我們知道 Spring 是一個容器,不同的框架在處理事務時用到的物件不同,原生的 JDBC 使用 Connection ,而 Mybatis 中使用 SqlSession 物件。而 Spring 為了整合這些不同的框架,定義了一個 PlatformTransactionManager 介面來統一標準,對不同的框架又有不同的實現類。

 

在 Spring 中根據 DAO 層技術的不同來選擇不同的事務處理的物件,是 JDBC 時使用 DataSourceTransactionManager,是 Hibernate 時使用 HibernateTransitionmanager 物件,核心物件就是 Transitionmanager。

 

在 Spring 中管理事務會涉及到這幾個屬性,事務隔離級別、是否只讀、事務的傳播行為,說到事務的傳播行為,指的就是不同的業務方法之間相互呼叫時,應該如何管理事務。Spring 中一共定義了 7 種傳播行為,無腦記住使用 required ,表示支援當前事務,若是不存在事務,就建立一個。例如在 A 呼叫 B 的時候,會首先使用 A 的事務,若 A 沒有事務,則新建立一個,不管 B 有沒有事務。

 

下面就是要實際操作一下,需要有具體的業務邏輯,還是那個轉賬的例子。來看看如何使用 Spring 來管理事務,有兩種常見的管理方式,我們一種一種的說。

 

使用 XML 配置

1 首先是導包,Spring 涉及的包是真的多,我有一個省事的方法,可能用到的 jar 包一下子匯入。

 

2 匯入新的約束檔案,不然在 XML 無法使用 tx 標籤。

 

3 準備目標物件和通知並配置。

目標物件 AccountServiceImpl 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public class AccountServiceImpl implements AccountService {

 

    private AccountDAO ad;

 

    @Transactional(isolation=Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,readOnly=false)

    @Override

    public void transfer(Integer from, Integer to, Double money) {

 

        ad.decreaseMoney(from, money);

 

        //int i = 1/0;

 

        ad.increaseMoney(to, money);

    }

 

    public void setAd(AccountDAO ad) {

        this.ad = ad;

    }

}

 

在 AOP 中通知即為增強的程式碼,而在處理事務時,要增強的程式碼無非就是開啟事務,提交事務和回滾事務,所以 Spring 已經為我們封裝好了處理事務的通知,我們只需要配置一下即可。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

<!-- 匯入 properties 配置檔案 -->

<context:property-placeholder location="classpath:db.properties"/>

<!-- 配置連線資料庫的核心處理物件-->

<bean name="transactionManager" class "org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref= "dataSource"></property>

</bean>

<!-- 配置通知-->

<tx:advice id="txAdvise" transaction-manager="transactionManager">

    <tx:attributes>

        <tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>

    </tx:attributes>

</tx:advice>

<!-- 配置 AOP ,以達成自動處理事務的要求-->

<aop:config>

    <aop:pointcut expression="execution(* yu.transation.*ServiceImpl.*(..))" id="txPointcut"/>

 

    <aop:advisor advice-ref="txAdvise" pointcut-ref="txPointcut"/>

</aop:config>

 

 

<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >

    <property name="jdbcUrl" value="${jdbc.jdbcUrl}" ></property>

    <property name="driverClass" value="${jdbc.driverClass}" ></property>

    <property name="user" value="${jdbc.user}" ></property>

    <property name="password" value="${jdbc.password}" ></property>

</bean>

<!-- 配置 DAO 層物件-->

<bean name="ad" class="yu.transation.AccountDaoImpl">

    <property name="dataSource" ref = "dataSource"></property>

</bean>

<!-- 配置 Service 層物件-->

<bean name = "accountService" class "yu.transation.AccountServiceImpl">

    <property name="ad" ref = "ad"></property>

</bean>

 

上面的配置檔案中,在配置通知時,我們具體到不同的方法會有不同的配置,在專案應用時,會使用萬用字元來進行配置。下面介紹一下使用註解來處理事務,看起來會比較簡單。

 

步驟和上面有重複的部分,需要導包匯入約束,接下來就是配置一下使用註解管理事務的開關

 

使用註解配置

 

1

2

<!-- 開啟註解配置  AOP 事務 -->

<tx:annotation-driven/>

 

下面是使用註解為 Service 中的方法配置事務處理的屬性,當然,每一個方法都寫會比較麻煩,也可以在類上面使用註解,若是某個方法的處理規則不一致就單獨使用註解配置一下。

 

1

2

3

@Transactional(isolation=Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,readOnly=false)

@Override

public void transfer(Integer from, Integer to, Double money) {...}

 

回顧一下,以上主要說了事務以及 Spring 中處理事務的方式,而這也正是 AOP 思想在 Spring 中的應用,我們可以看到不管是前面說的 IoC  還是 AOP 在這裡都有體現。

 

註解的出現是為了替換配置檔案,所以我就以配置檔案為主,並說一下與之對應的註解方式。

 

Spring 中的配置主要在核心配置檔案 applicationContext.xml 中,由不同的標籤來表示,所以首先我們就需要匯入各種約束,常用的約束有 bean、context、aop、tx 。

 

bean 標籤是最基本的標籤,主要用來配置各種物件。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

<!--

屬性介紹:

id: 為物件命名,唯一性標識,不能重複,不能使用特殊字元。

name: 和 id 的作用類似,區別在於可是使用特殊字元,可重複,但是不建議重複。

class: 指定物件的全類名。

init-method: 物件初始化之後立即執行的方法。

destroy-method: 物件銷燬之前執行的方法。

scope: 物件的作用範圍,可以設定單例 singleton 和多例 prototype。預設為單例

 -->

<bean name="userService" class="yu.service.UserServiceImpl" >

   <property name="" value="" ></property>

   <property name="" ref="" ></property>

</bean>

 

對應的註解有以下幾個,但是想要使用註解之前要首先配置一下……

 

1

2

<!-- 開啟註解配置,掃描包及其子包 -->

<context:component-scan base-package="yu"></context:component-scan>

 

使用註解的時候,我們可以使用 @Component 來表示將這個物件交由 Spring 管理,@Scope 來指定物件的作用域。之後便可以使用 @Resource 來獲取物件。

 

在註冊物件的時候我們可以使用 @Component ,但是若是每一個物件都是用這個註解,不能很好的分辨出物件屬於哪一層,所以 Spring 又提供了 @Controller @Service @Repository 來分別表示控制器層,Service 層和 DAO 層的物件,功能和 @Component 是一模一樣的。

 

同樣的在為物件賦值的時候,我們可以使用註解 @Autowired 來自動獲取容器中的物件,可是若是有重名的情況就需要另外一個註解 @Qualifier 來具體指定叫什麼名字,這樣就有點麻煩了,我們一般都是直接使用 @Resource 來指定物件。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

@Component("user")

@Scope("prototype")

public class User{

    private String name;

 

    @Value(value = "18"// 屬性注入,專案中不用。

    private Integer age;

 

    //@Autowired 自動裝配 Car 型別變數,同一型別

    //@Qualifier("car") 指定具體的是哪一個。

    @Resource(name = "car")  // 指名道姓指定是哪個物件

    private Car car;

 

    ...

 

    @PostConstruct

    public void init(){

        System.out.println("init 方法");

    }

    @PreDestroy

    public void destroy(){

        System.out.println("destory 方法");

    }

}

 

aop 相關的配置和註解

 

在 Spring 中我們可以自定義通知和切面,下面只是展示瞭如何配置,但是在具體的業務中應該不會出現 5 種通知齊上陣的現象。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<aop:config>

    <!-- 配置切點-->

    <aop:pointcut expression="execution(* yu.service.*ServiceImpl.*(..))" id="pc"/>

 

    <aop:aspect ref="myAdvice" >

        <!-- 指定名為before方法作為前置通知 -->

        <aop:before method="before" pointcut-ref="pc" />

        <!-- 後置 -->

        <aop:after-returning method="afterReturning" pointcut-ref="pc" />

        <!-- 環繞通知 -->

        <aop:around method="around" pointcut-ref="pc" />

        <!-- 異常攔截通知 -->

        <aop:after-throwing method="afterException" pointcut-ref="pc"/>

        <!-- 後置 -->

        <aop:after method="after" pointcut-ref="pc"/>

    </aop:aspect>

</aop:config>

 

同樣的,我們也可以使用註解來達到自定義配置的方式。同樣的套路,想用註解配置實現 aop,需要開啟註解配置 AOP 的開關。

 

1

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

 

之後就是在通知類中進行配置即可。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

@Aspect

//通知類

public class MyAdvice {

 

    // 快速配置切點表示式,方法直接呼叫即可

    @Pointcut("execution(* yu.service.*ServiceImpl.*(..))")

    public void pc(){}

 

    //前置通知

    @Before("MyAdvice.pc()")

    public void before(){

        System.out.println("這是前置通知!!");

    }

    //後置通知

    @AfterReturning("MyAdvice.pc()")

    public void afterReturning(){

        System.out.println("這是後置通知(如果出現異常不會呼叫)!!");

    }

 

context 主要是和全域性有關的配置

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<!-- 開啟註解配置物件,掃描包及其子包 -->

<context:component-scan base-package="yu"></context:component-scan>

 

  

 

<!-- 匯入 properties 配置檔案 -->

<context:property-placeholder location="classpath:db.properties"/>

 

<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >

    <property name="jdbcUrl" value="${jdbc.jdbcUrl}" ></property>

    <property name="driverClass" value="${jdbc.driverClass}" ></property>

    <property name="user" value="${jdbc.user}" ></property>

    <property name="password" value="${jdbc.password}" ></property>

</bean>

 

tx 配置事務管理中的通知

 

tx 用來配置通知物件,而這個物件是由 Spring 為我們寫好了,而事務管理依賴於資料庫連線物件,所以你能看到 transactionManager 物件依賴於 dataSource 物件。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

<bean name="transactionManager" class "org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref= "dataSource"></property>

</bean>

 

<tx:advice id="txAdvise" transaction-manager="transactionManager">

    <tx:attributes>

        <tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>

    </tx:attributes>

</tx:advice>

 

<aop:config>

    <aop:pointcut expression="execution(* yu.transation.*ServiceImpl.*(..))" id="txPointcut"/>

 

    <aop:advisor advice-ref="txAdvise" pointcut-ref="txPointcut"/>

</aop:config>

 

使用註解配置時還是需要開啟註解配置的開關

 

1

2

<!-- 開啟註解配置 AOP 事務 -->

<tx:annotation-driven/>

 

在具體的業務方法上或是類上使用註解 @Transactional 來配置事務處理的方式。

 

1

2

3

@Transactional(isolation=Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRED,readOnly=false)

@Override

public void transfer(Integer from, Integer to, Double money) {...}

 

最後有一個完美的意外,那就是 import 標籤。用於匯入其它的配置模組到主配置檔案中。

 

1

2

<!-- 匯入其它的 Spring 配置模組 -->

<import resource="yu/transation/applicationContext.xml"/>

鄭州男科醫院哪裡好

鄭州婦科醫院

鄭州做人流價格是多少錢

鄭州婦科醫院哪家好