7.16 SpringBootプロジェクト演習【学生が定着しました】(その2):プログラム的トランザクションと宣言的トランザクションを正しく理解する

CSDN が技術人材 1 億人を達成


序文

以上により、[学生登録]の最初のAPIである学生情報の照会が実装できましたので、次のプロセスは通常次のとおりです: 学生が定着していない場合は、学生に情報を入力して借入を申請するように求められます資格(借入カード). これもまた、この記事の目的です!

[データベース設計 - MySQL] でビジネス分析を行いました。学生情報の送信 (学生テーブルへの挿入)、借用カードの申請 (資格テーブルへの挿入)、これら 2 つの SQL 操作はワンステップ操作です。アトミック操作なので、データベーストランザクションがます使用され

[ 7.8 ] で宣言的トランザクション @Transactionalについて説明しましたが、それでもプログラムによるトランザクションが必要な場合もあります。そのため、この記事では、プログラムによるトランザクション宣言的なトランザクションを正しく理解するのに役立つ実践的なシナリオを組み合わせます

ここに画像の説明を挿入します


1. サービス層

StudentService定義方法は以下の通りです(studentBOは提出された学生情報)。

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

StudentServiceImpl対応する空の実装:

@Override
public void apply(StudentBO studentBO) {
    
    
    
}

1. 学生情報を提出する

まずデータベースのテーブル設計を見てみましょう。

ここに画像の説明を挿入します

幕間: フィールドの小さな調整です。
このフィールドについてはis_approved、後でクエリする方が便利であることを考慮して、申請が承認されたかどうか (0-審査予定、1-合格、2-不合格) に変更することにしました。
修正方法:

  1. 変更された構成: 以下に示すように、javaType を から に変更し「生成」generatorConfig.xmlダブ​​ルクリックして再生成します。is_approvedBooleanInteger
    ここに画像の説明を挿入します
  2. 生成後、Student クラスと StudentExample クラスが変更されていることを確認してください。
  3. 次に、StudentBOクラスを手動で変更しますprivate Integer isApproved;

話に戻り、学生情報の送信の実装は次のようになります。

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

ほとんどの監査には 3 つの状態があるため、それらは审核状态パブリック列挙クラスにカプセル化されます。

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. 借入資格の申請

何度でも応募でき、その都度応募記録が残る仕様になっております!

最初の注入QualificationMapper:

@Autowired
private QualificationMapper qualificationMapper;

挿入の場合qualification表、学生 ID と申請ステータス (0-審査保留、1-合格、2-不合格) を指定するだけで済みます。実装は次のとおりです。

// 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. 再提出

最初の 2 つのステップの実装が実際には不足している重新提交ため、完全なプロセスは次のようになります先查询学生信息

  • 存在しない場合は、最初の 2 つのステップに直接進んで送信してください。
  • 存在する場合は、通過しているかどうかを確認する必要があり、通過している場合は再提出できず、通過していない場合は再提出する必要があります。

查询合計を前に書きます两步校验。コードは次のとおりです。

// 查询学生
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, "已提交待审核,请勿重新提交!");
}

学生情報の送信には既存の状況もサポートする必要がある无则insert,有则updateため、変更されたコードは次のようになります。

// 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);
}

この時点では、全体的なプロセスは問題ないようですが、まだ少し残っています事务

4.事務

以前に宣言型トランザクションを使用したことがあります。非常に使いやすく、非常に優れています。[7.8] で詳細に説明されています。メソッドにアノテーションを追加するだけで済みます。

@Transactional(rollbackFor = Exception.class)

したがって、この記事では主にプログラムによるトランザクションについて説明します。

ご存知のとおり、トランザクションは良いものですが、长事务できる限りトランザクションを避け、データベースにできるだけ多く与える减压必要があるため、トランザクションの粒度はできるだけ小さく、トランザクションに関係のないコードは削除する必要があります。有効にできない場合は、有効にする必要はありません。

よく考えてみると今回のシナリオは適切で、本当に必要な事務は【1.学生情報の提出】と【2.借入資格の申請】なので、

  1. クエリロジックのためにトランザクションを開く必要はありません
  2. ロールバックされる可能性があるため、検証ロジック用のトランザクションを開くことがさらに必要です

この時点で宣言型トランザクションを使用したい場合は、@Transactional粒度が適用されるため適切ではありません方法。それでも使用したい場合は、2 つのメソッドに分割し、同じ種類の 2 つのメソッドを呼び出す必要があります。また、AOP アノテーションが有効であることを確認する必要もあります。そこで、プログラムによるトランザクションが登場します。トランザクションは に基づいて動作することができ代码块、よりきめ細かく柔軟であるためです。

では、プログラムによるトランザクションを使用するにはどうすればよいでしょうか? 私がお勧めするのは、Spring によってカプセル化された非常に使いやすいテンプレート
です。TransactionTemplateそれ自体は を継承しておりTransactionDefinition、内部的に注入されておりPlatformTransactionManagerexecute汎用メソッドをカプセル化しており、ロールバックとサブミットが明確に配置されています。

ここに画像の説明を挿入します

使用法は非常に単純で、TransactionTemplate基本的なテンプレートの使用法です。

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

次に、特定のアプリケーションに対して、最初に以下を注入しますTransactionTemplate

@Autowired
private TransactionTemplate transactionTemplate;

あとは、アクションとして【1.学生情報の提出】と【2.借入資格の申請】を行うだけ!

すべてが渡されるため、エラーが報告されます。Variable used in lambda expression should be final or effectively final

したがって、スチューデントを作成するプロセスをトランザクションの外部で記述して、トランザクションにfinal student渡すこと。完全な実装コードは次のとおりです。

// @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 レイヤー StudentController

サービス層を実装した後、StudentControllerを作成することでRestful APIを外部に提供しますが、主に新規なので以下のように定義したPOSTrequest@RequestBodyメソッドを使用します。StudentVO

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

constraintsここでは先ほどの Spring Validationの@NotBlankアノテーションを使用していますが、このアノテーションは String 型に適しており、追加後は null 、trim() 後は size>0 にすることはできません。

API の定義: コードの適用は次のとおりです。

@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. テスト

新しい学生アカウントli2gou、userId = 3 を登録します。

ここに画像の説明を挿入します

上記の学生情報クエリ API を呼び出しても、何も返されません。

この記事で実装された API を呼び出すとapply、結果は次のようになります。

ここに画像の説明を挿入します

データベースを見てみましょうstudent表

ここに画像の説明を挿入します

データベースを見てみましょうqualification表

ここに画像の説明を挿入します

すべて期待どおりです。再試行してください重复提交
ここに画像の説明を挿入します
最終的に再試行がinsert student成功するinsert qualificationと、学生がロールバックされるかどうかを確認するために例外がスローされます。テストしたとき、verify_user_idデータベース テーブルを空にできないように一時的に設定し、最後に成功回滚~

OK、すべて予想通りです。今日はもう終わりにしましょう。


やっと

これを見て参考になったら666をブラッシュアップしてください。よろしくお願いします〜

もっと優れた実践的な記事を読みたい場合は、私の実践的なコラムをお勧めします –> 私とから始められるフロントエンドドッグブラザーエンタープライズレベルの標準化されたプロジェクトの実践経験を0から1まで素早く身につける!

具体的なメリットや計画、技術選定については「序章」でご覧いただけます!

コラムを購読した後、私の WeChat アカウントを追加していただければ、各ユーザーに的を絞ったガイダンスを提供します。

さらに、私をフォローすることを忘れないでください: Tiangang gg、私が見つからない場合に備えて、新しい記事を見逃すのは簡単です: https://blog.csdn.net/scm_2008

おすすめ

転載: blog.csdn.net/scm_2008/article/details/133253044