文章目录

  • 事务概念
  • 1. 什么是事务
  • 2. 事务的四个特性( ACID )
  • 事务操作(搭建事务操作环境)
  • WEB
  • Service
  • Dao
  • 1. 在 dao 创建两个方法:多钱和少钱的方法,在 service 创建方法(转账的方法)
  • 在 dao 层里面创建两个方法
  • 在实现类中实现那两个方法
  • 在 service 类里面实现业务逻辑的代码
  • 测试类
  • 2. 上面的代码,如果正常执行没有问题,但是如果代码执行的过程中出现异常,会有问题
  • 3. 上面的问题该如何解决呢
  • 4. 事务操作的基本过程
  • Spring 事务管理介绍
  • 1. 把事务加到哪一层更合适
  • 2. 在 Spring 里面进行事务的管理操作
  • 有两种方式:编程式事务管理和声明式事务管理
  • 3. 声明式事务管理
  • (1)基于注解方式进行实现(使用)
  • (2)基于 xml 配置文件方式
  • 4. 在 Spring 进行声明式事务管理,底层使用 AOP
  • 5. Spring 事务管理 API
  • (1)提供一个借口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
  • 注解声明式事务管理
  • 1. 在 Spring 配置文件配置事务管理器
  • 2. 在 Spring 配置文件,开启事务注解
  • (1)在 Spring 配置文件引入名称空间 tx
  • (2)开启事务的注解
  • 3. 在 service 类上面(获取 service 类里面方法上面)添加事务注解
  • 声明式事务管理参数
  • propagation:事务传播行为
  • 事务的传播行为可以由传播属性指定。 Spring 定义了7种类传播行为。
  • ioslation :事务的隔离级别
  • 解决:通过设置隔离级别,解决读的问题
  • timeout :超时时间
  • readOnly :是否只读
  • rollbackFor :回滚
  • noRollbackFor :不回滚
  • XML 声名式事务管理
  • 第一步 配置事务管理器
  • 第二步 配置通知
  • 第三步 配置切入点和切面
  • 完全注解声明式事务管理
  • 配置类
  • 测试类



事务概念

1. 什么是事务

(1)事务是数据库操作的最基本单元,逻辑上的一组操作,要么都成功,如果有一个失败,那所有的操作都失败
(2)典型场景:银行转账
老师给同学转账100块,那就分2个步骤1. 老师扣掉100块2. 学生的账户加一百块 如果在第一步到第二步之间出现了异常,那么老师的钱少了,学生的钱却没有多
所以应该是,但凡中间出现了什么异常,这两步操作应该都不算成功,这就叫事务

2. 事务的四个特性( ACID )

(1) 原子性

指的是一个过程的所有操作不可分割,要么都成功,要么都失败

(2)一致性

指的是操作之前和操作之后的总量是不变的,就那上面的例子来距离,操作之前和操作之后的钱的总量应该是不变的

(3)隔离性

在多事务操作的事务的时候,每个事务之间不会产生影响

(4)持久性

当事务提交以后,表中的数据就真正发生了变化

事务操作(搭建事务操作环境)

WEB

进行视图的相关操作

Service

进行业务的相关操作

Dao

进行数据库的相关操作,不写业务上的相关操作

1. 在 dao 创建两个方法:多钱和少钱的方法,在 service 创建方法(转账的方法)

在 dao 层里面创建两个方法

package com.fairykunkun.dao;
public interface UserDao {
	// 多钱的方法
	public void addMoney ( );
	// 少钱的方法
	public void reduceMoney ( );
}

在实现类中实现那两个方法

package com.fairykunkun.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao {
	@Autowired
	private JdbcTemplate jdbcTemplate;
	// 多钱
	@Override
	public void addMoney ( ) {
		String sql = "update account_t set balance=balance+? where username=?";
		jdbcTemplate.update ( sql , 100 , "mary" );
	}
	// lucy 转账100给 mary
	// 少钱
	@Override
	public void reduceMoney ( ) {
		String sql = "update account_t set balance=balance-? where username=?";
		jdbcTemplate.update ( sql , 100 , "lucy" );
	}
}

在 service 类里面实现业务逻辑的代码

package com.fairykunkun.service;
import com.fairykunkun.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 ( );
	}
}

以上,我们的环境就搭建完成了

测试类

package com.fairykunkun;
import com.fairykunkun.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestUser {
	@Test
	public void test ( ) {
		ApplicationContext context =
				new ClassPathXmlApplicationContext ( "bean13.xml" );
		UserService userService = context.getBean ( "userService" , UserService.class );
		userService.accountMoney ( );
	}
}

这里我就不展示测试结果了,因为我连表都没有建,哈哈

2. 上面的代码,如果正常执行没有问题,但是如果代码执行的过程中出现异常,会有问题

这边模拟异常的情节我就不掩饰了
其实就是在扣线和价钱两个步骤的中间出现异常
要是真有这种异常,对于银行来说,这将会是一场灾难

3. 上面的问题该如何解决呢

  • 使用事务进行解决

要么都成功,要么一个失败,所有的都失败

4. 事务操作的基本过程

  • 第一步,开启事务操作
  • 第二步,开始进行业务上的操作
  • 第三步,没有发生异常,做一个事务的提交
  • 第四部,如果发生异常,做一个事务的回滚(也就是回到之前的状态)

Spring 事务管理介绍

1. 把事务加到哪一层更合适

  • service 层
  • 因为 service 层会调用不同的 dao 操作
  • 所以在 service 层里面添加事务会更加合适

2. 在 Spring 里面进行事务的管理操作

有两种方式:编程式事务管理和声明式事务管理

我们经常使用声明式事务管理,而不去使用编程式事务管理,因为编程式写的代码量太大了

3. 声明式事务管理

(1)基于注解方式进行实现(使用)

(2)基于 xml 配置文件方式

4. 在 Spring 进行声明式事务管理,底层使用 AOP

5. Spring 事务管理 API

(1)提供一个借口,代表事务管理器,这个接口针对不同的框架提供不同的实现类

注解声明式事务管理

1. 在 Spring 配置文件配置事务管理器

<?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:aop = "http://www.springframework.org/schema/aop"
	   xmlns:context = "http://www.springframework.org/schema/context"
	   xsi:schemaLocation = "
	   		http://www.springframework.org/schema/beans
	   		http://www.springframework.org/schema/beans/spring-beans.xsd
	   		http://www.springframework.org/schema/aop
	   		http://www.springframework.org/schema/aop/spring-aop.xsd
	   		http://www.springframework.org/schema/context
	   		http://www.springframework.org/schema/context/spring-context.xsd
	   	">
	<!-- 开启组件扫描 -->
	<context:component-scan base-package = "com.fairykunkun"></context:component-scan>
	<!-- 引入外部的属性文件 -->
	<context:property-placeholder location = "jdbc.properties"></context:property-placeholder>
	
	<!-- 配置连接池 -->
	<bean name = "dataSource" class = "org.apache.commons.dbcp2.BasicDataSource">
		<property name = "driverClassName" value = "${prop.driverClass}"/>
		<property name = "url" value = "${prop.url}"/>
		<property name = "username" value = "${prop.userName}"/>
		<property name = "password" value = "${prop.passWord}"/>
	</bean>
	<!-- JdbcTemplate 对象 -->
	<bean id = "jdbcTemplate" class = "org.springframework.jdbc.core.JdbcTemplate">
		<!-- 注入 dataSource -->
		<property name = "dataSource" ref = "dataSource"></property>
	</bean>
	<!-- 创建事务管理器 -->
	<bean id = "transactionManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入数据源 -->
		<property name = "dataSource" ref = "dataSource"></property>
	</bean>
</beans>

2. 在 Spring 配置文件,开启事务注解

(1)在 Spring 配置文件引入名称空间 tx

<beans xmlns = "http://www.springframework.org/schema/beans"
	   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:aop = "http://www.springframework.org/schema/aop"
	   xmlns:context = "http://www.springframework.org/schema/context"
	   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/aop
	   		http://www.springframework.org/schema/aop/spring-aop.xsd
	   		http://www.springframework.org/schema/context
	   		http://www.springframework.org/schema/context/spring-context.xsd
	   		http://www.springframework.org/schema/tx
	   		http://www.springframework.org/schema/tx/spring-tx.xsd
	   	">

(2)开启事务的注解

<?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:aop = "http://www.springframework.org/schema/aop"
	   xmlns:context = "http://www.springframework.org/schema/context"
	   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/aop
	   		http://www.springframework.org/schema/aop/spring-aop.xsd
	   		http://www.springframework.org/schema/context
	   		http://www.springframework.org/schema/context/spring-context.xsd
	   		http://www.springframework.org/schema/tx
	   		http://www.springframework.org/schema/tx/spring-tx.xsd
	   	">
	<!-- 开启组件扫描 -->
	<context:component-scan base-package = "com.fairykunkun"></context:component-scan>
	<!-- 引入外部的属性文件 -->
	<context:property-placeholder location = "jdbc.properties"></context:property-placeholder>
	<!-- 配置连接池 -->
	<bean name = "dataSource" class = "org.apache.commons.dbcp2.BasicDataSource">
		<property name = "driverClassName" value = "${prop.driverClass}"/>
		<property name = "url" value = "${prop.url}"/>
		<property name = "username" value = "${prop.userName}"/>
		<property name = "password" value = "${prop.passWord}"/>
	</bean>
	<!-- JdbcTemplate 对象 -->
	<bean id = "jdbcTemplate" class = "org.springframework.jdbc.core.JdbcTemplate">
		<!-- 注入 dataSource -->
		<property name = "dataSource" ref = "dataSource"></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>
</beans>

3. 在 service 类上面(获取 service 类里面方法上面)添加事务注解

  • @Transactional
  • (1) 这个注解可以加在类上面,也可以加在方法上面
  • (2)如果把这个注解添加在类上面,这个类里面所有的方法都添加事务
  • (2)如果把这个注解添加方法上面,为这个方法添加事务

这里我就不演示了,太简单了

声明式事务管理参数

propagation:事务传播行为

当一个事务方法被另外一个事务方法调用的时候,这个事务的方法该如何进行

事务的传播行为可以由传播属性指定。 Spring 定义了7种类传播行为。

  • REQUIRED

如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行

  • REQUIRED_NEW

当前的方法必须启动新事务,并在自己的事务内运行,如果有事务在运行,应该将它挂起

  • SUPPORTS

如果有事务在运行,当前的方法就在这个事务内运行,否则可以不运行在事务中

  • NOT-SUPPORTED

当前的方法不应该运行在事务中,如果有运行的事务,将它挂起

  • MANDATORY

当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常

  • NEVER

当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常

  • NESTED

如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行

  • 示例
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.REPEARABLE_READ)

ioslation :事务的隔离级别

(1)事务里面里面有一个特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性会产生很多问题
(2)有三个读的问题:脏读、不可重复读、虚(幻)读

  • 脏读

一个未提交的事务读取到了另一个未提交事务的数据

  • 不可重复读

一个未提交的事务读取到了另一个提交了的事务中的数据

  • 虚读

一个未提交事务读取到另一个提交事务添加数据

解决:通过设置隔离级别,解决读的问题

隔离级别

脏读

不可重复读

幻读

READ UNCOMMITED(读未提交)




READ COMMITTED(读已提交)




REPEATABLE READ(可重复度)




SERIALIZABLE(串行化)




  • 示例
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.REPEARABLE_READ)

timeout :超时时间

(1)事务需要在一定的时间内进行提交,否则需要进行回滚
(2)默认值为-1,设置时间以秒为单位进行计算

  • 示例
@Transactional(timeout=-1,propagation=Propagation.REQUIRED,isolation=Isolation.REPEARABLE_READ)

readOnly :是否只读

(1)读:查询操作,写:添加修改删除操作
(2)readOnly 默认值 false ,表示可以查询,可以添加修改删除操作
(3)设置 readOnly 值是 true ,设置成 true 之后,只能查询

rollbackFor :回滚

设置出现哪些异常进行事务回滚

noRollbackFor :不回滚

设置出现哪些异常不进行事务回滚

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:aop = "http://www.springframework.org/schema/aop"
	   xmlns:context = "http://www.springframework.org/schema/context"
	   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/aop
	   		http://www.springframework.org/schema/aop/spring-aop.xsd
	   		http://www.springframework.org/schema/context
	   		http://www.springframework.org/schema/context/spring-context.xsd
	   		http://www.springframework.org/schema/tx
	   		http://www.springframework.org/schema/tx/spring-tx.xsd
	   	">
	<!-- 开启组件扫描 -->
	<context:component-scan base-package = "com.fairykunkun"></context:component-scan>
	<!-- 引入外部的属性文件 -->
	<context:property-placeholder location = "jdbc.properties"></context:property-placeholder>
	<!-- 配置连接池 -->
	<bean name = "dataSource" class = "org.apache.commons.dbcp2.BasicDataSource">
		<property name = "driverClassName" value = "${prop.driverClass}"/>
		<property name = "url" value = "${prop.url}"/>
		<property name = "username" value = "${prop.userName}"/>
		<property name = "password" value = "${prop.passWord}"/>
	</bean>
	<!-- JdbcTemplate 对象 -->
	<bean id = "jdbcTemplate" class = "org.springframework.jdbc.core.JdbcTemplate">
		<!-- 注入 dataSource -->
		<property name = "dataSource" ref = "dataSource"></property>
	</bean>
	<!-- 创建事务管理器 -->
	<bean id = "transactionManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 注入数据源 -->
		<property name = "dataSource" ref = "dataSource"></property>
	</bean>
</beans>

第一步 配置事务管理器

<?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:aop = "http://www.springframework.org/schema/aop"
	   xmlns:context = "http://www.springframework.org/schema/context"
	   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/aop
	   		http://www.springframework.org/schema/aop/spring-aop.xsd
	   		http://www.springframework.org/schema/context
	   		http://www.springframework.org/schema/context/spring-context.xsd
	   		http://www.springframework.org/schema/tx
	   		http://www.springframework.org/schema/tx/spring-tx.xsd
	   	">
	<!-- 开启组件扫描 -->
	<context:component-scan base-package = "com.fairykunkun"></context:component-scan>
	<!-- 引入外部的属性文件 -->
	<context:property-placeholder location = "jdbc.properties"></context:property-placeholder>
	<!-- 配置连接池 -->
	<bean name = "dataSource" class = "org.apache.commons.dbcp2.BasicDataSource">
		<property name = "driverClassName" value = "${prop.driverClass}"/>
		<property name = "url" value = "${prop.url}"/>
		<property name = "username" value = "${prop.userName}"/>
		<property name = "password" value = "${prop.passWord}"/>
	</bean>
	<!-- JdbcTemplate 对象 -->
	<bean id = "jdbcTemplate" class = "org.springframework.jdbc.core.JdbcTemplate">
		<!-- 注入 dataSource -->
		<property name = "dataSource" ref = "dataSource"></property>
	</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 = "accountMoney" propagation="REQUIRED"/>
			<!-- <tx:method name = "account*"/> -->
		</tx:attributes>
	</tx:advice>
</beans>

第二步 配置通知

第三步 配置切入点和切面

<?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:aop = "http://www.springframework.org/schema/aop"
	   xmlns:context = "http://www.springframework.org/schema/context"
	   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/aop
	   		http://www.springframework.org/schema/aop/spring-aop.xsd
	   		http://www.springframework.org/schema/context
	   		http://www.springframework.org/schema/context/spring-context.xsd
	   		http://www.springframework.org/schema/tx
	   		http://www.springframework.org/schema/tx/spring-tx.xsd
	   	">
	<!-- 开启组件扫描 -->
	<context:component-scan base-package = "com.fairykunkun"></context:component-scan>
	<!-- 引入外部的属性文件 -->
	<context:property-placeholder location = "jdbc.properties"></context:property-placeholder>
	<!-- 配置连接池 -->
	<bean name = "dataSource" class = "org.apache.commons.dbcp2.BasicDataSource">
		<property name = "driverClassName" value = "${prop.driverClass}"/>
		<property name = "url" value = "${prop.url}"/>
		<property name = "username" value = "${prop.userName}"/>
		<property name = "password" value = "${prop.passWord}"/>
	</bean>
	<!-- JdbcTemplate 对象 -->
	<bean id = "jdbcTemplate" class = "org.springframework.jdbc.core.JdbcTemplate">
		<!-- 注入 dataSource -->
		<property name = "dataSource" ref = "dataSource"></property>
	</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 = "accountMoney" propagation = "REQUIRED"/>
			<!-- <tx:method name = "account*"/> -->
		</tx:attributes>
	</tx:advice>
	<!-- 配置切入点和切面 -->
	<aop:config>
		<!-- 配置切入点 -->
		<aop:pointcut id = "pt" expression = "execution(* com.fairykunkun.service.UserService.*(..))"/>
		<!-- 配置切面 -->
		<aop:advisor advice-ref = "txadvice" pointcut-ref = "pt"></aop:advisor>
	</aop:config>
</beans>

完全注解声明式事务管理

配置类

package com.fairykunkun.config;
import com.alibaba.druid.pool.DruidDataSource;
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.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration // 配置类
@ComponentScan ( basePackages = "com.fairykunkun" ) // 组件扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {
	// 创建数据库连接池
	@Bean
	public DruidDataSource getDruidDataSource ( ) {
		DruidDataSource dataSource = new DruidDataSource ( );
		dataSource.setDriverClassName ( "com.mysql.jdbc.Driver" );
		dataSource.setUrl ( "jdbc:mysql://192.168.56.10:3306/sys" );
		dataSource.setUsername ( "root" );
		dataSource.setPassword ( "root" );
		return dataSource;
	}
	// 创建 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;
	}
}

测试类

package com.fairykunkun;
import com.fairykunkun.config.TxConfig;
import com.fairykunkun.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestTxConfig {
	@Test
	public void test ( ) {
		ApplicationContext context =
				new AnnotationConfigApplicationContext ( TxConfig.class );
		UserService userService = context.getBean ( "userService" , UserService.class );
		userService.accountMoney ( );
	}
}