7.16 SpringBoot project practice [Students settled in] (Part 2): Correctly understand programmatic transactions and declarative transactions

CSDN achieves 100 million technical people


Preface

Through the above, we have implemented the first API of [Student Registration]: querying student information. The next process is usually as follows: If the student has not settled in, the student will be prompted to fill in the information and apply for borrowing qualifications (borrowing card). This is also This is exactly what this article aims to achieve!

I have done business analysis in [Database Design - MySQL]: submitting student information (inserting into the student table), applying for a borrowing card (inserting into the qualification table), these two SQL operations are one-step operations, that is, atomic operations, so they will be used Database transaction!

We talked about declarative transactions @Transactional in [ 7.8 ] , but sometimes programmatic transactions are still needed , so this article will combine practical scenarios to help you correctly understand programmatic transactions and declarative transactions !

Insert image description here


1. Service layer

StudentServiceThe definition method is as follows (studentBO is the submitted student information):

/**
 * 提交学生信息,并申请借阅证
 **/
void apply(StudentBO studentBO);

StudentServiceImplThe corresponding empty implementation of:

@Override
public void apply(StudentBO studentBO) {
    
    
    
}

1. Submit student information

Let’s take a look at the database table design first:

Insert image description here

Interlude: A small field adjustment.
For is_approvedthe field, considering that it will be more convenient to query later, I decided to change it to: whether the application was approved (0-to be reviewed, 1-passed, 2-failed).
Modification method:

  1. Modified configuration: Change the javaType from to and double-click generategeneratorConfig.xml to regenerate, as shown below:is_approvedBooleanInteger
    Insert image description here
  2. After generation, please confirm that the Student and StudentExample classes have been modified.
  3. Then, manually modify StudentBOthe classprivate Integer isApproved;

Returning to the topic, the implementation of submitting student information is as follows:

@Override
public void apply(StudentBO studentBO) {
    
    
    // 1. 提交学生信息
    // 初始化基础数据
    studentBO.setIsApproved(ExamineEnum.TO_BE_EXAMINE.getCode());
    studentBO.setIsFrozen(Boolean.FALSE);
    studentBO.setGmtCreate(new Date());
    studentBO.setGmtModified(new Date());
    Student po = CopyUtils.copy(studentBO, Student::new);
    studentMapper.insertSelective(po);
}

Because most audits also have three states, they are 审核状态encapsulated into a public enumeration class:

package org.tg.book.common.enums;

/**
 * 审核状态枚举
 **/
public enum ExamineEnum {
    
    
    TO_BE_EXAMINE(0, "待审核"),
    APPROVED(1, "审核通过"),
    REJECTED(2, "驳回"),
    ;

    Integer code;
    String msg;

    ExamineEnum(Integer code, String msg) {
    
    
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
    
    
        return this.code;
    }

    public void setCode(Integer code) {
    
    
        this.code = code;
    }

    public String getMsg() {
    
    
        return msg;
    }

    public void setMsg(String msg) {
    
    
        this.msg = msg;
    }
}

2. Apply for borrowing qualifications

We are designed so that you can apply multiple times and the application record will be saved each time!

First inject QualificationMapper:

@Autowired
private QualificationMapper qualificationMapper;

For insertion qualification表, you only need to provide the student ID and application status (0-pending review, 1-passed, 2-failed), and the implementation is as follows:

// 2. 申请借阅资格
Qualification qualification = new Qualification();
qualification.setStudentId(student.getId());
qualification.setStatus(ExamineEnum.TO_BE_EXAMINE.getCode());
qualification.setGmtCreate(new Date());
qualification.setGmtModified(new Date());
qualificationMapper.insertSelective(qualification);

3. Resubmit

The implementation of the first two steps is actually lacking 重新提交, so the complete process should be 先查询学生信息,

  • If it does not exist, go directly to the first two steps to submit;
  • If it exists, it needs to be verified whether it has passed. If it has passed, it cannot be resubmitted, otherwise it must be resubmitted.

Write the 查询sum 两步校验in front, the code is as follows:

// 查询学生
Student student = studentMapperExt.selectByUserId(studentBO.getUserId());
if (student != null) {
    
    
    // 如果已审核通过,不可以重新提交
    Assert.ifTrue(ExamineEnum.APPROVED.getCode().equals(student.getIsApproved()), "已审核通过,请勿重新提交!");
    // 如果未通过, 看是否有待审核的借阅记录
    QualificationExample example = new QualificationExample();
    example.createCriteria().andStudentIdEqualTo(student.getId())
            .andStatusEqualTo(ExamineEnum.TO_BE_EXAMINE.getCode());
    long count = qualificationMapper.countByExample(example);
    Assert.ifTrue(count > 0, "已提交待审核,请勿重新提交!");
}

Submitting student information also needs to support existing situations, 无则insert,有则updateso the modified code is as follows:

// 1. 提交学生信息
studentBO.setIsApproved(ExamineEnum.TO_BE_EXAMINE.getCode());
if (student == null) {
    
    
	// 初始化基础数据
    studentBO.setIsFrozen(Boolean.FALSE);
    studentBO.setGmtCreate(new Date());
    studentBO.setGmtModified(new Date());
    student = CopyUtils.copy(studentBO, Student::new);
    studentMapper.insertSelective(student);
} else {
    
    
    // 已存在,只拷贝新录入的赋值属性
    CopyUtils.copyPropertiesIgnoreNull(studentBO, student);
    studentMapper.updateByPrimaryKeySelective(student);
}

At this point, the overall process seems to be OK, but there is still a little bit left, that is 事务!

4. Affairs

We have used declarative transactions before. It is very easy to use and very cool. It has been explained in detail in [7.8]. You only need to add annotations to the method:

@Transactional(rollbackFor = Exception.class)

Therefore, this article mainly talks about programmatic transactions !

As we all know, although transactions are good, we should avoid 长事务them as much as possible and give them to the database as much as possible 减压. Therefore, the granularity of transactions should be as small as possible, and code unrelated to transactions should be removed as much as possible. If you can not enable it, there is no need to enable it!

Thinking about it carefully, our scenario is suitable. The places where we really need the affairs are [1. Submit student information] and [2. Apply for borrowing qualifications], so:

  1. There is no need to open a transaction for query logic
  2. It is more necessary to open transactions for verification logic because it may be rolled back

If you want to use declarative transactions at this time , it is not appropriate, because @Transactionalthe granularity is applied 方法. If you still want to use it, you need to split it into two methods, and you need to call two methods of the same type, and you also need to ensure that the AOP annotations take effect! So this is where programmatic transactions come in, because it can act on 代码块, and is much more granular and flexible!

OK, how to use programmatic transactions ?
What I recommend is TransactionTemplatethe very easy-to-use Template encapsulated by Spring for us! It itself inherits from TransactionDefinition, is injected internally PlatformTransactionManager, and encapsulates executethe generic methods: rollback and submission are clearly arranged. If you are interested, you can take a look at the process arrangement, so let’s use it!

Insert image description here

The usage is too simple, TransactionTemplatebasic template usage:

transactionTemplate.execute(action -> {
    
    
    // 事务执行的代码。。。
    // TODO 1。。。
    // TODO 2。。。
    
    // 最后根据需要返回,无返回值则return null
    return null;
});

Then for the specific application, first inject TransactionTemplate:

@Autowired
private TransactionTemplate transactionTemplate;

Then, you only need to pass in [1. Submit student information] and [2. Apply for borrowing qualifications] as actions!

Because all are passed in, an error will be reported:Variable used in lambda expression should be final or effectively final

Therefore, the process of building a student can also be mentioned outside the transaction, and then final studentpassed in. The complete implementation code is as follows:

// @Transactional(rollbackFor = Exception.class)
@Override
public void apply(StudentBO studentBO) {
    
    
    // 查询学生
    Student student = studentMapperExt.selectByUserId(studentBO.getUserId());
    if (student != null) {
    
    
        // 如果已审核通过,不可以重新提交
        Assert.ifTrue(ExamineEnum.APPROVED.getCode().equals(student.getIsApproved()), "已审核通过,请勿重新提交!");
        // 如果未通过, 看是否有待审核的借阅记录
        QualificationExample example = new QualificationExample();
        example.createCriteria().andStudentIdEqualTo(student.getId())
                .andStatusEqualTo(ExamineEnum.TO_BE_EXAMINE.getCode());
        long count = qualificationMapper.countByExample(example);
        Assert.ifTrue(count > 0, "已提交待审核,请勿重新提交!");
    }
    
    // 初始化基础数据
    studentBO.setIsApproved(ExamineEnum.TO_BE_EXAMINE.getCode());
    if (student == null) {
    
    
        studentBO.setIsFrozen(Boolean.FALSE);
        studentBO.setGmtCreate(new Date());
        studentBO.setGmtModified(new Date());
        student = CopyUtils.copy(studentBO, Student::new);
    } else {
    
    
        // 已存在,只拷贝新录入的赋值属性
        CopyUtils.copyPropertiesIgnoreNull(studentBO, student);
    }
    
    Student finalStudent = student;
    transactionTemplate.execute(action -> {
    
    
        // 1. 提交学生信息
        if (finalStudent.getId() == null) {
    
    
            studentMapper.insertSelective(finalStudent);
            studentBO.setId(finalStudent.getId());
        } else {
    
    
            studentMapper.updateByPrimaryKeySelective(finalStudent);
        }
        // 2. 申请借阅资格
        Qualification qualification = new Qualification();
        qualification.setStudentId(studentBO.getId());
        qualification.setStatus(ExamineEnum.TO_BE_EXAMINE.getCode());
        qualification.setGmtCreate(new Date());
        qualification.setGmtModified(new Date());
        qualificationMapper.insertSelective(qualification);
        return null;
    });
}

2. Web layer StudentController

After the service layer is implemented, we provide restful API to the outside world by creating StudentController. Because it is mainly new, we use POSTthe request @RequestBodymethod, which is defined StudentVOas follows:

@Data
public class StudentVO implements Serializable {
    
    
    @NotBlank(message = "学号不能为空")
    private String studentNo;
    @NotBlank(message = "学生姓名不能为空")
    private String studentName;
    @NotBlank(message = "学生昵称不能为空")
    private String nickName;
    @NotBlank(message = "学生所属院系不能为空")
    private String department;
    @NotBlank(message = "学生证照片不能为空")
    private String idCardImage;
}

The annotation constraintsin Spring Validation mentioned earlier is used here . This annotation is suitable for String type. It cannot be null after adding it, and size>0 after trim().@NotBlank

Define API: apply code is as follows:

@PostMapping("/apply")
public TgResult<StudentBO> apply(@Valid @RequestBody StudentVO studentVO) {
    
    
    Integer userId = AuthContextInfo.getAuthInfo().loginUserId();
    StudentBO studentBO = CopyUtils.copy(studentVO, StudentBO::new);
    studentBO.setUserId(userId);
    studentService.apply(studentBO);
    return TgResult.ok(studentBO);
}

3. Test

Register a new student account li2gou, userId = 3.

Insert image description here

Calling the student information query API above returns nothing.

Calling the API implemented in this article apply, the results are as follows:

Insert image description here

Take a look at the database student表:

Insert image description here

Take a look at the database qualification表:

Insert image description here

Everything is as expected, try again 重复提交:
Insert image description here
finally try again insert studentsuccessfully, insert qualificationand an exception will be thrown to see if the student will be rolled back. When I tested, I verify_user_idtemporarily set the database table to not allow it to be empty, and finally 成功回滚~

OK, everything is as expected, call it a day!


at last

If you see this and find it helpful, please brush 666. Thank you for your support~

If you want to read more good practical articles, I would like to recommend my practical column –> "Practical Combination of Front-End and Front-End Separation Projects Based on SpringBoot+SpringCloud+Vue", a column jointly created by me and the front-end dog brother, which allows you to start from Quickly acquire enterprise-level standardized project practical experience from 0 to 1!

The specific advantages, planning, and technology selection can be read in the " Opening Chapter "!

After subscribing to the column, you can add my WeChat account and I will provide targeted guidance for each user!

In addition, don’t forget to follow me: Tiangang gg , in case you can’t find me, it’s not easy to miss new articles: https://blog.csdn.net/scm_2008

Guess you like

Origin blog.csdn.net/scm_2008/article/details/133253044