1、JdbcTemplate
Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作
1.1 准备工作
①搭建子模块
搭建子模块:spring-jdbc-tx
②加入依赖
<dependencies> | |
<!--spring jdbc Spring 持久化层支持jar包--> | |
<dependency> | |
<groupId>org.springframework</groupId> | |
<artifactId>spring-jdbc</artifactId> | |
<version>6.0.2</version> | |
</dependency> | |
<!-- MySQL驱动 --> | |
<dependency> | |
<groupId>mysql</groupId> | |
<artifactId>mysql-connector-java</artifactId> | |
<version>8.0.30</version> | |
</dependency> | |
<!-- 数据源 --> | |
<dependency> | |
<groupId>com.alibaba</groupId> | |
<artifactId>druid</artifactId> | |
<version>1.2.15</version> | |
</dependency> | |
<!--spring test Spring 测试jar包--> | |
<dependency> | |
<groupId>org.springframework</groupId> | |
<artifactId>spring-test</artifactId> | |
<version>6.0.9</version> | |
<scope>test</scope> | |
</dependency> | |
<!--junit5--> | |
<dependency> | |
<groupId>org.junit.jupiter</groupId> | |
<artifactId>junit-jupiter</artifactId> | |
<version>RELEASE</version> | |
<scope>test</scope> | |
</dependency> | |
<!--spring tx Spring 事务管理jar包--> | |
<dependency> | |
<groupId>org.springframework</groupId> | |
<artifactId>spring-context</artifactId> | |
<version>6.0.11</version> | |
</dependency> | |
</dependencies> |
③创建jdbc.properties
jdbc.user=root | |
jdbc.password=root | |
jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false | |
jdbc.driver=com.mysql.cj.jdbc.Driver |
④配置Spring的配置文件
beans.xml
<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" | |
xsi:schemaLocation="http://www.springframework.org/schema/beans | |
http://www.springframework.org/schema/beans/spring-beans.xsd | |
http://www.springframework.org/schema/context | |
http://www.springframework.org/schema/context/spring-context.xsd"> | |
<!-- 导入外部属性文件 --> | |
<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.user}"/> | |
<property name="password" value="${jdbc.password}"/> | |
</bean> | |
<!-- 配置 JdbcTemplate --> | |
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> | |
<!-- 装配数据源 --> | |
<property name="dataSource" ref="druidDataSource"/> | |
</bean> | |
</beans> |
⑤准备数据库与测试表
CREATE DATABASE `spring`; | |
use `spring`; | |
CREATE TABLE `t_emp` ( | |
`id` int(11) NOT NULL AUTO_INCREMENT, | |
`name` varchar(20) DEFAULT NULL COMMENT '姓名', | |
`age` int(11) DEFAULT NULL COMMENT '年龄', | |
`sex` varchar(2) DEFAULT NULL COMMENT '性别', | |
PRIMARY KEY (`id`) | |
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
1.2 实现CURD
①装配 JdbcTemplate
创建测试类,整合JUnit,注入JdbcTemplate
package com.atguigu.spring6; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.jdbc.core.JdbcTemplate; | |
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; | |
public class JDBCTemplateTest { | |
private JdbcTemplate jdbcTemplate; | |
} |
②测试增删改功能
public void testUpdate() { | |
//添加功能 | |
String sql = "insert into t_emp values(null,?,?,?)"; | |
// 执行sql | |
int result = jdbcTemplate.update(sql, "张三", 23, "男"); | |
// 断言 | |
assert result == 1; | |
//修改功能 | |
//String sql = "update t_emp set name=? where id=?"; | |
//int result = jdbcTemplate.update(sql, "张三atguigu", 1); | |
//删除功能 | |
//String sql = "delete from t_emp where id=?"; | |
//int result = jdbcTemplate.update(sql, 1); | |
} |
③查询数据返回对象
package com.jie.jdbc.model; | |
/** | |
* TODO | |
* | |
* @author 阿杰 2416338031@qq.com | |
* @date 2024/1/1 14:50 | |
* @version 1.0 | |
*/ | |
public class Emp { | |
private Integer id; | |
private String name; | |
private Integer age; | |
private String sex; | |
public Integer getId() { | |
return id; | |
} | |
public void setId(Integer id) { | |
this.id = id; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public Integer getAge() { | |
return age; | |
} | |
public void setAge(Integer age) { | |
this.age = age; | |
} | |
public String getSex() { | |
return sex; | |
} | |
public void setSex(String sex) { | |
this.sex = sex; | |
} | |
public String toString() { | |
return "Emp{" + | |
"id=" + id + | |
", name='" + name + '\'' + | |
", age=" + age + | |
", sex='" + sex + '\'' + | |
'}'; | |
} | |
} | |
public void testSelectObject() { | |
//写法一 | |
// String sql = "select * from t_emp where id=?"; | |
// Emp empResult = jdbcTemplate.queryForObject(sql, | |
// (rs, rowNum) -> { | |
// Emp emp = new Emp(); | |
// emp.setId(rs.getInt("id")); | |
// emp.setName(rs.getString("name")); | |
// emp.setAge(rs.getInt("age")); | |
// emp.setSex(rs.getString("sex")); | |
// return emp; | |
// }, 1); | |
// System.out.println(empResult); | |
//写法二 | |
String sql = "select * from t_emp where id=?"; | |
Emp emp = jdbcTemplate.queryForObject(sql, | |
new BeanPropertyRowMapper<>(Emp.class),1); | |
// 断言 | |
assert emp != null; | |
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)); | |
System.out.println(list); | |
} |
⑤查询返回单个的值
@Test | |
//查询单行单列的值 | |
public void selectCount(){ | |
String sql = "select count(id) from t_emp"; | |
Integer count = jdbcTemplate.queryForObject(sql, Integer.class); | |
System.out.println(count); | |
} |
2、声明式事务
2.1 事务基本概念
2.2 编程式事务
编程式指的就是我们用代码方式来自己实现事务。
Connection conn = ...; | |
try { | |
// 开启事务:关闭事务的自动提交 | |
conn.setAutoCommit(false); | |
// 核心操作 | |
// 提交事务 | |
conn.commit(); | |
}catch(Exception e){ | |
// 回滚事务 | |
conn.rollBack(); | |
}finally{ | |
// 释放数据库连接 | |
conn.close(); | |
} |
比如说你现在开启事务、提交事务、回滚事务,这个过程中完全需要手动写代码实现。大家也看到它并不是很方便。
编程式的实现方式存在缺陷:
- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。
- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。
2.3 声明式事务
Spring框架提供了声明式事务管理的功能,允许开发者通过配置来定义事务规则,而无需编写大量的事务管理代码。声明式事务是通过Spring的AOP(面向切面编程)来实现的,通常使用注解或XML配置来定义事务。
用法:
① 注解
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 30, rollbackFor = Exception.class) | |
public void someTransactionalMethod() { | |
// 业务逻辑代码 | |
} |
②XML配置方式
<tx:advice id="txAdvice" transaction-manager="transactionManager"> | |
<tx:attributes> | |
<tx:method name="someTransactionalMethod" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="30" rollback-for="Exception"/> | |
</tx:attributes> | |
</tx:advice> | |
<aop:config> | |
<aop:pointcut id="transactionalMethods" expression="execution(* com.example.service.*.*(..))"/> | |
<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionalMethods"/> | |
</aop:config> |
所以,我们可以总结下面两个概念:
- 编程式:自己写代码实现功能
- 声明式:通过配置让框架实现功能
3、基于注解的声明式事务
3.1 准备工作
①添加配置
在beans.xml添加配置
<!--扫描组件--> | |
<context:component-scan base-package="com.jie.jdbc"/> |
②创建表
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); |
③创建组件
创建BookController:
package com.jie.jdbc.controller; | |
import com.jie.jdbc.service.BookService; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Controller; | |
public class BookController { | |
private BookService bookService; | |
public void buyBook(Integer bookId, Integer userId){ | |
bookService.buyBook(bookId, userId); | |
} | |
} |
创建接口BookService:
package com.jie.jdbc.service; | |
public interface BookService { | |
/** | |
* 购买书籍 | |
* | |
* @param bookId 书号 | |
* @param userId 用户id | |
*/ | |
void buyBook(Integer bookId, Integer userId); | |
} |
创建实现类BookServiceImpl:
package com.jie.jdbc.service.impl; | |
import com.jie.jdbc.mapper.BookMapper; | |
import com.jie.jdbc.service.BookService; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Service; | |
public class BookServiceImpl implements BookService { | |
private BookMapper bookMapper; | |
public void buyBook(Integer bookId, Integer userId) { | |
//查询图书的价格 | |
Integer price = bookMapper.getPriceByBookId(bookId); | |
//更新图书的库存 | |
bookMapper.updateStock(bookId); | |
//更新用户的余额 | |
bookMapper.updateBalance(userId, price); | |
} | |
} |
创建接口BookMapper:
package com.jie.jdbc.mapper; | |
public interface BookMapper { | |
/** | |
* 根据书号获取书的价格 | |
* | |
* @param bookId 书号 | |
* @return 书的价格 | |
*/ | |
Integer getPriceByBookId(Integer bookId); | |
/** | |
* 更新书的库存,每次库存减1 | |
* | |
* @param bookId 书号 | |
*/ | |
void updateStock(Integer bookId); | |
/** | |
* 更新用户的余额,每次减去书的价格 | |
* | |
* @param userId 用户id | |
* @param price 书的价格 | |
*/ | |
void updateBalance(Integer userId, Integer price); | |
} |
创建实现类BookMapperImpl:
package com.jie.jdbc.service.impl; | |
import com.jie.jdbc.mapper.BookMapper; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.jdbc.core.JdbcTemplate; | |
import org.springframework.stereotype.Repository; | |
public class BookMapperImpl implements BookMapper { | |
private JdbcTemplate jdbcTemplate; | |
public Integer getPriceByBookId(Integer bookId) { | |
String sql = "select price from spring.t_book where book_id = ?"; | |
return jdbcTemplate.queryForObject(sql, Integer.class, bookId); | |
} | |
public void updateStock(Integer bookId) { | |
String sql = "update spring.t_book set stock = stock - 1 where book_id = ?"; | |
jdbcTemplate.update(sql, bookId); | |
} | |
public void updateBalance(Integer userId, Integer price) { | |
String sql = "update spring.t_user set balance = balance - ? where user_id = ?"; | |
jdbcTemplate.update(sql, price, userId); | |
} | |
} |
3.2 测试无事务情况
让我们执行一下买书的代码。
import com.jie.jdbc.controller.BookController; | |
import org.junit.jupiter.api.Test; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; | |
"classpath:beans.xml") | (locations =|
public class TxByAnnotationTest { | |
private BookController bookController; | |
public void testBuyBook() { | |
// 1. 调用controller的buyBook方法 | |
bookController.buyBook(1, 1); | |
} | |
} |
用户余额为50,而图书价格为80
购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段
此时执行sql语句会抛出SQLException。
让我们来看看数据库的数据变化。
因为没有添加事务,图书的库存更新了,但是用户的余额没有更新
显然这样的结果是错误的,购买图书是一个完整的功能,更新库存和更新余额要么都成功要么都失败
3.3、加入事务
①添加事务配置
在spring配置文件中引入tx命名空间并在Spring的配置文件中添加配置:
<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 | |
http://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" /> | |
<!-- 配置数据源 --> | |
<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.user}"/> | |
<property name="password" value="${jdbc.password}"/> | |
</bean> | |
<!-- 配置 JdbcTemplate --> | |
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> | |
<!-- 装配数据源 --> | |
<property name="dataSource" ref="druidDataSource"/> | |
</bean> | |
<!--扫描组件--> | |
<context:component-scan base-package="com.jie.jdbc"/> | |
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> | |
<property name="dataSource" ref="druidDataSource"/> | |
</bean> | |
<!-- | |
开启事务的注解驱动 | |
通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务 | |
--> | |
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 --> | |
<tx:annotation-driven transaction-manager="transactionManager" /> | |
</beans> |
②添加事务注解
因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理
在BookServiceImpl的buybook()添加注解@Transactional
③观察结果
这次添加了事务注解,更新库存和更新余额都没有执行。
3.4 @Transactional注解标识的位置
@Transactional标识在方法上,则只会影响该方法 @Transactional标识的类上,则会影响类中所有的方法
3.5 事务属性:只读
①介绍
只读,是什么意思呢?它表示如果说咱们设置成只读属性,就表示你只能做这个查询操作,而不能做修改、添加、删除这些操作。
②使用方式
我们再来执行一下测试代码看看效果。
我们看他报的什么错,这里写到说你当前的操作不被允许。为什么?
因为目前你是一个read only,就是你只能做查操作。但是我刚才操作中,大家也看到里边是不是有修改操作,所以它就不被允许进行。这就是第一个属性,叫readOnly只读。只能做查询,涉及到写操作,比如添加、修改、删除,它就直接报这个错误。
3.6 事务属性:超时
①介绍
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。
概括来说就是一句话:超时回滚,释放资源。
②使用方式
③观察结果
我们再次执行测试代码。
3.7 事务属性:回滚策略
①介绍
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。
可以通过@Transactional中相关属性设置回滚策略
- rollbackFor属性:需要设置一个Class类型的对象
- rollbackForClassName属性:需要设置一个字符串类型的全类名
- noRollbackFor属性:需要设置一个Class类型的对象
- rollbackFor属性:需要设置一个字符串类型的全类名
②使用方式
③观察结果
改一下书籍价格和库存。
执行测试代码。
可以看到已经报错了,让我们看看数据库的数据。
虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是,当出现ArithmeticException不发生回滚,因此购买图书的操作正常执行
3.8 事务属性:隔离级别
①介绍
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。
SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
各个隔离级别解决并发问题的能力见下表:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
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)//串行化 |
3.9 事务属性:传播行为
①介绍
什么是事务的传播行为?
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。
②测试
创建接口CheckoutService:
package com.jie.jdbc.service; | |
public interface CheckoutService { | |
/** | |
* 结账 | |
* | |
* @param bookIds 购买的书籍id | |
* @param userId 用户id | |
* @return: void | |
* @date: 2023/10/30 4:16 | |
*/ | |
void checkout(Integer[] bookIds, Integer userId); | |
} |
创建实现类CheckoutServiceImpl:
package com.jie.jdbc.service.impl; | |
import com.jie.jdbc.service.BookService; | |
import com.jie.jdbc.service.CheckoutService; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Service; | |
import org.springframework.transaction.annotation.Transactional; | |
public class CheckoutServiceImpl implements CheckoutService { | |
private BookService bookService; | |
public void checkout(Integer[] bookIds, Integer userId) { | |
// 循环购买书籍 | |
for (Integer bookId : bookIds) { | |
// 调用购买书籍的方法 | |
bookService.buyBook(bookId, userId); | |
} | |
} | |
} |
在BookController中添加方法:
在数据库中将用户的余额修改为100元和修改图书价格
③观察结果
可以通过@Transactional中的propagation属性设置事务传播行为.
默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。
让我们测试一下代码。
经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。
所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了。
我们再修改一下事务的传播行为。
@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。
再次测试代码。
同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本。
3.10 全注解配置事务
①添加配置类
package com.jie.jdbc.config; | |
import com.alibaba.druid.pool.DruidDataSource; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.ComponentScan; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.context.annotation.Primary; | |
import org.springframework.jdbc.core.JdbcTemplate; | |
import org.springframework.jdbc.datasource.DataSourceTransactionManager; | |
import org.springframework.transaction.annotation.EnableTransactionManagement; | |
import javax.sql.DataSource; | |
public class SpringConfig { | |
/** | |
* @return DataSource 数据源 | |
* @Primary注解的作用是:当有多个同类型的Bean时,优先使用被@Primary注解的Bean | |
*/ | |
public DataSource getDataSource() { | |
DruidDataSource dataSource = new DruidDataSource(); | |
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); | |
dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false"); | |
dataSource.setUsername("root"); | |
dataSource.setPassword("root"); | |
return dataSource; | |
} | |
public JdbcTemplate getJdbcTemplate(DataSource dataSource) { | |
JdbcTemplate jdbcTemplate = new JdbcTemplate(); | |
jdbcTemplate.setDataSource(dataSource); | |
return jdbcTemplate; | |
} | |
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) { | |
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); | |
dataSourceTransactionManager.setDataSource(dataSource); | |
return dataSourceTransactionManager; | |
} | |
} |
②测试
@Test | |
public void testTxAllAnnotation() { | |
// 1. 调用controller的checkout方法 | |
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class); | |
// 2. 获取controller对象 | |
BookController accountService = applicationContext.getBean("bookController", BookController.class); | |
// 3. 调用方法 | |
accountService.buyBook(1, 1); | |
} |
4、基于XML的声明式事务
Spring基于XML的声明式事务管理是通过AOP(Aspect-Oriented Programming,面向切面编程)来实现的。它允许你通过XML配置文件来定义事务的属性,而不需要在业务逻辑中编写事务管理代码。以下是Spring基于XML的声明式事务是如何实现的简要步骤:
4.1、准备工作
可以直接把基于注解的声明式事务的工程直接拷贝一份,改个名字即可
4.2、修改Spring配置文件
将Spring配置文件中去掉tx:annotation-driven 标签,并添加配置:
<aop:config> | |
<!-- 配置事务通知和切入点表达式 --> | |
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.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>6.0.2</version> | |
</dependency> |