文章目录
事务作用:在数据层保障一系列的数据库操作同成功同失败
Spring事务的作用:在数据层或业务层保障一系列数据库操作的同成功同失败
1、编程式事务
事务功能的相关操作全部通过自己编写代码来实现:
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
编程式的实现方式存在缺陷:
- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
2、声明式事务
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。
封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。
- 好处1:提高开发效率
- 好处2:消除了冗余的代码
- 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化
所以,我们可以总结下面两个概念:
- 编程式:自己写代码实现功能
- 声明式:通过配置让框架实现功能
3、事务管理器
①顶级接口
[1]Spring 5.2以前
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
[2]从 Spring 5.2开始
PlatformTransactionManager 接口本身没有变化,它继承了 TransactionManager
public interface TransactionManager {
}
TransactionManager接口中什么都没有,但是它还是有存在的意义——定义一个技术体系。
②技术体系
我们现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 Mybatis 用的也是这个类。
DataSourceTransactionManager类中的主要方法:
- doBegin():开启事务
- doSuspend():挂起事务
- doResume():恢复挂起的事务
- doCommit():提交事务
- doRollback():回滚事务
模拟银行账户间转账业务
1.配置当前接口方法具有事务
@Transactional
public void transfer(String out,String in ,Double money) ;
}
2.配置事务管理器和开启注解式事务驱动
①使用全注解
在配置类中添加注解
@EnableTransactionManagement
使用形参注入dataSource
@Bean
public DataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://a.b.c.com:3306/dbname?useUnicode=true&characterEncoding=UTF-8");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
②使用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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<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>
jdbc.properties
jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/student?useUnicode=true&characterEncoding=utf8
jdbc.driver=com.mysql.jdbc.Driver
Spring事务角色
事务管理员:发起事务方,在spring中常指代业务层开启事务的方法
事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
属性 | 作用 | 示例 |
---|---|---|
readOnly | 设置是否为只读事务 | readOnly=true(只读事务) |
timeout | 设置事务超出时间 | timeout=-1(永不超时) |
rollbackFor | 设置事务回滚异常(Class) | roolbackFor={NullPointException.class} |
rollbackForClassName | 设置事务回滚异常(String) | 同上 |
noRollbackFor | 设置事务不回滚异常(Class) | noRollBackFor={NullPointException.class} |
noRollbackForClassName | 设置事务不回滚异常(String) | 同上格式为字符串 |
propagation | 设置事务传播行为 | propagation=Propagation.REQUIRES_NEW |
1、只读事务
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
在@Transactional注解里面设置 readOnly=true即可
@Transactional(readOnly = true)
注:只读属性不能加载增删改方法上
2、超时
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。
概括来说就是一句话:超时回滚,释放资源。
在需要添加事务的方法上设置注解:
@Transactional(timeout =10)//10秒
3、回滚和不回滚的异常
默认只针对运行时异常回滚,编译时异常不回滚
设置回滚的异常
- rollbackFor属性:需要设置一个Class类型的对象
- rollbackForClassName属性:需要设置一个字符串类型的全类名
@Transactional(rollbackFor = Exception.class)
设置不回滚的异常
在默认设置和已有设置的基础上,再指定一个异常类型,碰到它不回滚。
@Transactional(
noRollbackFor = FileNotFoundException.class
)
事务的隔离级别
在 @Transactional 注解中使用 isolation 属性设置事务的隔离级别。 取值使用 org.springframework.transaction.annotation.Isolation 枚举类提供的数值。
①读未提交
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
现象:A修改表中id=1的price=100(原来是200),但是还未commit
B查询id=1的price为100
就出现了脏读
②读已提交
@Transactional(isolation = Isolation.READ_COMMITTED)
现象:A修改表中id=1的price=100(原来是200),但是还未commit
B查询id=1的price为200
③可重复读
@Transactional(isolation = Isolation.REPEATABLE_READ)
现象:A查询表中id为1的price为200,B修改表中id=1的price为100,
A又查询表中id为1的price还是200
④串行化
@Transactional(isolation = Isolation.SERIALIZABLE)
现象:A更新表中id为1的price为100,还未commit
B查询表中id为1的price,此时B查询堵塞,等待A中的更新操作提交后,B才可查询
事务的传播行为
基于XML的声明式事务
1、加入依赖
相比于基于注解的声明式事务,基于 XML 的声明式事务需要一个额外的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
2、迁移代码
将上一个基于注解的 module 中的代码转移到新module。去掉 @Transactional 注解。
3、修改 Spring 配置文件
去掉 tx:annotation-driven 标签,然后加入下面的配置:
<aop:config>
<!-- 配置切入点表达式,将事务功能定位到具体方法上 -->
<aop:pointcut id="txPoincut" expression="execution(* *..*Service.*(..))"/>
<!-- 将事务通知和切入点表达式关联起来 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoincut"/>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method标签:配置具体的事务方法 -->
<!-- name属性:指定方法名,可以使用星号代表多个字符 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<!-- read-only属性:设置只读属性 -->
<!-- rollback-for属性:设置回滚的异常 -->
<!-- no-rollback-for属性:设置不回滚的异常 -->
<!-- isolation属性:设置事务的隔离级别 -->
<!-- timeout属性:设置事务的超时属性 -->
<!-- propagation属性:设置事务的传播行为 -->
<tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>
4、注意
即使需要事务功能的目标方法已经被切入点表达式涵盖到了,但是如果没有给它配置事务属性,那么这个方法就还是没有事务。所以事务属性必须配置。