8.1-spring 事务-声明式事务(使用)
8.1-spring 事务-声明式事务(使用)
事务可以保证数据操作原子性,即将对数据的操作看成一个整体,如果部分失败那么部分成功的也要回滚。spring 提供了声明式事务和编程式事务,以下对声明式事务做一些介绍。
jdbc 提供的事务操作
如果不使用spring框架提供的事务管理,可以使用数据库提供的java开发包来操作事务,如:
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
使用mysql-connector-java操作事务举例
① 创建数据库
以下,name 列数据唯一
CREATE DATABASE /*!32312 IF NOT EXISTS*/`spring_transactional_test` /*!40100 DEFAULT CHARACTER SET utf8 */;USE `spring_transactional_test`;/*Table structure for table `user` */DROP TABLE IF EXISTS `user`;CREATE TABLE `user` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`name` varchar(10) DEFAULT NULL,`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
② 使用mysql-connector-java提供的事务操作
package com.spring_transactional_test.service;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;public class JDBCTransactionalTest {private static final String URL = "jdbc:mysql://127.0.0.1:3306/spring_transactional_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true";private static final String USER = "root";private static final String PASSWORD = "root";private void insertUser(Connection conn,String name) throws SQLException {String sql = "INSERT INTO user (name) VALUES (?)";PreparedStatement pstmt = conn.prepareStatement(sql);pstmt.setString(1, name);pstmt.executeUpdate();}public void insrtDatas() {// 加载并注册JDBC驱动try {Class.forName("com.mysql.cj.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackTrace();return;}// 建立数据库连接try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) {// 设置不自动提交,开启事务支持conn.setAutoCommit(false);try {insertUser(conn,"user1");insertUser(conn,"admin1");// 如果没有异常,则提交事务conn.commit(); // 所有操作成功完成,提交事务System.out.println("Transaction completed successfully.");} catch (SQLException e) {// 如果出现异常,回滚事务try {conn.rollback(); // 回滚到上一个保存点或初始状态System.out.println("Transaction rolled back due to an error.");} catch (SQLException ex) {ex.printStackTrace();}e.printStackTrace(); // 打印原始异常信息以供调试使用} finally {// 确保在最后恢复自动提交模式,尽管在这个例子中我们手动控制了事务,但仍保持良好实践。conn.setAutoCommit(true); // 恢复自动提交模式,尽管在当前上下文中可能不是必需的。}} catch (SQLException e) {e.printStackTrace(); // 打印连接或事务管理相关的异常信息以供调试使用。}}public static void main(String[] args) {new JDBCTransactionalTest().insrtDatas();}
}
③ 测试事务可行性
- 成功测试
以上,插入 insertUser(conn,“user1”); insertUser(conn,“admin1”); 两条数据,由于数据库此时为空,且两条sql都没有报错,所以两条数据都成功了
- 事务生效测试
将插入数据改成 insertUser(conn,“user2”); insertUser(conn,“admin1”); 第一条user2数据数据库中没有,第二条admin1数据数据库中已经有了,执行后程序抛出异常:
刷新数据库,发现数据还是 user1 和 admin1 ,说明事务生效,user2 也不会插入成功Transaction rolled back due to an error. java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'admin1' for key 'name'at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117)at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1098)at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1046)at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1371)at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1031)at com.spring_transactional_test.service.JDBCTransactionalTest.insertUser(JDBCTransactionalTest.java:17)at com.spring_transactional_test.service.JDBCTransactionalTest.insrtDatas(JDBCTransactionalTest.java:36)at com.spring_transactional_test.service.JDBCTransactionalTest.main(JDBCTransactionalTest.java:60)
spring 声明式事务如何使用?
很简单,在需要加事务的方法上添加 @Transactional 注解即可,具体如下:
package com.spring_transactional_test.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;private void insertUser(String name){String sql = "INSERT INTO user (name) VALUES (?)";jdbcTemplate.update(sql, name);}@Transactionalpublic void insertDatas(){insertUser("user12");insertUser("admin1");}
}
测试类:
package com.spring_transactional_test;import com.spring_transactional_test.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@Slf4j
@SpringBootTest
public class MyApplicationTests {@Autowiredprivate UserService userService;@Testpublic void insertDataTest() {userService.insertDatas();}}
执行完测试类,程序抛出异常:
2025-09-18 11:20:34.016 INFO 22016 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2025-09-18 11:20:34.355 INFO 22016 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.org.springframework.dao.DuplicateKeyException: PreparedStatementCallback; SQL [INSERT INTO user (name) VALUES (?)]; Duplicate entry 'admin1' for key 'name'; nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'admin1' for key 'name'at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:247)at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70)at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1541)at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:667)at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:960)at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:1015)at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:1025)at com.spring_transactional_test.service.UserService.insertUser(UserService.java:16)at com.spring_transactional_test.service.UserService.insertDatas(UserService.java:22)at com.spring_transactional_test.service.UserService$$FastClassBySpringCGLIB$$44dfb7f0.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753)at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698)at com.spring_transactional_test.service.UserService$$EnhancerBySpringCGLIB$$b6019c26.insertDatas(<generated>)at com.spring_transactional_test.MyApplicationTests.insertDataTest(MyApplicationTests.java:18)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)at java.util.ArrayList.forEach(ArrayList.java:1249)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)at java.util.ArrayList.forEach(ArrayList.java:1249)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'admin1' for key 'name'at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117)at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1098)at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1046)at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1371)at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1031)at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)at org.springframework.jdbc.core.JdbcTemplate.lambda$update$2(JdbcTemplate.java:965)at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651)... 87 more2025-09-18 11:20:34.592 INFO 22016 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2025-09-18 11:20:34.607 INFO 22016 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
同样,数据库还是 user1 和,admin1 两条数据,说明事务生效了。