文章目录
- 事务概念
- 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 ( );
}
}