04【Spring声明式事、传播行为、AOP事务控制】_aop事务传播行为

set global transaction isolation level read uncommitted;
set global transaction isolation level read committed;
set global transaction isolation level repeatable read;
set global transaction isolation level serializable;



> 
> 需要重启客户端会话
> 
> 
> 


* 开启事务:



start transaction;
或者
begin;


* 提交事务:



commit;


* 回滚事务:



rollback;


### 1.2 Spring事务管理介绍


#### 1.2.1 编程式事务


* 什么是编程式事务?


编程式事务简单的来说就是采用编程的方式来管理事务,编程式事务需要将事务管理的代码写入到业务方法中,相对于核心业务而言,事务管理的代码显然**属于非核心业务**,**对核心业务代码的侵入性太大**;而且如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的**代码冗余**。


我们之前使用的JDBC API来操作事务就是编程式事务;


#### 1.2.2 声明式事务


* 什么是声明式事务?


声明式事务以声明的方式来实现事务管理,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。



> 
> 声明式事务简单的来说就是通过一些表达式声明(拦截)一堆需要被事务管理的方法,然后被声明(拦截)的方法通过AOP的方式进行事务管理(执行目标方法之前开启事务,执行目标方法之后提交或者回滚事务)
> 
> 
> 


显然声明式事务管理要优于编程式事务管理:它将事务管理代码从业务方法中分离出来,这正是Spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受干扰;一个普通的对象,只要加上注解就可以获得完全的事务支持。


声明式事务唯一不足地方是,它的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。


#### 1.2.3 Spring事务管理器


Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。


Spring所有的事务管理策略类都继承自`org.springframework.transaction.PlatformTransactionManager`接口。


它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。


* 事务管理器主要的实现:
	+ `DataSourceTransactionManager`:使用Spring JDBC 或 Mybatis 等基于DataSource数据源的持久化技术时,使用 该事务管理器
	+ `JpaTransactionManager`:使用JPA时采用的事务管理器
	+ `JtaTransactionManager`:具有多个数据源的全局事务使用该事务管理器
	+ `JdoTransactionManager`:使用JDO进行持久化时 ,使用该事务管理器
	+ `HibernateTransactionManager`:使用Hibernate进行持久化时,使用该事务管理器


#### 1.2.4 事务传播行为


当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。


例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为,在`org.springframework.transaction.TransactionDefinition`类中被定义。




| 静态常量 | 说明 |
| --- | --- |
| PROPAGATION\_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择,也是Spring的**默认值**。 |
| PROPAGATION\_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
| PROPAGATION\_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
| PROPAGATION\_REQUIRES\_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
| PROPAGATION\_NOT\_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
| PROPAGATION\_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
| PROPAGATION\_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION\_REQUIRED类似的操作。 |


`TransactionDefinition`类中不仅定义了事务的传播行为,也定义了很多事务的隔离级别:




| 静态常量 | 说明 |
| --- | --- |
| ISOLATION\_DEFAULT | 使用数据库默认的隔离级别,Spring默认值 |
| ISOLATION\_READ\_UNCOMMITTED | 读未提交 |
| ISOLATION\_READ\_COMMITTED | 读已提交 |
| ISOLATION\_REPEATABLE\_READ | 可重复读 |
| ISOLATION\_SERIALIZABLE | 串行化 |


### 1.3 Spring实现声明式事务


#### 1.3.1 案例准备


##### 1)SQL脚本:



drop database if exists spring;

create database spring;

use spring;

– 创建数据表
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(10),
money DOUBLE
);

– 添加数据
INSERT INTO account (name, money) VALUES (‘a’, 1000), (‘b’, 1000);


##### 2)Maven依赖:



<?xml version="1.0" encoding="UTF-8"?>


4.0.0

<groupId>com.dfbz</groupId>
<artifactId>01_Spring</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
    <!--spring基本环境依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>

    <!--spring对jdbc的支持(DataSourceTransaction)-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>

    <!--spring对事物的支持-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>

    <!--spring对切面AspectsJ的支持-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>

    <!--spring对junit的整合-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.1</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.18</version>
    </dependency>
</dependencies>

##### 3)jdbc.properties:



jdbc.username=root
jdbc.password=admin
jdbc.url=jdbc:mysql:///spring
jdbc.driverClassName=com.mysql.jdbc.Driver


##### 4)实体类:



package com.dfbz.entity;

import lombok.Data;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
public class Account {
private Integer id;
private String name;
private Double money;
}


#### 1.3.2 事务通知XML配置


##### 1)Spring配置:



<?xml version="1.0" encoding="UTF-8"?>

<!--组件扫描-->
<context:component-scan base-package="com.dfbz" />

<!--加载配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!--druid数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--引用数据源-->
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>

        <!--

isolation:事务隔离级别
DEFAULT:使用数据库默认的隔离级别(默认值)
READ_UNCOMMITTED:读未提交
READ_COMMITTED:读已提交
REPEATABLE_READ:可重复读
SERIALIZABLE:串行化
propagation: 事务传播行为
REQUIRED:必须要有事务,如果已经存在事务,则加入到这个事务中,如果没有事务则创建一个新的事务
SUPPORTS:支持当前事务,如果调用这个方法之前没有事务则不会开启新的事务
MANDATORY:必须存在事务,如果调用此方法之前没有事务,就抛出异常
REQUIRES_NEW:新建事务,如果调用此方法之前就已经开启过事务,则将之前的事务挂起
NOT_SUPPORTED:非事务方式,如果调用此方法之前就存在事务,则挂起事务
NEVER:非事务方式运行,如果调用此方法之前存在事务,则抛出异常
NESTED:如果存在事务,则嵌套在事务内执行,如果没有事务则与REQUIRED保存一致
read-only: 是否只读
true: 只读
false:非只读(默认值)
timeout: 方法执行超时时间,默认值为-1,代表永不超时,单位为秒
rollback-for: 指定特定的异常才回滚(默认情况下,任何的运行时异常事务都会回滚,但编译时异常都不会进行回滚),该配置针对于编译时异常;可以写多个,以逗号隔开

no-rollback-for: 指定一个特定的异常事务不回滚,可以写多个,以逗号隔开

–>

        <!--出现ArithmeticException异常不回滚-->
        <tx:method name="transfer" no-rollback-for="java.lang.ArithmeticException"/>
    </tx:attributes>

</tx:advice>

<!--配置切面-->
<aop:config>
    <!--定义切入点-->
    <aop:pointcut id="myPoint" expression="execution(\* com.dfbz.service.AccountService.\*(..))"/>

    <!--将通知应用到切入点上(织入)-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="myPoint"></aop:advisor>
</aop:config>

\*\*默认情况下,任何的运行时异常事务都会回滚,但编译时异常都不会进行回滚;\*\*我们可以通过一些配置来调整


* `no-rollback-for`:指定什么异常不回滚,可以写多个,以逗号隔开;
* `rollback-for`:该配置针对于编译时异常;可以写多个,以逗号隔开;


**上述两个参数在指定异常时,如果指定某个异常的父类,包括这个异常的所有子类异常都会回滚;**


**如:`no-rollback-for`指定的参数是Exception则代表Java中的任何异常都不回滚,**


**`rollback-for`指定的参数是`Exception`则Java中任何的异常都回滚;**


##### 2)业务类:



package com.dfbz.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
* @author lscl
* @version 1.0
* @intro:
*/
@Service
public class AccountService {

@Autowired
private JdbcTemplate jdbcTemplate;

/\*\*

* @param str : 谁要转账
* @param target: 转账给谁
* @param flag: 模拟异常 true:出现异常 false:不出现异常
*/
public void transfer(String str, String target, Boolean flag) {

    jdbcTemplate.update("update account set money=money-500 where name=?", str);

    if (flag) {
        // 模拟异常
        int i = 1 / 0;
    }

    jdbcTemplate.update("update account set money=money+500 where name=?", target);
}

/\*\*

*
* @param id
*/
public void findById(Integer id) {

    Account account = jdbcTemplate.queryForObject(
            "select \* from account where id=?",
            new BeanPropertyRowMapper<>(Account.class),
            id
    );
}

}


##### 3)测试类



package com.dfbz.test;

import com.dfbz.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值