Spring事务

spring事务

Spring 事务详解 | JavaGuide(Java面试+学习指南)

spring事务

事务:操作要么都成功,要么都失败。 具有以下四个特性:

  • 原子性: 操作不可分割
  • 一致性: 事务开始和结束对数据库完整性没有破坏(什么意思)
  • 隔离性:多个事互不干扰
  • 持久性:事务结束后,对数据的修改时永久的, 不会因为系统故障丢失

事务的应用很常见, 如银行卡转账, 你的账户少了1000块,别人的账户多了1000块, 这俩的处理应该要么都成功,要么都失败。 要是因为网络原因,你少了1000, 别人的账户没收到,会造成财产损失。

为了测试用法,采用springboot + mybatisplus + postgreSQL操作数据库, 并利用@Transactional注解测试。项目结构如下:其中sql脚本用于生成数据表

图片名称

数据库准备

maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wang</groupId>
    <artifactId>Study</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Study</name>
    <description>Study</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- mybatis plus 代码生成器 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.4.1</version>
        </dependency>


        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

sql脚本

脚本中创建的表的主键id会自增,创建完毕利用navicat可以看到绑定的序列:

nextval('person_id_seq'::regclass)

CREATE TABLE IF NOT EXISTS person
(
    id  SERIAL PRIMARY KEY,
    name character(100),
    money integer
)
    WITH (
        OIDS=FALSE
        );
ALTER TABLE person
    OWNER TO postgres;
COMMENT ON TABLE person
  IS '这是个人信息表';

spring配置

数据库连接+ sql脚本执行

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/whdb?stringtype=unspecified
    username: postgres
    password: 179530
    driver-class-name: org.postgresql.Driver
    hikari:
      maximum-pool-size: 200
      minimum-idle: 20
      max-lifetime: 20000
      connection-test-query: select 1
      leak-detection-threshold: 30000
      allow-pool-suspension: true

  sql:
    init:
      mode: always
      schema-locations: classpath:schema-all.sql

事务测试

springboot集成mybatis-plus

POJO

@Data
@TableName("person")
public class Person {
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    private String name;
    private Integer money;
}

Mapper: @Mapper注解可以省略,在启动类用@MapperScan("wang.transaction.mapper")

@Mapper
public interface PersonMapper extends BaseMapper<Person> {
}

Service接口

public interface PersonService extends IService<Person> {
}

Service实现类

@Service
public class PersonServiceImpl extends ServiceImpl<PersonMapper,Person> implements PersonService{
}

(这里闹出一个低级错误,在PersonServiceImpl中利用PersonService接口的方法操作数据库,这样会有循环依赖问题)

测试

在App的启动类中执行一次init方法存入两条记录到表person中


@Service
public class DataBaseServiceTest {
    @Autowired
    private PersonService personService;

     @PostConstruct
    public void init() {
         int money = 1000;
         Person p1 = personService.getById(1);
         p1.setMoney(money);
         Person p2 = personService.getById(2);
         p2.setMoney(money);
         personService.updateBatchById(Arrays.asList(p1,p2));

        System.out.println("save successfully");
    }


    @Transactional
    public void run() {
        int money = 200;
        Person p1 = personService.getById(1);
        p1.setMoney(p1.getMoney() + money);
        personService.updateById(p1);
       //  int a = 1 / 0;
        Person p2 = personService.getById(2);
        p2.setMoney(p2.getMoney() - money);
        personService.updateById(p2);
    }
}

注释第28行发现执行符合预期,取消注释执行后发现二者都没有被修改

事务失效的几种场景

聊聊spring事务失效的12种场景,太坑了 - 腾讯云开发者社区-腾讯云 (tencent.com)

Transactional修改的方法不是public

将上面run方法的权限修改为private, 结果发现p1更新了,p2没更新,事务失效

Transactional修改的是final方法

 @Transactional
public final void run() {
	...
}

异常被catch捕获

(其实可以指定异常的类型)

@Transactional
    public void run() {
        int money = 200;
        Person p1 = personService.getById(1);
        p1.setMoney(p1.getMoney() + money);
        personService.updateById(p1);
        try {
            int a = 1 / 0;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        Person p2 = personService.getById(2);
        p2.setMoney(p2.getMoney() - money);
        personService.updateById(p2);
    }

发现p1更新了,p2没更新 , 事务失效

内部调用

内部某个方法A调用一个被Transactional注解修饰的方法B,则B不会生成事务,其中A有没有被@Transactional修饰都是如此。

  @Transactional
    public  void run() {
        int money = 200;
        Person p1 = personService.getById(1);
        p1.setMoney(p1.getMoney() + money);
        personService.updateById(p1);
        int a = 1 / 0;
        Person p2 = personService.getById(2);
        p2.setMoney(p2.getMoney() - money);
        personService.updateById(p2);
    }

    @Transactional
    public  void run2() {
        run();
    }

如果真的想在类中调用自己的事务方法怎么办?将这段事务方法剥离出去,再注入bean调用:

剥离的事务代码

@Component
public class Test {
    @Autowired
    private DataBaseServiceTest dataBaseServiceTest;

   @Bean
    public void start(){
       dataBaseServiceTest.run();
    }
}

未被spring管理

这个没啥好说的,但又容易被忽略

多线程调用

Bean1中事务A (run),开启线程调用Bean2中事务B (run2),分为两种情况:(1)调用A之前发生了异常 -- > 回滚 (2)事务B发生了异-->不回滚

 @Transactional
    public void run() {
        int money = 200;
        Person p1 = personService.getById(1);
        p1.setMoney(p1.getMoney() + money);
        personService.updateById(p1);

        int a = 1 / 0;
        new Thread(() -> {
            int b = 1 / 0;
            dataBaseServiceTest2.run2(money);
        }).start();
    }
@Service
public class DataBaseServiceTest2 {
    @Autowired
    private PersonService personService;

    @Transactional
    public void run2(int money) {
        Person p2 = personService.getById(2);
        p2.setMoney(p2.getMoney() - money);
        personService.updateById(p2);
    }
}

数据库不支持事务

posted @   shmilyt  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示