Spring篇(事务篇 - 基础介绍)
目录
一、JdbcTemplate(持久化技术)
1. 简介
2. 准备工作
2.1. 引入依赖坐标
2.2. 创建jdbc.properties
2.3. 配置Spring的配置文件
3. 测试
3.1. 在测试类装配 JdbcTemplate
3.2. 测试增删改功能
查询一条数据为实体类对象
查询多条数据为一个list集合
查询单行单列的值
二、SpringTransactional(事务管理)
1. 事务类型
1.1. 编程式事务
缺陷
1.2. 声明式事务
好处
1.3. 总结
2. 基于注解的声明式事务
2.1. 准备工作
加入依赖
创建jdbc.properties
配置Spring的配置文件
创建表
创建组件
2.2. 测试无事务情况
创建测试类
模拟场景
观察结果
2.3. 加入事务
添加事务配置
添加事务注解
观察结果
2.4. @Transactional注解标识的位置
2.5. 事务属性
1、只读:readOnly
介绍
使用方式
注意
2、超时:timeout
介绍
使用方式
观察结果
3、回滚策略:四种
介绍
使用方式
观察结果
4、事务隔离级别:Isolation
介绍
四种隔离级别
使用方式
5、事务传播行为:propagation
介绍
测试
观察结果
6、总结
3. 基于XML的Transactional
3.1. 场景模拟
3.2. 修改 Spring 配置文件
一、JdbcTemplate(持久化技术)
1. 简介
Spring 框架对 JDBC 进行了封装,使用 JdbcTemplate 可以让我们非常方便的实现对数据库的操作。
2. 准备工作
在使用JdbcTemplate之前,我们需要进行一些依赖配置,加入mysql-connector-java数据库连接驱动,
配置druid连接池技术,引入spring-orm包,在执行持久化层操作,与持久化层技术进行整合过程中,
需要使用orm、jdbc、tx三个jar包,导入·orm 包就可以通过 Maven 的依赖传递性把其他两个也jar包一起导入
2.1. 引入依赖坐标
<!-- Spring 持久化层支持jar包 --><!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包 --><!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 --><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>5.3.1</version></dependency>
整体:
<dependencies><!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.1</version></dependency><!-- Spring 持久化层支持jar包 --><!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包 --><!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 --><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>5.3.1</version></dependency><!-- Spring 测试相关 --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.1</version></dependency><!-- junit测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency><!-- 数据源 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.31</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.1</version></dependency></dependencies>
2.2. 创建jdbc.properties
有数据源环境,必定需要配置数据源文件:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
2.3. 配置Spring的配置文件
再通过配置Spring的配置文件引入数据源:
<!--引入jdbc.properties--><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="url" value="${jdbc.url}"></property><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property></bean><bean class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean>
3. 测试
3.1. 在测试类装配 JdbcTemplate
环境搭建完毕:开始测试环境是否搭建成功
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JdbcTemplateTest {@Autowiredprivate JdbcTemplate jdbcTemplate;}
3.2. 测试增删改功能
环境搭建成功后:开始测试 JdbcTemplate 的增删改功能
@Test
//测试增删改功能
public void testUpdate(){ String sql = "insert into t_emp values(null,?,?,?)"; int result = jdbcTemplate.update(sql, "张三", 23, "男"); System.out.println(result);
}
增删改测试完毕:开始测试 JdbcTemplate 的查询功能
查询功能主要有查询一条数据为实体类对象,查询多条数据为一个list集合,查询单行单列的值
查询一条数据为实体类对象
@Test
//查询一条数据为一个实体类对象
public void testSelectEmpById(){ String sql = "select * from t_emp where id = ?"; Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), 1); System.out.println(emp);
}
查询多条数据为一个list集合
@Test
//查询多条数据为一个list集合
public void testSelectList(){ String sql = "select * from t_emp"; List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class)); list.forEach(emp -> System.out.println(emp));
}
查询单行单列的值
1、测试操作
@Test
//查询单行单列的值
public void selectCount(){ String sql = "select count(id) from t_emp"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); System.out.println(count);
}
2、创建jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
3、配置Spring的配置文件
<!--引入jdbc.properties--><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="url" value="${jdbc.url}"></property><property name="username" value="${jdbc.username}"></property><property name="password" value="${jdbc.password}"></property></bean><bean class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean>
4、测试
1、在测试类装配 JdbcTemplate
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JdbcTemplateTest {@Autowiredprivate JdbcTemplate jdbcTemplate;}
2、测试增删改功能
@Test
//测试增删改功能
public void testUpdate(){ String sql = "insert into t_emp values(null,?,?,?)"; int result = jdbcTemplate.update(sql, "张三", 23, "男"); System.out.println(result);
}
3、查询一条数据为实体类对象
@Test
//查询一条数据为一个实体类对象
public void testSelectEmpById(){ String sql = "select * from t_emp where id = ?"; Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), 1); System.out.println(emp);
}
4、查询多条数据为一个 list 集合
@Test
//查询多条数据为一个list集合
public void testSelectList(){ String sql = "select * from t_emp"; List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class)); list.forEach(emp -> System.out.println(emp));
}
5、查询单行单列的值
@Test
//查询单行单列的值
public void selectCount(){ String sql = "select count(id) from t_emp"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); System.out.println(count);
}
二、SpringTransactional(事务管理)
1. 事务类型
1.1. 编程式事务
事务功能的相关操作全部通过自己编写代码来实现:
Connection conn = ...; try { // 开启事务:关闭事务的自动提交 conn.setAutoCommit(false); // 核心操作 // 提交事务 conn.commit(); }catch(Exception e){ // 回滚事务 conn.rollBack(); }finally
{ // 释放数据库连接 conn.close(); }
缺陷
- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐
- 代码复用性不高: 如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
1.2. 声明式事务
既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行
相关的封装封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作
好处
- 好处1:提高开发效率
- 好处2:消除了冗余的代码
- 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方
面的优化
1.3. 总结
所以,我们可以总结下面两个概念:
- 编程式:自己写代码实现功能
- 声明式:通过配置让框架实现功能
2. 基于注解的声明式事务
2.1. 准备工作
加入依赖
<dependencies><!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.1</version></dependency><!-- Spring 持久化层支持jar包 --><!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包 --><!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 --><dependency><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>5.3.1</version></dependency><!-- Spring 测试相关 --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.1</version></dependency><!-- junit测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency><!-- 数据源 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.31</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.3.1</version></dependency></dependencies>
创建jdbc.properties
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
配置Spring的配置文件
<!--扫描组件-->
<context:component-scan base-package="com.zheng.travel.spring.tx.annotation"></context:component-scan><!-- 导入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" /> <!-- 配置数据源 -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driver}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/>
</bean><!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 装配数据源 --> <property name="dataSource" ref="druidDataSource"/>
</bean>
创建表
CREATE TABLE t_book ( book_id int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', book_name varchar(20) DEFAULT NULL COMMENT '图书名称', price int(11) DEFAULT NULL COMMENT '价格', stock int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)', PRIMARY KEY (book_id)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;insert into t_book(book_id,book_name,price,stock) values (1,'斗破苍穹',80,100),(2,'斗罗大陆',50,100);CREATE TABLE t_user ( user_id int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', username varchar(20) DEFAULT NULL COMMENT '用户名', balance int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)', PRIMARY KEY (user_id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert into t_user(user_id,username,balance) values (1,'admin',50);
创建组件
1、创建BookController
@Controller
public class BookController { @Autowired private BookService bookService; public void buyBook(Integer bookId, Integer userId){ bookService.buyBook(bookId, userId); }}
2、创建接口BookService
public interface BookService { void buyBook(Integer bookId, Integer userId);
}
3、创建实现类BookServiceImpl
@Service
public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; @Override public void buyBook(Integer bookId, Integer userId) { //查询图书的价格 Integer price = bookDao.getPriceByBookId(bookId); //更新图书的库存 bookDao.updateStock(bookId); //更新用户的余额 bookDao.updateBalance(userId, price); }}
4、创建接口BookDao:
public interface BookDao { Integer getPriceByBookId(Integer bookId); void updateStock(Integer bookId); void updateBalance(Integer userId, Integer price);
}
5、创建实现类BookDaoImpl
@Repository
public class BookDaoImpl implements BookDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Integer getPriceByBookId(Integer bookId) { String sql = "select price from t_book where book_id = ?"; return jdbcTemplate.queryForObject(sql, Integer.class, bookId); } @Override public void updateStock(Integer bookId) { String sql = "update t_book set stock = stock - 1 where book_id = ?"; jdbcTemplate.update(sql, bookId); } @Override public void updateBalance(Integer userId, Integer price) { String sql = "update t_user set balance = balance - ? where user_id = ?"; jdbcTemplate.update(sql, price, userId); }}
2.2. 测试无事务情况
创建测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:tx-annotation.xml")
public class TxByAnnotationTest { @Autowired private BookController bookController; @Test public void testBuyBook(){ bookController.buyBook(1, 1); }}
模拟场景
用户购买图书,先查询图书的价格,再更新图书的库存和用户的余额
假设用户id为1的用户,购买id为1的图书用户余额为50,而图书价格为80
购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段
此时执行sql语句会抛出SQLException
观察结果
因为没有添加事务,图书的库存更新了,但是用户的余额没有更新
显然这样的结果是错误的,购买图书是一个完整的功能,更新库存和更新余额要么都成功要么都失败
2.3. 加入事务
添加事务配置
在Spring的配置文件中添加配置:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property>
</bean><!-- 开启事务的注解驱动 通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务--><!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 --><tx:annotation-driven transaction-manager="transactionManager" />
注意:导入的名称空间需要 tx 结尾的那个
添加事务注解
因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理
在BookServiceImpl的buybook()添加注解@Transactional
观察结果
由于使用了Spring的声明式事务,更新库存和更新余额都没有执行
现在环境搭建完毕,增删改查了解完毕,就需要正式开始了解事务操做方案:
首先,我们要明确编程式事务和声明式事务两种概念
2.4. @Transactional注解标识的位置
@Transactional标识在方法上,则只会影响该方法
@Transactional标识的类上,则会影响类中所有的方法
2.5. 事务属性
例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行
- REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中,一般的选择(默认值)
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
- MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
- REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- NEVER:以非事务方式运行,如果当前存在事务,抛出异常
- NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
- 超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
- 是否只读:建议查询时设置为只读
1、只读:readOnly
介绍
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,
这个操作不涉及写操作,这样数据库就能够针对查询操作来进行优化
使用方式
@Transactional(readOnly = true)
public void buyBook(Integer bookId, Integer userId) { //查询图书的价格 Integer price = bookDao.getPriceByBookId(bookId); //更新图书的库存 bookDao.updateStock(bookId); //更新用户的余额 bookDao.updateBalance(userId, price); //System.out.println(1/0);
}
注意
对增删改操作设置只读会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modificationare not allowed
2、超时:timeout
介绍
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源
而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以
执行
简单来说就是:超时回滚,释放资源
使用方式
@Transactional(timeout = 3)
public void buyBook(Integer bookId, Integer userId) { try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } //查询图书的价格 Integer price = bookDao.getPriceByBookId(bookId); //更新图书的库存 bookDao.updateStock(bookId); //更新用户的余额 bookDao.updateBalance(userId, price); //System.out.println(1/0);
}
观察结果
执行过程中抛出异常:
org.springframework.transaction.TransactionTimedOutException: Transaction timed out:deadline was Fri Jun 04 16:25:39 CST 2022
3、回滚策略:四种
介绍
声明式事务默认只针对运行时异常回滚,编译时异常不回滚
可以通过@Transactional中相关属性设置回滚策略
- rollbackFor:需要设置一个Class类型的对象
- rollbackForClassName:需要设置一个字符串类型的全类名
- noRollbackFor:需要设置一个Class类型的对象
- rollbackFor:需要设置一个字符串类型的全类名
使用方式
@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) { //查询图书的价格 Integer price = bookDao.getPriceByBookId(bookId); //更新图书的库存 bookDao.updateStock(bookId); //更新用户的余额 bookDao.updateBalance(userId, price); System.out.println(1/0);
}
观察结果
虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是
当出现ArithmeticException不发生回滚,因此购买图书的操作正常执行
4、事务隔离级别:Isolation
介绍
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题,
一个事务与其他事务隔离的程度称为隔离级别,SQL标准中规定了多种事务隔离级别,
不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱
四种隔离级别
- 读未提交:READ UNCOMMITTED允许Transaction01读取Transaction02未提交的修改。
- 读已提交:READ COMMITTED\要求Transaction01只能读取Transaction02已提交的修改。
- 可重复读:REPEATABLE READ确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
- 串行化:SERIALIZABLE确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
各个隔离级别解决并发问题的能力见下表:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
READ UNCOMMITTED | 有 | 有 | 有 |
READ COMMITTED | 无 | 有 | 有 |
REPEATABLE READ | 无 | 无 | 有 |
SERIALIZABLE | 无 | 无 | 无 |
各种数据库产品对事务隔离级别的支持程度:
隔离级别 | Oracle | MySQL |
READ UNCOMMITTED | × | √ |
READ COMMITTED | √(默认) | √ |
REPEATABLE READ | × | √(默认) |
SERIALIZABLE | √ | √ |
使用方式
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
5、事务传播行为:propagation
介绍
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播,
测试
1、创建接口CheckoutService
public interface CheckoutService { void checkout(Integer[] bookIds, Integer userId);
}
2、创建实现类CheckoutServiceImpl
@Service
public class CheckoutServiceImpl implements CheckoutService { @Autowired private BookService bookService; @Override @Transactional //一次购买多本图书 public void checkout(Integer[] bookIds, Integer userId) { for (Integer bookId : bookIds) { bookService.buyBook(bookId, userId); } }
}
3、在BookController中添加方法
@Autowired
private CheckoutService checkoutService;public void checkout(Integer[] bookIds, Integer userId){ checkoutService.checkout(bookIds, userId);
}
在数据库中将用户的余额修改为100元
观察结果
可以通过@Transactional中的propagation属性设置事务传播行为
修改BookServiceImpl中buyBook()上,注解@Transactional的propagation属性
@Transactional(propagation = Propagation.REQUIRED)
默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行
经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行
所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个
checkout()回滚,即只要有一本书买不了,就都买不了
@Transactional(propagation = Propagation.REQUIRES_NEW)
表示不管当前线程上是否有已经开启的事务,都要开启新事务
同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,
第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本
6、总结
基于注解的声明式配置
依赖导入,在导入引入spring-orm包的时候就已经导入相关配置,现在我们其它的数据源环境搭建完毕后,就可
以进行事务配置,我们在在Spring的配置文件中添加配置。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property></bean><tx:annotation-driven transaction-manager="transactionManager" />
transaction-manager属性的默认值是transactionManager,
如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性,
我们需要导入名称空间为 tx 结尾的那个,开启事务的注解驱动,
从而通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务。
@Transactional注解标识的位置,
如果@Transactional标识在方法上,则只会影响该方法
如果@Transactional标识的类上,则会影响类中所有的方法
3. 基于XML的Transactional
3.1. 场景模拟
参考基于注解的声明式事务
3.2. 修改 Spring 配置文件
将Spring配置文件中去掉tx:annotation-driven 标签,并添加配置:
<aop:config> <!-- 配置事务通知和切入点表达式 --> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.yjxz.spring.tx.xml.service.impl.*.*(..))"></aop:advisor></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>
注意:基于xml实现的声明式事务,必须引入aspectJ的依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version></dependency>
相关文章:

Spring篇(事务篇 - 基础介绍)
目录 一、JdbcTemplate(持久化技术) 1. 简介 2. 准备工作 2.1. 引入依赖坐标 2.2. 创建jdbc.properties 2.3. 配置Spring的配置文件 3. 测试 3.1. 在测试类装配 JdbcTemplate 3.2. 测试增删改功能 查询一条数据为实体类对象 查询多条数据为一个…...

qt EventFilter用途详解
一、概述 EventFilter是QObject类的一个事件过滤器,当使用installEventFilter方法为某个对象安装事件过滤器时,该对象的eventFilter函数就会被调用。通过重写eventFilter方法,开发者可以在事件处理过程中进行拦截和处理,实现对事…...

[ 钓鱼实战系列-基础篇-6 ] 一篇文章让你了解邮件服务器机制(SMTP/POP/IMAP)-1
🍬 博主介绍 👨🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…...
wordpress伪静态规则
WordPress 伪静态规则是指将 WordPress 生成的动态 URL 转换为静态 URL 的规则,这样做可以提高网站的搜索引擎优化(SEO)效果,并且使得 URL 更加美观、易于记忆。伪静态规则通常需要在服务器的配置文件中设置,不同的服务器环境配置方法有所不同…...

缓存框架JetCache源码解析-缓存定时刷新
作为一个缓存框架,JetCache支持多级缓存,也就是本地缓存和远程缓存,但是不管是使用着两者中的哪一个或者两者都进行使用,缓存的实时性一直都是我们需要考虑的问题,通常我们为了尽可能地保证缓存的实时性,都…...

docker配置mysql8报错 ERROR 2002 (HY000)
通过docker启动的mysql,发现navicat无法连接,后来进入容器内部也是无法连接,产生以下错误 root9f3b90339a14:/var/run/mysqld# mysql -u root -p Enter password: ERROR 2002 (HY000): Cant connect to local MySQL server through socket …...

【Linux】为什么环境变量具有全局性?共享?写时拷贝优化?
环境变量表具有全局性的原因: 环境变量表之所以具有全局性的特征,主要是因为它们是在进程上下文中维护的,并且在大多数操作系统中,当一个进程创建另一个进程(即父进程创建子进程)时,子进程会继承…...
如何在Linux中找到MySQL的安装目录
前言 发布时间:2024-10-22 在日常管理和维护数据库的过程中,了解MySQL的确切安装位置对于执行配置更改、更新或者进行故障排查是非常重要的。本文将向您介绍几种在Linux环境下定位MySQL安装路径的方法。 通过命令行工具快速定位 使用 which 命令 首…...
机器人备件用在哪些领域
机器人备件,作为机器人技术的重要组成部分,被广泛应用于多个领域,以提高生产效率、降低成本、增强产品质量,并推动相关行业的智能化发展。以下是一些主要的应用领域: 制造业: 机器人备件在制造业中的应用最…...
基于单片机优先级的信号状态机设计
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、背景知识二、使用步骤1.定义相应状态和信号列表2.获取最高优先级信号3.通用状态机实现4.灯的控制函数 总结 前言 在嵌入式系统中,设备控制的灵…...
数字电路week3
数字电路学习 九.补充 1.Verilog和fpga verilog是一种描述电路的语言,出现于上世纪80年代 非:~与: &,或: |,异或: ^ fpga:一种可编程逻辑器件 FPGA 由大量的逻辑单元、查找表(LUTs)、触发…...

如何在 Linux 中对 USB 驱动器进行分区
如何在 Linux 中对 USB 驱动器进行分区 一、说明 为了在 Linux 上访问 USB 驱动器,它需要有一个或多个分区。由于 USB 驱动器通常相对较小,仅用于临时存储或轻松传输文件,因此绝大多数用户会选择只配置一个跨越整个 USB 磁盘的分区。但是&a…...
【STM32+HAL】STM32CubeMX学习目录
一、基础配置篇 【STM32HAL】微秒级延时函数汇总-CSDN博客 【STM32HAL】CUBEMX初始化配置 【STM32HAL】定时器功能小记-CSDN博客 【STM32HAL】PWM呼吸灯实现 【STM32HAL】DACDMA输出波形实现-CSDN博客 【STM32HAL】ADCDMA采集(单通道多通道)-CSDN博客 【STM32HAL】三重A…...

PPT自动化:Python如何修改PPT文字和样式!
文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 使用 Python 修改 PPT 文本内容📝 遍历所有幻灯片和文本框📝 设置和修改文本样式📝 复制和保留文本样式⚓️ 相关链接 ⚓️📖 介绍 📖 在日常工作中,PPT 的文字内容和样式修改似乎是一项永无止境的…...

4:Java的介绍与基础4:for语句
4.1for循环 for循环也是一个非常重要的东西,再代码中是一个循环的作用,在python的文章中也介绍过了for循环的使用方法,其实在Java中也是一样的逻辑,但是有着不一样的表达,现在我们来讲一下关于for循环的东西。 因为循…...

R语言机器学习算法实战系列(十二)线性判别分析分类算法 (Linear Discriminant Analysis)
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍LDA的原理LDA的步骤教程下载数据加载R包导入数据数据预处理数据描述数据切割构建模型预测测试数据评估模型模型准确性混淆矩阵模型评估指标ROC CurvePRC Curve保存模型总结优点:缺…...
[LeetCode] 50. Pow(x, n)
题目描述: 实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,xn )。 示例 1: 输入:x 2.00000, n 10 输出:1024.00000示例 2: 输入:x 2.10000, n 3 输出…...
Vue学习笔记(七、事件修饰符 .stop .capture .self .once .prevent)
先看一段基本的代码: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>VueBaseCode</title><script src"./lib/vue.js"></script><style>.inner {width:…...

web网站搭建(静态)
准备工作: 关闭防火墙: [rootlocalhost ~]# systemctl disable --now firewalld 修改enforce为permissive [rootlocalhost ~]# setenforce 0 [rootlocalhost ~]# geten getenforce getent [rootlocalhost ~]# getenforce Permissive 重启服务 [rootloca…...
高效特征选择策略:提升Python机器学习模型性能的方法
高效特征选择策略:提升Python机器学习模型性能的方法 目录 🔍 特征选择的重要性📊 相关性分析🔄 递归特征消除 (RFE)🌳 基于模型的特征选择 1. 🔍 特征选择的重要性 特征选择在机器学习中至关重要&#…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...