编程式事务与声明式事务的理解
补充:什么是事务?
事务是一个重要概念,尤其在数据库管理系统中。事务是指一组操作。,这些操作要么全部成功执行,要么全部不执行,确保数据的一致性和完整性
编程式事务
编程式事务是指手动编写程序来管理事务,即通过编写代码的方式直接控制事务的提交和回滚。在 Java 中,通常使用事务管理器(如 Spring 中的 `PlatformTransactionManager`)来实现编程式事务。
编程式事务的主要优点是灵活性高,可以按照自己的需求来控制事务的粒度、模式等等。但是,编写大量的事务控制代码容易出现问题,对代码的可读性和可维护性有一定影响 。
声明式事务(必须要有声明式事务的框架----------sprint-tx)
声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。
开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免我们直接进行事务操作!
使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性。
程序员只需要在配置文件即可(注解/xml)
指定那些方法需要添加事务以及事务的属性即可
区别:
- 编程式事务需要手动编写代码来管理事务
- 而声明式事务可以通过配置文件或注解来控制事务。
事务管理器
spring的事务管理会帮我们提供一个增强类(事务增强)有三个方法(开启事务(前置),提交事务(返回),事务回滚(异常));
但是持久层的框架有很多(不同框架事务的操作不同);所以提供了一个接口(事务管理器-----------具体提供事务方法的);在事务增强中通过 接口.方法()--------实现对应方法
spring提供了多种实现类(对应不同的数据库):
DataSoureceTransactionManager重写三个方法(jdbc,jdbcTampalte,myBatis框架)
我们使用的是那种数据库,就将对应的实现配置到ioc容器中;
然后在事务增强类上使用@Autowired(将实现类注入到增强类组件)
事务管理器
1. Spring声明式事务对应依赖
- spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
- spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager
- spring-orm: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等
2. Spring声明式事务对应事务管理器接口
我们现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 JDBC方式、JdbcTemplate方式、Mybatis方式的事务实现!
DataSourceTransactionManager类中的主要方法:
- doBegin():开启事务
- doSuspend():挂起事务
- doResume():恢复挂起的事务
- doCommit():提交事务
- doRollback():回滚事务
基本实现
什么时候需要开启事务:
开启事务通常用于确保一组操作的原子性,一致性,隔离性,持久性
①多个操作需要原子性
当一组操作需要全部成功或者失败时
②数据一致性要求高
当多个操作需要保持数据的一致性时,事务可以确保在操作过程中数据不会处于不一致的状态
③并发控制
当多个用户或进程同时访问或修改同一数据时,事务可以通过 隔离级别来控制并访问
④错误恢复
当操作过程中可能发生错误时,事务可以确保在错误发生时回滚到操作前的状态,避免数据破损
⑥审计和日志记录
步骤:
1 选择对应的事务管理器实现加入到ioc容器
spring声明式事务给我们提供了各种管理器的实现
需要那种,就加入到ioc容器即可(在配置类中做此操作-------------类似于将第三方组件加入到ioc)
2 只需要使用注解指定那些方法需要添加事务即可---------------声明式事务
3要在配置类开启事务注解的支持(@EnableTransactionManagement)
* 给方法添加事务的操作
* 1 选定我们需要的事务管理器的实现类,在配置类中创建该类的对象,使用注解的方法将该类添加到ioc
* 注意:该组件需要引入连接池的组件
* 2 在配置类上加上 @EnableTransactionManagement 表示开启事务注解
* 3 在我们想要添加事务的方法上加上 @Transactional那么该方法就有事务了
*添加事务:
* @Transactional
* 位置:方法/类上
* 方法:当前方法有事务
* 类上:类下所有方法都有事务
代码举例:
配置类
@Configuration
@ComponentScan("com.atguigu")
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement//开启事务注解的支持
public class JavaConfig {@Value("${atguigu.url}")private String url;@Value("${atguigu.driver}")private String driver;@Value("${atguigu.username}")private String username;@Value("${atguigu.password}")private String password;@Beanpublic DruidDataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl(url);dataSource.setDriverClassName(driver);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DruidDataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}@Beanpublic TransactionManager transactionManager(DruidDataSource dataSource){/*内部要进行事务操作,是基于连接池的所以要将连接池给它*/DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}
}
Service类(要添加事务的方法在其中)
@Service
public class StudentService {@Autowiredprivate StudentDao studentDao;/***添加事务:* @Transactional* 位置:方法/类上* 方法:当前方法有事务* 类上:类下所有方法都有事务*/@Transactionalpublic void changeInfo(){studentDao.updateAgeById(88,1);int i=1/0;System.out.println("-----------");studentDao.updateNameById("test2",1);}
}
测试代码:
@SpringJUnitConfig(JavaConfig.class)//指定那个是配置类
public class SpringTxTest {
@Autowired
private StudentService studentService;
@Test
public void test(){
studentService.changeInfo();
}
}
事务的几个属性设置
只读模式
* 只读模式
* 只读模式可以提升查询事务的效率!推荐事务中只有查询代码是,使用只读模式
* 默认: boolean readOnly() default false;
* 解释: 一般情况下,都是通过类添加注解添加事务!
* 类下所有方法全部都有事务!
* 查询方法可以通过再次添加注解,设置只读模式提高效率!
1. 只读介绍(效率会提高,但是不允许做修改)
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
2 设置方式
// readOnly = true把当前事务设置为只读 默认是false!
@Transactional(readOnly = true)
事务超时时间设置
1. 需求
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。
概括来说就是一句话:超时回滚,释放资源。
2 设置:
/**
* timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!
*/
@Transactional(readOnly = false,timeout = 3)
* 超时时间
* 默认:永不超时 -1
* 设置 timeout = 时间 (秒数)
* 超过时间就会回滚事务和释放异常
* 如果类上设置事务属性,设置了超时时间,而方法上有设置了事务属性,没有设置超时时间,那么时间限制会不会生效
* 不会;因为:方法上的注解会将类上的注解覆盖
事务异常指定问题
* 指定异常回滚和指定异常不回滚
* 默认情况下,指定发生运行时异常事务才会回滚!
* 我们可以指定Exception异常来控制所有异常都回滚!
* @Transactional(rollbackFor = Exception.class)
* noRollbackFor=回滚异常范围内,控制某个异常不回滚。
代码举例
在service类中
@Transactional(rollbackFor = Exception.class,noRollbackFor = FileAlreadyExistsException.class)
public void changeInfo(){
studentDao.updateAgeById(88,1);
int i=1/0;
System.out.println("-----------");
studentDao.updateNameById("test2",1);
}
事务隔离级别
数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括:
1. 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
2. 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
3. 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。------------------------------默认是可重复读(mysql中)
4. 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
不同的隔离级别适用于不同的场景,需要根据实际业务需求进行选择和调整。
* 隔离级别设置
* 推荐设置第二个隔离级别!
* isolation = Isolation.READ_COMMITTED(读已提交)
在service类中
@Transactional(readOnly = true,isolation = Isolation.READ_COMMITTED)
public void getStudentInfo(){
//查询没有必要添加事务
//获取学生信息 查询数据库 不修改
}
事务传播行为
在执行业务方法1时(事务1)要执行业务方法2(事务2)
那么:方法2的事务是否会加入方法1的事务呢?
这取决于事务传播的指定行为:事务之间的调用如何影响子事务
事务传播行为属性设置到子事务上
代码举例:
@Transactional
public void MethodA(){
// ...
MethodB();
// ...
}
//在被调用的子方法中设置传播行为,代表如何处理调用的事务! 是加入,还是新事务等!
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void MethodB(){
// ...
}
1. propagation属性
@Transactional 注解通过 propagation 属性设置事务的传播行为。它的默认值是:
Propagation propagation() default Propagation.REQUIRED;
propagation 属性的可选值由 org.springframework.transaction.annotation.Propagation 枚举类提供:
* 声明两个独立修改数据库的事务业务方法
* propagation = Propagation.REQUIRED 父方法有事务,我们就加入到父方法的事务!
* 最终是同一个事务!(推荐使用默认值)
* propagation = Propagation.REQUIRES_NEW
* 不管父方法中是否有事务,我都是独立的事务!
* 两个事务或者三个事务!
*/
代码举例
在TopService中(将两个事务合成了一个事务)
@Autowired
private StudentService studentService;
@Transactional
public void topService(){
studentService.changeAge();
studentService.changeName();
}
在SudentService中
@Transactional(propagation = Propagation.REQUIRED)
public void changeAge(){
studentDao.updateAgeById(998,1);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void changeName(){-
studentDao.updateNameById("二狗子",1);
}
**注意:**
在同一个类中,对于@Transactional注解的方法调用,事务传播行为不会生效。这是因为Spring框架中使用代理模式实现了事务机制,在同一个类中的方法调用并不经过代理,而是通过对象的方法调用,因此@Transactional注解的设置不会被代理捕获,也就不会产生任何事务传播行为的效果。