面的几个章节已经分析了spring基于@AspectJ的源码,那么接下来我们分析一下Aop的另一个重要功能,事物管理。

事务的介绍

1.数据库事物特性

  • 原子性
    多个数据库操作是不可分割的,只有所有的操作都执行成功,事物才能被提交;只要有一个操作执行失败,那么所有的操作都要回滚,数据库状态必须回复到操作之前的状态
  • 一致性
    事物操作成功后,数据库的状态和业务规则必须一致。例如:从A账户转账100元到B账户,无论数据库操作成功失败,A和B两个账户的存款总额是不变的。
  • 隔离性
    当并发操作时,不同的数据库事物之间不会相互干扰(当然这个事物隔离级别也是有关系的)
  • 持久性
    事物提交成功之后,事物中的所有数据都必须持久化到数据库中。即使事物提交之后数据库立刻崩溃,也需要保证数据能能够被恢复。

2.事物隔离级别

当数据库并发操作时,可能会引起脏读、不可重复读、幻读、第一类丢失更新、第二类更新丢失等现象。

  • 脏读
    事物A读取事物B尚未提交的更改数据,并做了修改;此时如果事物B回滚,那么事物A读取到的数据是无效的,此时就发生了脏读。
  • 不可重复读
    一个事务执行相同的查询两次或两次以上,每次都得到不同的数据。如:A事物下查询账户余额,此时恰巧B事物给账户里转账100元,A事物再次查询账户余额,那么A事物的两次查询结果是不一致的。
  • 幻读
    A事物读取B事物提交的新增数据,此时A事物将出现幻读现象。幻读与不可重复读容易混淆,如何区分呢?幻读是读取到了其他事物提交的新数据,不可重复读是读取到了已经提交事物的更改数据(修改或删除)

对于以上问题,可以有多个解决方案,设置数据库事物隔离级别就是其中的一种,数据库事物隔离级别分为四个等级,通过一个表格描述其作用。

隔离级别 脏读 不可重复读 幻象读
READ UNCOMMITTED 允许 允许 允许
READ COMMITTED 脏读 允许 允许
REPEATABLE READ 不允许 不允许 允许
SERIALIZABLE 不允许 不允许 不允许

3.Spring事物支持核心接口

 

  • TransactionDefinition-->定义与spring兼容的事务属性的接口
public interface TransactionDefinition {
// 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中。
int PROPAGATION_REQUIRED = 0;
// 支持当前事物,如果当前没有事物,则以非事物方式执行。
int PROPAGATION_SUPPORTS = 1;
// 使用当前事物,如果当前没有事物,则抛出异常。
int PROPAGATION_MANDATORY = 2;
// 新建事物,如果当前已经存在事物,则挂起当前事物。
int PROPAGATION_REQUIRES_NEW = 3;
// 以非事物方式执行,如果当前存在事物,则挂起当前事物。
int PROPAGATION_NOT_SUPPORTED = 4;
// 以非事物方式执行,如果当前存在事物,则抛出异常。
int PROPAGATION_NEVER = 5;
// 如果当前存在事物,则在嵌套事物内执行;如果当前没有事物,则与PROPAGATION_REQUIRED传播特性相同
int PROPAGATION_NESTED = 6;
// 使用后端数据库默认的隔离级别。
int ISOLATION_DEFAULT = -1;
// READ_UNCOMMITTED 隔离级别
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
// READ_COMMITTED 隔离级别
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
// REPEATABLE_READ 隔离级别
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
// SERIALIZABLE 隔离级别
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
// 默认超时时间
int TIMEOUT_DEFAULT = -1;
// 获取事物传播特性
int getPropagationBehavior();
// 获取事物隔离级别
int getIsolationLevel();
// 获取事物超时时间
int getTimeout();
// 判断事物是否可读
boolean isReadOnly();
// 获取事物名称
@Nullable
String getName();
}
  1. Spring事物传播特性表:
传播特性名称 说明
PROPAGATION_REQUIRED 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中
PROPAGATION_SUPPORTS 支持当前事物,如果当前没有事物,则以非事物方式执行
PROPAGATION_MANDATORY 使用当前事物,如果当前没有事物,则抛出异常
PROPAGATION_REQUIRES_NEW 新建事物,如果当前已经存在事物,则挂起当前事物
PROPAGATION_NOT_SUPPORTED 以非事物方式执行,如果当前存在事物,则挂起当前事物
PROPAGATION_NEVER 以非事物方式执行,如果当前存在事物,则抛出异常
PROPAGATION_NESTED

如果当前存在事物,则在嵌套事物内执行;

如果当前没有事物,则与PROPAGATION_REQUIRED传播特性相同

  1. Spring事物隔离级别表:
事务隔离级别  脏读  不可重复读  幻读
 读未提交(read-uncommitted)  是  是  是
 不可重复读(read-committed)  否   是  是
 可重复读(repeatable-read)  否   否   是
 串行化(serializable)  否   否   否 

mysql默认的事务隔离级别为 可重复读repeatable-read

  3.PlatformTransactionManager-->Spring事务基础结构中的中心接口

public interface PlatformTransactionManager {
// 根据指定的传播行为,返回当前活动的事务或创建新事务。
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
// 就给定事务的状态提交给定事务。
void commit(TransactionStatus status) throws TransactionException;
// 执行给定事务的回滚。
void rollback(TransactionStatus status) throws TransactionException;
}

Spring将事物管理委托给底层的持久化框架来完成,因此,Spring为不同的持久化框架提供了不同的PlatformTransactionManager接口实现。列举几个Spring自带的事物管理器:

事物管理器 说明
org.springframework.jdbc.datasource.DataSourceTransactionManager 提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理
org.springframework.orm.jpa.JpaTransactionManager 提供对单个javax.persistence.EntityManagerFactory事务支持,用于集成JPA实现框架时的事务管理
org.springframework.transaction.jta.JtaTransactionManager 提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器
  • TransactionStatus-->事物状态描述
  1. TransactionStatus接口
public interface TransactionStatus extends SavepointManager, Flushable {
// 返回当前事务是否为新事务(否则将参与到现有事务中,或者可能一开始就不在实际事务中运行)
boolean isNewTransaction();
// 返回该事务是否在内部携带保存点,也就是说,已经创建为基于保存点的嵌套事务。
boolean hasSavepoint();
// 设置事务仅回滚。
void setRollbackOnly();
// 返回事务是否已标记为仅回滚
boolean isRollbackOnly();
// 将会话刷新到数据存储区
@Override
void flush();
// 返回事物是否已经完成,无论提交或者回滚。
boolean isCompleted();
}
  1. SavepointManager接口
public interface SavepointManager {
// 创建一个新的保存点。
Object createSavepoint() throws TransactionException;
// 回滚到给定的保存点。
// 注意:调用此方法回滚到给定的保存点之后,不会自动释放保存点,
// 可以通过调用releaseSavepoint方法释放保存点。
void rollbackToSavepoint(Object savepoint) throws TransactionException;
// 显式释放给定的保存点。(大多数事务管理器将在事务完成时自动释放保存点)
void releaseSavepoint(Object savepoint) throws TransactionException;
}

Spring编程式事物

CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`balance` int(11) DEFAULT NULL COMMENT '账户余额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='--账户表'
  • 实现
 import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition; import javax.sql.DataSource; /**
* Spring编程式事物
* @author: Chenhao
* @create: 2019-10-08 11:41
*/
public class MyTransaction { private JdbcTemplate jdbcTemplate;
private DataSourceTransactionManager txManager;
private DefaultTransactionDefinition txDefinition;
private String insert_sql = "insert into account (balance) values ('100')"; public void save() { // 1、初始化jdbcTemplate
DataSource dataSource = getDataSource();
jdbcTemplate = new JdbcTemplate(dataSource); // 2、创建物管理器
txManager = new DataSourceTransactionManager();
txManager.setDataSource(dataSource); // 3、定义事物属性
txDefinition = new DefaultTransactionDefinition();
txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 3、开启事物
TransactionStatus txStatus = txManager.getTransaction(txDefinition); // 4、执行业务逻辑
try {
jdbcTemplate.execute(insert_sql);
//int i = 1/0;
jdbcTemplate.execute(insert_sql);
txManager.commit(txStatus);
} catch (DataAccessException e) {
txManager.rollback(txStatus);
e.printStackTrace();
} } public DataSource getDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/my_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8");
dataSource.setUsername("root");
dataSource.setPassword("chenhao1991@");
return dataSource;
} }
  • 测试类及结果
public class MyTest {
@Test
public void test1() {
MyTransaction myTransaction = new MyTransaction();
myTransaction.save();
}
}

运行测试类,在抛出异常之后手动回滚事物,所以数据库表中不会增加记录。

基于@Transactional注解的声明式事物

其底层建立在 AOP 的基础之上,对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。通过声明式事物,无需在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。

  • 接口
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; /**
* 账户接口
* @author: ChenHao
* @create: 2019-10-08 18:38
*/
@Transactional(propagation = Propagation.REQUIRED)
public interface AccountServiceImp {
void save() throws RuntimeException;
}
  • 实现
import org.springframework.jdbc.core.JdbcTemplate;

/**
* 账户接口实现
* @author: ChenHao
* @create: 2019-10-08 18:39
*/
public class AccountServiceImpl implements AccountServiceImp { private JdbcTemplate jdbcTemplate; private static String insert_sql = "insert into account(balance) values (100)"; @Override
public void save() throws RuntimeException {
System.out.println("==开始执行sql");
jdbcTemplate.update(insert_sql);
System.out.println("==结束执行sql"); System.out.println("==准备抛出异常");
throw new RuntimeException("==手动抛出一个异常");
} public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
  • 配置文件
<?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: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"> <!--开启tx注解-->
<tx:annotation-driven transaction-manager="transactionManager"/> <!--事物管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean> <!--数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/my_test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="chenhao1991@"/>
</bean> <!--jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean> <!--业务bean-->
<bean id="accountService" class="com.chenhao.aop.AccountServiceImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean> </beans>
  • 测试
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; /**
* @author: ChenHao
* @create: 2019-10-08 18:45
*/
public class MyTest { @Test
public void test1() {
// 基于tx标签的声明式事物
ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
AccountServiceImp studentService = ctx.getBean("accountService", AccountServiceImp.class);
studentService.save();
}
}
  • 测试
==开始执行sql
==结束执行sql
==准备抛出异常 java.lang.RuntimeException: ==手动抛出一个异常 at com.lyc.cn.v2.day09.AccountServiceImpl.save(AccountServiceImpl.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)

测试方法中手动抛出了一个异常,Spring会自动回滚事物,查看数据库可以看到并没有新增记录。

注意:默认情况下Spring中的事务处理只对RuntimeException方法进行回滚,所以,如果此处将RuntimeException替换成普通的Exception不会产生回滚效果。

接下来我们就分析基于@Transactional注解的声明式事物的的源码实现。

 
 

spring5 源码深度解析----- @Transactional注解的声明式事物介绍(100%理解事务)的更多相关文章

  1. spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理

    @Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供 ...

  2. spring5 源码深度解析----- Spring事务 是怎么通过AOP实现的?(100%理解Spring事务)

    此篇文章需要有SpringAOP基础,知道AOP底层原理可以更好的理解Spring的事务处理. 自定义标签 对于Spring中事务功能的代码分析,我们首先从配置文件开始人手,在配置文件中有这样一个配置 ...

  3. spring5 源码深度解析-----ApplicationContext容器refresh过程

    在之前的博文中我们一直以BeanFactory接口以及它的默认实现类XmlBeanFactory为例进行分析,但是Spring中还提供了另一个接口ApplicationContext,用于扩展Bean ...

  4. spring5 源码深度解析----- 事务的回滚和提交(100%理解事务)

    上一篇文章讲解了获取事务,并且通过获取的connection设置只读.隔离级别等,这篇文章讲解剩下的事务的回滚和提交 回滚处理 之前已经完成了目标方法运行前的事务准备工作,而这些准备工作最大的目的无非 ...

  5. spring5 源码深度解析----- 事务增强器(100%理解事务)

    上一篇文章我们讲解了事务的Advisor是如何注册进Spring容器的,也讲解了Spring是如何将有配置事务的类配置上事务的,实际上也就是用了AOP那一套,也讲解了Advisor,pointcut验 ...

  6. Spring5源码深度解析(一)之理解Configuration注解

    代码地址:https://github.com/showkawa/spring-annotation/tree/master/src/main/java/com/brian 1.Spring体系结构 ...

  7. spring5 源码深度解析----- AOP的使用及AOP自定义标签

    我们知道在面向对象OOP编程存在一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志,安全检测等,我们只有在每个对象里引入公共行为,这样程序中就产生了大量的重复代码,所以有了面向对 ...

  8. spring5 源码深度解析----- 创建AOP代理之获取增强器

    在上一篇的博文中我们讲解了通过自定义配置完成了对AnnotationAwareAspectJAutoProxyCreator类型的自动注册,那么这个类到底做了什么工作来完成AOP的操作呢?首先我们看看 ...

  9. spring5 源码深度解析----- AOP代理的生成

    在获取了所有对应bean的增强后,便可以进行代理的创建了.回到AbstractAutoProxyCreator的wrapIfNecessary方法中,如下所示: protected static fi ...

随机推荐

  1. CodeForces 1058 F Putting Boxes Together 树状数组,带权中位数

    Putting Boxes Together 题意: 现在有n个物品,第i个物品他的位置在a[i],他的重量为w[i].每一个物品移动一步的代价为他的w[i].目前有2种操作: 1. x y 将第x的 ...

  2. codeforces 27 E. Number With The Given Amount Of Divisors(数论+dfs)

    题目链接:http://codeforces.com/contest/27/problem/E 题意:问因数为n个的最小的数是多少. 题解:一般来说问到因数差不多都会想到素因子. 任意一个数x=(p1 ...

  3. hive正则表达式的用法

    regexp_replace用法 1.  截取字符串中的汉字部分: 举个栗子:select regexp_replace('七夕节comming!来啦','([^\\u4E00-\\u9FA5]+)' ...

  4. 详解JAVA字符串类型switch的底层原理

    基础 我们现在使用的Java的版本,基本上是都支持String类型的.当然除了String类型,还有int.char.byte.short.enum等等也都是支持的.然而在其底部实现中,还是基于 整型 ...

  5. Django框架下的增强分页组件

    本文通过文章同步功能推送至博客园,显示排版可能会有所错误,请见谅! 描述:Django框架内置了分页功能,但其只能满足简单需求,难以实现复杂功能. 实现代码: #!/usr/bin/env pytho ...

  6. Spring Cloud Eureka 分布式开发之服务注册中心、负载均衡、声明式服务调用实现

    介绍 本示例主要介绍 Spring Cloud 系列中的 Eureka,使你能快速上手负载均衡.声明式服务.服务注册中心等 Eureka Server Eureka 是 Netflix 的子模块,它是 ...

  7. Net Core基于TopShelf程序运行于服务模式

    目录 Net Core基于TopShelf程序运行于服务模式 1 背景 2 优势 2.1 服务模式可设置重启条件 2.2 避免误操作 3.使用 3.1 GUI方式安装Topshelf包 4 配置 5 ...

  8. json-lib包引入失败的解决方法

    要想使用json-lib的依赖必须加入<classifier>jdk15</classifier> 这一行,否则就导入依赖时就会失败报错,下载不下来jar包,因为json-li ...

  9. Redis常用命令(Set、Hash、Zset)

    1.Set(单值多value) 1. sadd.smembers key . > SADD set01 (integer) > SMEMBERS set01 ) " ) &quo ...

  10. APP功能测试要点(功能测试重点)

    APP功能测试要点 1.功能性测试 根据产品需求文档编写测试用例而进行测试,包括客户端的单个功能模块以及功能业务逻辑(功能交互)如:涉及输入的地方需要考虑等价类,边界值,异常或非法等 1.1 安装与卸 ...