一文弄懂Spring事务相关知识

Posted 赵晓东-Nastu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文弄懂Spring事务相关知识相关的知识,希望对你有一定的参考价值。

目录

一、什么是事务

二、事务的特性

三、事务引发的三种情况

四、事务的隔离级别

五、事务的传播行为

(1)Required

(2)REQUIRED_NEW

(3)Propagation.REQUIRES_NEW

(3)propagtaion.Never

(4)propagation.NOT_SUPPORTED

(5)propagtioin.supports

(6)Propagation.NESTED 嵌套事务

(7)propagation.MANDATORY

(8)事务传播特性总结

六、事务如何配置

七、 Propagation.REQUIRES_NEW Lock wait timeout exceeded; try restarting transact


一、什么是事务

        事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败

二、事务的特性

原子性:原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响

一致性:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

隔离性:隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每个用户开启的事务,才能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

持久性:持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作

三、事务引发的三种情况

脏读:(读取了未提交的新事物,然后被回滚了)

首先左边的同学先进行查询数据,读取到是4000,然后右边的同学给左边的同学添加了1000元,然后左边的同学读取到了5000元,然后右边的同学提交失败,进行数据回滚,然后左边的同学读取的就是错误的数据。

不可重复读(读取了提交的新事物,指更新操作)

如花查看自己的余额有100元,然后小明将如花的钱取走了50,然后进行事务的提交,如果再次查看自己的余额,发现为50

幻读(也是读取了提交的新事物,指增删操作)

在事务A多次读取,事务B对数据进行新增操作,导致事务A多次读取的数据不一致 

 如花查看100块,小明挣了50,放入了50块,而且事务正常提交,如花再次查看发现变成了150块。主要的原因是新增产生的问题,。

不可重复读和幻读的区别

不可重复读是修改的数据导致两次数据不一致,幻读是指增删导致两个读取的不一致。

四、事务的隔离级别

READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读、和幻读。

READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更数据,可表面脏读,仍会出现不可重复读和幻读问题
REPEATABLE READ(可重复读):确保事务可以多次从一个字段读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。

SERIALIZABLE(序列化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低

五、事务的传播行为

在Spring中默认为PROPAGATION_REQUIRED

什么是事务的传播行为?

指的就是当一个事务方法被另一个事务方法调用时,这个事务方法是如何运行的?

(1)Required

首先,我们来设计数据库

现在在同一个类里面写增加和减少的代码

public class UserService {

	//注入dao
	@Autowired
	private Zxd zxd;
	@Autowired
	private UserDao userDao;

	//转账的方法
	@Transactional
	public void accountMoney() {

		userDao.reduceMoney();
		xxx();

	}

	public void xxx() {
		userDao.addMoney();
		//模拟异常
		int i = 100;
		if (i == 100) {
			throw new NullPointerException("demo");
		}
	}


}

在accountMoney总的上面进行Transactional,结果进行了回滚。

现在把@Transactional放到xxx()

 

 

现在分别放到两个类中

@Service
public class TxRequiredService {
	@Autowired
	private UserDao userDao;
	@Transactional
	public void addMoney(){
		userDao.addMoney();
	}

	@Transactional
	public void reduceMoney(){
		userDao.reduceMoney();
	}

	@Transactional
	public void testRequired(){
		reduceMoney();
		addMoney();
		int i =100;
		if (i==100){
			throw  new NullPointerException("demo");
		}
	}
}

 

@Service
public class TxService {
	@Autowired
	private TxRequiredService txRequiredService;

	@Autowired
	private UserDao userDao;

	//三个都加了Transactional

	public void testRequired(){
		txRequiredService.reduceMoney();
		txRequiredService.addMoney();
		int i = 100;
		if (i == 100){
			throw  new NullPointerException("demo");
		}
	}

现在是在TxService的testRequired的方法上是没有加Transactional的,在addMoney上面是加Transactional,它是可以进行回滚的。

 

(1)

@Service
public class TxRequiredService {
	@Autowired
	private UserDao userDao;
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void addMoney(){
		userDao.addMoney();
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void reduceMoney(){
		userDao.reduceMoney();
	}
@Service
public class TxService {
	@Autowired
	private TxRequiredService txRequiredService;

	@Autowired
	private UserDao userDao;

	//三个都加了Transactional


	public void testRequired(){
		txRequiredService.reduceMoney();
		txRequiredService.addMoney();
		int i = 100;
		if (i == 100){
			throw  new NullPointerException("demo");
		}
	}

在TxService没有加Transactional,在TxRequiredService中的reduceMoney()加Required_NEW在addMoney加Required,发现结果并没有进行回滚

(2)REQUIRED_NEW

@Service
public class TxService {
	@Autowired
	private TxRequiredService txRequiredService;

	@Autowired
	private UserDao userDao;

	//三个都加了Transactional

	@Transactional
	public void testRequired(){
		txRequiredService.reduceMoney();
		txRequiredService.addMoney();
		int i = 100;
		if (i == 100){
			throw  new NullPointerException("demo");
		}
	}

 

@Service
public class TxRequiredService {
	@Autowired
	private UserDao userDao;
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void addMoney(){
		userDao.addMoney();
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void reduceMoney(){
		userDao.reduceMoney();
	}

 

在TxService中的testRequired加上Transactional,发现数据进行了回滚

(3)Propagation.REQUIRES_NEW


@Service
public class TxService {
	@Autowired
	private TxRequiredService txRequiredService;

	@Autowired
	private UserDao userDao;

	//三个都加了Transactional

	@Transactional
	public void testRequired(){
		txRequiredService.reduceMoney();
		txRequiredService.addMoney();
		int i = 100;
		if (i == 100){
			throw  new NullPointerException("demo");
		}
	}
@Service
public class TxRequiredService {
	@Autowired
	private UserDao userDao;

	public void addMoney(){
		userDao.addMoney();
	}

	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void reduceMoney(){
		userDao.reduceMoney();
	}

 

Propagation.REQUIRES_NEW中进行创建新的连接。

(3)propagtaion.Never

@Service
public class TxUserService {
	@Autowired
	private TxUserService txUserService;
	@Autowired
	private UserDao userDao;
	
	@Transactional
	public void laoda(){
		userDao.reduceMoney();
		txUserService.xiaodi();
		int i = 100;
		if (i==100){
			throw new RuntimeException("laoda出错了");
		}
	}
	@Transactional(propagation =Propagation.NEVER)
	public void xiaodi(){
		userDao.addMoney();
		int i =100 ;
		if (i==100){
			throw new RuntimeException("xiaodi出错了");
		}
	}
}

运行结果报错,并且数据库没有发生改变。 

Exception in thread "main" org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

(4)propagation.NOT_SUPPORTED

	@Transactional
	public void laoda(){
		userDao.reduceMoney();
		txUserService.xiaodi();
		int i = 100;
		if (i==100){
			throw new RuntimeException("laoda出错了");
		}
	}
	@Transactional(propagation =Propagation.NOT_SUPPORTED)
	public void xiaodi(){
		userDao.addMoney();
		int i =100 ;
		if (i==100){
			throw new RuntimeException("xiaodi出错了");
		}
	}

从运行结果来看,加的是没有发生回滚的,减的是发生回滚了。

(5)propagtioin.supports

	@Transactional
	public void laoda(){
		userDao.reduceMoney();
		txUserService.xiaodi();
		int i = 100;
		if (i==100){
			throw new RuntimeException("laoda出错了");
		}
	}
	@Transactional(propagation =Propagation.SUPPORTS)
	public void xiaodi(){
		userDao.addMoney();
		int i =100 ;
		if (i==100){
			throw new RuntimeException("xiaodi出错了");
		}
	}

(6)Propagation.NESTED 嵌套事务

//	Propagation.NESTED 嵌套事务
	@Transactional
	public void laoda(){
		userDao.reduceMoney();
		txUserService.xiaodi();
		int i = 99;
		if (i==100){
			throw new RuntimeException("laoda出错了");
		}
	}
	@Transactional(propagation =Propagation.NESTED)
	public void xiaodi(){
		userDao.addMoney();
		int i =100 ;
		if (i==100){
			throw new RuntimeException("xiaodi出错了");
		}
	}

现在让老大不出异常,让小弟出异常 

可以看出,xiaodi的回滚了,不影响老大的没有进行回滚。

现在老大出异常,看一下xiaodi是不是会回滚

	@Transactional
	public void laoda(){
		userDao.reduceMoney();
		try {
			txUserService.xiaodi();
		}catch (Exception e){
			e.printStackTrace();
		}
		int i = 100;
		if (i==100){
			throw new RuntimeException("laoda出错了");
		}
	}
	@Transactional(propagation =Propagation.NESTED)
	public void xiaodi(){
		userDao.addMoney();
		int i =90 ;
		if (i==100){
			throw new RuntimeException("xiaodi出错了");
		}
	}

 现在让老大出异常,xiaodi也会回滚

//	Propagation.NESTED 嵌套事务
	@Transactional
	public void laoda(){
		userDao.reduceMoney();
		try {
			txUserService.xiaodi();
		}catch (Exception e){
			e.printStackTrace();
		}
		int i = 100;
		if (i==100){
			throw new RuntimeException("laoda出错了");
		}
	}
	@Transactional(propagation =Propagation.NESTED)
	public void xiaodi(){
		userDao.addMoney();
		int i =90 ;
		if (i==100){
			throw new RuntimeException("xiaodi出错了");
		}
	}

(7)propagation.MANDATORY

public void laoda(){
		userDao.reduceMoney();
		try {
			txUserService.xiaodi();
		}catch (Exception e){
			e.printStackTrace();
		}
		int i = 100;
		if (i==100){
			throw new RuntimeException("laoda出错了");
		}
	}
	@Transactional(propagation =Propagation.MANDATORY)
	public void xiaodi(){
		userDao.addMoney();
		int i =90 ;
		if (i==100){
			throw new RuntimeException("xiaodi出错了");
		}
	}

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

(8)事务传播特性总结

【1】死活不要事务的

propagatioin_never:没有就非事务执行,有就抛出异常

propagatioin_not_supported: 没有就非事务执行,有就直接挂起,然后非事务执行

 【2】可有可无的

propagation_supports:有就用,没有就算了

【3】必须有事务的

propagation_requires_new: 有没有都新建事务,如果原来有,就将原来的挂起

propagation_nested:如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。(绑定在一起)

propagation_required:如果没有,就新建一个事务,如果有就加入当前事务。

propagation_mandatory:如果没有,就抛出异常;如果有,就使用当前事务。

六、事务如何配置

(1)数据库

(2)创建Service,搭建dao,完成对象创建和注入关系

service注入dao,在dao注入jdbcTemplate,在JdbcTemplate注入dataSource

package com.tx.service;

import com.tx.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

	//注入dao
	@Autowired
	private UserDao userDao;

	//转账的方法
	public void accountMoney(){
		//lucy少100
		userDao.reduceMoney();

		//mary多100
		userDao.addMoney();
	}
}

(3) 创建dao

UserDao

public interface UserDao {
	//多钱
	public void addMoney();
	//少钱
	public void reduceMoney();

}
package com.tx.dao;

import com.iocbean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

@Repository
public class UserDaoImpl implements UserDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	//多钱
	@Override
	public void addMoney() {
		String sql = "update t_account set money=money+? where username=?";
		jdbcTemplate.update(sql,100,"lucy");
	}

	//少钱
	@Override
	public void reduceMoney() {
		String sql = "update t_account set money=money-? where username=?";
		jdbcTemplate.update(sql,100,"mary");
	}
}
package com.tx.test;

import com.tx.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TXText {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		UserService userService = context.getBean("userService", UserService.class);
		userService.accountMoney();

	}
}
<?xml version="1.0" encoding="UTF-8"?>
<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"
	   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">



	<!-- 开启注解扫描 -->
	<context:component-scan base-package="com.tx"></context:component-scan>

	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
		<property name="url" value="jdbc:mysql://localhost:3306/user_db?serverTimezone=GMT"/>
		<property name="username" value="root"/>
		<property name="password" value="root"/>
	</bean>

	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<!--		注入dataSource-->
		<property name="dataSource" ref="dataSource"/>
	</bean>


</beans>

这个是搭建的项目,此时还没有用到事务。

 

上面这个银行转账正常执行是没有问题的,如果上面的代码在执行过程中产生异常

@Service
public class UserService {

	//注入dao
	@Autowired
	private UserDao userDao;

	//转账的方法
	public void accountMoney(){
		//lucy少100
		userDao.reduceMoney();

		//模拟异常
		int i =100;
		if (i==100){
			throw  new NullPointerException("demo");
		}

		//mary多100
		userDao.addMoney();
	}
}

(3)xml和注解的方式

<?xml version="1.0" encoding="UTF-8"?>
<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:tx="http://www.springframework.org/schema/tx"
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   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
	">



	<!-- 开启注解扫描 -->
	<context:component-scan base-package="com.tx"></context:component-scan>

	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
		<property name="url" value="jdbc:mysql://localhost:3306/user_db?serverTimezone=GMT"/>
		<property name="username" value="root"/>
		<property name="password" value="root"/>
	</bean>

	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<!--		注入dataSource-->
		<property name="dataSource" ref="dataSource"/>
	</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>

</beans>
@Service
public class TxService {
	@Autowired
	private TxRequiredService txRequiredService;

	@Autowired
	private UserDao userDao;

	//三个都加了Transactional

	@Transactional
	public void testRequired(){
		txRequiredService.reduceMoney();
		txRequiredService.addMoney();
		int i = 100;
		if (i == 100){
			throw  new NullPointerException("demo");
		}
	}

	//只有在总的方法上加Transactional
	@Transactional
	public void testDaoRequired(){
		userDao.reduceMoney();
		userDao.addMoney();
		int i = 100;
		if (i == 100){
			throw  new NullPointerException("demo");
		}
	}
	//现在总的方法上不加,在一个上面加
	@Transactional
	public void testRequiredOne(){
		txRequiredService.reduceMoney();
		txRequiredService.addMoney();
		int i = 100;
		if (i == 100){
			throw  new NullPointerException("demo");
		}
	}

}

(4)xml方式

<?xml version="1.0" encoding="UTF-8"?>
<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:tx="http://www.springframework.org/schema/tx"
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   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
	">



	<!-- 开启注解扫描 -->
	<context:component-scan base-package="com.tx"></context:component-scan>

	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
		<property name="url" value="jdbc:mysql://localhost:3306/user_db?serverTimezone=GMT"/>
		<property name="username" value="root"/>
		<property name="password" value="root"/>
	</bean>

	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<!--		注入dataSource-->
		<property name="dataSource" ref="dataSource"/>
	</bean>
<!--	创建事务管理器-->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--		注入数据源-->
		<property name="dataSource" ref="dataSource"></property>
	</bean>
<!--	第二步: 配置通知-->
	<tx:advice id="txadvice">
<!--		配置事务参数-->
		<tx:attributes>
<!--			指定那种规则的方法上面添加事务,配置方法-->
			<tx:method name="testRequired" propagation="REQUIRED"/>
<!--			<tx:method name="test*"/>-->
		</tx:attributes>
	</tx:advice>
<!--	3、配置切入点和切面-->
	<aop:config>
<!--		配置切入点-->
		<aop:pointcut id="pt" expression="execution(* com.tx.service.TxRequiredService.*(..))"/>
<!--		配置切面-->
		<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
	</aop:config>
</beans>

(5)xml和注解的方式

package com.tx.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration //配置类
@ComponentScan(basePackages="com.tx")
@EnableTransactionManagement//开启事务
public class TxConfig {
	//创建数据库连接池
	@Bean
	public DriverManagerDataSource driverManagerDataSource(){
		DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
		driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
		driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/user_db?serverTimezone=GMT");
		driverManagerDataSource.setUsername("root");
		driverManagerDataSource.setPassword("root");
		return driverManagerDataSource;
	}

	//JdbcTemplate对象
	@Bean
	public JdbcTemplate getJdbcTemplate(DataSource dataSource){
		//到ioc容器中根据类型找到dataSource
		JdbcTemplate jdbcTemplate = new JdbcTemplate();
		//注入dataSource
		jdbcTemplate.setDataSource(dataSource);
		return jdbcTemplate;
	}
	//创建事务管理器
	@Bean
	public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
		DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
		transactionManager.setDataSource(dataSource);
		return transactionManager;
	}


}

 

 

七、 Propagation.REQUIRES_NEW Lock wait timeout exceeded; try restarting transact

业务描述:

TxService中的testRequired()方法是Transactional,然后TxRequiredService中有两个方法一个是Propagatioin.ReQUIRES_NEW,另一个是默认REQUIRE

@Service
public class TxService {
	@Autowired
	private TxRequiredService txRequiredService;

	@Autowired
	private UserDao userDao;

	//三个都加了Transactional

	@Transactional
	public void testRequired(){
		txRequiredService.reduceMoney();
		txRequiredService.addMoney();
		int i = 100;
		if (i == 100){
			throw  new NullPointerException("demo");
		}
	}

 

@Service
public class TxRequiredService {
	@Autowired
	private UserDao userDao;
	@Transactional(propagation = Propagation.REQUIRES_NEW)
	public void addMoney(){
		userDao.addMoney();
	}

	@Transactional
	public void reduceMoney(){
		userDao.reduceMoney();
	}

最后出现的效果

Propagation.REQUIRES_NEW Lock wait timeout exceeded; try restarting transact

用语句进行检查

然后通过lock_data发现是'1',锁的是根1

执行explain,发现rows为2,这个rows就是mysql认为必须要逐行去检查和判断的记录的条数。

这一行中就检查了一行,所以我们需要把第一个sql改成检查一行的,直接命中,那么我们就要建立两个索引,然后直接命中

就不会存在扫描然后锁的情况了。

以上是关于一文弄懂Spring事务相关知识的主要内容,如果未能解决你的问题,请参考以下文章

一文带你看懂Spring事务!

给Android工程师的音视频教程之一文弄懂MediaCodec

一文弄懂什么是RPC

猿蜕变16——一文搞懂Spring事务花式玩法

一文让你搞懂Spring的统一事务模型

一文搞懂 Spring 数据库事务操作!