Spring:声明式事务

  1. 准备数据
  2. 基于注解是声明式事务
    1. 事务配置
      1. xml配置
      2. 配置类配置
    2. 使用@Transactional
    3. @Transactional的位置
  3. 基于XML的声明式事务
    1. Spring的配置文件
    2. 测试

准备数据

CREATE TABLE `t_user` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `username` varchar(20) DEFAULT NULL COMMENT '用户名',
  `balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert  into `t_user`(`user_id`,`username`,`balance`) values (1,'张三',100);
insert  into `t_user`(`user_id`,`username`,`balance`) values (2,'李四',100);

基于注解是声明式事务

事务配置

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"
       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">

    <!--引入外部属性文件,创建数据源对象-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="username" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--创建jdbcTemplate对象,注入数据源-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
    <!-- 事务管理器   -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
    <!--
        开启事务的注解驱动
        通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
    -->
    <!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
    <tx:annotation-driven transaction-manager="transactionManager" />
</beans>

配置类配置

//用于启用注解驱动的事务管理,这相当于XML配置中的<tx:annotation-driven />元素。
@EnableTransactionManagement 
@Configuration  
@PropertySource("classpath:jdbc.properties")  
public class AppConfig {  
  
    @Value("${jdbc.url}")  
    private String jdbcUrl;  
  
    @Value("${jdbc.driver}")  
    private String jdbcDriver;  
  
    @Value("${jdbc.user}")  
    private String jdbcUser;  
  
    @Value("${jdbc.password}")  
    private String jdbcPassword;  
  
    // 配置数据源  
    @Bean  
    public DataSource druidDataSource() {  
        DruidDataSource dataSource = new DruidDataSource();  
        dataSource.setUrl(jdbcUrl);  
        dataSource.setDriverClassName(jdbcDriver);  
        dataSource.setUsername(jdbcUser);  
        dataSource.setPassword(jdbcPassword);  
        return dataSource;  
    }  
  
    // 配置 JdbcTemplate  
    @Bean  
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {  
        return new JdbcTemplate(dataSource);  
    }

    /**
     * 配置事务管理器
     * @param dataSource 就是上面配置的数据源
     * @return
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

使用@Transactional

@SpringJUnitConfig(classes = AppConfig.class)
public class TestTX {

    @Autowired
    private JdbcTemplate jdbcTemplate;

//    @Transactional
    @Test
    public void test2(){
        // 张三转给李四50元
        transfer();
    }
    
    @Transactional
    public String transfer(){
        String sql1 = "update t_user set balance = balance-50 where user_id = ? and username=?";
        int r1 = jdbcTemplate.update(sql1,1,"张三");
        System.out.println("r1="+r1);
        String sql2 = "update t_user set balance = balance+50 where user_id = ? and username=?";
        int r2 = jdbcTemplate.update(sql2,2,"李四");
        System.out.println("r2="+r2);
        return "success";
    }
}

在JUnit的@Test函数上使用事务注解@Transactional时,函数无论抛异常与否,事务都会自动回滚,这是Spring测试框架为了确保测试的独立性和可重复性而设计的默认行为。这种设计使得每个测试方法都可以在一个干净的环境中运行,不会受到前一个测试方法的影响。

所以@Test@Transactional不能一起使用

@Transactional的位置

@Transactional标识在方法上,则只会影响该方法

@Transactional标识的类上,则会影响类中所有的方法

基于XML的声明式事务

Spring的配置文件

<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--开启组件扫描-->
    <context:component-scan base-package="com.lin.xmltx"></context:component-scan>

    <!--数据源对象-->
    <!--引入外部属性文件,创建数据源对象-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
         <property name="url" value="${jdbc.url}"></property>
         <property name="driverClassName" value="${jdbc.driver}"></property>
         <property name="username" value="${jdbc.user}"></property>
         <property name="password" value="${jdbc.password}"></property>
     </bean>

    <!--JdbcTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>

    <!--事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
    <!--配置事务通知切面(增强)-->
    <!-- tx:advice标签:配置事务通知 -->
    <!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
    <!-- transaction-manager属性:关联事务管理器 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- tx:method标签:配置具体的事务方法 -->
            <!-- name属性:指定方法名,可以使用星号代表多个字符 -->
            <!-- read-only属性:设置只读属性 -->
            <!-- rollback-for属性:设置回滚的异常 -->
            <!-- no-rollback-for属性:设置不回滚的异常 -->
            <!-- isolation属性:设置事务的隔离级别 -->
            <!-- timeout属性:设置事务的超时属性 -->
            <!-- propagation属性:设置事务的传播行为 -->
            <tx:method name="trans*"></tx:method>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="update*" read-only="false" propagation="REQUIRED"></tx:method>
            <tx:method name="buy*" read-only="false" propagation="REQUIRED"></tx:method>
        </tx:attributes>
    </tx:advice>

    <!--配置切入点和通知使用的方法-->
    <aop:config>
        <aop:pointcut id="pt" expression="execution(* com.lin.xmltx.service.*.*(..))"/>
        <aop:pointcut id="pt2" expression="execution(* com.lin.xmltx.TestBookTx.*(..))"/>
        <aop:pointcut id="pt3" expression="execution(* com.lin.xmltx.TestMethod.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"></aop:advisor>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt2"></aop:advisor>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt3"></aop:advisor>
    </aop:config>
 </beans>

注意:基于xml实现的声明式事务,必须引入aspectJ的依赖

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>6.0.2</version>
</dependency>

测试

事务的切面点

@Component
public class TestMethod {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public String updateBalance(){
        String sql1 = "update t_user set balance = balance-50 where user_id = ? and username=?";
        int r1 = jdbcTemplate.update(sql1,1,"张三");
        System.out.println("r1="+r1);
        String sql2 = "update t_user set balance = balance+50 where user_id = ? and username=?";
        int r2 = jdbcTemplate.update(sql2,2,"李四");
        System.out.println("r2="+r2);
        int i =1/0; // 模拟错误
        return "success";
    }
}

测试类

@SpringJUnitConfig(locations = "classpath:beans-xml.xml")
public class TestBookTx {
    @Autowired
    private TestMethod testMethod;
    @Test
    public void test2(){
        // 张三转给李四50元
        testMethod.updateBalance();
    }
}

注意:切面点无法放在测试类下,否则基于xml的声明式事务会无效

@SpringJUnitConfig(locations = "classpath:beans-xml.xml")
public class TestBookTx {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private TestMethod testMethod;
    @Test
    public void test2(){
        // 张三转给李四50元
        testMethod.updateBalance();
    }
    // 想要验证updateBalance,事务无效
    public String updateBalance(){
        String sql1 = "update t_user set balance = balance-50 where user_id = ? and username=?";
        int r1 = jdbcTemplate.update(sql1,1,"张三");
        System.out.println("r1="+r1);
        String sql2 = "update t_user set balance = balance+50 where user_id = ? and username=?";
        int r2 = jdbcTemplate.update(sql2,2,"李四");
        System.out.println("r2="+r2);
        int i =1/0;
        return "success";
    }
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com

×

喜欢就点赞,疼爱就打赏