终极篇章——解释Spring事务,全网最全,看不懂不可能

编程入门 行业动态 更新时间:2024-10-25 21:33:05

文章目录

  • Spring事务
    • 0x01_回顾事务的概念
      • 事务是什么?
      • 事务的特点(ACID)
      • 事务的并发问题
        • 脏读(Dirty read)
        • 不可重复读(Unrepeatableread)
        • 幻读 (Phantom read)
      • 事务的隔离级别
      • 学习Spring中事务控制要了解的几个点
    • 0x02_项目搭建
      • 创建模块
      • 导入依赖
      • 准备数据库
      • 项目结构搭建
    • 0x03_注解方式实现事务控制
      • 配置事务控制
    • 0x04_物理事务和逻辑事务
    • 0x05_事务的传播行为
      • PROPAGATION_REQUIRED(默认值)
      • PROPAGATN_REQUIRES_NEW
      • PROPAGATION_SUPPORTS
      • PROPAGATION_MANDATORY
      • NOT_SUPPORTED
      • PROPAGATION_NEVER
    • 0x06_事务的隔离级别
    • 0x07_其他配置事务的参数
    • 0x08_XML方式控制事务
    • 0x09_零XML方式配置事务

Spring事务

欢迎关注公众号“小东方不败”,码字不易!

0x01_回顾事务的概念

相信你在学习mysql或者JDBC的时候一定接触过事务,这里的0x01_回顾事务的概念只作为简单的回顾,方便你回顾起事务的基本概念。

事务是什么?

事务(Transaction)指的是一个操作序列,该操作序列中的多个操作要么都做,要么都不做,是一个不可分割的工作单位,是数据库环境中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。

目前常用的存储引擎有InnoDB(MySQL5.5以后默认的存储引擎)和MyISAM(MySQL5.5之前默认的存储引擎),其中InnoDB支持事务处理机制,而MyISAM不支持。

事务的特点(ACID)

ACID指的是:事务的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。这四个特性简称为ACID特性。


事务处理可以确保除非事务性序列内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的序列,可以简化错误恢复并使应用程序更加可靠。

但并不是所有的操作序列都可以称为事务,这是因为一个操作序列要成为事务,必须满足事务的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability).

原子性

原子是自然界最小的颗粒,具有不可再分的特性。事务中的所有操作可以看做一个原子,事务是应用中不可再分的最小的逻辑执行体

使用事务对数据进行修改的操作序列,要么全部执行,要么全不执行。通常,某个事务中的操作都具有共同的目标,并且是相互依赖的。如果数据库系统只执行这些操作中的一部分,则可能会破坏事务的总体目标,而原子性消除了系统只处理部分操作的可能性。

一致性

一致性是指事务执行的结果必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库中只包含事务成功提交的结果时,数据库处于一致性状态。一致性是通过原子性来保证的

例如:在转账时,只有保证转出和转入的金额一致才能构成事务。也就是说事务发生前和发生后,数据的总额依然匹配。

隔离性

隔离性是指各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务,都是隔离的。也就是说:并发执行的事务之间既不能看到对方的中间状态,也不能相互影响

例如:在转账时,只有当A账户中的转出和B账户中转入操作都执行成功后才能看到A账户中的金额减少以及B账户中的金额增多。并且其他的事务对于转账操作的事务是不能产生任何影响的。

持久性

持久性指事务一旦提交,对数据所做的任何改变,都要记录到永久存储器中,通常是保存进物理数据库,即使数据库出现故障,提交的数据也应该能够恢复。但如果是由于外部原因导致的数据库故障,如硬盘被损坏,那么之前提交的数据则有可能会丢失。

事务的并发问题

事务的并发问题分为以下三种

脏读(Dirty read)

当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

不可重复读(Unrepeatableread)

指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。

幻读 (Phantom read)

幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读

不可重复读和幻读区别

不可重复读的重点是修改,幻读的重点在于新增或者删除。

解决不可重复读和幻读的问题

解决不可重复读只需锁住满足条件的行,解决幻读需要锁表

事务的隔离级别

事务的隔离级别用于决定如何控制并发用户读写数据的操作。数据库是允许多用户并发访问的,如果多个用户同时开启事务并对同一数据进行读写操作的话,有可能会出现脏读、不可重复读和幻读问题,所以MySQL中提供了四种隔离级别来解决上述问题

事务的隔离级别从低到高依次为READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READ以及SERIALIZABLE,隔离级别越低,越能支持高并发的数据库操作。

隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED×
REPEATABLE READ××
SERIALIZABLE×××

学习Spring中事务控制要了解的几个点

1.Spring事务控制的实现有两种编程风格:

  • 编程式:其实就是try-catch-finally,举个例子:
try{
           ......
            //设置事务手动提交
            ......
        }catch (Exception e){
  					//捕获异常
            ......
            //回滚事务
            ......
        }finally {
            //提交事务
            ......
        }

编程式进行事务控制有以下缺点:

  1. 太多无用的固定代码
  2. 如果一个请求需要调用多个服务接口,难以更精细的控制事务
  3. 跨多种底层数据层,如jdbc,mybatis,hibernate,jta,难以统一编码方式。

  • 声明式(重点)

有两种开发方法:注解和XML,其中XML配置起来略微麻烦,不建议掌握,了解即可。但是注解方式进行事务控制是必须掌握的。

Spring声明式事务的实现方式,底层就是AOP,AOP的底层就是动态代理。

spring提供了声明式事务,使得我们不用关注底层的具体实现,屏蔽了多种不同的底层实现细节,为了支持多种复杂业务对事务的精细控制,spring提供了事务的传播属性,结合声明式事务,成就了一大事务利器。

0x02_项目搭建

创建模块

创建一个标准的maven模块即可(不勾选任何archetype)

导入依赖

在pom.xml中导入依赖:

注意,个别依赖,需要根据你本地的环境而更改,比如mysql的驱动,我的mysql版本是8,所以用的驱动是下面这样的。

<dependencies>
    <!--        Spring核心容器包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.23</version>
    </dependency>
    <!--        Spring切面包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.23</version>
    </dependency>
    <!--        druid数据源(连接池)-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>
    <!--        spring aop联盟包 包含了针对面向切面的接口。 通常Spring等其它具备动态织入功能的框架依赖此包。-->
    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>1.0</version>
    </dependency>
    <!--        mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
    </dependency>
    <!--        SpringJDBC包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.23</version>
    </dependency>
    <!--        spring映射依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.3.23</version>
    </dependency>
    <!--        apache commons日志包-->
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <!--        Spring事务控制包tx-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.23</version>
    </dependency>
    <!--        junit测试的依赖-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <!--        lombok  在实体类中使用注解,可以不用自己写get,set,toString,构造方法等方法-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
        <scope>provided</scope>
    </dependency>

</dependencies>

准备数据库

这里直接提供一个account表的建表语句:

/*
 Navicat Premium Data Transfer

 Source Server         : testconn
 Source Server Type    : MySQL
 Source Server Version : 80026
 Source Host           : localhost:3306
 Source Schema         : mydb

 Target Server Type    : MySQL
 Target Server Version : 80026
 File Encoding         : 65001

 Date: 05/11/2022 20:12:33
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for accout
-- ----------------------------
DROP TABLE IF EXISTS `accout`;
CREATE TABLE `accout` (
  `id` int DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `money` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of accout
-- ----------------------------
BEGIN;
INSERT INTO `accout` (`id`, `name`, `money`) VALUES (1, 'Amy', '10000');
INSERT INTO `accout` (`id`, `name`, `money`) VALUES (2, 'Bob', '10000');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

项目结构搭建

resources目录下的两个配置文件:

jdbc.properties

注意:

1.根据你的实际情况修改properties配置文件,需要更改用户名和密码的话。

2.其次如果你的mysql版本是8,建议用下面的URL(当然要连接的数据库名可能需要根据你的数据库更改),特别是后面的参数?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

以避免发生不可描述的错误

3.如果你的mysql版本不是8,那么请注意,你想要自己百度寻找适合你的版本的URL写法。(其次,也要根据你的情况,修改依赖)

jdbc_username=root
jdbc_password=root
jdbc_driver=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

applicationContext.xml


pojo/Account实体类:

package com.bones.pojo;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    private Integer id;
    private String name;
    private Integer money;
}

service/AccountService接口

package com.bones.service;

public interface AccountService {
    int transferMoney(int from,int to ,int money);
}

service/impl/AccountServiceImpl实现类:

package com.bones.service.impl;

import com.bones.dao.AccountDao;
import com.bones.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao  accountDao;
    @Override
    public int transferMoney(int from, int to, int money) {
        int rows = 0;
        rows += accountDao.transferMoney(from,0-money);
        int i = 1/0;
        rows += accountDao.transferMoney(to, money);
        return rows;
    }
}

dao/AccountDao接口:

package com.bones.dao;

public interface AccountDao {
    int transferMoney(int id,int money);
}

dao/impl/AccountDaoImpl

package com.bones.dao.impl;

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

@Repository
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public int transferMoney(int id, int money) {
        String sql = "update account set money = money +? where id = ?";
        return jdbcTemplate.update(sql, money, id);
    }
}

test包下面的测试类:

package com.bones.test01;

import com.bones.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test1 {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    @Test
    public void testTransferMoney(){
        AccountService accountService = context.getBean(AccountService.class);
        int res = accountService.transferMoney(1, 2, 1000);
        System.out.println("res = " + res);

    }
}

上面的service层的实现类中,模拟了一个异常int i = 1/0,为的是模拟并发情况下的异常情况。(虽然 / by zero不是一个并发异常):

@Override
public int transferMoney(int from, int to, int money) {
    int rows = 0;
    rows += accountDao.transferMoney(from,0-money);
    int i = 1/0;
    rows += accountDao.transferMoney(to, money);
    return rows;
}

异常前的方法:accountDao.transferMoney(from,0-money);是执行了并且提交了事务,但是异常之后的方法accountDao.transferMoney(to, money);,没有执行。此时账户就出现了不一致。所以应该将一个转出钱和另一个账户收到钱作为一个事务,进行执行,提交。如果出现异常,那么已经执行的方法应该回滚,然后不管是否出现异常都应该提交。

下面实验完成Spring中的控制事务。

0x03_注解方式实现事务控制

配置事务控制

applicationContext.xml添加控制事务控制的配置:

<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

如果报红,那么就是没有配置tx的命名空间:

然后在需要事务控制的方法前面加上@Transactional注解即可。

@Transactional注解也可以加在类上,此时表示对于这个类的所有方法进行事务控制。

0x04_物理事务和逻辑事务

Spring事务模块的文档中有描述了两个专业术语,分别是物理事务(physical transaciton)逻辑事务(logic transaction)。读者可能会一头雾水,因为Spring文档中并没有对这两个术语进行过多的介绍。

物理事务
在JDBC的事务API中展示了事务对应的是Connection,一个Connection只能开启一个事务。所谓物理事务指的就是一个Connection。

逻辑事务
在一个复杂的业务系统中,可能会调用多个service,每个service都有自己的事务(标注了@Transactional),此时我们需要根据事务传播方式(Propagation)来决定当前事务的行为(比如要挂起创建新事务,还是加入当前事务)。

我们可以认为每个注解了@Transactional的方法都是一个逻辑事务,这些逻辑事务被Spring事务管理,Spring会根据事务传播方式来决定是否开启新事务。


一个物理事务对应着一个JDBC Connection。一个Connection可能会存在多个逻辑事务,这取决于Propagation的配置。

0x05_事务的传播行为

这个主要说的是@Transactionalpropagation配置

@Transactional源码中,有默认值Propagation.REQUIRED

@Transactional源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    String[] label() default {};

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    String timeoutString() default "";

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

TransactionDefinition类中,spring提供了6种传播属性,接下来分别用简单示例来说明。

面试会问到的是PROPAGATION_REQUIRED(默认值)和PROPAGATN_REQUIRES_NEW

下文提到的加入当前事务,指的是底层使用同一个Connection,但是事务状态对象是可以重新创建的,并不影响。文章提到的当前只存在一个事务,表示的是共用底层的一个Connection,而不在乎创建了多少个事务状态对象(TransactionStatus,是对事务状态的抽象)。

PROPAGATION_REQUIRED(默认值)

如果当前已经存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。

(下面的例子是基于上面的代码的。)

数据库中重置为money都是10000

@Transactional
public void service(){
    serviceA();
    serviceB();
}

@Transactional
void serviceA(){
    accountDao.transferMoney(1,1000);
}
@Transactional
void serviceB(){
    accountDao.transferMoney(2,1000);
}

serviceA serviceB都声明了事务,默认情况下,propagation=PROPAGATION_REQUIRED,整个service调用过程中,只存在一个共享的事务,当有任何异常发生的时候,所有操作回滚。

现在在serviceA或者serviceB或者service任何一个方法中制造异常,都会回滚。

PROPAGATN_REQUIRES_NEW

如果当前存在事务,先把当前事务相关内容封装到一个实体,然后重新创建一个新事务,接受这个实体为参数,用于事务的恢复。更直白的说法就是暂停当前事务(当前无事务则不需要),创建一个新事务。 针对这种情况,两个事务没有依赖关系,可以实现新事务回滚了,但外部事务继续执行。

@Transactional
public void service(){
    serviceA();
    try {
        serviceB();
    }catch (Exception e){

    }
}
@Transactional
void serviceA(){
    accountDao.transferMoney(1,1000);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
void serviceB(){
    int i = 1/0;
    accountDao.transferMoney(2,1000);
}

当调用service接口时,由于serviceB使用的是REQUIRES_NEW,它会创建一个新的事务,但由于serviceB抛出了运行时异常,导致serviceB整个被回滚了,而在service方法中,捕获了异常,所以serviceA是正常提交的。 注意,service中的try … catch 代码是必须的,否则service也会抛出异常,导致serviceA也被回滚。

PROPAGATION_SUPPORTS

如果当前已经存在事务,那么加入该事务,否则创建一个所谓的空事务(可以认为无事务执行)。

public void service(){
    serviceA();
    throw new RuntimeException();
}

@Transactional(propagation=Propagation.SUPPORTS)
void serviceA(){
    accountDao.transferMoney(1,1000);
}

service执行时当前没有事务,所以service中抛出的异常不会导致 serviceA回滚

PROPAGATION_MANDATORY

当前方法必须在一个事务中运行,如果没有事务,将抛出异常

@Transactional(propagation = Propagation.REQUIRED)
public void service(){
    serviceA();
    int i = 1/0;
}

@Transactional(propagation=Propagation.MANDATORY)
void serviceA(){
    accountDao.transferMoney(1,1000);
}

service()调用serviceA()方法的时候,因为外部已经存在了物理事务(通过Propagation.REQUIRED创建),那么serviceA()将会加入这个事务,如果内部事务回滚了,外部事务也会回滚,这一点和Propagation.REQUIRED相同。

NOT_SUPPORTED

如果当前存在事务,挂起当前事务,然后新的方法在没有事务的环境中执行,没有spring事务的环境下,sql的提交完全依赖于 defaultAutoCommit属性值

PROPAGATION_NEVER

如果当前存在事务,则抛出异常,否则在无事务环境上执行代码。

总结一下:

事务传播行为类型说明
PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择(默认)。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

0x06_事务的隔离级别

  1. DEFAULT (默认)
    这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。
    MySQL默认REPEATABLE_READ

Oracle默认READ_COMMITTED

  1. READ_UNCOMMITTED (读未提交)
    这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。

  2. READ_COMMITTED (读已提交)
    保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。

  3. REPEATABLE_READ (可重复读)
    这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读。

  4. SERIALIZABLE(串行化)
    这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读。

0x07_其他配置事务的参数

timeout 超时时间

事务一定要在多长时间之内提交,如果不提交就会回滚

readOnly 只读事务

事务是否只能读取数据库的数据,如果为true,则不允许进行增删改

rollbackFor 指定发生回滚的异常

当方法发生哪些异常时才会回滚

noRollbackFor 指定不发生回滚的异常

当方法发生哪些异常时,不会回滚

0x08_XML方式控制事务

实际中XML方式控制事务用的很少,因为配置较为繁琐。

关键的配置:

<!--    配置通知-->
<tx:advice id="txAdvice">
<!--        配置事务参数-->
    <tx:attributes>
        <tx:method name="transferMoney" isolation="DEFAULT" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!--    配置AOP-->
<aop:config>
<!--        配置切入点-->
    <aop:pointcut id="pt" expression="execution(* com.bones.service.AccountService.transferMoney(..))"/>
<!--        配置切面-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>

完整的applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework/schema/beans"
       xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework/schema/p"
       xmlns:c="http://www.springframework/schema/c"
       xmlns:util="http://www.springframework/schema/util"
       xmlns:context="http://www.springframework/schema/context"
       xmlns:aop="http://www.springframework/schema/aop"
       xmlns:tx="http://www.springframework/schema/tx"
       xsi:schemaLocation="http://www.springframework/schema/beans
       http://www.springframework/schema/beans/spring-beans.xsd
       http://www.springframework/schema/util
       http://www.springframework/schema/util/spring-util.xsd
       http://www.springframework/schema/context
       http://www.springframework/schema/context/spring-context.xsd
        http://www.springframework/schema/aop
        http://www.springframework/schema/aop/spring-aop.xsd
         http://www.springframework/schema/tx
        http://www.springframework/schema/tx/spring-tx.xsd
">
<!--    Spring注解扫描-->
    <context:component-scan base-package="com.bones"/>
<!--    读取properties的配置-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
<!--    配置druid数据源(连接池)-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc_url}"/>
        <property name="username" value="${jdbc_username}"/>
        <property name="password" value="${jdbc_password}"/>
        <property name="driverClassName" value="${jdbc_driver}"/>
    </bean>
    <!--    配置JdbcTemplate对象,并向里面注入DataSource对象(druid)-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--        通过set方法注入druid数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

<!--    配置通知-->
    <tx:advice id="txAdvice">
<!--        配置事务参数-->
        <tx:attributes>
            <tx:method name="transferMoney" isolation="DEFAULT" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

<!--    配置AOP-->
    <aop:config>
<!--        配置切入点-->
        <aop:pointcut id="pt" expression="execution(* com.bones.service.AccountService.transferMoney(..))"/>
<!--        配置切面-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>


</beans>

0x09_零XML方式配置事务

其实XML方式在springboot中就会淘汰,变成yml配置。所以零XML方式配置事务也作为了解即可(其实就是准备一个配置类SpringConfig)。

SpringConfig.xml

package com.bones.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.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
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.bones")//扫描哪个包
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement //开启注解控制事务
public class SpringConfig {
    @Value("${jdbc_username}")
    private String username;
    @Value("${jdbc_password}")
    private String password;
    @Value("${jdbc_url}")
    private String url;
    @Value("${jdbc_driver}")
    private String driver;


    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setPassword(password);
        druidDataSource.setUsername(username);
        druidDataSource.setUrl(url);
        druidDataSource.setDriverClassName(driver);
        return druidDataSource;
    }

    @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;
    }

}

注意:

  • @Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器

  • @EnableTransactionManagement 开启注解控制事务

  • @Value("${jdbc_username}")读取properties配置文件中的内容

  • @Bean
        public JdbcTemplate getJdbcTemplate(DataSource dataSource){
            JdbcTemplate jdbcTemplate = new JdbcTemplate();
            jdbcTemplate.setDataSource(dataSource);
            return jdbcTemplate;
        }
    

    上面初始化JdbcTemplate对象的方法中,需要传入参数DataSource dataSource,特别注意,这个参数是从Spring容器中取的。getDataSourceTransactionManager方法也是一样。

更多推荐

终极篇章——解释Spring事务,全网最全,看不懂不可能

本文发布于:2023-06-13 07:28:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1356752.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:不可能   篇章   最全   看不懂   事务

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!