Spring事务一网打尽
- 什么是事务
- 首先说一个坑
- Spring 中的事务
- 两种用法
- 三大基础设施
- 编程性事务
- TransactionManager 实现编程性事务
- TransactionTemplate 实现编程性事务
- 声明式事务
- XML配置声明式事务
- 注解配置声明式事务
- 注解+XML混合配置声明式事务
什么是事务
这里要额外补充一点:只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的!
首先说一个坑
在单元测试方法里面不能用事务注解,不然,代码增删改永远不会生效
Spring 中的事务
两种用法
三大基础设施
-
PlatformTransactionManager
PlatformTransactionManager 就像以前学的 JDBC,它就是一个接口,一个规范
public interface PlatformTransactionManager extends TransactionManager { TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
-
TransactionDefinition
TransactionDefinition 主要定义的一些事务的属性,看源码就清楚了
-
TransactionStatus
你可以理解为事务本身,当然也可以说是事务状态
编程性事务
TransactionManager 实现编程性事务
- 导入必须的依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.20</version> </dependency> <!--jdbc事务相关的代码--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.30</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency>
- 全部代码如下
@Service public class UserServie { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private PlatformTransactionManager transactionManager; @Autowired private TransactionTemplate transactionTemplate; public void transfer() { // 定义默认的事务属性 DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition(); // 获取 transactionStatus TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition); try { jdbcTemplate.update("update user set money = ? where username = ?;", 33, "hok"); // 提交事务 transactionManager.commit(transactionStatus); } catch (DataAccessException e) { e.printStackTrace(); transactionManager.rollback(transactionStatus); } }
public class App { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserServie userServie = ctx.getBean(UserServie.class); userServie.transfer(); } }
<?xml version="1.0" encoding="UTF-8"?> <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" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--配置包扫描--> <context:component-scan base-package="com.lhg.springtx" /> <!-- 配置数据源 --> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource" > <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://120.26.161.184:3306/wxpay" /> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager" /> </bean> <!--JdbcTemplate是 Spring 对 JDBC 的封装,用于操作数据库及事务的--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" > <property name="dataSource" ref="dataSource" /> </bean> </beans>
- 运行结果如下,可以看到数据正确修改
- 如果转账过程中出现了问题会怎么样?
如下图可以看到数据库更改并没有生效,事务回滚成功
TransactionTemplate 实现编程性事务
把 transfer 方法改成如下,实地验证会发现有同样的效果@Service public class UserServie { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private PlatformTransactionManager transactionManager; @Autowired private TransactionTemplate transactionTemplate; public void transfer() { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { try { jdbcTemplate.update("update user set money = ? where username = ?;", 888, "hok"); int i = 1/0; } catch (DataAccessException e) { status.setRollbackOnly(); throw new RuntimeException(e); } } }); } }
从上面可以看到,编程性事务对业务方法侵入性太强了,实际项目开发一般不会去用
声明式事务
XML配置声明式事务
首先在上面的基础上再加个依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置包扫描-->
<context:component-scan base-package="com.lhg.springtx" />
<!-- 配置数据源 -->
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource" >
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://120.26.161.184:3306/wxpay" />
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
<!--JdbcTemplate是 Spring 对 JDBC 的封装,用于操作数据库及事务的-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
<property name="dataSource" ref="dataSource" />
</bean>
<!-- XML 配置事务分为三个步骤:
1、配置事务管理器
2、配置事务通知
3、配置AOP
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置事务的属性:
isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认隔离级别
propagation:用于指定事务的传播行为,默认值是REQUERD,表示一定会有事务,增删改的选择,查询方法可以使用SUPPORTS
read-only:用于指定事务是否只读,只有查询方法才能设置为true,默认值是false,表示读写
rollback-for:用于指定一个异常,当该异常产生时,事务回滚,产生其它异常时事务不回滚,没有默认值,表示任何异常都回滚
no-rollback-for:用于指定一个异常,当该异常产生时事务不回滚,产生其它异常时事务回滚,没有默认值,表示任何异常都回滚
-->
<tx:method name="add*" propagation="REQUIRED" read-only="false"/><!--通用匹配-->
<tx:method name="find*" propagation="SUPPORTS" read-only="true" /><!--匹配以find开头的方法,优先级更高-->
<tx:method name="transfer*" />
</tx:attributes>
</tx:advice>
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt" expression="execution(* com.lhg.springtx.UserServie.*(..))"/>
<!--建立切入点表达式与事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
</aop:config>
</beans>
业务逻辑代码改成如下,实际运行发现已正确修改
@Service
public class UserServie {
@Autowired
private JdbcTemplate jdbcTemplate;
public void transfer() {
jdbcTemplate.update("update user set money = ? where username = ?;", 666, "hok");
}
}
当然如果执行过程中出异常了,事务是能够正确回滚的,这里就不再截图了
public void transfer() {
jdbcTemplate.update("update user set money = ? where username = ?;", 999, "hok");
int i = 1/0;
}
注解配置声明式事务
依赖和上面一样,无需更改,然后定义一个 Java配置类
@Configuration
@ComponentScan(basePackages = "com.lhg.springtx")
@EnableTransactionManagement
public class JavaConfig {
@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://120.26.161.184:3306/wxpay?serverTimezone=Asia/Shanghai");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
}
业务层代码如下,需要在哪个方法上加事务就在哪个方法上加个@Transactional注解
@Service
public class UserServie {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void transfer() {
jdbcTemplate.update("update user set money = ? where username = ?;", 222, "hok");
int i = 1/0;
}
}
启动执行
public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
UserServie userServie = ctx.getBean(UserServie.class);
userServie.transfer();
}
}
运行后会发现事务依然能够生效,这里就不一一截图了
注解+XML混合配置声明式事务
说白了就是想把这一坨干掉,换成<tx:annotation-driven />
来看看 xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置包扫描-->
<context:component-scan base-package="com.lhg.springtx" />
<!-- 配置数据源 -->
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource" >
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://120.26.161.184:3306/wxpay" />
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
<!--JdbcTemplate是 Spring 对 JDBC 的封装,用于操作数据库及事务的-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
<property name="dataSource" ref="dataSource" />
</bean>
<!--事务注解支持-->
<tx:annotation-driven />
</beans>
业务代码如下
@Service
public class UserServie {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void transfer() {
jdbcTemplate.update("update user set money = ? where username = ?;", 333, "hok");
int i = 1/0;
}
}
启动运行
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServie userServie = ctx.getBean(UserServie.class);
userServie.transfer();
}
}
当然最终效果还是一样,事务正常
注解+XML混合配置需要根据实际项目需要,不一定是像上面这样…