一文了解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://www.mzph.cn/diannao/9827.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构学习/复习12

一、排序概念与应用 二、插入排序 三、希尔排序 当间隔数为1时则为插入排序 1.一组一组排 2.多组并排 3.间隔数变化直至为1 四、性能测速代码

Java类加载器介绍

在Java中&#xff0c;类加载器是一种动态加载类的机制&#xff0c;它负责在运行时查找、加载和链接类文件。当Java应用程序需要创建某个类的对象时&#xff0c;类加载器会在运行时查找该类对应的.class文件&#xff0c;并将其加载到Java虚拟机中。Java类加载器通常分为三层&…

【Android】Kotlin学习之数据容器(数组for循环遍历)

数组遍历 1. for ( item in arr){…} 2. for ( i in arr.indeces ) {…} (遍历下标) 3. for ((index, item) in arr.withInfex()) {…} (遍历下标和元素) 4. arr.forEach {} ( 遍历元素 ) 5. arr.forEachIndexed{index, item -> …}

Python尝试安装 pyaudio 时遇到的错误信息表示安装过程失败,原因是找不到 Python.h 头文件

环境&#xff1a; Python 3.8.10 WSL2 问题描述&#xff1a; 尝试安装 pyaudio 时遇到的错误信息表示安装过程失败&#xff0c;原因是找不到 Python.h 头文件 error: subprocess-exited-with-error Building wheel for pyaudio (pyproject.toml) did not run successfully…

大模型微调之 在亚马逊AWS上实战LlaMA案例(七)

大模型微调之 在亚马逊AWS上实战LlaMA案例&#xff08;七&#xff09; 微调SageMaker JumpStart上的LLaMA 2模型 这是在us-west-2的测试结果。 展示了如何使用SageMaker Python SDK部署预训练的Llama 2模型&#xff0c;并将其微调到你的数据集&#xff0c;用于领域适应或指令…

【Nginx】如何在 Nginx 中阻止来自特定国家的 IP 地址访问

文章目录 前言一、准备工作二、查看 Nginx 服务器都拥有哪些模块2.1 先查看本地nginx是否有ngx_http_geoip2模块2.2 安装nginx并配置ngx_http_geoip2模块2.2.1下载所需版本的nginx到服务器2.2.2 先安装所需依赖2.2.3 解压文件2.2.4 下载ngx_http_geoip2模块2.2.5 编译安装nginx…

通用型产品发布解决方案(后端环境搭建)

文章目录 后端renren脚手架配置1.解压后放到项目目录下2.新建商品模块1.创建一个新模块 sunliving-commodity2.删除两个不必要的文件3.pom.xml 引入依赖 3.maven进行聚合管理1.将刚才配置的pom.xml文件复制到父项目下并进行修改2.手动将这个pom.xml加入项目&#xff08;如果右下…

算法设计与分析 动态规划/回溯

1.最大子段和 int a[N]; int maxn(int n) {int tempa[0];int ans0;ansmax(temp,ans);for(int i1;i<n;i){if(temp>0){tempa[i];}else tempa[i];ansmax(temp,ans);}return ans; } int main() {int n,ans0;cin>>n;for(int i0;i<n;i) cin>>a[i];ansmaxn(n);co…

H5 处理点击元素高亮、自定义按钮、去除焦点边框

1、设置移动设备上点击元素时出现的高亮颜色 *{-webkit-tap-highlight-color: transparent; }2、如果你想要自定义按钮的样式&#xff0c;你可以使用 -webkit-appearance: none; 来移除按钮的默认样式 .button {-webkit-appearance: none;appearance: none; /* 兼容性更好的通…

光检测器——光纤通信学习笔记七

光检测器 光检测器的基本介绍 作用&#xff1a;把接受到的光信号转换成电信号 光接收器的灵敏度、光源的发光功率和光纤的损耗三者决定了光纤通信的传输距离&#xff1b; 光电二极管 光电转换的基本原理 之前提到&#xff0c;PN结由于内部载流子的扩散运动形成了内部电场&…

Hive Windows Functions 窗口函数

Hive Windows Functions 窗口函数 在 Hive 中&#xff0c;窗口函数&#xff08;Window Functions&#xff09;用于在查询结果中执行聚合、排序和分析操作&#xff0c;而无需将数据分组。窗口函数允许你在查询结果中的一组行上执行计算&#xff0c;而不会改变原始数据的行数&am…

Vue3知识总结-1

前面学习一段时间的前端&#xff0c;但是没有进行过太多的练习&#xff0c;并且对于里面一些重要的知识点也没有去着重的记忆&#xff0c;所以打算在学习Vue3的时候&#xff0c;做一些笔记&#xff0c;方便后面翻看。这个笔记会对于学习一些做一些&#xff0c;而不是一个整体的…

前端之深拷贝

前提&#xff1a; 就是在实际开发中&#xff0c;我有一个编辑的弹窗&#xff0c;可以查看和编辑&#xff0c;因为弹窗里面是一个步骤条&#xff0c;点击下一步就要向对应的接口发送请求&#xff0c;考虑到就比如我点击下一步&#xff0c;此次表箱信息其实不需要修改&#xff0…

推荐 6 个超好用的 iterm2 zsh 插件

大家好啊&#xff0c;今天给大家分享几个我日常使用的 iterm2 插件&#xff0c;每一个都很有用&#xff0c;希望能给帮助你提高使用命令行的效率&#xff5e; zsh-autosuggestions 插件地址&#xff1a;https://github.com/zsh-users/zsh-autosuggestions 效果展示 当你输入…

AI中转站计费平台系统源码一站式解决方案安装说明

AI中转站计费平台系统源码一站式解决方案安装说明 功能 | Features AI 联网功能 AI online searching service 多账户均衡负载 Multi-account load balancing HTTP2 Stream 实时响应功能 HTTP2 Stream real-time response function 节流和鉴权体系 Throttling and authenticati…

【17-Ⅱ】Head First Java 学习笔记

HeadFirst Java 本人有C语言基础&#xff0c;通过阅读Java廖雪峰网站&#xff0c;简单速成了java&#xff0c;但对其中一些入门概念有所疏漏&#xff0c;阅读本书以弥补。 第一章 Java入门 第二章 面向对象 第三章 变量 第四章 方法操作实例变量 第五章 程序实战 第六章 Java…

开源代码分享(28)-含分布式光伏的配电网集群划分和集群电压协调控制

参考文献&#xff1a; [1] Chai Y , Guo L , Wang C ,et al.Network Partition and Voltage Coordination Control for Distribution Networks With High Penetration of Distributed PV Units[J].IEEE Transactions on Power Systems, 2018:3396-3407.DOI:10.1109/TPWRS.2018…

基于微信小程序的校园二手交易平台设计与实现(论文+源码)_kaic

基于微信小程序的校园二手交易平台 设计与实现 摘 要 随着绿色低碳消费和循环经济的理念越来越深入人心,大学生二手商品市场发展迅猛&#xff0c;而大部分二手交易平台运输方式与收售方式对于大学生用户群体并不适用&#xff0c;所以急需一款针对大学生二手商品交易的软件&…

搜索的未来:OpenAI 的 GPT 如何彻底改变行业

搜索的未来&#xff1a;OpenAI 的 GPT 如何彻底改变行业 概述 搜索引擎格局正处于一场革命的风口浪尖&#xff0c;而 OpenAI 的 GPT 处于这场变革的最前沿。最近出现了一种被称为“im-good-gpt-2-chatbot”的神秘聊天机器人&#xff0c;以及基于 ChatGPT 的搜索引擎的传言&am…

WebSocket基础知识

WebSocket是什么&#xff1f; WebSocket 是一种网络通信协议&#xff0c;它提供了全双工通信机制&#xff0c;允许服务器主动向客户端发送消息&#xff0c;而不仅限于响应客户端的请求。它使用类似于 HTTP 的握手来建立连接&#xff0c;然后使用单独的持久连接来进行通信。这种…