【Spring5】事务详解


银行转账大家都不陌生,银行转账就是一个典型的事务操作。转账要么成功,要么失败,中间有一个错误就会有导致转账失败。

事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败

事务四个特性(ACID)

  • 原子性
  • 一致性
  • 隔离性
  • 持久性

就以上面的银行转账为例我们来解释Spring中的事务相关内容

事务环境配置

配置文件

事务是数据库操作最基本单元,因此首先肯定要连接数据库啦,修改配置文件

druidbean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">

    <!--组件扫描-->
    <context:component-scan base-package="com.amhu.yx.spring"></context:component-scan>

    <!--配置连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3308/springdb" />
        <property name="username" value="root" />
        <property name="password" value="" />
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>

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

</beans>

创建表并添加数据

在这里插入图片描述

创建service,搭建dao,完成对象创建和注入

service 种注入dao
UserService

@Service
public class UserService {
    
    
    //注入dao
    @Autowired
    private UserDao userDao;
}

在dao注入JdbcTemplate
UserDaoImpl

@Repository
public class UserDaoImpl implements UserDao{
    
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

在dao中创建数据库中增加和减少钱的方法,在service中创建转账的方法(调用dao中方法)
UserDao

public interface UserDao {
    
    
    void outCount();
    void entryCount();
}

UserDaoImpl

@Repository
public class UserDaoImpl implements UserDao{
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;

    //从出账账户中扣钱
    @Override
    public void outCount() {
    
    
        String sql = "update user set money=money-? where username=?";
        jdbcTemplate.update(sql,100,"jack");
    }
    
    //从入账账户中加钱
    @Override
    public void entryCount() {
    
    
        String sql = "update user set money=money+? where username=?";
        jdbcTemplate.update(sql,100,"tom");
    }
}

``

@Service
public class UserService {
    
    

    //注入dao
    @Autowired
    private UserDao userDao;

    //转账的方法
    public void accountMoney() {
    
    
        userDao.outCount();//jack少了100块钱
        userDao.entryCount();//tom增加了100块钱
    }
}

测试方法:testAccountMoney

 @Test
    public void testAccountMoney() {
    
    
        ApplicationContext context = new ClassPathXmlApplicationContext("druidbean.xml");
        UserService userService= context.getBean("userService", UserService.class);
        userService.accountMoney();
    }

结果:
在这里插入图片描述

分析

上面的代码看似没有问题,但是如果代码执行过程中出现异常,那么该怎么办呢?
我们在转账方法中模拟一个异常:
在这里插入图片描述
这里出账方法是正常执行的,但是因为中间有一个异常,入账方法没有得到执行,运行测试代码:
在这里插入图片描述
可以看到jack少了100元,但是tom却没有增加钱,实际生活中,如果你转账的时候不小心出现异常,钱唰一下就没了,这谁顶得住?

解决

我们这里就用到了事务来解决这个问题:
将代码放在异常捕获中,如果出现异常,就进行事务回滚,比如上面将jack的钱退还给jack。

//转账的方法
public void accountMoney() {
    
    
    try {
    
    
        //第一步,开启事务

        //第二部 进行业务操作
        userDao.outCount();//jack少了100块钱

        int i = 10 / 0;//模拟异常

        userDao.entryCount();//tom增加了100块钱
        
        //如果无异常发生,提交事务
    }catch (Exception e){
    
    
        //出现异常,事务回滚
    }
}

Spring 事务管理介绍

1、事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)
2、在 Spring 进行事务管理操作有两种方式:

  • 编程式事务管理
  • 声明式事务管理(推荐使用)
    3、声明式事务管理
  • 基于注解方式(使用)
  • 基于 xml 配置文件方式
    4、在 Spring 进行声明式事务管理,底层使用 AOP 原理
    5、Spring 事务管理 API
    (1)提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类
    在idea中任意一个地方输入
    在这里插入图片描述
    将鼠标放在中间,然后按CTRL + H 就可以查看相关类的继承结构图:
    在这里插入图片描述

注解方式实现声明式事务管理

  1. 在Spring配置文件中配置事务管理器,就是创建DataSourceTransactionManager对象
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据源-->
    <property name="dataSource " ref="dataSource"></property>
</bean>
  1. 在 spring 配置文件,开启事务注解
    (1)在 spring 配置文件引入名称空间 tx
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">
                           
</beans>

(2)开启事务注解

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

(3)在service类上(获取service类的方法上)添加事务注解@Transactional
在这里插入图片描述

  • @Transactional 既可以添加到类上,也可以添加到方法上
  • 如果注解添加到类上,表示该类中所有方法都添加事务
  • 如果注解添加到方法上,只是为这个方法添加事务

修改数据库中表:
在这里插入图片描述
重新开始运行测试代码,注意我们并没有修改代码,并且去掉刚刚加上的try-catch语句,恢复到之前的样子,与之前的区别只是添加了@Transactional 注解:
在这里插入图片描述
再次运行,查看结果:
在这里插入图片描述
发现没有任何改变。

声明式事务管理参数配置

  1. 在service类上添加注解@Transactional,在这个注解里面可以配置事务相关参数
    在这里插入图片描述

参数说明

  1. propagation : 事务传播行为
    多事务方法直接进行调用,这个过程中事务是如何进行管理的。
    在这里插入图片描述
    事务方法:对数据库表数据进行变化的方法
    在这里插入图片描述在这里插入图片描述

  2. ioslation : 事务隔离级别
    (1) 事务有特性称为隔离性,多十五操作之间不会产生影响,不考虑隔离性会产生多种问题
    (2) 有三个读问题

    • 脏读
      一个未提交事务读取到另一个未提交事务的数据。
    • 不可重复读
      一个未提交事务读取到已提交事务的数据
    • 虚(幻)读
      一个未提交的事务读取到另一提交事务添加的数据

(3)通过设置事务隔离级别,解决读问题
在这里插入图片描述
mysql中默认级别是:REPEATABLE_READ
在这里插入图片描述

  1. timeout : 超时时间
    规定时间内事务未提交,进行回滚
    默认值是-1(不超时)
    自己设置以为单位

  2. readOnly : 是否只读
    读:查询操作
    写:添加修改删除操作
    readOnly默认值false
    若设置为true后,事务只能进行查询操作

  3. rollbackFor : 回滚
    设置出现中哪些异常进行事务回滚

  4. noRollbackFor : 不回滚
    设置出现中哪些异常不进行事务回滚

XML配置文件方式实现声明式事务管理

1、在 spring 配置文件中进行配置
第一步 配置事务管理器
第二步 配置通知
第三步 配置切入点和切面

<!--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="accountMoney" propagation="REQUIRED"/>
	<!--<tx:method name="account*"/>-->
	</tx:attributes>
</tx:advice>
<!--3 配置切入点和切面-->
<aop:config>
	<!--配置切入点-->
	<aop:pointcut id="pt" expression="execution(*
	com.atguigu.spring5.service.UserService.*(..))"/>
	<!--配置切面-->
	<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>

完全注解方式

不用配置文件,完全使用注解

  1. 创建配置类,替代xml文件
    TxConfig
package com.amhu.yx.spring.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.amhu.yx.spring")//注解扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
    
    

    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
    
    
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3308/springdb");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("");

        return druidDataSource;
    }

    //创建JdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
    
    
        //到ioc容器中根据类型注入datasSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

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

测试方法 testConfig()

    @Test
    public void testConfig(){
    
    
        ApplicationContext context =
                new AnnotationConfigApplicationContext(TxConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        userService.accountMoney();

    }

其它代码与之前相同:
UserDao

public interface UserDao {
    
    
    void outCount();
    void entryCount();
}

UserDaoImpl

@Repository
public class UserDaoImpl implements UserDao{
    
    

    @Autowired
    private JdbcTemplate jdbcTemplate;

    //从出账账户中扣钱
    @Override
    public void outCount() {
    
    
        String sql = "update user set money=money-? where username=?";
        jdbcTemplate.update(sql,100,"jack");
    }

    //从入账账户中加钱
    @Override
    public void entryCount() {
    
    
        String sql = "update user set money=money+? where username=?";
        jdbcTemplate.update(sql,100,"tom");
    }
}

UserService

@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserService {
    
    

    //注入dao
    @Autowired
    private UserDao userDao;

    //转账的方法
    public void accountMoney() {
    
    
        userDao.outCount();//jack少了100块钱

        userDao.entryCount();//tom增加了100块钱

    }
}

猜你喜欢

转载自blog.csdn.net/weixin_45468845/article/details/107725127