十七、事务

本文通过银行转账的例子,详细介绍了Spring中事务管理的环境搭建、异常情况、事务操作以及声明式事务管理的注解和XML配置方式。从数据库表创建到事务回滚,展示了事务在业务逻辑中的重要性,以及Spring如何利用AOP实现事务的自动管理。同时,对比了注解式和XML配置式的不同,并提供了相应的测试案例来验证事务效果。
摘要由CSDN通过智能技术生成

1. 事务环境的搭建

以银行转账为例子:

1.创建数据库的表,添加记录

create table bank_account
(
    id       int auto_increment comment '主键',
    username varchar(32) null comment '用户名',
    money    int         null comment '钱数',
    constraint bank_account_pk
        primary key (id)
);

create unique index bank_account_id_uindex
    on bank_account (id);

INSERT INTO lipw_test.bank_account (username, money) VALUES ('zhangsan', 100);
INSERT INTO lipw_test.bank_account (username, money) VALUES ('lisi', 100);

Service

package com.demo.study_spring.bank.service;

import com.demo.study_spring.bank.dao.BankAccountDao;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class BankAccountServiceImpl {

    @Resource
    private BankAccountDao bankAccountDao;


    public void change() {
        bankAccountDao.reduceMoney();
        bankAccountDao.addMoney();
    }
}

Dao

package com.demo.study_spring.bank.dao;

public interface BankAccountDao {

    void addMoney();

    void reduceMoney();
}

daoImpl

package com.demo.study_spring.bank.dao;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

@Repository
public class BankAccountDaoImpl implements BankAccountDao {

    @Resource
    private JdbcTemplate jdbcTemplate;


    @Override
    public void addMoney() {
        String sql = "update bank_account set money=money + ? where username = ?";
        int query = jdbcTemplate.update(sql, 20, "zhangsan");
        System.out.println("addMoney" + query);
    }

    @Override
    public void reduceMoney() {
        String sql = "update bank_account set money=money - ? where username = ?";
        int query = jdbcTemplate.update(sql, 20, "lisi");
        System.out.println("reduceMoney" + query);
    }
}

配置文件:

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

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

       <!--数据库连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/lipw_test" />
                <property name="username" value="root" />
                <property name="password" value="root" />
                <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        </bean>

        <!--创建JdbcTemplate 对象-->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!--注入dataSource-->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
</beans>

测试类:

@Test
    public void testBank() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
        BankAccountServiceImpl bankAccountServiceImpl = context.getBean("bankAccountServiceImpl", BankAccountServiceImpl.class);

        bankAccountServiceImpl.change();;
    }

测试结果:

 

2. 如果出现异常(引出事务)

数据库还原

如果在

 

添加了异常

执行测试方法:

 

 出现数据不符。

 为了解决这个问题,就要使用事务。

3. 事务操作

1. 事务一般要添加到service层上(业务逻辑层)

2. 在spring进行事务的管理操作

        1.有两种方式:1.编程式事务管理,2.声明式事务管理(一般使用)

3. 声明式事务管理

        1. 基于注解方式

        2. 基于xml配置文件方式

4.在spring进行声明式事务管理,底层使用AOP原理

5. Spring 事务管理API

        1. 提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类

 

4. 事务操作注解式事务管理

1. 在spring 配置文件配置事务管理器

2. 在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:aop="http://www.springframework.org/schema/aop"
       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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

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

       <!--数据库连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/lipw_test" />
                <property name="username" value="root" />
                <property name="password" value="root" />
                <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        </bean>

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

        <!--创建实现类-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                <!--注入数据源-->
                <property name="dataSource" ref="dataSource"></property>
        </bean>

        <!--开启事务注解-->
        <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

3. 在service类上面或者service 类里面的方法 添加上注解。

        

package com.demo.study_spring.bank.service;

import com.demo.study_spring.bank.dao.BankAccountDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
@Transactional
public class BankAccountServiceImpl {

    @Resource
    private BankAccountDao bankAccountDao;


    public void change() {

        try {
            // 第一步 开启事务
            // 第二步 进行业务操作
            bankAccountDao.reduceMoney();
            int a = 1/0;
            bankAccountDao.addMoney();
            // 第三步 事务的提交
        } catch (Exception e) {
            e.printStackTrace();
            // 第四步, 出现异常,事务回滚
        }




    }
}

1. 在Transaction,这个注解添加到类上面,也可以添加到方法上面

2.如果这个注解添加类上面,这个类里面所有的方法都添加事务

3. 如果把这个注解添加方法上面,为这个方法添加事务

事务操作(声明式事务管理参数)

1. 在service类上面添加注解@Tranactional,在这个注解里面可以配置事务相关参数

/**
     * 事务的传播行为.
     * <p>
     * 多事务方法直接进行调用,这个过程中事务是如何进行管理的
     * 事务方法:对数据库表数据进行变化的操作
     * Spring 框架事务传播行为有7种
     *
     * 1. REQUIRED: 如果add 方法本身有事务,调用update方法之后,update使用当前add方法里面的事务
     * 2. REQUIRED_NEW: 使用add方法调用update方法,如果add无论是否有事务,都创建新的事务
     * 3. SUPPOPTS: 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
     * 4. NO_SUPPORTE: 当前的方法不应该运行在事务中,如果有运行的事务,将他挂起
     * 5. MANDATORY: 当前的方法必须运行在事务内部,如果没有正在运行的事务,就掏出异常
     * 6. NEVER: 当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常
     * 7. NESTED:如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行。
     *</p>
     */
    Propagation propagation() default Propagation.REQUIRED;

    /**
     *事务的隔离级别
     * 1. 事务有特性称为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题
     * 2. 有三个读问题,脏读、不可重复读、虚幻读
     * 3. 脏读:一个未提交的事务读取到另一个未提交的事务的数据
     * 4. 不可重复读: 一个未提交事务,获取到已提交事务的修改数据
     * 5. 虚读:
     */
    Isolation isolation() default Isolation.DEFAULT;

    /**
     * 超时时间
     * 1. 事务需要在一定的时间内提交,如果不提交就会回滚
     * 2. 默认值是-1,
     *
     */
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;


    String timeoutString() default "";

    /**
     * 是否只读
     * readOnly: 默认值false,表示可以查询,可以添加修改,删除操作
     * 设置 readOnly 默认值是true,设置成true之后,只能查询
     *
     */
    boolean readOnly() default false;

    /**
     * 回滚
     * 1. 设置出现哪些异常进行事务回滚
     *
     */
    Class<? extends Throwable>[] rollbackFor() default {};


    String[] rollbackForClassName() default {};

    /**
     * 不回滚
     * 1. 设置出现哪些异常不会混滚
     *
     */
    Class<? extends Throwable>[] noRollbackFor() default {};


    String[] noRollbackForClassName() default {};

 

 

 

 4. 事务操作(XML 声明式事务管理)

1. 在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:aop="http://www.springframework.org/schema/aop"
       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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/tx.xsd">

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

        <!--数据库连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/lipw_test" />
                <property name="username" value="root" />
                <property name="password" value="root" />
                <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        </bean>

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

        <!--1. 创建事务管理器-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                <!--注入数据源-->
                <property name="dataSource" ref="dataSource"></property>
        </bean>

        <!--2. 配置通知-->
        <tx:advice id ="txadvice">
                <!--配置事务参数-->
                <tx:attributes>
                        <!--指定那种规则的方法上面添加事务-->
                        <tx:method name="change" propagation="REQUIRED"/>
                </tx:attributes>
        </tx:advice>


        <!--3. 配置切入点和切面-->
        <aop:config>
                <!--配置切入点-->
                <aop:pointcut id="pt" expression="execution(* com.demo.study_spring.bank.service.BankAccountServiceImpl.*(..))"/>
                <!--配置切面-->
                <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
        </aop:config>
</beans>

service修改

package com.demo.study_spring.bank.service;

import com.demo.study_spring.bank.dao.BankAccountDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
//@Transactional(readOnly = true,timeout = -1, propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
public class BankAccountServiceImpl {

    @Resource
    private BankAccountDao bankAccountDao;


    public void change() {

//        try {
//            // 第一步 开启事务
//            // 第二步 进行业务操作
//            bankAccountDao.reduceMoney();
//            int a = 1/0;
//            bankAccountDao.addMoney();
//            // 第三步 事务的提交
//        } catch (Exception e) {
//            e.printStackTrace();
//            // 第四步, 出现异常,事务回滚
//        }


        bankAccountDao.reduceMoney();
        int a = 1/0;
        bankAccountDao.addMoney();
    }
}

测试方法:

测试前数据库都还原为100

@Test
    public void testBankXML() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
        BankAccountServiceImpl bankAccountServiceImpl = context.getBean("bankAccountServiceImpl", BankAccountServiceImpl.class);
        bankAccountServiceImpl.change();;
    }

结果:

 

 5. 事务操作(完全注解声明式事务管理)

新增配置文件

package com.demo.study_spring.bank.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
// 开启组件扫描
@ComponentScan(basePackages = "com.demo.study_spring.bank")
// 开启事务
@EnableTransactionManagement
public class TxConfig {

    /**
     * 创建数据库连接池
     */
    @Bean
    public DruidDataSource getDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/lipw_test");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("root");
        return druidDataSource;
    }

    /**
     * 创建 JdbcTemplate
     */
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    /**
     * 创建事务管理器
     */
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

编写测试方法

 @Test
    public void testBankAnno() {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
        BankAccountServiceImpl bankAccountServiceImpl = annotationConfigApplicationContext.getBean("bankAccountServiceImpl", BankAccountServiceImpl.class);
        bankAccountServiceImpl.change();;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值