推荐工具 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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://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 {
//创建数据库连接池
@Bean
public 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 //根据类型注入dataSource
public JdbcTemplate getJdbcTemplate(DruidDataSource druidDataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(druidDataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource druidDataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(druidDataSource);
return dataSourceTransactionManager;
}
}
第二步业务中使用声明式事务注解@Transactional
//参数
@Transactional
public 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 {
@Bean
public 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;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public 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 {
@Resource
private PlatformTransactionManager txManager;
@Resource
private DataSource dataSource;
private static JdbcTemplate jdbcTemplate;
@Test
public 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>() {
@Override
public void accept(TransactionStatus transactionStatus) {
//执行业务操作
}
});
execute
:有返回值场景,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
Integer result = transactionTemplate.execute(new TransactionCallback<Integer>() {
@Nullable
@Override
public Integer doInTransaction(TransactionStatus status) {
return jdbcTemplate.update("insert into t_user (name) values (?)", "executeWithoutResult-3");
}
});
public class test {
@Resource
private DataSource dataSource;
private static JdbcTemplate jdbcTemplate;
private static PlatformTransactionManager platformTransactionManager;
@Test
public 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>() {
@Override
public 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.避免 Spring 的 AOP 的自调用问题在 Spring 的 AOP 代理下,只能目标方法由外部调用,
若同一类中的:
其他没有@Transactional注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚,
除此之外,即使是有@Transcation注解的,被调用的事务方法也会失效。
3.数据库引擎不支持事务
4.没有被 Spring 管理 没有添加@Service等注解放入到容器中
5.数据源没有配置事务管理器
6.把异常吃了,然后又不抛出来,事务也不会回滚!
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
}
}
7.异常类型错误 默认是RuntimeException自动回滚,
@Transactional
public 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事务介绍