Article directory
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 !
1. Service layer
StudentService
The definition method is as follows (studentBO is the submitted student information):
/**
* 提交学生信息,并申请借阅证
**/
void apply(StudentBO studentBO);
StudentServiceImpl
The 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:
Interlude: A small field adjustment.
Foris_approved
the 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:
- Modified configuration: Change the javaType from to and double-click generate
generatorConfig.xml
to regenerate, as shown below:is_approved
Boolean
Integer
- After generation, please confirm that the Student and StudentExample classes have been modified.
- Then, manually modify
StudentBO
the 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,有则update
so 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:
- There is no need to open a transaction for query logic
- 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 @Transactional
the 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 TransactionTemplate
the very easy-to-use Template encapsulated by Spring for us! It itself inherits from TransactionDefinition
, is injected internally PlatformTransactionManager
, and encapsulates execute
the 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!
The usage is too simple, TransactionTemplate
basic 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 student
passed 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 POST
the request @RequestBody
method, which is defined StudentVO
as 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
constraints
in 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.
Calling the student information query API above returns nothing.
Calling the API implemented in this article apply
, the results are as follows:
Take a look at the database student表
:
Take a look at the database qualification表
:
Everything is as expected, try again 重复提交
:
finally try again insert student
successfully, insert qualification
and an exception will be thrown to see if the student will be rolled back. When I tested, I verify_user_id
temporarily 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