[Spring] Spring5——事务简介

目录

一、事务概述

1、什么是事务

2、事务的四个特性(ACID)

二、搭建事务操作环境

1、dao、service 两层结构

2、示例

3、模拟异常(事务场景引入)

三、Spring 事务管理

1、事务管理介绍

2、声明式事务管理——注解方式

3、声明式事务管理——注解的参数配置

4、声明式事务管理——XML方式

5、声明式事务管理——完全注解开发(详细)


一、事务概述

1、什么是事务

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

2、事务的四个特性(ACID)

(1)原子性

  • 要么都成功,要么全失败

(2)一致性

  • 事务只能把数据库从一个有效(正确)的状态“转移”到另一个有效(正确)的状态

(3)隔离性

  • 多事务操作时,事务之间不会相互影响

(4)持久性

  • 即使发生了异常情况,数据库中的数据也应该能够被恢复,并且不会丢失或损坏

二、搭建事务操作环境

通过模拟银行转账这一例子,来演示在 Spring5 中如何进行事务操作

1、dao、service 两层结构

JavaEE 中有三层结构,分别是:web、service、dao。其中数据库操作由 dao 实现,业务逻辑由 service 实现,因此我们主要关注这两层结构。

[Spring] Spring5——事务简介_第1张图片

收入和支出的方法都执行后,在构成转账的方法,才是一个完整操作。

2、示例

(1)创建数据库、表,添加记录

[Spring] Spring5——事务简介_第2张图片

(2)创建 UserDao 和 UserService,并完成注入

(3)在 dao 中创建两个方法:收入和支出

package com.demo.dao.impl;

import com.demo.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;

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

    @Override
    public int income(String username, BigDecimal dif) {
        String sql = "update \"BankUser\" set balance=balance+? where username=?";
        return jdbcTemplate.update(sql, dif, username);
    }

    @Override
    public int expense(String username, BigDecimal dif) {
        String sql = "update \"BankUser\" set balance=balance-? where username=?";
        return jdbcTemplate.update(sql, dif, username);
    }
}

(4)在 service 中创建方法:转账

package com.demo.service.impl;

import com.demo.dao.UserDao;
import com.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    @Override
    public void remittance(String name1, String name2, BigDecimal dif) {
        // name1 减少 dif
        userDao.expense(name1, dif);
        // name2 增加 dif
        userDao.income(name2, dif);
    }
}

(5)测试代码

import com.demo.pojo.User;
import com.demo.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.math.BigDecimal;

public class remittanceTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("JdbcTemplate.xml");
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.remittance("wyt", "gyt", new BigDecimal(25));
    }
}

(6)运行结果

[Spring] Spring5——事务简介_第3张图片

3、模拟异常(事务场景引入)

假设在转出一方转出后,出现了异常,那么按照事务的特性,就应当将操作回滚

[Spring] Spring5——事务简介_第4张图片

而在上面的代码中,就会使得转出一方出了钱,而接收一方没有收到钱。

[Spring] Spring5——事务简介_第5张图片

三、Spring 事务管理

1、事务管理介绍

(1)由于异常一般出现在业务逻辑处理,所以一般将事务管理放在 service 层

(2)Spring 事务管理有两种方式:

  • 编程式事务管理(一般不用),前面的模拟异常就是使用编程式;(编程式事务管理会造成代码冗余,一定要用编程式的话,可以使用 filter 来全局捕获异常)
  • 声明式事务管理(一般使用);

(3)声明式事务管理有两种实现方式:

  • 基于 xml 配置文件方式;(一般不用)
  • 基于注解方式;(一般使用)

(4)在 Spring 进行声明式事务管理时,需要使用 AOP 原理

(5)Spring 事务管理 API

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

[Spring] Spring5——事务简介_第6张图片

2、声明式事务管理——注解方式

(1)在 spring 配置文件中配置事务管理器

  • 事务管理器同样使用 set 方法赋值 dataSource,因此也需要使用


    

(2)在 spring 配置文件中开启事务注解

  • 在配置文件中,引入名称空间 tx;
xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
  • 开启事务注解,指定我们配置的事务管理器;

(3)为 service 类或 service 类方法添加事务注解 @Transactional

  • 如果把 @Transactional 添加类上面,这个类里面所有的方法都添加事务;
  • 如果把 @Transactional 添加类方法上面,仅为这个方法添加事务;

[Spring] Spring5——事务简介_第7张图片

(4)输出结果

  • 测试代码和前面示例的一样,而此次没有出现转出人少钱、接收人没有多钱的情况了,说明事务回滚成功。

[Spring] Spring5——事务简介_第8张图片

3、声明式事务管理——注解的参数配置

在声明式的事务处理中,要配置一个切面,其中就用到了 propagation 等多个参数。需要明确的是,他们本质上都是对类方法被调用时的一种描述

(1)propagation 事务传播行为

propagation 表示这些方法怎么使用事务,是用还是不用。其常用属性有 3 种:

  • REQUIRED:支持(加入)当前事务,如果当前没有事务,就新建一个事务。(默认、常用)
  • REQUIRED_NEW:必须新建事务,如果当前存在事务,把当前事务挂起
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
@Transactional(propagation = Propagation.REQUIRED)

(2)isolation 事务隔离级别

多事务的并发操作容易引发读问题,事务的隔离级别会产生不同的读问题:脏读、不可重复读、幻读。

(2-1)名词解释 

  • 脏读(脏数据):事务 A 修改了数据 D,但还未提交,就被事务 B 读取了,如果此时 A 进行了回滚,那么 B 读到的数据就是脏数据。依据脏数据所做的操作很可能不正确。
  • 不可重复读:事务 A 需要多次读取数据 D,如果在 A 下一次读取 D 之前,事务 B 修改了 D 并提交,那么 A 前后读取到的数据就不一致了。此情况称为不可重复读。
  • 幻觉读:一个未提交的事务 A,读取到了另一个已经提交的事务 B 所添加的数据 D。

(2-2)设置事务隔离级别解决读问题

  • 默认一般是:REPEATABLE READ,可重复读。

[Spring] Spring5——事务简介_第9张图片

(2-3)示例 

@Transactional(isolation =  Isolation.SERIALIZABLE)

(3)timeout 超时时间

  • 事务需要在一个规定时间内完成提交,如果超时则会回滚。默认值是 -1 ,设置时间以秒单位进行计算。

(4)readOnly 是否只读

  • 读:查询操作,写:添加修改删除操作;
  • readOnly 默认值 false,表示可以查询,也可以进行添加修改删除操作;
  • 设置 readOnly 值是 true 之后,只能查询;

(5)rollbackFor 回滚

  • 设置出现哪些异常,则进行回滚

(6)noRollbackFor 不回滚

  • 设置出现哪些异常,不进行回滚

4、声明式事务管理——XML方式

(1)配置事务管理器

与注解方式配置事务管理器一致。



    

(2)配置通知

在 AOP 中讲过,通知就是增强的逻辑部分,加上源代码部分,构成动态代理。



    
    
        
         
         
    

(3)配置切入点和切面

把事务加到哪些类、哪些方法。作用与注解方式的 @Transactional 一致



    
    
    
    

5、声明式事务管理——完全注解开发(详细)

前面的注解方式中,还是有一部分需要编写配置文件。

(1)创建数据库配置文件

prop.driverClassName = org.postgresql.Driver
prop.url = jdbc:postgresql://localhost:5432/MyDatabase
prop.username = postgres
prop.password = 123456

(2)创建 jdbc 配置类,替代 xml 配置文件中的数据库连接池

  • @Bean:https://blog.csdn.net/joyride_run/article/details/133611380
  • @PropertySource:用于加载 Properties 属性配置文件,它需要指定配置文件所在的路径和文件名称。(classpath 详见:https://blog.csdn.net/y_chengbo/article/details/110118195)
  • @Value:用于将基本数据类型的值注入到成员变量,它等价于 中的 value 属性。 在指定值时如果值是变量,则需要使用 ${变量名称} 的方式注入,如果不使用 ${} 的话则把字符赋值给变量
package com.demo.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.Connection;

@PropertySource("classpath:dataSource.properties")
public class JdbcConfig {
    @Value("${prop.driverClassName}")
    private String driver;

    @Value("${prop.url}")
    private String url;

    @Value("${prop.username}")
    private String username;

    @Value("${prop.password}")
    private String password;

    // 创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

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

    // 创建 connection 对象
    @Bean
    public Connection getConnection(DataSource dataSource) {
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return conn;
    }

}

(3)创建 transaction 配置类,替代 xml 中的事务管理

  • @EnableTransactionManagement:开启事务
package com.demo.config;

import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

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

(4)创建 Spring 配置类

  • @Configuration: 表示当前的类是一个配置类,它就相当于 Spring 的 xml 配置文件
  • @ComponentScan:注解扫描,用于扫描指定包下的哪些类添加了 @Component、@Service、@Repository、@Controller、@Autowired 等注解  
  • @Import: 导入别的配置类
package com.demo.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan({"com.demo"})
@Import({JdbcConfig.class})
public class SpringConfig {
    
}

(5)测试代码

import com.demo.config.transactionConfig;
import com.demo.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.math.BigDecimal;

public class remittanceTest {

    @Test
    public void completeAnnotation() {
        ApplicationContext context = new AnnotationConfigApplicationContext(transactionConfig.class);
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.remittance("wyt", "gyt", new BigDecimal(25));
    }
}

(6)运行结果

原本两个 user 的 balance 都是 50,现在转账了 25:

[Spring] Spring5——事务简介_第10张图片

若给转账功能手动引入一个异常,观察事务管理是否起作用:

[Spring] Spring5——事务简介_第11张图片

[Spring] Spring5——事务简介_第12张图片

显然遇到异常成功回滚,没有破坏一致性。 

你可能感兴趣的:(Spring,spring,java,数据库)