事务控制

事务

简介

当我们的事务涉及到多个表,或者多个sql语句时,需要我们进行加入到一个事务中数据库事务 | 偷掉月亮 (moonshuo.cn)

在哪里添加事务???

我们仔细观察在我们的三层结构dao,service与controller层中,dao控制单个语句的执行,但是service是对象完成的操作的地方,比如添加学生,查询表格最终都是service集成了dao的一个或者多个sql,事务控制应该在service进行

两种控制事务的方式:

  1. jdbc:connection.commit(),connection.rollback()
  2. mybaits:sqlSession.commit,sqlSession.rollback

事务的处理方式有什么不足:

  1. 不同的数据库访问技术,处理事务的对象,方法不同
  2. 需要掌握数据库事务的处理逻辑,什么时间提交等等

解决方法:

​ spring提供了处理事务的统一模型 ,可以完成mybaits等等事务处理机制

(来自动力节点)

image-20220507171225464

事务内部提交,回滚事务,使用的事务管理器对象,代替你完成commit,rollback
事务管理器是一个接口和他的众多实现类。
接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback
实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
mybatis访问数据库—spring创建好的是DataSourceTransactionManager
hibernate访问数据库—-spring创建的是HibernateTransactionManager

方法

我们不需要进行代码编写,但是需要告诉spring使用哪一种技术

1.需要声明事务管理器对象

2.开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务。
spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。
spring给业务方法加入事务:
在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知

1
2
3
4
5
6
7
8
9
10
11
@Around("你要增加的事务功能的业务方法名称")
Object myAround(){
开启事务,spring给你开启
try{
buy(1001,10);
spring的事务管理器.commit();
}catch(Exception e){
spring的事务管理器.rollback();
}

}

3.在你的方法的上面加入@Trancational

其他

你的业务方法需要什么样的事务,说明需要事务的类型。
说明方法需要的事务:
1)事务的隔离级别:有4个值。
DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
➢ SERIALIZABLE:串行化。不存在并发问题。

  1. 事务的超时时间: 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。
    单位是秒, 整数值, 默认是 -1.

3)事务的传播行为 : 控制业务方法是不是有事务的, 是什么样的事务的。
7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的。

1
2
3
4
5
6
7
8
9
10
11
//指定的方法必须在事务内执行,若当前存在事务,就加入到当前事务中,若当前没有事务,则创建一个新的事务,是spring默认的事务传播行为,比如方法1调用方法2,而方法1本身已经声明事务,而方法2被 PROPAGATION_REQUIRED修饰,那么方法2与方法1同属于一个事务,如果方法1不是事务,那么方法2会自动创建一个事务
PROPAGATION_REQUIRED
//有事务的时候,新建事务,将原来的挂起,没有事务,也创建事务
PROPAGATION_REQUIRES_NEW
//如果方法被设置成PROPAGATION_SUPPORTS,那么该方法拥有事务也可以,不拥有事务也可以执行,查询操作有无事务皆可
PROPAGATION_SUPPORTS
//几乎不会使用
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED

当我们的业务方法执行成功,没有异常抛出,spring在方法执行之后提交事务,调用commit方法,而当出现异常的时候,spring调用回滚方法

示例演示

代码

商品表

image-20220507194334479

销售记录表

image-20220507194403669

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class BuyServiceImpl implements BuyService {
GoodsDAO goodsDAO;
SaleDAO saleDAO;
@Override
public void buyGood() {
//我首先购买了一部手机所以要获得手机这个对象
Goods goods=goodsDAO.getGood("手机");
//sale中加入这个
saleDAO.addSale(goods);
//减少数量
goodsDAO.reduceGoodNum(goods);
}

public void setGoodsDAO(GoodsDAO goodsDAO) {
this.goodsDAO = goodsDAO;
}

public void setSaleDAO(SaleDAO saleDAO) {
this.saleDAO = saleDAO;
}
}

我们正常测试会没有发生异常,但是加入我们人为的加入异常措施,我们会发现出现了问题

image-20220507204906248

解决

@Transactional

适合中小项目使用的, 注解方案。
spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务。@Transactional注解是spring框架自己注解,放在public方法的上面,表示当前方法具有事务。可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等

propageation:设置事务的传播属性

isolcation:设置事务的隔离级别

readOnly:设置该方法对数据库的操作是否只读

rollbackFor表示需要回滚的异常类

等等

使用步骤

  • 声明事务管理器
1
2
3
4
5
6
7
<!--启用事务管理器的事务处理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定对哪一个事务进行管理-->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!--开启事务注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
  • 方法上面加注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,readOnly = false,rollbackFor = {

ArithmeticException.class})
//其实上面的赋值大都是默认的,而且默认运行异常就会回滚事务,所以只要使用@Transactional也可以,但是此注解只会对public起作用
public void buyGood() {
//我首先购买了一部手机所以要获得手机这个对象
Goods goods=goodsDAO.getGood("手机");
saleDAO.addSale(goods);
System.out.println(10/0);
goodsDAO.reduceGoodNum(goods);
}

public void setGoodsDAO(GoodsDAO goodsDAO) {
this.goodsDAO = goodsDAO;
}

public void setSaleDAO(SaleDAO saleDAO) {
this.saleDAO = saleDAO;
}
}

此时就不会发生以上的错误了

Aspectj

适合大型项目,拥有很多的类方法需要大量的配置,使得业务方法和事务配置完全分离

加入依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

声明事务管理器,方法需要的事务属性

1
2
3
4
5
6
7
8
9
10
11
12
<!--启用事务管理器的事务处理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定对哪一个事务进行管理-->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!--声明业务方法事务属性,id表示这一段的配置内容-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--<tx:attributes>配置事务属性,name配置方法名称,当然也可以使用通配符,也可以在这里配置隔离级别等信息-->
<tx:attributes>
<tx:method name="buyGood"/>
</tx:attributes>
</tx:advice>

配置aop,指定那些类需要创建代理

1
2
3
4
5
6
7
<!--配置aop,表示那些类需要应用事务-->
<aop:config>
<!--execution(* *..service..*.*(..)),表示所有返回值 ,所有包和子包的service中的子包的所有类和所有方法-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!--配置增强器,,关联事务的配置属性-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>

这样程序的事务也完成了