一文了解spring事务特性

推荐工具 objectlog

对于重要的一些数据,我们需要记录一条记录的所有版本变化过程,做到持续追踪,为后续问题追踪提供思路。objectlog工具是一个记录单个对象属性变化的日志工具,工具采用spring切面和mybatis拦截器相关技术编写了api依赖包,以非侵入方式实现对标记的对象属性进行记录,仅需要导入依赖即可,几乎不需要对原系统代码改动,下面展示简单的效果(根据对象field渲染即可):
在这里插入图片描述

该系统具有以下特点:

  • 简单易用:系统将核心逻辑抽离,采用非侵入方式,只需要导入依赖后标注相关注解即可。
  • 业务共享:系统可以同时供多个业务系统使用,彼此之间互不影响。
  • 自动解析:能自动解析对象的属性变化,自动生成变化记录。
  • 便于扩展:支持更多对象属性类型的扩展,支持自定义解析处理逻辑等。
  • 工具性能:工具采用线程模式,脱离业务主线程,避免了解析过程对业务性能的影响。

开源地址:https://gitee.com/opensofte/objectlog,有兴趣的朋友可以看看点个star.

Spring事务说明

Spring事务本质是对数据库事务的支持,如果数据库不支持事务(例如MySQL的MyISAM引擎不支持事务),则Spring事务也不会生效。

事务的特征(ACID)

  • 原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。
  • 一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等。
  • 隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。
  • 持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除

并发事务的问题

  • 脏读(Dirty Reads)
    一个事务可以读取另一个事务未提交的数据.
  • 不可重复读(Non-Repeatable Reads)
    同一个事务中执行两次相同的查询, 可能得到不一样的结果. 这是因为在查询间隔内,另一个事务修改了该记录并提交了事务.
  • 幻读(Phantom Reads)
    当某个事务在读取某个范围内的记录时, 另一个事务又在该范围内插入了新的记录, 当之前的事务再次读取该范围的记录时, 会产生幻行.
  • 更新丢失(Lost Update)
    多个事务修改同一行记录(都未提交), 后面的修改覆盖了前面的修改.

解决这些问题需要进行事务隔离

Spring事务的传播方式

事务传播行为是指一个事务方法A被另一个事务方法B调用时,这个事务A应该如何处理。事务A应该在事务B中运行还是另起一个事务,
这个有事务A的传播行为决定。
事务传播属性定义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;
int PROPAGATION NESTED = 6;
常量名称常量解释
PROPAGATION_REQUIRED支持当前事务,如果当前没有事务,就新建一个事务。这是Spring默认人的事务的传播。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。使用JtaTransactionManager作为事务管理器
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事事务失败抛出异常,外层事务捕获,也可以不处理回滚操作。使用JtaTransactionManager作为事务管理器

PROPAGATION_REQUIRED

如果存在一个事务,则支持当前事务,如果没有事务则开启事务。

在这里插入图片描述
如下例子,单独调用methodB时,当前上下文没有事务,所以会开开启一个新的事务。
调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,
因此就加入到当前事务A中来。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {methodB(); // do something
}@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {// do something
}

PROPAGATION_SUPPORTS

如果存在一个事务,支持当前事务。如果没有事务,则非事事务的执行.
在这里插入图片描述
单独的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {methodB();// do something
}// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {// do something
}

PROPAGATION_MANDATORY

如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
在这里插入图片描述
当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常书throw new IllegalTransactionStateException("Transactionpropagation 'mandatory' but no existing transaction found")当调用methodA时,methodB则加入到methodA的事务中,以事务方式执行。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {methodB();// do something
}@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {// do something
}

PROPAGATION_NEVER

总是非事务地执行,如果存在一个活动事务,则抛出异常。

PROPAGATION_NESTED

如果一个活动的事务存在,则运行在一个嵌套的事务中。

如果没有活动事务,则按TransactionDefinition.PROPAGATIONREQUIRED属性执行。

这是一个嵌套事务,使用JDBC3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC驱动的
java.sql.Savepoint类。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为
true(属性值默认为false)。

在这里插入图片描述

@Transactional(propagation = Propagation.REQUIRED)
methodA(){doSomeThingA();methodB();doSomeThingB();
}@Transactional(propagation = Propagation.NEWSTED)
methodB(){// do something
}

单独调用methodB方法,则按REQUIRED属性执行。如果调用methodA方法,则相当于:

main(){Connection con = null;Savepoint savepoint = null;try{con = getConnection();con.setAutoCommit(false);doSomeThingA();savepoint = con2.setSavepoint();try{methodB();} catch(RuntimeException ex) {con.rollback(savepoint);} finally {//释放资源}doSomeThingB();con.commit();} catch(RuntimeException ex) {con.rollback();} finally {//释放资源}
}

当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。

需要注意的是,这时的事务并没有进行提交,如果后续的代码(do!SomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

PROPAGATION_REQUIRES_NEW

使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。它会开启一个新的事务。如果一个事务已经存在,则先将这个有在的事务挂起。
在这里插入图片描述
从下面代码可以看出,事务B与事务A是两个独立的事务,互不相干。事务B是否成功并不依赖于事务A。如果methodA方法在调用 methodB方法后的doSomeThingB方法失败了,而methodB方法月听做的结果依然被提交。而除了methodB之外的其它代码导致的果却被回滚了

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {doSomeThingA();methodB();doSomeThingB();// do something else
}@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {// do something
}

当调用methodA(),相当于

public static void main(){TransactionManager tm = null;try{//获得一个JTA事务管理器tm = getTransactionManager();tm.begin();//开启一个新的事务Transaction ts1 = tm.getTransaction();doSomeThing();tm.suspend();//挂起当前事务try{tm.begin();//重新开启第二个事务Transaction ts2 = tm.getTransaction();methodB();ts2.commit();//提交第二个事务} Catch(RunTimeException ex) {ts2.rollback();//回滚第二个事务} finally {//释放资源}//methodB执行完后,恢复第一个事务tm.resume(ts1);doSomeThingB();ts1.commit();//提交第一个事务} catch(RunTimeException ex) {ts1.rollback();//回滚第一个事务} finally {//释放资源}
}

PROPAGATION_NOT_SUPPORTED

总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。
在这里插入图片描述

Spring事务的隔离级别

事务隔离级别定义TransactionDefinition

int ISOLATION DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION SERIALIZABLE = 8;
隔离级别解释
ISOLATION_DEFAULT这是个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。
ISOLATION_READ_UNCOMMITTED这是事务最低的隔离级别,它允许另外一个事务可以看到这个事事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED保证一个事务修改的数据提交后才能被另外一个事务读取。另外个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ当前事务执行开始后,所读取到的数据都是该事务刚开始时所读取的数据和自己事务内修改的数据。这种事务隔离级别下,无论其他事务对数据怎么修改,在当前事务下读取到的数据都是该事务开始时的数据,所以这种隔离级别下可以避免不可重复读的问题,但还是有可能出现幻读
ISOLATION_SERIALIZABLE这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行

环境准备

mysql> select * from account;       
+----+-------+---------+----------+ 
| id | owner | balance | currency | 
+----+-------+---------+----------+ 
|  1 | one   |     100 | CNY      | 
|  2 | two   |     100 | CNY      | 
|  3 | three |     100 | CNY      | 
+----+-------+---------+----------+ 
3 rows in set (0.06 sec)

ISOLATION_READ_UNCOMMITTED

在这里插入图片描述
我分别设置事务1、事务2隔离级别为read uncommitted,从图中步骤3、4都可以看到,为了方便,后面的展示将不再说明。

  • 分别开始事务1、事务2(步骤5、6)。

  • 在事务1中,进行一个简单的查询,用来对比数据前后变化。

  • 在事务2中,查看 id 为 1 的账户,金额为 100 元。

  • 在事务1中,对 id 为 1 的账户余额减去 10 元,然后查询确认一下余额已经更改为 90 元。

  • 但是,如果在事务2中再次运行相同的 select 语句怎么办?
    你会看到余额被修改为了 90 元,而不是先前的 100 元。请注意,事务1并未提交,但事务2却看到了事务1所做的更改。这就是脏读现象,因为我们使用的事务隔离级别为read uncommitted(读未提交)。

ISOLATION_READ_COMMITTED

read-committed隔离级别只能防止脏读,但是会出现不可重复读和幻读。

在这里插入图片描述
设置隔离级别为 read committed,并开始事务。

  • ③ 在事务1中,进行一个简单的查询,用来对比数据前后变化。

  • ④ 在事务2中,查看 id 为 1 的账户,金额为 90 元。

  • ⑤⑥ 在事务1中,通过更新帐户余额减去 10 元,然后查询确认一下余额已经更改为 80 元,让我们看看此更改是否对事务2可见。

  • ⑦ 事务2中可以看到,其余额仍然与以前一样为 90 元。
    这是因为事务正在使用read-committed隔离级别,并且由于事务1还没有提交,所以它的写入数据不能被其他事务看到。
    因此,读已提交 (read-committed) 隔离级别可以防止脏读现象。那么对于不可重复读和幻读呢?

  • ⑧ 在事务2中,执行另一个操作,查询大于或等于 90 元的账户。

  • ⑨ 事务1进行提交。

  • ⑩ 现在,如果我们再次在事务2中查询帐户1余额,我们可以看到余额已更改为 80元 。所以,获得帐户1余额的同一查询返回了不同的值。 这就叫不可重复读。

  • 另外,在步骤11中,再次运行如⑧中的操作,这次只得到了2条记录,而不是以前的3条,因为事务1提交后,账户1的余额已经减少到 80 元了。

执行了相同的查询,但是返回了不同的行数。由于其他事务的提交,而导致一行数据消失,这种现象叫做幻读。

ISOLATION_REPEATABLE_READ

在这里插入图片描述

  • 设置隔离级别为 Repeatable Read,并开始事务。

  • ③查询事务1中的所有帐户,然后④查询事务2中ID为1的帐户,除此之外,还要⑤查询余额至少为80元的所有帐户。 这将用于验证幻读是否仍然发生。

  • 回到事务1⑥更新账户1余额减去 10 元;可以看到⑦帐户1的余额减少到了 70 元。

我们知道脏读已在较低的隔离级别read-committed不会出现。因此,由于以下规则,我们不需要在此级别进行检查:
在较低隔离级别被阻止的了读现象,不会出现在较高级别。

  • 因此,让我们⑧提交事务1,然后转移到⑨事务2,看看它是否能读取到事务1所做的新更改。

可以看到,该查询返回账户1的余额与先前相同,为 80 元,尽管事务1将账户1的余额更改为 70 元,并成功提交。
.
这是因为Repeatable Read(可重复读)隔离级别确保所有读查询都是可重复的,这意味着即使其他已提交的事务对数据进行了更改,它也始终返回相同的结果。

  • 我们重新运行⑩查询余额至少 80 元的帐户。

它仍然返回与之前相同的3条记录。所以在Repeatable_Read隔离级别中,可以解决不可重复读的问题。
.
但是,我想知道如果我们还运行步骤 11,从事务1更新过的帐户1的余额中减去10,会发生什么情况? 它将余额更改为70、60还是抛出错误? 试试吧!
.
结果没有报错,该账户余额现在改为了 60 元,这是正确的值,因此事务1早已经提交而将余额修改为了 70 元。
.
但是,从事务2的角度来看,这是没有意义的,因为在上一个查询中,它获取到的是 80 元的余额,但是从帐户中减去 10 元后,现在却得到 60 元。数学运算在这里不起作用,因为此事务仍受到其他事务的并发更新的干扰。

ISOLATION_SERIALIZABLE

在这里插入图片描述

  • 设置隔离级别为 Serializable,并开始事务。
  • ③查询事务1中的所有帐户,然后④查询事务2中ID为1的帐户。
  • 回到⑤事务1更新账户1余额减去 10 元。

有趣的是,这一次更新被阻止了。 事务2的 select 查询语句阻塞了事务1中的 update 更新语句。
.
原因是,在Serializable隔离级别中,MySQL隐式地将所有普通的 SELECT 查询转换为 SELECT FOR SHARE。 持有 SELECT FOR SHARE 锁的事务只允许其他事务读取行,而不能更新或删除行。
.
因此,有了这种锁定机制,我们以前看到的不一致数据场景不再可能出现。
.
但是,这个锁有一个超时持续时间。因此,如果事务2在该持续时间内未提交或回滚以释放锁,我们将看到锁等待超时错误(⑤下面显示错误)。
.
因此,当在应用程序中使用Serializable隔离级别时,请确保实现了一个事务重试策略,以防超时发生。

好的,将事务回滚,现在我将重新测试,看看另一种情况:
在这里插入图片描述

  • 这一次,到步骤⑤的时候,我不会让锁等待超时发生,然后到步骤⑥也进行了跟⑤一样的操作。
  • 到⑥这里,发生了死锁,因为现在事务2也需要等待事务1的 select 查询的锁。

所以请注意,除了锁等待超时之外,还需要处理可能出现的死锁情况。

现在,然我们尝试重启这两个事务:
在这里插入图片描述

  • 这次操作还是跟上面相同,到步骤⑤时,我们知道会阻塞,但如果此时步骤⑥事务2提交了,会怎样呢?
  • 如你所见,在提交了事务2后,事务2的 select 锁立即释放,从而⑤事务1中不再阻塞,更新成功。

Spring事务实现方式

开启事务支持

首先我们需要引入事务标签tx

<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

声明式事务

基于xml声明式事务
  <!-- 直接配置连接池 --><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql://localhost:3306/user_db"></property><property name="username" value="root"></property><property name="password" value="yuan159951."></property></bean><!-- 1、创建事务管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!--注入数据源--><property name="dataSource" ref="dataSource"></property></bean><!-- 2、配置通知 --><tx:advice id="txAdvice"><!-- 配置事务参数 --><tx:attributes><!-- 指定那种规则的方法上添加事务 --><tx:method name="*" propagation="REQUIRED" read-only="false" rollback-for="*"/><tx:method name="find*" propagation="SUPPORTS" read-only="true"/>			</tx:attributes></tx:advice><!--3、配置切入点和切面--><aop:config><!-- 切入点 --><aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.*(..))"/><!-- 配置切面 --><aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/></aop:config>
基于注解声明式事务

第一步配置基本的事务信息

  <!-- 直接配置连接池 --><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql://localhost:3306/user_db"></property><property name="username" value="root"></property><property name="password" value="yuan159951."></property></bean><!-- 创建事务管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!--注入数据源--><property name="dataSource" ref="dataSource"></property></bean><!-- 开启事务注解 --><tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

当然上面的xml配置可以替换为以下内容,两者是等价的

@Configuration
@ComponentScan(basePackages = "com.atguigu.spring5")//开启注解扫描
@EnableTransactionManagement//开启事务
public class TxConfig {//创建数据库连接池@Beanpublic DruidDataSource getDruidDataSource(){DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");druidDataSource.setUrl("jdbc:mysql://localhost:3306/user_db");druidDataSource.setUsername("root");druidDataSource.setPassword("yuan159951.");return druidDataSource;}//创建jdbcTemplate@Bean  //根据类型注入dataSourcepublic JdbcTemplate getJdbcTemplate(DruidDataSource druidDataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(druidDataSource);return jdbcTemplate;}//创建事务管理器@Beanpublic DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource druidDataSource){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(druidDataSource);return dataSourceTransactionManager;}
}

第二步业务中使用声明式事务注解@Transactional

	//参数@Transactionalpublic void doBusiness(){//业务逻辑}

@Transactional开启事务时,spring内部会执行一些操作,为了方便大家理解,咱们看看伪代码:

/有一个全局共享的threadLocal对象 resources
static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
//获取一个db的连接
DataSource datasource = platformTransactionManager.getDataSource();
Connection connection = datasource.getConnection();
//设置手动提交事务
connection.setAutoCommit(false);
Map<Object, Object> map = new HashMap<>();
map.put(datasource,connection);
resources.set(map);

编程式事务

开启事务配置

先来个配置类,将事务管理器PlatformTransactionManager、事务模板TransactionTemplate都注册到spring中。

@Configuration
@ComponentScan
public class MainConfig3 {@Beanpublic DataSource dataSource() {org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/user_db");dataSource.setUsername("root");dataSource.setPassword("root123");dataSource.setInitialSize(5);return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource) {return new JdbcTemplate(dataSource);}@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}@Beanpublic TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {return new TransactionTemplate(transactionManager);}
}
事务定义与状态
public interface TransactionDefinition {int getPropagationBehavior(); // 返回事务的传播行为int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据int getTimeout();  // 返回事务必须在多少秒内完成boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
} 
public interface TransactionStatus{boolean isNewTransaction(); // 是否是新的事物boolean hasSavepoint(); // 是否有恢复点void setRollbackOnly();  // 设置为只回滚boolean isRollbackOnly(); // 是否为只回滚boolean isCompleted; // 是否已完成
} 
PlatformTransactionManager

在这里插入图片描述

在这里插入图片描述

  • 1.获取事务管理器;
  • 2.创建事务属性对象;
  • 3.获取事务状态对象;
  • 4.创建JDBC模板对象;
  • 5.业务数据操作处理;
public class test {@Resourceprivate PlatformTransactionManager txManager;@Resourceprivate  DataSource dataSource;private static JdbcTemplate jdbcTemplate;@Testpublic void testdelivery(){//定义事务隔离级别,传播行为,DefaultTransactionDefinition def = new DefaultTransactionDefinition();  def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);  def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);  //事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务TransactionStatus status = txManager.getTransaction(def);  jdbcTemplate = new JdbcTemplate(dataSource);try {  jdbcTemplate.update("insert into testtranstation(sd) values(?)", "1");  //提交status中绑定的事务txManager.commit(status); } catch (RuntimeException e) {  //回滚txManager.rollback(status);  } }}

在类中增加了两个属性:一个是 TransactionDefinition 类型的属性,它用于定义一个事务;另一个是 PlatformTransactionManager 类型的属性,用于执行事务管理操作。

如果方法需要实施事务管理,我们首先需要在方法开始执行前启动一个事务,调用PlatformTransactionManager.getTransaction(…) 方法便可启动一个事务。创建并启动了事务之后,便可以开始编写业务逻辑代码,然后在适当的地方执行事务的提交或者回滚。

TransactionTemplate

TransactionTemplate主要有2个方法,executeWithoutResult,execute

  • executeWithoutResult:无返回值场景,需传递一个Consumer对象,在accept方法中做业务操作
transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {@Overridepublic void accept(TransactionStatus transactionStatus) {//执行业务操作}
});
  • execute:有返回值场景,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
Integer result = transactionTemplate.execute(new TransactionCallback<Integer>() {@Nullable@Overridepublic Integer doInTransaction(TransactionStatus status) {return jdbcTemplate.update("insert into t_user (name) values (?)", "executeWithoutResult-3");}
});
public class test {@Resourceprivate  DataSource dataSource;private static JdbcTemplate jdbcTemplate;private static PlatformTransactionManager platformTransactionManager;@Testpublic void test2() throws Exception {//定义一个JdbcTemplate,用来方便执行数据库增删改查jdbcTemplate = new JdbcTemplate(dataSource);//1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)platformTransactionManager = new DataSourceTransactionManager(dataSource);//2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。DefaultTransactionDefinition  transactionDefinition = new DefaultTransactionDefinition();transactionDefinition.setTimeout(10);//3.创建TransactionTemplate对象TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager,transactionDefinition);/*** 4.通过TransactionTemplate提供的方法执行业务操作* 主要有2个方法:* (1).executeWithoutResult(Consumer<TransactionStatus> action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作* (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作* 调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。* 那么什么时候事务会回滚,有2种方式:* (1)transactionStatus.setRollbackOnly();将事务状态标注为回滚状态* (2)execute方法或者executeWithoutResult方法内部抛出异常* 什么时候事务会提交?* 方法没有异常 && 未调用过transactionStatus.setRollbackOnly();*/transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {@Overridepublic void accept(TransactionStatus transactionStatus) {jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");}});System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));}
}

Spring事务失效的几种常见

1. @Transactional 应用到 非public (只有public方法才有效)2.避免 SpringAOP 的自调用问题在 SpringAOP 代理下,只能目标方法由外部调用,
若同一类中的:其他没有@Transactional注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚,除此之外,即使是有@Transcation注解的,被调用的事务方法也会失效。3.数据库引擎不支持事务4.没有被 Spring 管理 没有添加@Service等注解放入到容器中5.数据源没有配置事务管理器6.把异常吃了,然后又不抛出来,事务也不会回滚!@Transactionalpublic void updateOrder(Order order) {try {// update order} catch {}}7.异常类型错误 默认是RuntimeException自动回滚,@Transactionalpublic void updateOrder(Order order) {try {// update order} catch {throw new Exception("更新错误");}}@Transactional(rollbackFor = Exception.class)值得注意的是这个配置仅限于 Throwable 异常类及其子类。8.@Transactional的扩展配置不支持事务@Transactional(propagation = Propagation.NOT_SUPPORTED)public void updateOrder(Order order) {}Propagation.NOT_SUPPORTED:表示不以事务运行,当前若存在事务则挂起。这表示不支持以事务的方式运行,所以即使事务生效也是白搭!

参考博文

mysql和postgresql事务隔离级别差异

spring事务介绍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3030105.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【.Net动态Web API】参数模型绑定来源

🚀前言 本文是《.Net Core进阶编程课程》教程专栏的导航站(点击链接,跳转到专栏主页,欢迎订阅,持续更新…) 专栏介绍:通过源码实例来讲解Asp.Net Core进阶知识点,让大家完全掌握每一个知识点。 专栏适用于人群:Web后端开发人员 ———————————————— …

ABB机器人IRB360介绍

随着自动化技术的不断发展&#xff0c;分拣和包装行业的应用也越来越广泛。 工业机器人扮演的角色也随之不断增加&#xff0c;其中ABB机器人的一款产品IRB 360 FlexPicker 在抓取和包装技术方面占有重要的地位。与传统的刚性自动化技术相比较&#xff0c;IRB 360具有高灵活性、…

Navicat Data Modeler Ess for Mac:强大的数据库建模设计软件

Navicat Data Modeler Ess for Mac是一款专为Mac用户设计的数据库建模与设计工具&#xff0c;凭借其强大的功能和直观的界面&#xff0c;帮助用户轻松构建和管理复杂的数据库模型。 Navicat Data Modeler Ess for Mac v3.3.17中文直装版下载 这款软件支持多种数据库系统&#x…

SQL注入(sqli-labs第一关)

sqli-labs第一关 方法一&#xff1a;手工注入 来到第一关&#xff0c;图上说我们需要一个数字的参数 于是我们先手工注入?id1 and 11 跟?id1 and 12发现页面没有报错 每张截图上面页面中有select查询语句&#xff0c;这是我在第一关的源码中加上了echo "$sql ";…

信息技术自主可控的意义,针对国产化替换,服务器虚拟化或比公有云更具优势

我们之前在文章《博通收购VMware后&#xff0c;经销商和用户如何应对&#xff1f;新出路&#xff1a;虚拟化国产替代&#xff0c;融入信创云生态》中提到&#xff1a; 从信创整体发展和政策标准来看&#xff0c;供应商必须满足两个条件&#xff1a;一是融入国产信息技术生态&am…

换新机,统信UOS V20桌面专业版(1070)教你轻松搞定数据迁移丨年度更新

硬件装备升级&#xff0c;数据如何快速迁移&#xff1f; 规模化汰换PC&#xff0c;怎样实现便捷部署&#xff1f; 统信UOS V20桌面专业版&#xff08;1070&#xff09; 带来两大换机神器&#xff01; 整机备份、数据迁移 手把手帮你轻松构建新质生产力工具&#xff01; 一键克…

短信群发公司通道有哪些要求

短信群发公司通道有哪些要求 网络稳定性 短信群发公司的通道在进行时需要具备良好的网络稳定性。这意味着通道需要能够稳定连接到互联网&#xff0c;并具备高速传输能力。在网络不稳定或者传输速度慢的情况下&#xff0c;可能会受到影响&#xff0c;甚至导致失败。 高可靠性 …

【算法入门赛】B. 自助店评分(C++、STL、推荐学习)题解与代码

比赛地址&#xff1a;https://www.starrycoding.com/contest/8 题目描述 在上一场的入门教育赛中&#xff0c;牢 e e e找到了所有自助店的位置&#xff0c;但是他想发现一些“高分好店”&#xff0c;于是他利用爬虫技术从“小众点评APP”中爬取了武汉所有自助店的评分。 评分…

《ESP8266通信指南》15-MQTT连接、订阅MQTT主题并打印消息(基于Lua|适合新手|非常简单)

往期 《ESP8266通信指南》14-连接WIFI&#xff08;基于Lua&#xff09;-CSDN博客 《ESP8266通信指南》13-Lua 简单入门&#xff08;打印数据&#xff09;-CSDN博客 《ESP8266通信指南》12-Lua 固件烧录-CSDN博客 《ESP8266通信指南》11-Lua开发环境配置-CSDN博客 《ESP826…

面试中算法(找到两个数组的中位数)

有两个升序数组&#xff0c;如何找出这两个数组归并以后新的升序数组的中位数? 中位数把一个升序数组分成了长度相等的两部分&#xff0c;其中左半部分的最大值永远小于或等于右半部分的最小值。 如上图所示&#xff0c;对于偶数长度的数组&#xff0c;可以根据中位数分成长度…

一文了解webpack和vite中Tree-Shaking

1、什么是Tree-Shaking 1.1 摇树优化&#xff08;Tree Shaking&#xff09;是Webpack中一种用于优化JavaScript代码的技术。它的目标是通过静态分析&#xff0c;从代码中剔除未被使用的模块&#xff0c;从而减少最终打包文件的大小。 1.2 Tree-shaking 它的名字来源于通过摇晃…

物联网技术在数字化工厂中的应用,你知道多少?——青创智通

工业物联网解决方案-工业IOT-青创智通 物联网&#xff08;IoT&#xff09;技术在数字化工厂的应用正日益成为工业革命的重要推动力。随着科技的飞速发展&#xff0c;物联网技术不断革新&#xff0c;其在数字化工厂中的应用也呈现出愈发广泛和深入的态势。本文将详细探讨物联网…

传输层之 TCP 协议

TCP协议段格式 源/目的端口号&#xff1a;表示数据是从哪个进程来&#xff0c;到哪个进程去。 序号&#xff1a;发送数据的序号。 确认序号&#xff1a;应答报文的序号&#xff0c;用来回复发送方的。 4 位首部长度&#xff1a;一个 TCP 报头&#xff0c;长度是可变的&#xff…

Android 屏幕适配全攻略(上)-掌握屏幕单位,应对千变万化的设备

本文从 Android 开发中常见的长度单位 px、dp、sp 入手&#xff0c;详细介绍了它们的特点及转换关系。 接着深入探讨了屏幕尺寸、分辨率、像素密度等重要的屏幕指标&#xff0c;帮助读者全面理解它们之间的联系。最后&#xff0c;通过实例代码演示了如何在代码中进行单位转换&…

第一章 buffer cache管理 - 2 原理机制

本章节主要介绍缓冲区管理器机制&#xff0c;从原理上介绍共享缓冲区如何管理内存页。 1、缓冲区管理器的结构 PostgreSQL缓冲区管理器由缓冲区hash表、缓冲区buffer描述符和缓冲池组成。下面依次介绍这几个结构。 1.1 缓冲区标签 typedef struct buftag {RelFileNode rnod…

Python运维之协程

目录 一、定义协程 二、并发 三、异步请求 协程是一种轻量级的线程&#xff0c;它通过保存和恢复寄存器上下文和栈来实现调度切换&#xff0c;从而保留函数执行的状态。 这种机制使得协程在处理I/O密集型任务时效率较高&#xff0c;因为它们可以在I/O操作期间让出CPU&#…

5g视频彩信和普通彩信有什么区别

5G视频彩信和普通彩信有什么区别 随着科技的不断进步&#xff0c;手机通信技术也在迅速发展。5G技术的出现&#xff0c;为彩信传输提供了更高的速度和更广的带宽。在这种背景下&#xff0c;5G视频彩信和普通彩信成为了人们关注的焦点。本文将探讨这两种彩信的区别。 5G视频彩信…

Java数组的应用---选择排序(Select Sort)

一、需求&#xff1a;选择排序(Select Sort)&#xff0c;进行升序显示 在一组排列中把最大的数取出来放在一个新的列表里&#xff0c;再删去&#xff0c;在取最大的数出来&#xff0c;依次类推直到取到最后一个数字 二、定义一个无序的一维数组&#xff0c;并输出数组 程序运…

BBS客户端服务器的编写

根据网络编程中的内容&#xff0c;我们本篇文章将讲解一个bbs通信的项目&#xff0c;首先让我们了解一下什么是bbs. 一、bbs介绍 BBS&#xff0c;即Bulletin Board System的缩写&#xff0c;中文译为“电子公告板系统”或“网络论坛”。它是一个在网络上进行信息交流和讨论的…

重装前端整体流程

用户管理 --汇总 -- 明细-CSDN博客 一、node 这个看环境变量 2023最新版Node.js下载安装及环境配置教程&#xff08;非常详细&#xff09;从零基础入门到精通&#xff0c;看完这一篇就够了_nodejs安装及环境配置-CSDN博客 配置到国内镜像的时候&#xff0c;去看&#xff0c;淘…