赞
踩
目录
编程式事务是指手动编写程序来管理事务,即通过编写代码的方式直接控制事务的提交和回滚。在 Java 中,通常使用事务管理器(如 Spring 中的 `PlatformTransactionManager`)来实现编程式事务。
编程式事务的主要优点是灵活性高,可以按照自己的需求来控制事务的粒度、模式等等。但是,编写大量的事务控制代码容易出现问题,对代码的可读性和可维护性有一定影响。
- Connection conn = ...;
-
- try {
- // 开启事务:关闭事务的自动提交
- conn.setAutoCommit(false);
- // 核心操作
- // 业务代码
- // 提交事务
- conn.commit();
-
- }catch(Exception e){
-
- // 回滚事务
- conn.rollBack();
-
- }finally{
-
- // 释放数据库连接
- conn.close();
-
- }
编程式的实现方式存在缺陷:
- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。
开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免我们直接进行事务操作!
使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性。
- 编程式事务需要手动编写代码来管理事务
- 而声明式事务可以通过配置文件或注解来控制事务。
我们在学习Spring事务的过程中就需要用到Spring事务管理器。
- spring-tx: 包含声明式事务实现的基本规范(事务管理器规范接口和事务增强等等)
- spring-jdbc: 包含DataSource方式事务管理器实现类DataSourceTransactionManager
- spring-orm: 包含其他持久层框架的事务管理器实现类例如:Hibernate/Jpa等
![](image/image_s2BCX_Qltm.png)
我们现在要使用的事务管理器是org.springframework.jdbc.datasource.DataSourceTransactionManager,将来整合 JDBC方式、JdbcTemplate方式、Mybatis方式的事务实现!
DataSourceTransactionManager类中的主要方法:
- doBegin():开启事务
- doSuspend():挂起事务
- doResume():恢复挂起的事务
- doCommit():提交事务
- doRollback():回滚事务
pom依赖
- <dependencies>
- <!--spring context依赖-->
- <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>6.0.6</version>
- </dependency>
-
- <!--junit5测试-->
- <dependency>
- <groupId>org.junit.jupiter</groupId>
- <artifactId>junit-jupiter-api</artifactId>
- <version>5.3.1</version>
- </dependency>
-
-
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-test</artifactId>
- <version>6.0.6</version>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>jakarta.annotation</groupId>
- <artifactId>jakarta.annotation-api</artifactId>
- <version>2.1.1</version>
- </dependency>
-
- <!-- 数据库驱动 和 连接池-->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>8.0.25</version>
- </dependency>
-
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>druid</artifactId>
- <version>1.2.8</version>
- </dependency>
-
- <!-- spring-jdbc -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-jdbc</artifactId>
- <version>6.0.6</version>
- </dependency>
-
- <!-- 声明式事务依赖-->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-tx</artifactId>
- <version>6.0.6</version>
- </dependency>
-
-
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-aop</artifactId>
- <version>6.0.6</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-aspects</artifactId>
- <version>6.0.6</version>
- </dependency>
- </dependencies>
配置文件jdbc.properties
- gedeshidai.driver=com.mysql.jdbc.Driver
- gedeshidai.url=jdbc:mysql://localhost:3306/studb?serverTimezone=UTC&characterEncoding=utf8
- gedeshidai.username=root
- gedeshidai.password=root
Spring配置文件
- @Configuration
- @ComponentScan("com.gedeshidai")
- @PropertySource("classpath:jdbc.properties")
- public class JavaConfig {
-
- @Value("${gedeshidai.driver}")
- private String driver;
- @Value("${gedeshidai.url}")
- private String url;
- @Value("${gedeshidai.username}")
- private String username;
- @Value("${gedeshidai.password}")
- private String password;
-
-
-
- //druid连接池
- @Bean
- 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(DataSource dataSource){
- JdbcTemplate jdbcTemplate = new JdbcTemplate();
- jdbcTemplate.setDataSource(dataSource);
- return jdbcTemplate;
- }
-
- }
dao层
- @Repository
- public class StudentDao {
-
- @Autowired
- private JdbcTemplate jdbcTemplate;
-
- public void updateNameById(String name,Integer id){
- String sql = "update students set name = ? where id = ? ;";
- int rows = jdbcTemplate.update(sql, name, id);
- }
-
- public void updateAgeById(Integer age,Integer id){
- String sql = "update students set age = ? where id = ? ;";
- jdbcTemplate.update(sql,age,id);
- }
- }
service层
- @Service
- public class StudentService {
-
- @Autowired
- private StudentDao studentDao;
-
- public void changeInfo(){
- studentDao.updateAgeById(100,1);
- System.out.println("-----------");
- studentDao.updateNameById("test1",1);
- }
- }
测试环境搭建
- /**
- * projectName: com.gedeshidai.test
- *
- * description:
- */
- @SpringJUnitConfig(JavaConfig.class)
- public class TxTest {
-
- @Autowired
- private StudentService studentService;
-
- @Test
- public void testTx(){
- studentService.changeInfo();
- }
- }
数据库代码
- create database studb;
-
- use studb;
- CREATE TABLE students (
- id INT PRIMARY KEY,
- name VARCHAR(50) NOT NULL,
- gender VARCHAR(10) NOT NULL,
- age INT,
- class VARCHAR(50)
- );
- INSERT INTO students (id, name, gender, age, class)
- VALUES
- (1, '张三', '男', 20, '高中一班'),
- (2, '李四', '男', 19, '高中二班'),
- (3, '王五', '女', 18, '高中一班'),
- (4, '赵六', '女', 20, '高中三班'),
- (5, '刘七', '男', 19, '高中二班'),
- (6, '陈八', '女', 18, '高中一班'),
- (7, '杨九', '男', 20, '高中三班'),
- (8, '吴十', '男', 19, '高中二班');
错误解决
类文件具有错误的版本 61.0, 应为 52.0
请删除该文件或确保该文件位于正确的类路径子目录中。
当出现这种情况可以去检查自己jdk版本,博主的jdk1.8和这个冲突了,所以改成了jdk17或以上版本。
@Transactional
注释添加位置:方法/类上
方法:当前方法有事务
类上:类下所有的方法都有事务
在JavaConfig.java中,我们需要添加第三方的类进入到IOC容器
在提交事务的方法/类上添加注释:@Transactional,博主这里是在类上添加的。
当在类上添加注释后会,运行测试类会出现如下报错:
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
- // readOnly = true把当前事务设置为只读 默认是false!
-
- @Transactional(readOnly = true)
会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
如果一个类中每一个方法上都使用了 @Transactional 注解,那么就可以将 @Transactional 注解提取到类上。反过来说:@Transactional 注解在类级别标记,会影响到类中的每一个方法。同时,类级别标记的 @Transactional 注解中设置的事务属性也会延续影响到方法执行时的事务属性。除非在方法上又设置了 @Transactional 注解。
对一个方法来说,离它最近的 @Transactional 注解中的事务属性设置生效。
在类级别@Transactional注解中设置只读,这样类中所有的查询方法都不需要设置@Transactional注解了。因为对查询操作来说,其他属性通常不需要设置,所以使用公共设置即可。
然后在这个基础上,对增删改方法设置@Transactional注解 readOnly 属性为 false。
代码实现:
当我们添加已读,把报错那一行注释后,运行测试结果如下:
这里可能小伙伴会想,既然进行修改的方法加只读不会运行,那么我们只进行查询是不是就可以加只读模式了?
答案当然是:可以的!
但是,既然我们是查询了,也就没必要添加事务了呀!
所以已读模式一般都是添加到类上的,类下的所有方法都有事务,查询方法可以通过再次添加注解,设置为只读模式,提高效率。(@Transactional默认为非只读模式)
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。
概括来说就是一句话:超时回滚,释放资源。
如果类上设置事务属性,方法也设置了事务注解!那么方法会不会生效呢?
答案是:不会生效!!!
因为方法上的注解覆盖了类上的注解(直接在方法上设置注解是比在类上的优先级大的)
默认只针对运行时异常回滚,编译时异常不回滚。情景模拟代码如下:
- @Service
- public class StudentService {
-
- @Autowired
- private StudentDao studentDao;
-
- /**
- * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!
- * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!
- * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
- */
- @Transactional(readOnly = false,timeout = 3)
- public void changeInfo() throws FileNotFoundException {
- studentDao.updateAgeById(100,1);
- //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内!
- new FileInputStream("xxxx");
- studentDao.updateNameById("test1",1);
- }
- }
rollbackFor属性:指定哪些异常类才会回滚,默认是 RuntimeException and Error 异常方可回滚!
- /**
- * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!
- * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!
- * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
- */
- @Transactional(readOnly = false,timeout = 3,rollbackFor = Exception.class)
- public void changeInfo() throws FileNotFoundException {
- studentDao.updateAgeById(100,1);
- //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内!
- new FileInputStream("xxxx");
- studentDao.updateNameById("test1",1);
- }
在默认设置和已有设置的基础上,再指定一个异常类型,碰到它不回滚。
noRollbackFor属性:指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
- @Service
- public class StudentService {
-
- @Autowired
- private StudentDao studentDao;
-
- /**
- * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!
- * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!
- * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
- */
- @Transactional(readOnly = false,timeout = 3,rollbackFor = Exception.class,noRollbackFor = FileNotFoundException.class)
- public void changeInfo() throws FileNotFoundException {
- studentDao.updateAgeById(100,1);
- //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内!
- new FileInputStream("xxxx");
- studentDao.updateNameById("test1",1);
- }
- }
数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括:
1. 读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
2. 读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
3. 可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
4. 串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
不同的隔离级别适用于不同的场景,需要根据实际业务需求进行选择和调整。
- package com.gedeshidai.service;
-
- import com.gedeshidai.dao.StudentDao;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Isolation;
- import org.springframework.transaction.annotation.Transactional;
-
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
-
- /**
- * projectName: com.gedeshidai.service
- */
- @Service
- public class StudentService {
-
- @Autowired
- private StudentDao studentDao;
-
- /**
- * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!
- * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!
- * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
- * isolation = 设置事务的隔离级别,mysql默认是repeatable read!
- */
- @Transactional(readOnly = false,
- timeout = 3,
- rollbackFor = Exception.class,
- noRollbackFor = FileNotFoundException.class,
- isolation = Isolation.REPEATABLE_READ)
- public void changeInfo() throws FileNotFoundException {
- studentDao.updateAgeById(100,1);
- //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内!
- new FileInputStream("xxxx");
- studentDao.updateNameById("test1",1);
- }
- }
举例代码:
- @Transactional
- public void MethodA(){
- // ...
- MethodB();
- // ...
- }
-
- //在被调用的子方法中设置传播行为,代表如何处理调用的事务! 是加入,还是新事务等!
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void MethodB(){
- // ...
- }
@Transactional 注解通过 propagation 属性设置事务的传播行为。它的默认值是:
Propagation propagation() default Propagation.REQUIRED;
propagation 属性的可选值由 org.springframework.transaction.annotation.Propagation 枚举类提供:
| 名称 | 含义 |
| ----------------- | ------------------------- |
| REQUIRED 
默认值 | 如果父方法有事务,就加入,如果没有就新建自己独立! |
| REQUIRES\_NEW | 不管父方法是否有事务,我都新建事务,都是独立的! |
- @Service
- public class StudentService {
-
- @Autowired
- private StudentDao studentDao;
-
- /**
- * timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!
- * rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!
- * noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
- * isolation = 设置事务的隔离级别,mysql默认是repeatable read!
- */
- @Transactional(readOnly = false,
- timeout = 3,
- rollbackFor = Exception.class,
- noRollbackFor = FileNotFoundException.class,
- isolation = Isolation.REPEATABLE_READ)
- public void changeInfo() throws FileNotFoundException {
- studentDao.updateAgeById(100,1);
- //主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内!
- new FileInputStream("xxxx");
- studentDao.updateNameById("test1",1);
- }
-
-
- /**
- * 声明两个独立修改数据库的事务业务方法
- */
- @Transactional(propagation = Propagation.REQUIRED)
- public void changeAge(){
- studentDao.updateAgeById(99,1);
- }
-
- @Transactional(propagation = Propagation.REQUIRED)
- public void changeName(){
- studentDao.updateNameById("test2",1);
- int i = 1/0;
- }
-
- }
- @Service
- public class TopService {
-
- @Autowired
- private StudentService studentService;
-
- @Transactional
- public void topService(){
- studentService.changeAge();
- studentService.changeName();
- }
- }
- @SpringJUnitConfig(classes = AppConfig.class)
- public class TxTest {
-
- @Autowired
- private StudentService studentService;
-
- @Autowired
- private TopService topService;
-
- @Test
- public void testTx() throws FileNotFoundException {
- topService.topService();
- }
- }
在同一个类中,对于@Transactional注解的方法调用,事务传播行为不会生效。这是因为Spring框架中使用代理模式实现了事务机制,在同一个类中的方法调用并不经过代理,而是通过对象的方法调用,因此@Transactional注解的设置不会被代理捕获,也就不会产生任何事务传播行为的效果。
1. Propagation.REQUIRED:如果当前存在事务,则加入当前事务,否则创建一个新事务。
2. Propagation.REQUIRES\_NEW:创建一个新事务,并在新事务中执行。如果当前存在事务,则挂起当前事务,即使新事务抛出异常,也不会影响当前事务。
3. Propagation.NESTED:如果当前存在事务,则在该事务中嵌套一个新事务,如果没有事务,则与Propagation.REQUIRED一样。
4. Propagation.SUPPORTS:如果当前存在事务,则加入该事务,否则以非事务方式执行。
5. Propagation.NOT\_SUPPORTED:以非事务方式执行,如果当前存在事务,挂起该事务。
6. Propagation.MANDATORY:必须在一个已有的事务中执行,否则抛出异常。
7. Propagation.NEVER:必须在没有事务的情况下执行,否则抛出异常。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。