0108 spring的申明式事务
背景
互联网的金融和电商行业,最关注数据库事务。
| 业务核心 | 说明 |
|---|---|
| 金融行业-金融产品金额 | 不允许发生错误 |
| 电商行业-商品交易金额,商品库存 | 不允许发生错误 |
面临的难点:
高并发下保证: 数据一致性,高性能;
spring对事物的处理:
采用AOP技术提供事务支持,申明式事务,去除了代码中重复的try-catch-finally代码;
两个场景的解决方案:
| 场景 | 解决办法 |
|---|---|
| 库存扣减,交易记录,账户金额的数据一致性 | 数据库事务保证一致性 |
| 批量处理部分任务失败不影响批量任务的回滚 | 数据库事务传播行为 |
jdbc处理事务
代码
package com.springbootpractice.demo.demo_jdbc_tx.biz;
import lombok.SneakyThrows;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Objects;
import java.util.Optional;
/**
* 说明:代码方式事务编程 VS 申明式事物编程
* @author carter
* 创建时间: 2020年01月08日 11:02 上午
**/
@Service
public class TxJdbcBiz {
private final JdbcTemplate jdbcTemplate;
public TxJdbcBiz(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@SneakyThrows
public int insertUserLogin(String username, String note) {
Connection connection = null;
int result = 0;
try {
connection = Objects.requireNonNull(jdbcTemplate.getDataSource()).getConnection();
connection.setAutoCommit(false);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
final PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO user_login(user_name,password,sex,note) VALUES(?,?,?,?)");
preparedStatement.setString(1, username);
preparedStatement.setString(2, "abc123");
preparedStatement.setInt(3, 1);
preparedStatement.setString(4, note);
result = preparedStatement.executeUpdate();
connection.commit();
} catch (Exception e) {
Optional.ofNullable(connection)
.ifPresent(item -> {
try {
item.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
});
e.printStackTrace();
} finally {
Optional.ofNullable(connection)
.filter(this::closeConnection)
.ifPresent(item -> {
try {
item.close();
} catch (SQLException e) {
e.printStackTrace();
}
});
}
return result;
}
private boolean closeConnection(Connection item) {
try {
return !item.isClosed();
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
@Transactional
public int insertUserLoginTransaction(String username, String note) {
String sql = "INSERT INTO user_login(user_name,password,sex,note) VALUES(?,?,?,?)";
Object[] params = {username, "abc123", 1, note};
return jdbcTemplate.update(sql, params);
}
}
测试代码
package com.springbootpractice.demo.demo_jdbc_tx;
import com.springbootpractice.demo.demo_jdbc_tx.biz.TxJdbcBiz;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.TransactionManager;
import org.springframework.util.Assert;
@SpringBootTest
class DemoJdbcTxApplicationTests {
@Autowired
private TxJdbcBiz txJdbcBiz;
@Autowired
private TransactionManager transactionManager;
@Test
void testInsertUserTest() {
final int result = txJdbcBiz.insertUserLogin("monika.smith", "xxxx");
Assert.isTrue(result > 0, "插入失败");
}
@Test
void insertUserLoginTransactionTest() {
final int result = txJdbcBiz.insertUserLoginTransaction("stefan.li", "hello transaction");
Assert.isTrue(result > 0, "插入失败");
}
@Test
void transactionManagerTest() {
System.out.println(transactionManager.getClass().getName());
}
}
代码中有一个很讨厌的地方,就是 try-catch-finally;
流程图
A[开始] --> B(开启事务)
B --> C{执行SQL}
C -->|发生异常| D[事务回滚]
C -->|正常| E[事物提交]
D --> F[释放事务资源]
E --> F[释放事务资源]
F --> G[结束]
整体流程跟AOP的流程非常的相似,使用AOP,可以把执行sql的步骤抽取出来单独实现,其它的固定流程放到通知里去做。
申明式事务
通过注解@Transaction来标注申明式事务,可以标准在类或者方法上;
| @Tranaction使用位置 | 说明 |
|---|---|
| 类上或者接口上 | 类中所有的 公共非静态方法 都将启用事务,spring推荐放在实现类上,否则aop必须基于接口的代理生效的时候才能生效 |
| 方法上 | 本方法 |
@Transaction的源码和配置项
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.transaction.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
说明:
| 属性 | 说明 |
|---|---|
| isolation | 事务的隔离级别 |
| propagation | 传播行为 |
| rollbackFor,rollbakcForClassName | 哪种异常会触发事务回滚 |
| value | 事务管理器 |
| timeout | 事务超时时间 |
| readOnly | 是否是只读事务 |
| noRollbackFor,noRollbackForClassName | 哪些异常不会触发事务回滚 |
事务的安装过程:
springIOC容器启动的时候,会把@Transactional注解的配置信息解析出来,然后存到事务定义器(TransactionDefinition),并记录哪些类的方法需要启动事务,采取什么策略去执行事务。
我们要做的只是标注@Transactional和配置属性即可;
流程如图:
A[开始] --> B(开启和设置事务)
B --> C{执行方法逻辑}
C -->|发生异常| D[事务回滚]
C -->|正常| E[事物提交]
D --> F[释放事务资源]
E --> F[释放事务资源]
F --> G[结束]
使用方式大大简化;
代码
@Transactional
public int insertUserLoginTransaction(String username, String note) {
String sql = "INSERT INTO user_login(user_name,password,sex,note) VALUES(?,?,?,?)";
Object[] params = {username, "abc123", 1, note};
return jdbcTemplate.update(sql, params);
}
事务管理器
事务的打开,提交,回滚都是放在事务管理器上的。TransactionManager;
TransactionManager代码
package org.springframework.transaction;
public interface TransactionManager {
}
这是一个空接口,实际起作用的是PlatfromTransactionManager;
PlatfromTransactionManager代码:
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}
3个架子的事务管理器对比:
| 架子 | 事务管理 | 说明 |
|---|---|---|
| spring-jdbc | DatasourceTransactionManager | |
| jpa | JpaTransactionManager | |
| mybatis | DatasourceTransactionManager |
mybatis的代码实例点我!
事务隔离级别
场景:电商行业的库存扣减,时刻都是多线程的环境中扣减库存,对于数据库而言,就会出现多个事务同事访问同一记录,这样引起的数据不一致的情况,就是数据库丢失更新。
数据库事务4个特性
即ACID
| 事务的特性 | 英文全称 | 说明 |
|---|---|---|
| 原子性 | Atomic | 一个事务中包含多个步骤操作A,B,C,原子性是标识这些操作要目全部成功,要么全部失败,不会出现第三种情况 |
| 一致性 | Consistency | 在事务完成后,所有的数据都保持一致状态 |
| 隔离性 | Isolation | 多个线程同时访问同一数据,每个线程处在不同的事务中,为了压制丢失更新的产生,定了隔离级别,通过隔离性的设置,可以压制丢失更新的发生,这里存在一个选择的过程 |
| 持久性 | Durability | 事务结束后,数据都会持久化,断电重启后也是可以提供给程序继续使用 |
隔离级别:
| 隔离级别 | 说明 | 问题 | 并发性能 |
|---|---|---|---|
| 读未提交【read uncommitted】 | 允许事务读取另外一个事务没有提交的数据,事务要求比较高的情况下不适用,适用于对事务要求不高的场景 | 脏读(单条) | 并发性能最高 |
| 读已提交【read committed】 | 一个事务只能读取另外一个事务已经提交的数据 | 不可重复读(单条) | 并发性能一般 |
| 可重复读【read repeated】 | 事务提交的时候也会判断最新的值是否变化 | 幻想读(多条数据而言) | 并发性能比较差 |
| 串行化【serializable】 | 所有的sql都按照顺序执行 | 数据完全一致 | 并发性能最差 |
选择依据
| 隔离级别 | 脏读 | 不可重复读 | 幻象读 |
|---|---|---|---|
| 读未提交 | 是 | 是 | 是 |
| 读已提交 | 否 | 是 | 是 |
| 可重复读 | 否 | 否 | 是 |
| 串行化 | 否 | 否 | 否 |
按照实际场景的允许情况来设置事务的隔离级别;
隔离级别会带来锁的代价;优化方法:
- 乐观锁,
- redis分布式锁,
- zk分布式锁;
| 数据库 | 事务隔离级别 | 默认事务隔离级别 |
|---|---|---|
| mysql | 4种 | 可重复读 |
| oracle | 读已提交,串行化 | 读已提交 |
springboot配置应用默认的事务隔离级别:spring.datasource.xxx.default-transaction-isolation=2
| 数字 | 对应隔离级别 |
|---|---|
| -1 | 无 |
| 1 | 读未提交 |
| 2 | 读已提交 |
| 4 | 可重复读 |
| 8 | 串行化 |
事务传播行为
传播行为是方法之间调用事务采取的策略问题。
场景:一个批量任务处在一个事务A中,每个单独是事务都有一个独立的事务Bn; 子任务的回滚不影响事务A的回滚;
传播行为源码
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
列举了7种传播配置属性,下面分别说明:
| 传播行为 | 父方法中存在事务子方法行为 | 父方法中不存在事务子方法行为 |
|---|---|---|
| REQUIRED | 默认传播行为,沿用, | 创建新的事务 |
| SUPPORTS | 沿用; | 无事务,子方法中也无事务 |
| MANDATORY | 沿用 | 抛出异常 |
| REQUIRES_NEW | 创建新事务 | 创建新事务 |
| NOT_SUPPORTED | 挂起事务,运行子方法 | 无事务,运行子方法 |
| NEVER | 抛异常 | 无事务执行子方法 |
| NESTED | 子方法发生异常,只回滚子方法的sql,而不回滚父方法中的事务 | 发生异常,只回滚子方法的sql,跟父方法无关 |
常用的三种传播行为:
- REQUIRED
- REQUIRES_NEW
- NESTED
代码测试这三种传播行为:
spring使用了save point的技术来让子事务回滚,而父事务不会滚;如果不支持save point,则新建一个事务来运行子事务;
| 区别点 | RequestNew | Nested |
|---|---|---|
| 传递 | 拥有自己的锁和隔离级别 | 沿用父事务的隔离级别和锁 |
@Transaction自调用失效问题
事务的实现原理是基于AOP,同一个类中方法的互相调用,是自己调用自己,而没有代理对象的产生,就不会用到aop,所以,事务会失效;
解决办法:通过spring的ioc容器得到当前类的代理对象,调用本类的方法解决;
原创不易,转载请注明出处。
0108 spring的申明式事务的更多相关文章
- 玩转Spring全家桶笔记 04 Spring的事务抽象、事务传播特性、编程式事务、申明式事务
1.Spring 的事务抽象 Spring提供了一致的事务模型 JDBC/Hibernate/Mybatis 操作数据 DataSource/JTA 事务 2.事务抽象的核心接口 PlatformTr ...
- spring aop 声明式事务管理
一.声明式事务管理的概括 声明式事务(declarative transaction management)是Spring提供的对程序事务管理的方式之一. Spring的声明式事务顾名思义就是采用声明 ...
- Spring之声明式事务
在讲声明式事务之前,先回顾一下基本的编程式事务 编程式事务: //1.获取Connection对象 Connection conn = JDBCUtils.getConnection(); try { ...
- 【Spring】——声明式事务配置详解
项目中用到了spring的事务: @Transactional(rollbackFor = Exception.class, transactionManager = "zebraTrans ...
- Spring AOP声明式事务异常回滚(转)
转:http://hi.baidu.com/iduany/item/20f8f8ed24e1dec5bbf37df7 Spring AOP声明式事务异常回滚 近日测试用例,发现这样一个现象:在业务代码 ...
- @Transactional、Spring的声明式事务
传送门 一.Spring的声明式事务 需要在xml文件中配置 <!--配置事务管理器类--> <bean id="transactionManager" clas ...
- 使用注解实现Spring的声明式事务管理
使用注解实现Spring的声明式事务管理,更加简单! 步骤: 1) 必须引入Aop相关的jar文件 2) bean.xml中指定注解方式实现声明式事务管理以及应用的事务管理器类 3)在需要添加事务控制 ...
- Spring(四)Spring JdbcTemplate&声明式事务
JdbcTemplate基本使用 01-JdbcTemplate基本使用-概述(了解) JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装.spr ...
- 保护亿万数据安全,Spring有“声明式事务”绝招
摘要:点外卖时,你只需考虑如何拼单:选择出行时,你只用想好目的地:手机支付时,你只需要保证余额充足.但你不知道这些智能的背后,是数以亿计的强大数据的支持,这就是数据库的力量.那么庞大数据的背后一定会牵 ...
随机推荐
- MySQL之多表查询(笛卡尔积查询、内连接、外连接(左外连接,右外连接)、union、union all )
多表查询 测试数据 create table emp (id int,name char(10),sex char,dept_id int); insert emp values(1,"大黄 ...
- Servlet 学习(八)
Filter 1.功能 Java Servlet 2.3 中新增加的功能,主要作用是对Servlet 容器的请求和响应进行检查和修改 Filter 本身并不生成请求和响应对象,它只提供过滤作用 在Se ...
- STM32 的系统滴答定时器( Systick) 彻底研究解读
作者:王健 前言 SysTick 比起那些 TIM 定时器可以说简单多啦~~~~~哥的心情也好了不少, 嘎嘎!! ARM Cortex-M3 内核的处理器内部包含了一个 SysTick 定时器,它是一 ...
- JSON转换的实现
String转成JSON这个依赖很重要,我们将围绕fastjson中的JSONObject这个类来谈转换 <dependency> <groupId>com.alibaba&l ...
- Celeste 机制研究
0. 简介.惯例.总论 Celeste (塞莱斯特) 是一个具有优秀手感的平台跳跃游戏. 虽然操作所使用的按键很少, 但是却有着复杂的组合机制. 在游戏实现上, Celeste 是一个锁定 60 帧 ...
- 题解 CF165D 【Beard Graph】
思路:将黑边标记为1,白边标记为100000,树链剖分 如果查询时ans超过100000,那就有白边,输出-1,不然直接输出ans #include<bits/stdc++.h> #def ...
- python中对闭包的理解
运行环境声明:本人的代码在sublime text 3中写的,可以Ctrl+b运行.python版本是python3.6.如果您直接运行的,请自觉加入if __name__ == '__main__' ...
- A. Optimal Currency Exchange 兑换硬币,剩下的钱最少
A. Optimal Currency Exchange time limit per test 1.5 seconds memory limit per test 512 megabytes inp ...
- 在 Mac/Windows 系统中使用 Laradock 搭建基于 Docker 的 Laravel 开发环境 (改)
开篇 Use Docker First And Learn About It Later 简介 Laradock 是为 Docker 提供的完整 PHP 本地开发环境,和 Homestead 一样提供 ...
- P1002 A+B for Polynomials (25分)
1002 A+B for Polynomials (25分) This time, you are supposed to find A+B where A and B are two polyn ...