数据库事务--springboot事务处理

二、简单事务使用 模拟一

1、数据库

db.sql

drop database  if exists  supermarket;
create database supermarket;
use supermarket;
drop table if exists `order_item`;
CREATE TABLE `order_item` (
  `item_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT   '订单ID',
  `order_no` varchar(64) NOT NULL COMMENT '订单号',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `goods_id` bigint(20) NOT NULL COMMENT '商品ID',
  `price` decimal(12,2) NOT NULL COMMENT '单价',
  `count` int(11) NOT NULL COMMENT '数量',
  PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '订单项表';


drop table if exists `good_stock` ;
CREATE TABLE `good_stock` (
  `stock_id` bigint(20) NOT NULL AUTO_INCREMENT  COMMENT '库存ID',
  `goods_id` bigint(20) NOT NULL COMMENT '商品ID',
  `total` int(11) NOT NULL COMMENT '总数量',
  `sold` int(11) NOT NULL COMMENT '已售出',
  PRIMARY KEY (`stock_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '商品库存表';

insert into  good_stock (goods_id,total,sold) values(100000,10,0);

student.sql

DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) NOT NULL,
  `sex` varchar(255) DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8;



INSERT INTO `student` VALUES (1001,'张三','boy'),(1002,'李四','girl'),(1003,'王五','secret'),(1004,'hehe','female'),(1005,'hehe','female'),(1006,'hehe','female'),(1007,'hehe','female'),(1008,'hehe','female'),(1009,'hehe','female');

2、pom.xml

<?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.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.study</groupId>
    <artifactId>springboot-transactional</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-transactional</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

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

</project>

2、实体类

src/main/java/com/study/springboottransactional/entity/Student.java

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


@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Student {
    
    
    private int id;
    private String name;
    private String sex;
}

3、配置信息

src/main/resources/application.properties

#mysql的配置信息
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#支持SQL 输出
spring.jpa.show-sql=true
#format 一下 SQL 进行输出
spring.jpa.properties.hibernate.format_sql=true
#自动生成开启,让表数据会自动跟随entity类的变化而变化
spring.jpa.hibernate.ddl-auto=update
#开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。
mybatis.configuration.mapUnderscoreToCamelCase=true

4、接口

src/main/java/com/study/springboottransactional/repository/StudentRespository.java

@Repository
public interface StudentRespository {
    
    
    @Update(value = "insert into student(name,sex) " +
            "values(#{student.name},#{student.sex})")
    void insertStudent(@Param("student") Student student);

    @Select(value = "select * from student where id=(select @@IDENTITY)")
    Student findStudentByNear();
}

src/main/java/com/study/springboottransactional/service/StudentService.java

@Service
public class StudentService {
    
    
    @Autowired
    private StudentRespository studentRespository;

    public Student addStudent(String name,String sex){
    
    
        Student stu = Student.builder().name(name).sex(sex).build();
        studentRespository.insertStudent(stu);
        //模拟在数据库操作完成之后发生一个异常操作
//        int i = 1/0;
        return studentRespository.findStudentByNear();
    }
}

src/main/java/com/study/springboottransactional/controller/StudentController.java

@RestController  //直接返回数据
public class StudentController {
    
    
    @Autowired
    private StudentService studentService;
    @RequestMapping("stu/add")
    public Object addStudent(@Param("name") String name, @Param("sex") String sex){
    
    
        return studentService.addStudent(name, sex);
    }
}

src/main/java/com/study/springboottransactional/SpringbootTransactionalApplication.java

@MapperScan("com.study.springboottransactional.repository")
@SpringBootApplication
public class SpringbootTransactionalApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(SpringbootTransactionalApplication.class, args);
    }

}

5、未加入异常执行结果

127.0.0.1:8080/stu/add?name=lisi&sex= male

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IdqY9PXX-1690892178853)(003-springboot事务处理.assets/image-20211117214206597.png)]

5、加入异常

src/main/java/com/study/springboottransactional/service/StudentService.java

@Service
public class StudentService {
    
    
    @Autowired
    private StudentRespository studentRespository;

    public Student addStudent(String name,String sex){
    
    
        Student stu = Student.builder().name(name).sex(sex).build();
        studentRespository.insertStudent(stu);
        //模拟在数据库操作完成之后发生一个异常操作
        int i = 1/0;//加入 的异常
        return studentRespository.findStudentByNear();
    }
}

127.0.0.1:8080/stu/add?name=wangwu&sex= male

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8p9fc4QF-1690892178854)(003-springboot事务处理.assets/image-20211117214503479.png)]

查看一下数据库wangwu 是否插入数据库,发现插入了,就有问题

6、解决

加入@Transactional注解

@Service
@Transactional
public class StudentService {
    
    
    @Autowired
    private StudentRespository studentRespository;

    public Student addStudent(String name,String sex){
    
    
        Student stu = Student.builder().name(name).sex(sex).build();
        studentRespository.insertStudent(stu);
        //模拟在数据库操作完成之后发生一个异常操作
//        int i = 1/0;
        return studentRespository.findStudentByNear();
    }
}

删除wang五这条数据再次测试

127.0.0.1:8080/stu/add?name=wangwu&sex= male

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZfiaSdOG-1690892178855)(003-springboot事务处理.assets/image-20211117214846117.png)]

这次数据库中没有插入

三、@Transactional 注解讲解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IN4r5wGD-1690892178855)(003-springboot事务处理.assets/image-20230710204858012.png)]

1、@Transactional的属性

1.1、readOnly 只读

该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写, 默认值为false。 例如:
@Transactional(readOnly=true)

使用这个,就无法增删改,只能读取

@Service
@Transactional
public class StudentService {
    
    
    @Autowired
    private StudentRespository studentRespository;
		@Transactional(readOnly = true)
    public Student addStudent(String name,String sex){
    
    
        Student stu = Student.builder().name(name).sex(sex).build();
        studentRespository.insertStudent(stu);

        return studentRespository.findStudentByNear();
    }
}



127.0.0.1:8080/stu/add?name=wangwu&sex= male 报错

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fkrLQP6T-1690892178855)(003-springboot事务处理.assets/image-20230710205233319.png)]

127.0.0.1:8080/stu/findAl

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-afSGK0gr-1690892178856)(003-springboot事务处理.assets/image-20230710205314478.png)]

1.2、rollbackFor 和 rollbackForClassName

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cKvowDWX-1690892178856)(003-springboot事务处理.assets/image-20211118112142926.png)]

出现某些异常,进行回滚

一般用rollbackFor

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cc7LE9Un-1690892178857)(003-springboot事务处理.assets/image-20211118112248881.png)]

出现某些异常,不进行回滚

一般用noRollbackFor

回滚是有默认值的,runtimeexception、和error

1.3、propagation

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zaR2zF1r-1690892178857)(003-springboot事务处理.assets/image-20211119103504077.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BC5wkh2T-1690892178857)(springboot事务处理.assets/image-20201207192749196.png)]

绝大部分都可以用这个事务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7be5JdOB-1690892178858)(../../../../../网易云/课程笔记/002-单节点系统/005-框架源码/001- Spring框架原理/006-spring事务管理.assets/image-20211117210418845.png)]

儿子干完 老爸再干活

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-41DnSaEx-1690892178858)(../../../../../网易云/课程笔记/002-单节点系统/005-框架源码/001- Spring框架原理/006-spring事务管理.assets/image-20211117210446720.png)]

依靠外人活着

1.4、timeout

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p2hK7MLX-1690892178859)(003-springboot事务处理.assets/image-20211119104204484.png)]

1.5、isolation

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FhAJ4iaV-1690892178859)(003-springboot事务处理.assets/image-20211119105644059.png)]

四、@Transactional中的属性propagation讲解

➢@Transactional(propagation=Propagation.REQUIRED) :如果有事务,那么加入事务,没有的话新建一个(默认情况
下)
➢@Transactional(propagation=Propagation.NOT_ SUPPORTED) :容器不为这个方法开启事务
➢@Transactional(propagation=Propagation.REQUIRES_ NEW):不管是否存在事务,都创建一个新的事 务,原来的挂
起新的执行完毕,继续执行老的事务
➢@Transactional(propagation=Propagation.MANDATORY): 必须在-一个已有的事务中执行,否则抛出异常
➢@Transactional(propagation=Propagation.NEVER) :必须在一个没有 的事务中执行,否则抛出异常(与
Propagation.MANDATORY相反)
➢@Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那
就用事务.如果其他bean没有声明事务,那就不用事务.

1、propagation.REQUIRED

@Transactional(propagation=Propagation.REQUIRED):

  • 当A方法调用B方法的时候

    • 如果A方法没有开启事务,因为此时B方法开启了事务,A方法就会默认开启事务

    • 如果运行的时候B方法发生异常回滚,A方法也会发生回滚

    • 平时使用使用最多的

2、propagation.REQUIRES_NEW

@Transactional(propagation=Propagation.REQUIRES_ NEW):

不管是否存在事务,都创建一个新的事务 ,原来的挂起,新的执行完毕,继续执行老的事务

如果案例中的改成REQUIRES_NEW,如果B方法产生异常回滚,如果A方法没有捕捉处理掉异常,A方法也会回滚

3、propagation. MANDATORY

@Transactional(propagation=Propagation.MANDATORY): 

必须在一个已有的事务中执行,否则抛出异常

4、其他属性使用很少

五、@Transactional中的属性propagation属性timeout

5.1、介绍的demo

➢设置事务的超时时间,单位秒:
➢属性解释:
当某个业务运行的时间超过你的预期时,可以使用
该属性来让该业务抛出异常并且强制回滚;

5.2、使用场景

比如银行业务中,当用户进行取现出票失败时,

可以使用该属性来强制结束业务,进行回滚,减少用户等待的时间。

好处是能告诉你 失败,不会让客户一直刷新界面

5.3、demo

六、propagation属性isolation

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mhz67oCH-1690892178859)(003-springboot事务处理.assets/image-20211120194717013.png)]

一般使用可重复读

七、实战中的应用

1、场景描述

在商城项目中,往往会存在这两张表:订单表和库存表,那么这两张表在很多业务中都是同时成功或失败的,比
如用户下单,退货等操作,所以我们需要将这两个操作放在一个事务中进行控制。

2、数据库

drop database  if exists  supermarket;
create database supermarket;
use supermarket;
drop table if exists `order_item`;
CREATE TABLE `order_item` (
  `item_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT   '订单ID',
  `order_no` varchar(64) NOT NULL COMMENT '订单号',
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `goods_id` bigint(20) NOT NULL COMMENT '商品ID',
  `price` decimal(12,2) NOT NULL COMMENT '单价',
  `count` int(11) NOT NULL COMMENT '数量',
  PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '订单项表';


drop table if exists `good_stock` ;
CREATE TABLE `good_stock` (
  `stock_id` bigint(20) NOT NULL AUTO_INCREMENT  COMMENT '库存ID',
  `goods_id` bigint(20) NOT NULL COMMENT '商品ID',
  `total` int(11) NOT NULL COMMENT '总数量',
  `sold` int(11) NOT NULL COMMENT '已售出',
  PRIMARY KEY (`stock_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 comment '商品库存表';

insert into  good_stock (goods_id,total,sold) values(100000,10,0);

3、实体类

src/main/java/com/study/springboottransactional/entity/OrderItem.java

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderItem {
    
    
    //订单ID
    private Long itemId;
    //订单号
    private String orderNo;
    //用户ID
    private Long userId;
    //商品ID
    private Long goodsId;
    //单价
    private BigDecimal price;
    //数量
    private Integer count;
}

src/main/java/com/study/springboottransactional/entity/GoodStock.java

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GoodStock {
    
    
     //库存ID
    private Long stockId;
    //商品ID
    private Long goodsId;
    //总数量
    private Integer total;
    //已售出
    private Integer sold;
}

src/main/java/com/study/springboottransactional/repository/GoodStockRepository.java

@Repository
public interface GoodStockRepository{
    
    

    @Update("update good_stock set sold=sold+#{reduceCount} where goods_id =#{goodsId} and total>=sold+#{reduceCount};")
    int reduceStock(@Param("goodsId") long goodsId, @Param("reduceCount") int reduceCount);
}

src/main/java/com/study/springboottransactional/repository/OrderItemRepository.java

SpringBoot @Repository解析 - 简书 (jianshu.com)

@Repository
public interface OrderItemRepository{
    
    
    @Update(value = "insert into order_item(order_no,user_id,goods_id,price,count) " +
            "values(#{orderItem.orderNo},#{orderItem.userId},#{orderItem.goodsId},#{orderItem.price},#{orderItem.count})")
    void insertOrderItem(@Param("orderItem") OrderItem orderItem);

    @Select(value = "select * from order_item where item_id=(select @@IDENTITY)")
//    @Results({
    
    
//            @Result(property = "itemId", column = "item_id"),
//            @Result(property = "orderNo", column = "order_no"),
//            @Result(property = "userId",column = "user_id"),
//            @Result(property = "goodsId",column = "goods_id")
//    })
    OrderItem findOrderItemByNear();
}

src/main/java/com/study/springboottransactional/service/OrderService.java

@Service
public class OrderService {
    
    
    @Autowired
    private OrderItemRepository orderItemRepository;
    @Autowired
    private GoodStockRepository goodStockRepository;

    public OrderItem addOrder(long goodsId, long userId, int count){
    
    
        //创建订单
        OrderItem orderItem = OrderItem.builder().orderNo(UUID.randomUUID().toString().replace("-", "")).
                price(new BigDecimal(goodsId)).userId(userId).count(count).goodsId(goodsId).build();
        orderItemRepository.insertOrderItem(orderItem);
        //扣减库存
        int updateRows = goodStockRepository.reduceStock(goodsId, count);
        if(updateRows <= 0){
    
    
            throw new IllegalArgumentException("下单失败,库存不足!");
        }
//        if(true) {
    
    
//            throw new RuntimeException("手动抛出异常,但是不回滚");
//        }
        OrderItem orderItemByNear = orderItemRepository.findOrderItemByNear();
        return orderItemByNear;
    }
}

127.0.0.1:8080/add/order?goodsld= 00000&count= 1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pR1pG1b9-1690892178860)(003-springboot事务处理.assets/image-20230710211707659.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nv4Zyhtg-1690892178860)(003-springboot事务处理.assets/image-20230710211726169.png)]

库存表的数量是10,但是却卖了11件 ,total为库存数量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D1IyzJOm-1690892178860)(003-springboot事务处理.assets/image-20230710212009482.png)]

以下是卖出的数量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KL4CNR60-1690892178861)(003-springboot事务处理.assets/image-20230710211832856.png)]

@Service
public class OrderService {
    
    
    @Autowired
    private OrderItemRepository orderItemRepository;
    @Autowired
    private GoodStockRepository goodStockRepository;

    @Transactional
    public OrderItem addOrder(long goodsId, long userId, int count){
    
    
        //创建订单
        OrderItem orderItem = OrderItem.builder().orderNo(UUID.randomUUID().toString().replace("-", "")).
                price(new BigDecimal(goodsId)).userId(userId).count(count).goodsId(goodsId).build();
        orderItemRepository.insertOrderItem(orderItem);
        //扣减库存
        int updateRows = goodStockRepository.reduceStock(goodsId, count);
        if(updateRows <= 0){
    
    
            throw new IllegalArgumentException("下单失败,库存不足!");
        }
//        if(true) {
    
    
//            throw new RuntimeException("手动抛出异常,但是不回滚");
//        }
        OrderItem orderItemByNear = orderItemRepository.findOrderItemByNear();
        return orderItemByNear;
    }
}


可以@Transactional(noRollbackFor = RuntimeException.class)加上这个不回滚

猜你喜欢

转载自blog.csdn.net/weixin_39213232/article/details/132050077