Spring的事务抽象
Spring提供了一致的事务管理抽象,该抽象能实现为不同的事务API提供一致的编程模型。无视我们使用jdbc、hibernate、mybatis哪种方式来操作数据,无视事务是jta事务还是jdbc事务。
事务
事务(transaction),一般是指要做的或所做的事情。在计算机术语中是指访问或者更新数据库中各项数据项的一个程序执行单元(unit)。事务通常由高级数据库操作语言或编程语言书写的用户程序的执行所引起,并用begin transaction
和 end transaction
语句来界定。
为什么需要事务
事务是为了解决数据安全操作提出的解决方案,事务的控制实际上就是控制数据的安全访问于隔离。举一个简单的例子:如果我们去银行转账,A账户将自己的1000元转账给B,那么业务实现的逻辑首先是将A的余额减少1000,然后往B的余额增加100,假如这个过程中出现意外,导致过程中断,A已经扣款成功,B还没来及增加,就会导致B损失了1000元,所以必须做出控制,要求A账户转帐业务撤销。这才能保证业务的正确性,完成这个操走就需要事务,将A账户资金减少和B账户资金增加放到同一个事务里,要么全部执行成功,要么全部撤销,这样就保证了数据的安全性。
事务的四大特性
- 原子性:事务是数据库的逻辑工作单位,而且必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。
- 一致性:事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。
- 隔离型:一个事务的执行不能被其他事务所影响。
- 持久性:一个事务一旦提交,事务的操作便永久性的保存在db中。即便是在数据库系统遇到故障的情况下也不会丢失。
Java事务类型
在Java中事务类型有三种:jdbc事务、jta事务以及容器事务
jdbc事务
在jdbc中处理事务,都是通过connection完成,在同一事务中所有的操作,都在使用同一个connection对象完成,jdbc默认是开启事务的,并且默认完成提交操作。而在jdbc中有三种事务有关的操作:
- setAutoCommit:设置是否自动提交事务,如果为true则表示自动提交,每一个sql独立存在一个事务,如果设置为false,则需要手动commit进行提交
- commit:手动提交事务
- 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事务的缺点:
- 冗长、复杂
- 需要显示事务控制
- 需要显示处理受检查异常
并且jdbc仅仅是为了完成事务操作提供了基础的API支持,通过操作jdbc我们可以将多个sql语句放到同一个事务中,保证acid特性,但是当遇到跨库跨表的sql,简单的jdbc事务就无法满足了,基于这种问题,jta事务出现了。
jta事务
jta(Java transaction API)提供了跨数据库连接的事务管理能力。jta事务管理则由jta容器实现。
jta的构成
在jta中有几个重要的概念:
- 高层应用事务界定接口,供事务客户界定事务边界
- X/Open XA协议(资源之间的一种标准化的接口)的标准Java映射 ,它可以使事务性的资源管理器参与由外部事务管理器控制的事务中
- 高层事务管理器接口,允许应用程序为其管理的程序界定事务的边界范围
jta中的重要接口
jta的重要接口主要位于javax.transaction包中
UserTransaction
:让应用程序得以控制事务的开始、挂起、提交与回滚等操作,由java或者ejb组件调用TransactionManager
:用于应用服务管理事务的状态Transaction
:用于控制执行事务操作XAResource
:用于在分布式事务环境下,协调事务管理器和资源管理器的工作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类,这个类定义事务类的基本属性:
- 传播行为
- 隔离规则
- 回滚规则
- 事务超时设置
- 事务是否只读
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
从打印结果中可以看到:
- TransactionTemplate默认使用的是DataSourceTransactionManager
- 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的事务抽象的更多相关文章
- 玩转Spring全家桶笔记 04 Spring的事务抽象、事务传播特性、编程式事务、申明式事务
1.Spring 的事务抽象 Spring提供了一致的事务模型 JDBC/Hibernate/Mybatis 操作数据 DataSource/JTA 事务 2.事务抽象的核心接口 PlatformTr ...
- Spring框架事务支持模型的优势
全局事务 全局事务支持对多个事务性资源的操作,通常是关系型数据库和消息队列.应用服务器通过JTA管理全局性事务,API非常烦琐.UserTransaction通常需要从JNDI获取,意味着需要与JND ...
- Spring事务专题(四)Spring中事务的使用、抽象机制及模拟Spring事务实现
Spring中事务的使用示例.属性及使用中可能出现的问题 前言 本专题大纲如下: 对于专题大纲我又做了调整哈,主要是希望专题的内容能够更丰富,更加详细,本来是想在源码分析的文章中附带讲一讲事务使用中的 ...
- spring笔记--事务管理之声明式事务
事务简介: 事务管理是企业级应用开发中必不可少的技术,主要用来确保数据的完整性和一致性, 事务:就是一系列动作,它们被当作一个独立的工作单元,这些动作要么全部完成,要么全部不起作用. Spring中使 ...
- (spring-第20回【AOP基础篇】)Spring与事务
要想了解Spring的事务,首先要了解数据库事务的基本知识,数据库并发会产生很多问题,Spring使用ThreadLocal技术来处理这些问题,那么我们必须了解Java的ThreadLocal技术.下 ...
- spring,mybatis事务
概述事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性.Spring Framework对事务管理提供了一致的抽象,其特点如下: 为不同的事务API提供一致的编程模型,比 ...
- spring,mybatis事务管理配置与@Transactional注解使用[转]
spring,mybatis事务管理配置与@Transactional注解使用[转] spring,mybatis事务管理配置与@Transactional注解使用 概述事务管理对于企业应用来说是至关 ...
- Spring中事务的5种属性总结
Sping的事务 和 数据库的事务是不同的概念,数据库的事务一般称为底层事务 Spring的事务是对这种事务的抽象 我称之为逻辑事务 Spring对事务的功能进行了扩展,除了基本的Isolation之 ...
- CSDN上看到的一篇有关Spring JDBC事务管理的文章(内容比较全) (转)
JDBC事务管理 Spring提供编程式的事务管理(Programmatic transaction manage- ment)与声明式的事务管理(Declarative transaction ma ...
随机推荐
- 从零开始学Electron笔记(一)
前端技术在最近几年迅猛发展,在任何开发领域我们都能看到前端的身影,从PC端到手机端,从APP到小程序,似乎前端已经无所不能,这就要求我们需要不断地去学习来提升自己!前段时间尤大通过直播介绍了一下Vue ...
- day63 django入门(4)
目录 一.CBV源码解析 二.模版语法 1 传值 2 过滤器(最多只能传两个参数) 3 标签 4 自定义过滤器,标签,inclusion_tag 4.1 自定义过滤器 4.2 自定义标签(可以传多个参 ...
- Centos 6.4最小化安装后的优化(1)
一.更新yum官方源 Centos 6.4系统自带的更新源速度比较慢,相比各位都有所感受,国内的速度慢的让人受不了.为了让centos6.4系统使用速度更快的yum更新源,一般都会选择更换源,详细步骤 ...
- 从0开始,手把手教你使用React开发答题App
项目演示地址 项目演示地址 项目源码 项目源码 其他版本教程 Vue版本 小程序版本 项目代码结构 前言 React 框架的优雅不言而喻,组件化的编程思想使得React框架开发的项目代码简洁,易懂,但 ...
- MySQL和PHP中以整型存储IP地址
正文:将IP地址以整型存储 一般我们在数据库中会用到ip地址用来查记录的等等,而ip地址是分为四段的,一般是用varchar或char类型存储.但是其实有更好的存储方法就是以整型存储IP地址. 因为c ...
- WeChat小程序开发-初学者笔记(一)
WeChat小程序开发学习第一天: 完成学习目标: 1.安装并了解Wechat小程序的基本环境, 2.可以利用已学知识的结合简单实现helloWorld界面. 学习过程: 1.首先在微信平台上进行相关 ...
- flask 源码专题(四):wtforms Form实例化流程以及csrf验证
class LoginForm(Form): #首先执行后得到的结果是UnboundField()对象 name=simple.StringField( label='用户名', validators ...
- Spring Boot中Tomcat是怎么启动的
Spring Boot一个非常突出的优点就是不需要我们额外再部署Servlet容器,它内置了多种容器的支持.我们可以通过配置来指定我们需要的容器. 本文以我们平时最常使用的容器Tomcat为列来介绍以 ...
- Redis之字典
概念 字典,又称为符号表.关联数组或映射(map),是一种用于保存键值对(key-value pair)的抽象数据结构.字典中每个键都是独一无二的,程序可以根据键来更新值,或者删除整个键值对. 用途 ...
- 爆力破解Windows操作系统登录密码核心技术
一.不借助U盘等工具二.已将win7登录账户为test,密码为666666,全套C/C++黑客资料请加:726920220QQ 1.将电脑开机关机几次,进入以下界面