Spring提供了一致的事务管理抽象,该抽象能实现为不同的事务API提供一致的编程模型。无视我们使用jdbc、hibernate、mybatis哪种方式来操作数据,无视事务是jta事务还是jdbc事务。

事务

事务(transaction),一般是指要做的或所做的事情。在计算机术语中是指访问或者更新数据库中各项数据项的一个程序执行单元(unit)。事务通常由高级数据库操作语言或编程语言书写的用户程序的执行所引起,并用begin transactionend transaction语句来界定。

为什么需要事务

事务是为了解决数据安全操作提出的解决方案,事务的控制实际上就是控制数据的安全访问于隔离。举一个简单的例子:如果我们去银行转账,A账户将自己的1000元转账给B,那么业务实现的逻辑首先是将A的余额减少1000,然后往B的余额增加100,假如这个过程中出现意外,导致过程中断,A已经扣款成功,B还没来及增加,就会导致B损失了1000元,所以必须做出控制,要求A账户转帐业务撤销。这才能保证业务的正确性,完成这个操走就需要事务,将A账户资金减少和B账户资金增加放到同一个事务里,要么全部执行成功,要么全部撤销,这样就保证了数据的安全性。

事务的四大特性

  1. 原子性:事务是数据库的逻辑工作单位,而且必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。
  2. 一致性:事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。
  3. 隔离型:一个事务的执行不能被其他事务所影响。
  4. 持久性:一个事务一旦提交,事务的操作便永久性的保存在db中。即便是在数据库系统遇到故障的情况下也不会丢失。

Java事务类型

在Java中事务类型有三种:jdbc事务、jta事务以及容器事务

jdbc事务

在jdbc中处理事务,都是通过connection完成,在同一事务中所有的操作,都在使用同一个connection对象完成,jdbc默认是开启事务的,并且默认完成提交操作。而在jdbc中有三种事务有关的操作:

  1. setAutoCommit:设置是否自动提交事务,如果为true则表示自动提交,每一个sql独立存在一个事务,如果设置为false,则需要手动commit进行提交
  2. commit:手动提交事务
  3. rollback:手动回滚结束事务

使用jdbc事务的基本步骤如下:

@Test
public void testTX(){
String url = "jdbc:mysql://127.0.0.1:3306/test";
String username = "root";
String password = "1234"; String sourceUserId = "leo";
String desUserId = "xnn";
int money = 500; Connection connection = null;
try {
//1.加载数据库驱动程序
Class.forName("com.mysql.jdbc.Driver");
//获得数据库连接
connection = DriverManager.getConnection(url, username, password);
//开启事务
connection.setAutoCommit(false);//如果为true的话,sql语句会分别执行,修改数据库;如果为false的话,会激活事务 //多条数据操作数据
Statement sql = connection.createStatement();
sql.executeUpdate("UPDATE user_info SET balance = balance-" + money + " WHERE user_id = '" + sourceUserId+"'");
sql.executeUpdate("UPDATE user_info SET balance = balance+" + money + " WHERE user_id = '" + desUserId+"'");
//提交事务
connection.commit(); } catch (SQLException e) {
e.printStackTrace();
try{
//回滚
connection.rollback();
}catch (SQLException ex){
} } catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

从代码中我们可以看出来jdbc事务的缺点:

  1. 冗长、复杂
  2. 需要显示事务控制
  3. 需要显示处理受检查异常

并且jdbc仅仅是为了完成事务操作提供了基础的API支持,通过操作jdbc我们可以将多个sql语句放到同一个事务中,保证acid特性,但是当遇到跨库跨表的sql,简单的jdbc事务就无法满足了,基于这种问题,jta事务出现了。

jta事务

jta(Java transaction API)提供了跨数据库连接的事务管理能力。jta事务管理则由jta容器实现。

jta的构成

在jta中有几个重要的概念:

  1. 高层应用事务界定接口,供事务客户界定事务边界
  2. X/Open XA协议(资源之间的一种标准化的接口)的标准Java映射 ,它可以使事务性的资源管理器参与由外部事务管理器控制的事务中
  3. 高层事务管理器接口,允许应用程序为其管理的程序界定事务的边界范围

jta中的重要接口

jta的重要接口主要位于javax.transaction包中

  1. UserTransaction:让应用程序得以控制事务的开始、挂起、提交与回滚等操作,由java或者ejb组件调用
  2. TransactionManager:用于应用服务管理事务的状态
  3. Transaction:用于控制执行事务操作
  4. XAResource:用于在分布式事务环境下,协调事务管理器和资源管理器的工作
  5. XID:用来为事务标示的java映射id

需要注意的是前三个接口仅存在于javaee.jar中,在javase中并不存在。

jta事务编程的基本步骤

//配置JTA事务,建立对应的数据源
//1.建立事务:通过创建UserTransaction类的实例来开始一个事务
Context ctx = new InitialContext(p) ;
UserTransaction trans = (UserTransaction) ctx.lookup("javax. Transaction.UserTransaction");
//开始事务
trans.begin();
//找到数据源,进行绑定
DataSource ds = (DataSource) ctx.lookup("mysqldb");
//建立数据库连接
Connection mycon = ds.getConnection();
//执行了sql操作
stmt.executeUpdate(sqlS);
//提交事务
trans.commit();
//关闭连接
mycon.close();

jta事务的优缺点

可以看出,jta的优点很明显,提供了分布式下的事务解决方案,并且执行严格的acid操作,但是标准的jta事务在日常开发中并不常用,其原因就是jta的缺点导致的,例如jta的实现相当复杂,JTA UserTransaction需要从JNDI获取,即我们如果要实现JTA一般情况下也需要实现JNDI,并且JTA只是个简易的容器,使用复杂,在灵活的需求下很难实现代码复用,因为我们需要一个能给我们进行完成容器事务操作的框架

Spring的事务与事务抽象

Spring给我们封装了一套事务机制,并且提供了完善的事务抽象,将事务所需要的步骤进行抽象划分,并以编程的方式提供一个标准API,如下:

try{

//1.开启事务
//2.执行数据库操作 //3.提交事务 }catch(Exception ex){ //处理异常
//4.回滚事务 }finally{ //关闭连接,资源清理
}

Spring事务抽象的核心接口

Spring的抽象事务模型基于接口PlatformTransactionManager,该接口有不同的多种实现,每一种实现都有对应的一个特定的数据访问技术,大体如下:

PlatformTransactionManager及其相关属性

事务管理器接口PlatformTransactionManager通过getTransaction方法来得到事务,参数为TransactionDefinition类,这个类定义事务类的基本属性:

  1. 传播行为
  2. 隔离规则
  3. 回滚规则
  4. 事务超时设置
  5. 事务是否只读
public interface TransactionDefinition {
int getPropagationBehavior(); // 返回事务的传播行为
int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getTimeout(); // 返回事务必须在多少秒内完成
boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
}

其中最重要的是事务的传播行为以及隔离规则

事务的七种传播行为如下所示:

传播性 描述
PROPAGATION_REQUIRED 0 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中
PROPAGATION_SUPPORTS 1 事务可有可无。有就支持当前事务,没有就以非事务方式执行
PROPAGATION_MANDATORY 2 支持当前事务,如果当前没有事务,就抛出异常
PROPAGATION_REQUIRES_NEW 3 无论是否有事务都得新建个事务,如果当前存在事务,把当前事务挂起
PROPAGATION_NOT_SUPPORTED 4 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
PROPAGATION_NEVER 5 以非事务方式执行,如果当前存在事务,则抛出异常
PROPAGATION_NESTED 6 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行

事务的隔离级别如下所示:

在看事务隔离级别前需要先了解下什么是脏读、不可重复读、幻读

  • 脏读:脏读就是指当一个事务正在访问数据库,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务获取数据进行操作,结果将刚刚未提交的数据获取到了
  • 不可重复读:不可重复读是指在一个事务内,多次读同一数据,前后读取的结果不一致。在事务A还没结束时,另外一个事务B也访问该同一数据。那么,在事务A中的两次读取数据的过程中,由于事务B对当前数据进行修改操作,导致事务A两次读取的数据不一致,因此称为是不可重复读
  • 幻读:幻读是指当事务不是独立执行时发生的一种现象,例如事务A对表中的一个数据进行了修改,这种修改涉及到表中的全部数据行。同时事务B也修改了这个表中的数据,这种修改是向表中插入一行新数据。那么就会发生操作事务A的用户发现表中还存在没有修改的数据行,就好像发生了幻觉一样。

为了解决这些问题,事务的隔离级别就出现了,对应的效果如下:

隔离性 脏读 不可重复读 幻读
DEFAULT -1 使用数据库设置的隔离级别
READ_UNCOMMITTED 1
READ_COMMITTED 2
REPEATABLE_READ 3
SERIALIZABLE 4

Spring和数据库都有事务隔离级别。Spring默认隔离级别是按照数据库的隔离级别处理的。

与不同数据访问技术对应的PlatformTransactionManager实现

TransactionManager类 数据库访问技术
DataSourceTransactionManager 在仅使用JDBC时适用
HibernateTransactionManager 在适用Hibernate而没有适用JPA时适用。同时在实现时还可能使用JDBC
JtaTransactionManager 在使用全局事务时适用

可以看到Spring并不是提供了完整的事务操作API,而是提供了多种事务管理器,将事务的职责托管给了Hibernate、JTA等持久化机制平台框架来实现,而仅仅提供一个通用的事务管理器接口org``.``springframework``.``transaction.PlatformTransactionManager,并且给各大持久化事务平台框架提供了对应的事务管理器,用来限制其通用行为,但是具体事务实现将由各大平台自己去实现:

Public interface PlatformTransactionManager{
// 由TransactionDefinition得到TransactionStatus对象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滚
Void rollback(TransactionStatus status) throws TransactionException;
}

在Spring中使用事务

Spring中使用事务,可以直接在业务中以编程的方式使用;也可以通过注解以声明式形式使用。

编程式事务

Spring提供了TransactionTemplate工具类可以很方便的使用编程式事务。默认情况下TransactionTemplate使用的是DataSourceTransactionManager。

package com.lucky.spring;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; /**
* Created by zhangdd on 2020/7/26
*/
@SpringBootApplication
@Slf4j
public class Application implements CommandLineRunner { public static void main(String[] args) {
SpringApplication.run(Application.class, args);
} @Autowired
JdbcTemplate jdbcTemplate; @Autowired
TransactionTemplate transactionTemplate; @Override
public void run(String... args) throws Exception { log.info("default transaction manage:{}", transactionTemplate.getTransactionManager().getClass().getSimpleName()); log.info("count before transaction:{}", getCount());
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
jdbcTemplate.execute("insert into product(name,description) values ('spring boot in action','书籍 spring boot in action') ");
log.info("count in transaction:{}", getCount());
transactionStatus.setRollbackOnly();
}
});
log.info("count after transaction:{}", getCount()); } private long getCount() {
return (long) jdbcTemplate.queryForList("select count(*) as cnt from product")
.get(0).get("cnt");
}
}

打印结果如下:

2020-07-26 18:33:38.839  INFO 41176 --- [           main] com.lucky.spring.Application             : default transaction manage:DataSourceTransactionManager
2020-07-26 18:33:38.842 INFO 41176 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2020-07-26 18:33:39.051 INFO 41176 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2020-07-26 18:33:39.071 INFO 41176 --- [ main] com.lucky.spring.Application : count before transaction:7
2020-07-26 18:33:39.082 INFO 41176 --- [ main] com.lucky.spring.Application : count in transaction:8
2020-07-26 18:33:39.087 INFO 41176 --- [ main] com.lucky.spring.Application : count after transaction:7

从打印结果中可以看到:

  1. TransactionTemplate默认使用的是DataSourceTransactionManager
  2. TransactionTemplate通过execute方法完成了事务的操作

声明式事务



如上图所示Spring的声明式事务是建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中

使用@Transactional注解

@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该注解来覆盖类级别的定义。

虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

@Transactional的rollbackFor属性可以设置一个 Throwable 的数组,用来表明如果方法抛出这些异常,则进行事务回滚。默认情况下如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚。

package com.lucky.spring.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; /**
* Created by zhangdd on 2020/7/26
*/
@Service
public class ProductServiceImpl implements ProductService { @Autowired
JdbcTemplate jdbcTemplate; @Override
//通过设置@Transactional就开启了事务
@Transactional
public void insertRecord() {
jdbcTemplate.execute("insert into product(name,description) values ('spring boot in action 1','书籍 spring boot in action 1') ");
} @Override
@Transactional(rollbackFor = RuntimeException.class)
public void insertThenRollback() throws RuntimeException {
jdbcTemplate.execute("insert into product(name,description) values ('spring boot in action 2','书籍 spring boot in action 2') ");
throw new RuntimeException();
} /**
* 调用带有事务注解的方法
* 这种形式 事务不会回滚,即数据会插入到数据库里
*
* @throws RuntimeException
*/
@Override
public void invokeInsertThenRollback() throws RuntimeException {
insertThenRollback();
} /**
* 调用的普通方法发生了异常
* <p>
* 数据会回滚
*/
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void insertRecordWhenCrash() {
jdbcTemplate.execute("insert into product(name,description) values ('spring boot in action 3','书籍 spring boot in action 3') ");
crash();
} private void crash() throws RuntimeException {
throw new RuntimeException();
}
}
  • @Transactional是以代理的形式完成的事务。invokeInsertThenRollback()方法没有被@Transactional修饰,所以即使内部调用insertThenRollback()这个方法事务也没有生效。

Spring的事务抽象的更多相关文章

  1. 玩转Spring全家桶笔记 04 Spring的事务抽象、事务传播特性、编程式事务、申明式事务

    1.Spring 的事务抽象 Spring提供了一致的事务模型 JDBC/Hibernate/Mybatis 操作数据 DataSource/JTA 事务 2.事务抽象的核心接口 PlatformTr ...

  2. Spring框架事务支持模型的优势

    全局事务 全局事务支持对多个事务性资源的操作,通常是关系型数据库和消息队列.应用服务器通过JTA管理全局性事务,API非常烦琐.UserTransaction通常需要从JNDI获取,意味着需要与JND ...

  3. Spring事务专题(四)Spring中事务的使用、抽象机制及模拟Spring事务实现

    Spring中事务的使用示例.属性及使用中可能出现的问题 前言 本专题大纲如下: 对于专题大纲我又做了调整哈,主要是希望专题的内容能够更丰富,更加详细,本来是想在源码分析的文章中附带讲一讲事务使用中的 ...

  4. spring笔记--事务管理之声明式事务

    事务简介: 事务管理是企业级应用开发中必不可少的技术,主要用来确保数据的完整性和一致性, 事务:就是一系列动作,它们被当作一个独立的工作单元,这些动作要么全部完成,要么全部不起作用. Spring中使 ...

  5. (spring-第20回【AOP基础篇】)Spring与事务

    要想了解Spring的事务,首先要了解数据库事务的基本知识,数据库并发会产生很多问题,Spring使用ThreadLocal技术来处理这些问题,那么我们必须了解Java的ThreadLocal技术.下 ...

  6. spring,mybatis事务

    概述事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性.Spring Framework对事务管理提供了一致的抽象,其特点如下: 为不同的事务API提供一致的编程模型,比 ...

  7. spring,mybatis事务管理配置与@Transactional注解使用[转]

    spring,mybatis事务管理配置与@Transactional注解使用[转] spring,mybatis事务管理配置与@Transactional注解使用 概述事务管理对于企业应用来说是至关 ...

  8. Spring中事务的5种属性总结

    Sping的事务 和 数据库的事务是不同的概念,数据库的事务一般称为底层事务 Spring的事务是对这种事务的抽象 我称之为逻辑事务 Spring对事务的功能进行了扩展,除了基本的Isolation之 ...

  9. CSDN上看到的一篇有关Spring JDBC事务管理的文章(内容比较全) (转)

    JDBC事务管理 Spring提供编程式的事务管理(Programmatic transaction manage- ment)与声明式的事务管理(Declarative transaction ma ...

随机推荐

  1. [开源][示例更新]eCharts配置简化包OptionCreator[typescript版]

    前言 eCharts作为国内优秀的开源图表工具,功能强大,但是使用中也存在一定的问题. 文档更新较慢,文档说明不详细. 前端使用的弱类型语言,数据结构在灵活的同时,也容易造成一些问题.例如某些属性到底 ...

  2. jira仪表盘的建立与共享

    一般在项目测试阶段,可以通过jira仪表盘清晰的展示bug的各个状态的数量,各个开发人员的bug数量. 有效督促开发解决问题. 也为测试日报提供了良好的数据支持,减少人工统计的工作量. 1.建议筛选器 ...

  3. element-ui的upload组件的clearFiles方法的调用

    element-ui使用中碰到的问题 <template> <div> <el-button @click="clearFiles">重新上传& ...

  4. 1-The next outbreak we're not ready

    When I was a kid, the disaster we worried about most was a nuclear war. [wen aɪ wəz ə kɪd]

  5. Viper解析&加载配置

    Viper解析&加载配置 1    Viper是什么 Viper是一个方便Go语言应用程序处理配置信息的库.它可以处理多种格式的配置.它支持的特性: 设置默认值 从JSON.TOML.YAML ...

  6. python实现图片文字提取,准确率高达99%,强无敌!!!

    上次我使用的百度AI开放平台的API接口实现图片的转化,后来有许多小伙伴都私信问我,怎么获取百度AI平台的AK和SK.为了统一回答大家的问题,今天我又使用百度API实现了一个从图片中提取文字和识别身份 ...

  7. 软件测试工程师入门——Linux【使用说明书】

    先来说一下linux是什么? linux 是一个开源.免费的操作系统,其稳定性.安全性.处理多并发已经得到业界的认可,目前很多中性,大型甚至是巨型项目都在使用linux. linux 内核:redha ...

  8. 在VS2017中创建C++的代码块模板

    在VS2017中创建C++的代码块模板 有任何问题,请留言!!! 在VS2017中有工具–>代码片段管理器,方便我们使用固有的代码块模板,同时我们也可以自定义模板. 在VS2017中代码片段的模 ...

  9. 微信小程序实战:表单与选择控件的结合

    先上代码. login.wxml <mp-toptips msg="{{error}}" type="error" show="{{error} ...

  10. p46_IPv4地址

    IP地址:全世界唯一的32位/4字节标识符,标识路由器主机的接口. IP地址::={<网络号>,<主机号>} 图中有6个子网 比如222.1.3.0是网络号,3是主机号,222 ...