Spring5️⃣声明式事务(todo
1、事务
MySQL 默认自动提交事务:每执行一条 DML 语句,MySQL 立即将事务提交到数据库。
- 事务操作
- 控制事务 :BEGIN 或 START TRANSACTION
- 提交事务 :COMMIT
- 回滚事务 :ROLLBACK
- 四大特性:ACID
- 并发事务:脏读、不可重复读、幻读
- 隔离级别:RU、RC、RR、Serializable
- 传播行为:REQUIRED、SUPPORT、MANDATORY 等
Spring 事务管理
- 编程式事务管理:代码级别(类似 JDBC 事务管理)
- 声明式事务管理:配置
2、编程式事务
2.1、JDBC 事务管理
回顾 JDBC 事务管理
Connection 对象提供操作事务的方法。
-
查看、设置自动提交状态
-
提交事务
-
回滚事务
boolean getAutoCommit() throws SQLException; void setAutoCommit(boolean autoCommit) throws SQLException; void commit() throws SQLException; void rollback() throws SQLException;
示例
SQL 可能发生运行时异常(而非编译期异常),需通过 try-catch 捕获。
-
开启事务。
-
执行 SQL。
-
若无异常,则正常提交事务。
-
若发生异常,则捕获并回滚事务。
public static void update(String sql1, String sql2) throws SQLException { // 省略:注册驱动、获取连接、获取SQL执行对象 try { // 开启事务 if (connection.getAutoCommit()) { connection.setAutoCommit(false); } // 执行SQL int count1 = statement.executeUpdate(sql1); // 此处可模拟异常,如int i = 1/0; int count2 = statement.executeUpdate(sql2); // 提交事务 connection.commit(); } catch (Exception e) { // 回滚事务 connection.rollback(); e.printStackTrace(); } // 释放资源 }
2.2、Spring API
2.2.1、平台事务管理器
PlatformTransactionManager:定义操作事务的方法
类似 JDBC 事务管理,将事务信息封装为状态对象。
-
获取事务:根据事务传播级别,返回当前事务或创建新事务
-
提交事务
-
回滚事务
TransactionStatus getTransaction(TransactionDefination defination); void commit(TransactionStatus status); void rollback(TransactionStatus status);
2.2.2、事务定义对象
TransactionDefination:定义事务信息
-
成员变量:事务传播行为、事务隔离级别、超时时间。
-
常用方法
方法 默认值 获取事务传播行为 int getPropagationBehavior() REQUIRED 获取事务隔离级别 int getIsolationLevel DEFAULT,即数据库的默认隔离级别 获取超时时间 int getTimeout() 基础事务系统的默认超时 是否只读 boolean isReadOnly() false(建议 DQL 设为 true)
事务的传播行为、隔离级别
参考:MySQL 事务
2.2.3、事务状态对象
TransactionStatus:定义事务的运行状态
-
继承自 TransactionExecution 接口的方法
// 当前事务是否是新事务 boolean isNewTransaction(); // 设置事务仅回滚 void setRollbackOnly(); // 事务是否标记为仅回滚 boolean isRollbackOnly(); // 事务是否已完成:即是否已提交或回滚 boolean isCompleted();
-
内部定义的方法
// 当前事务是否在内部存储回滚点 boolean hasSavepoint();
关于编程式事务,需了解
- JDBC 的事务管理操作
- 以及 Spring 提供的 API 即可。
==3、声明式事务
声明式事务:AOP 的应用。
采用声明的方式来管理事务。
- 声明:通过在 Spring 配置文件中配置的方式,代替代码级别的事务管理。
- 步骤
- 导入依赖:spring-jdbc
- 注册 bean:事务管理器
- 事务配置
- AOP 配置
3.1、环境搭建
3.1.1、搭建环境
先 整合 MyBatis ,再进行以下测试。
Mapper
增删改操作需要提交事务,因此在 Mapper 中添加 insert 方法和 delete 方法来测试。
UserMapper
int insertUser(User user);
int deleteUser(long id);
UserMapper.xml
<insert id="insertUser" parameterType="user">
insert into mybatis.user(id, name, password)
values (#{id}, #{name}, #{password});
</insert>
<delete id="deleteUser" parameterType="_long">
delete
from mybatis.user
where id = #{id}
</delete>
UserMapperImpl
public class UserMapperImpl implements UserMapper {
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public int insertUser(User user) {
return sqlSession.getMapper(UserMapper.class).insertUser(user);
}
@Override
public int deleteUser(long id) {
return sqlSession.getMapper(UserMapper.class).deleteUser(id);
}
}
测试
分别对两个方法进行测试
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
int i = -1;
i = userMapper.insertUser(new User(10001, "u10001", "123456"));
if (i > 0) {
System.out.println("插入成功");
}
i = userMapper.insertUser(new User(10002, "u10002", "123456"));
if (i > 0) {
System.out.println("插入成功");
}
}
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
// List<User> users = userMapper.listUsers();
//
// for (User user : users) {
// System.out.println(user);
// }
int i = -1;
i = userMapper.deleteUser(10001);
if (i > 0) {
System.out.println("删除成功");
}
}
3.1.2、测试事务
UserMapper.xml
将 delete 语句修改为错误的 SQL 语句(将 delete 关键字改成 deletes),使其无法正确执行。
<delete id="deleteUser" parameterType="_long">
deletes
from mybatis.user
where id = #{id}
</delete>
UserMapperImpl
将 listUsers 方法作为事务来测试,调用 insert 和 delete 方法,查看结果。
@Override
public List<User> listUsers() {
// 增加用户101
insertUser(new User(101,"u101","123456"));
// 删除用户10002
deleteUser(10002);
return sqlSession.getMapper(UserMapper.class).listUsers();
}
JUnit
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<User> users = userMapper.listUsers();
}
结果:
-
删除语句报错
-
用户101成功插入,用户10002未被删除
我们想要的结果是:要么都成功,要么都失败。
即要么用户101 插入成功且用户10002被删除,要么用户101没插入且用户10002没被删除。
因此需要开启事务,来达到预期结果。
3.2、基于 XML 实现
3.2.1、导入配置
mybatis-config
使用 AOP 实现 Spring 声明式事务,因此需要导入 AOP 和 事务的配置信息。
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
3.2.2、设置事务
mybatis-config
- 开启事务管理
- 使用的 dataSource 数据源,必须与 sqlSessionFactory 是同一个。
- 配置事务通知
- tx-mehod:要开启事务的方法
- name:方法名,可以使用通配符
*
- propagation:事务传播机制;
- REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- name:方法名,可以使用通配符
- 配置AOP
<!-- 开启事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"/>
</bean>
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="listUsers" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置AOP:切入事务 -->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* indi.jaywee.mapper.UserMapperImpl.* (..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
3.2.3、测试
UserMapperImpl
测试 insert 用户103、delete 用户 10002,查看结果
@Override
public List<User> listUsers() {
// 增加用户101
insertUser(new User(103,"u103","123456"));
// 删除用户10002
deleteUser(10002);
return sqlSession.getMapper(UserMapper.class).listUsers();
}
Junit
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<User> users = userMapper.listUsers();
}
结果:
-
删除语句报错
-
用户103未被插入,用户10002未被删除:即事务成功开启