spring事务实现方式
1. 手动式事务
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;public class JdbcTransactionExample {public static void main(String[] args) {Connection conn = null;try {// 1. 创建数据库连接conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");// 2. 开启事务conn.setAutoCommit(false);// 3. 执行数据库操作updateData(conn);insertData(conn);// 4. 提交事务conn.commit();} catch (SQLException e) {e.printStackTrace();// 5. 回滚事务try {if (conn != null) {conn.rollback();}} catch (SQLException ex) {ex.printStackTrace();}} finally {// 6. 关闭数据库连接try {if (conn != null) {conn.close();}} catch (SQLException e) {e.printStackTrace();}}}private static void updateData(Connection conn) throws SQLException {String sql = "UPDATE table_name SET column_name = ? WHERE id = ?";try (PreparedStatement stmt = conn.prepareStatement(sql)) {stmt.setString(1, "new value");stmt.setInt(2, 1);stmt.executeUpdate();}}private static void insertData(Connection conn) throws SQLException {String sql = "INSERT INTO table_name (column1, column2) VALUES (?, ?)";try (PreparedStatement stmt = conn.prepareStatement(sql)) {stmt.setString(1, "value1");stmt.setString(2, "value2");stmt.executeUpdate();}}
}
通过 JDBC 原生命令操作事务
2. 声明式事务
public class UserService {
private final DataSource dataSource;@Autowired
public UserService(DataSource dataSource) {this.dataSource = dataSource;
}@Transactional
public void performTransaction() {Connection conn = null;try {conn = dataSource.getConnection();// 3. 执行数据库操作updateData(conn);insertData(conn);} catch (SQLException e) {e.printStackTrace();throw new RuntimeException("Transaction failed.", e);} finally {// 6. 关闭数据库连接try {if (conn != null) {conn.close();}} catch (SQLException e) {e.printStackTrace();}}
}private void updateData(Connection conn) throws SQLException {String sql = "UPDATE table_name SET column_name = ? WHERE id = ?";try (PreparedStatement stmt = conn.prepareStatement(sql)) {stmt.setString(1, "new value");stmt.setInt(2, 1);stmt.executeUpdate();}
}private void insertData(Connection conn) throws SQLException {String sql = "INSERT INTO table_name (column1, column2) VALUES (?, ?)";try (PreparedStatement stmt = conn.prepareStatement(sql)) {stmt.setString(1, "value1");stmt.setString(2, "value2");stmt.executeUpdate();}
}
如果有事务注解我们是如何使用的?
- 在方法上添加
@Transactional
注解即可。
Q:为什么事务注解这么方便,原理是什么(动态代理)?
- 声明式事务的书写方式就是在方法上加上一个
@Transactional
注解,这里涉及到了Spring的AOP思想,即:把当前方法作为一个切面,对其执行前后进行功能增强。 - AOP的原理就是动态代理,通过扫描对应的注解,创建相应的代理对象,并实现功能增强。简单看一下源码:
在try块中代理对象调用了增强方法,catch块中对异常情况进行回滚处理,finally块中清理本次事务的信息。
Q:事务注解有哪些参数可以控制?
-
传播行为(Propagation):
- REQUIRED(0) : 默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- SUPPORTS(1):如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- MANDATORY(2):如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- REQUIRES_NEW(3):表示创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
- NOT_SUPPORTED(4):以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- NEVER(5):以非事务方式运行,如果当前存在事务,则抛出异常。
- NESTED(6):如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。
-
回滚处理(RollbackFor):控制事务什么时候回滚,如果不配置,默认在遇到RuntimeException和Error时才回滚。
@Transactional(rollbackFor = Exception.class)
配置之后,遇见非运行时异常时回滚。
-
隔离级别(Isolation):与数据库隔离级别一致,默认为Default,直接使用数据库的隔离级别。注意:如果Spring的隔离级别与MySQL不一致,则以Spring为准。
Q:事务失效的场景有哪些?
- 事务方法执行期间出现了异常,但是并未指定rollbackFor:Spring默认只会在遇到error和RunTimeException时才会回滚。
public boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);
}
- 事务方法执行期间出现了异常,但被方法本身捕获: 使用catch进行捕获之后,Spring无法感知到异常,无法回滚。
- 同一个类种方法互相调用: 因为Spring事务的本质是动态代理,通过生成代理对象去调用方法,并且在方法前后增加事务效果;同一类中的方法调用时,无法使用代理对象调用,使用的是this调用,因此无法实现动态代理效果。
- 方法不是public: Spring源码做了判断,如果不是Public会直接返回。
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;
}
- 方法是final或static: 考虑动态代理的实现原理,无论是基于JDK还是CGLib,都不允许final和static的修饰。