目录
一、事务的种类
二、Spring事务管理器
三、事务注解使用
四、事务注解属性
一、事务的种类
1.编程式事务
所谓编程式事务就是用代码手写事务,包含了事务的开始,具体事务,事务的提交和事务的回滚。在这期间就会产生一些冗余问题,比如事务的开始,事务的提交,事务的回滚。都是一些重复的代码,如果在事务很多的情况下,代码量会比较庞大,并且十分臃肿。
传统的编程式事务
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 业务代码
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
2.声明式事务
声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。
开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免我们直接进行事务操作!
优点:
使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性。
区别:
- 编程式事务需要手动编写代码来管理事务
- 而声明式事务可以通过配置文件或注解来控制事务。
二、Spring事务管理器
2.1事务管理器介绍
使用SpringTx时候由于持久层框架的不同(如JDBC、JDBCTemplate、Mybatis等)使得Spring的事务管理接口有多个对应的实现类分别如下
- spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
- spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager
- spring-orm: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等
由于一般使用MyBatis,JDBC,JDBCTemplate都是使用DataSourceTransactionManager类。DataSourceTransactionManager的常用方法如下
方法名 | 描述 |
---|---|
doBegin() | 开启一个新的事务。 |
doSuspend() | 用于挂起当前事务上下文,并且暂停现有的事务。 |
doResume() | 恢复挂起的事务上下文,并继续已有的事务。 |
doCommit() | 提交当前事务。 |
doRollback() | 回滚当前事务。 |
2.2事务管理器配置
需要先导入对应的依赖
<!-- 声明式事务依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>6.0.6</version>
</dependency>
在配置类中的具体配置
@Configuration
@ComponentScan("com.alphamilk")
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement
public class JavaConfig {
@Value("${alphamilk.url}")
private String url;
@Value("${alphamilk.driver}")
private String driver;
@Value("${alphamilk.username}")
private String username;
@Value("${alphamilk.password}")
private String password;
@Bean
//druid连接池
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
//JDBCTemplate
public JdbcTemplate jdbcTemplate( ){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource());
return jdbcTemplate;
}
// 设置TransactionManger事务管理接口
@Bean
public TransactionManager transactionManager(DataSource dataSource){
// 创建DataSourceTransaction对象
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
// 配置对应的数据源
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
三、事务注解使用
使用的注解
注解 | 描述 |
---|---|
@EnableTransactionManagement | 启用事务管理。需要在配置类上添加该注解。 |
@Transactional | 标记需要事务管理的方法或类。可以在方法级别或类级别使用 |
使用的范围:类或是方法
当作用在方法上时候,其对应的仅仅是对某一个具体的事务声明。
案例代码:
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
@Transactional
public void changeInfo(){
studentDao.updateAgeById(20,1);
// 设置异常,如果发生异常,则事务会自动回滚,即上面代码虽然执行,但是不会提交
int i = 1/0;
System.out.println("-----------");
studentDao.updateNameById("test2",1);
}
}
当作用在类上的时候,对类上所有的方法进行事务声明
案例代码:
@Service
@Transactional
public class StudentService {
@Autowired
private StudentDao studentDao;
public void changeInfo(){
studentDao.updateAgeById(20,1);
System.out.println("-----------");
studentDao.updateNameById("test2",1);
}
}
注意:在使用事务注解的时候,配置种记得需要设置开启事务声明的注解,并且注意@Transactional注解同时作用于类和方法上时候,作用在类上的注解会将类上的注解覆盖。即不享受类上事务声明的效果
@Configuration
@ComponentScan("com.alphamilk")
@PropertySource("classpath:jdbc.properties")
//开启事务声明注解
@EnableTransactionManagement
四、事务注解属性
事务注解中有多种属性,分别是只读模式,超出时间,,,。并且每一个属性都有其相应的异常
1.只读模式/设置
属性名称:readOnly (默认为false)
属性解释
当处于readOnly开启状态的时候,只能进行读取信息的操作,无法执行修改的操作,这么做可以提高事务的效率。
案例代码:
@Transactional(readOnly = true)
public void changeInfo(){
studentDao.updateAgeById(20,1);
System.out.println("-----------");
studentDao.updateNameById("test2",1);
}
对应的事务异常(如果在只读状态下进行了非只读操作会引发下面的异常)
2.超时时间并回滚
属性名称:timeout
属性解释,在注解后同各国timeout = value实现如果超过value秒则会触发对应的异常。注意默认-1,表示无限长的限制时间。
案例代码:
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
@Transactional(timeout = 3)
public void changeInfo(){
//非只读方法
studentDao.updateAgeById(20,1);
System.out.println("-----------");
//设置事务时间超过最长限制时间
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
studentDao.updateNameById("test2",1);
}
}
对应的事务异常(如果在超过事务的处理时间时候会引发下列异常)
3.指定事务异常
我们知道Exception异常有两个子类 分别是RuntimeException与IOException,而@Transaction异常在默认情况下只能够捕捉Runtime异常时候才会进行事务回滚,而如果发生了IOException异常则不会进行事务回滚
属性名称:rollbackFor (指定异常回滚)noRollBackFor(在指定异常范围内不进行回滚,基本不会使用)
案例代码:
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
// 指定异常为Exception,即包含其所有子异常
@Transactional(rollbackFor = Exception.class)
public void changeInfo(){
studentDao.updateAgeById(20,1);
System.out.println("-----------");
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
studentDao.updateNameById("test2",1);
}
}
4.事务的隔离级别
事务可能会对数据进行修改,那么就会引发许多数据的问题,比如,脏读,幻读,不可重复读等问题。具体相关描述如下
-
脏读(Dirty Read): 脏读是指一个事务读取了另一个事务未提交的数据。假设有两个事务,事务A进行了修改但还未提交,事务B读取了事务A的修改后的数据,然后事务A回滚了。此时,事务B读取到的数据是无效或不一致的,这就是脏读。
-
幻读(Phantom Read): 幻读是指一个事务在同一个查询中多次进行读取,但是得到的结果集却不一致。假设事务A首先执行一个查询,返回了一些行,然后事务B插入了一些符合事务A查询条件的新行,接着事务A再次执行同样的查询,但结果集却发生了变化。这种情况下,事务A就发生了幻读,因为之前查询的结果集已经发生了变化。
-
不可重复读(Non-repeatable Read): 不可重复读是指在同一个事务中,多次读取同一条数据,但是得到的结果却不一致。假设事务A首先读取一条数据,然后事务B修改了该数据并提交,接着事务A再次读取同一条数据,发现结果与之前不同。这种情况下,事务A就遇到了不可重复读,因为同一条数据在事务执行期间发生了更改。
对此事务就有对应的隔离级别来避免相关问题的发生,但是注意,隔离级别越高,那么对应程序的性能将会越低。具体隔离级别如下
- 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
- 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
- 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
- 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。 不同的隔离级别适用于不同的场景,需要根据实际业务需求进行选择和调整。
事务属性名称:isolation
isolation的四个设置如下
隔离级别 | 描述 |
---|---|
READ UNCOMMITTED (读未提交) | 最低级别的隔离级别。它允许事务读取其他未提交事务所做的修改。可能出现脏读、幻读以及不可重复读。 |
READ COMMITTED (读已提交) | 事务只能读取已经提交的数据,不会读取未提交的数据。避免了脏读,但仍然可能出现幻读和不可重复读问题。 |
REPEATABLE READ (可重复读) | 事务在开始时读取一致的快照,并将该快照用于整个事务过程。确保了事务期间其他事务的插入操作不会影响到当前事务,但仍然可能出现幻读问题。 |
SERIALIZABLE (串行化) | 最高级别的隔离级别,保证事务串行执行,避免了脏读、幻读和不可重复读。所有并发事务按照顺序执行,但可能会牺牲一些并发性能。 |
注意:不同的数据库的默认事务隔离级别都是不同的,比如mysql就是第三级别。推荐设置第二级别即读已提交。
使用案例代码:
public class StudentService {
@Autowired
private StudentDao studentDao;
//设置为读已提交隔离级别
@Transactional(rollbackFor = Exception.class,
isolation = Isolation.READ_COMMITTED)
public void changeInfo(){
studentDao.updateAgeById(20,1);
System.out.println("-----------");
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
studentDao.updateNameById("test2",1);
}
}
5. 事务的传播性
所谓的事务传播,即一个事务中调用了另一个事务,而对另一个事务是否加入原来事务还是独立出来单独的事务需要进行区分,则引入了事务的传播性来定义
属性:propagation
两个常用可选值:
传播属性 | 描述 |
---|---|
REQUIRED (默认值) | 如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务。这是大多数情况下的推荐选择,确保所有操作都在一个事务中执行。 |
REQUIRES_NEW | 创建一个新的事务,并挂起当前事务(如果存在)。新的事务独立于任何其他现有事务执行,并始终在自己的事务中操作。适用于需要完全独立的事务执行的场景。 |
案例代码:
// 设置为New 意味着算是一个独立的事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void changeName(){
studentDao.updateNameById("黄小浓",2);
}
@Transactional(propagation = Propagation.REQUIRED)
public void changeAge(){
studentDao.updateAgeById(55,2);
}
这里具体解释一下是否独立意味着什么,如果独立了,那么如果在合并的事务中发生了异常,则事务将不会进行回滚,而在默认的Require的情况下,会先判断是否有其对应的父事务,如果没有才会进行创建事务。
本章总结
一、事务的种类
1.认识编程式事务与声明式事务
二、Spring事务管理器
1.认识几种常见的事务管理器
2.熟悉并且会配置DataSourceTransactionManger事务管理器
三、事务注解使用
1.熟悉@EnableTransactionManger、@Transaction注解
2.熟悉事务注解的范围
四、事务属性
1.熟悉事务的多种属性,比如只读(readonly)、超时时间(timeout)、指定异常、事务隔离级别、事务的传播