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混合配置需要根据实际项目需要,不一定是像上面这样…



















