Spring's Road to God の第 49 章: Spring トランザクションによる MQ でのトランザクション メッセージの実装

Spring's Road to God の第 49 章: Spring トランザクションによる MQ でのトランザクション メッセージの実装

1. この記事の 2 つの目的

1. メッセージ配信の 5 つの方法について話し合う

2. トランザクション メッセージの配信を実現するコードを手動で記述します。

2. メッセージ配信の 5 つの方法について話し合う

2.1. ビジネスシナリオ

電子商取引では、商品の注文後にポイントをユーザーに送信する必要があるが、注文テーブルとポイントテーブルが別のデータベースにあり、分散トランザクションが発生します。

私たちは信頼できるメッセージでこれに対処します。

  1. 注文が正常に行われた後にポイントを送信する操作を実現するために mq を使用します
  2. 製品の注文が正常に完了すると、メッセージが mq に配信され、ポイント システムがそのメッセージを消費してユーザーのポイントを増やします。

主に、商品の注文と mq? へのメッセージ配信の操作を実装する方法について説明します。それぞれの方法の長所と短所は?

2.2. 方法 1

プロセス

  • step1 : ローカルトランザクションを開く
  • step2 : 買い物注文を作成する
  • step3 : メッセージを mq に投稿する
  • step4 : ローカルトランザクションを送信する

この方法は、トランザクションがコミットされる前にメッセージを送信します。

潜在的な問題

  • ステップ 3 で例外が発生しました。その結果、ステップ 4 が失敗し、製品の発注に失敗し、製品の発注業務に直接影響を及ぼしました。
  • ステップ 4 で例外が発生しましたが、他のステップは成功しました。製品の注文は失敗し、メッセージは正常に配信され、ユーザーがポイントを追加しました。

2.3. 方法 2

次にやり方を変えて、取引後にメッセージを送信します。

プロセス

  • step1 : ローカルトランザクションを開く
  • step2 : 買い物注文を作成する
  • step3 : ローカルトランザクションを送信する
  • step4 : mq にメッセージを投稿する

考えられる問題

ステップ 4 で例外が発生しましたが、他のステップは成功しました。製品の注文は正常に行われ、配送メッセージは失敗し、ユーザーはポイントを増加しませんでした。

上記の 2 つはより一般的な方法ですが、最も間違いが発生しやすい方法でもあります。

2.4. 方法 3

  • step1 : ローカルトランザクションを開く
  • step2 : 買い物注文を作成する
  • ステップ 3 : メッセージを送信する必要があるレコード t_msg_record をローカル ライブラリに挿入します。
  • step3 : ローカルトランザクションを送信する
  • ステップ5 : タイマーを追加し、t_msg_recordをポーリングし、mqに送信するレコードをポストします。

このメソッドは、データベース トランザクション、ビジネス、およびメッセージ レコードをアトミ​​ックな操作として使用します。ビジネスが成功した後は、メッセージ ログが存在する必要があります。最初の 2 つの方法で発生する問題は解決されます。ビジネス システムが比較的単純であれば、この方法を使用できます。

マイクロサービスの場合、上記の方法はあまり良くなく、サービスごとに上記の操作が必要となり、拡張にも向きません。

2.5. 方法 4

メッセージ サービスメッセージ ライブラリを追加します。これは、メッセージの保存、mq へのメッセージの送信と配信を担当します。

  • step1 : ローカルトランザクションを開く
  • step2 : 買い物注文を作成する
  • ステップ 3 : 現在のトランザクション ライブラリにログを挿入します。一意のビジネス ID (msg_order_id) を生成し、msg_order_id を注文に関連付け、現在のトランザクションが存在するライブラリに保存します。
  • ステップ 4 : メッセージ サービスを呼び出します。msg_order_id を伝え、最初にメッセージをウェアハウスに入れます。この時点ではメッセージの状態は送信待ちであり、メッセージ ID (msg_id) を返します。
  • step5 : ローカルトランザクションを送信する
  • step6 : 上記がすべて成功した場合は、メッセージ サービスを呼び出して mq にメッセージを配信します。上記が失敗した場合は、メッセージ サービスを呼び出してメッセージの送信をキャンセルします。

上記の方法は大きく進歩したと考えられますが、考えられる問題を引き続き分析してみましょう。

  1. システムにはメッセージサービスが追加されており、商品の発注業務はこのサービスに依存しており、業務への依存度が高いため、メッセージサービスが利用できなくなると業務全体が停止してしまいます。
  2. ステップ 6 が失敗した場合、メッセージは待機状態になります。このとき、ビジネス側は、ビジネスが正常に実行されたかどうかを確認するためのチェックバック インターフェイス (msg_order_id クエリ経由) を提供する必要があります。メッセージ サービスは、スケジュールされたタスクを追加する必要があります。待機状態のメッセージについては、補償処理を実行し、ビジネスが正常に処理されたかどうかを確認し、メッセージが配信されるかキャンセルされるかを判断します。
  3. Step4はメッセージサービスに依存するため、メッセージサービスの性能が悪いと、現在の業務のトランザクション投入時間が長くなり、デッドロックが発生しやすくなり、同時実行性能が低下します通常、トランザクション内でリモート呼び出し処理を行うことはタブーです。リモート呼び出しのパフォーマンスと時間は多くの場合制御不能であり、そのため現在のトランザクションが大規模なトランザクションになり、他の障害が発生します。

2.6、ウェイ5

上記の方法で改善を続け、より良い方法が現れました。

  • ステップ 1 : グローバルに一意のビジネス メッセージ ID (bus_msg_id) を生成し、メッセージ サービスを呼び出し、bus_msg_id を伝え、最初にメッセージをウェアハウスに置きます。この時点で、メッセージのステータスは送信保留中であり、メッセージ ID ( msg_id)
  • step2 : ローカルトランザクションを開く
  • step3 : 買い物注文を作成する
  • step4 : 現在のトランザクションライブラリにログを挿入します(step3のビジネスをbus_msg_idに関連付けます)
  • step5 : ローカルトランザクションを送信する
  • step6 : 上記が成功した場合はメッセージサービスを呼び出してmqにメッセージを届ける場合と、上記が失敗した場合はメッセージサービスを呼び出してメッセージの送信をキャンセルする場合があります。

ステップ 6 が失敗した場合、メッセージは送信中の状態になりますが、この時点で、ビジネス パーティは、ビジネスが正常に実行されたかどうかを確認するために (bus_msg_id クエリ経由で) チェックバック インターフェイスを提供する必要があります。

メッセージ サービスは、ステータスが送信保留中のメッセージを補うために新しいスケジュールされたタスクを追加し、ビジネスが正常に処理されたかどうかを確認して、メッセージが配信されるかキャンセルされるかを決定する必要があります。

方法 5 と方法 4 と比較して、より良い点の 1 つは、メッセージ サービスへの呼び出しとメッセージ ランディング操作がトランザクションの外部で実行されることです。この小さな改善は、実際には非常に優れた最適化であり、メッセージ サービスの実行時間を短縮します。 Alibaba には方法 5 をサポートするメッセージ ミドルウェアRocketMQがあり、それを使用できます。

[外部リンク画像の転送に失敗しました。ソース サイトには盗難防止リンク メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-dvO9tmaL-1684758577552)(%E6%96%B0%E5%BB%BA) %E6%96%87% E6%9C%AC%E6%96%87%E6%A1%A3/1369022-20211106211732829-1943650884.png)]

以下では、コードを通じてメソッド 4 を実装します。

3. モード 4 コードの実装

3.1. データベースの準備

3 つのテーブル:

t_user: ビジネス ライブラリ内のユーザー テーブル。しばらくの間ユーザー登録をシミュレートし、登録が成功した後にメッセージを投稿するために使用されます。

t_msg_order: メッセージ順序テーブル。このテーブルはビジネス ライブラリに配置されます。ビジネス操作中にメッセージを送信する必要がある場合、ビジネス操作のトランザクションで同時にデータが t_msg_order テーブルに挿入されます。ビジネス操作が成功すると、t_msg_order テーブルは確実に正常に挿入されます データの一部については、メッセージ送信時に t_msg_order の ID が伝えられます メッセージ サービスは、この ID を使用してビジネス データベースの t_msg_order のレコードを確認できます。記録が存在する場合は、業務が成功していることを意味します。

t_msg: メッセージ テーブル。送信されたすべてのメッセージ情報がこのテーブルに配置されます。主なフィールドは次のとおりです: メッセージの内容、msg_order_id: t_msg_order テーブルの ID

DROP DATABASE IF EXISTS javacode2018;
CREATE DATABASE if NOT EXISTS javacode2018;

USE javacode2018;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user(
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(256) NOT NULL DEFAULT '' COMMENT '姓名'
);

DROP TABLE IF EXISTS t_msg;
CREATE TABLE t_msg(
  id INT PRIMARY KEY AUTO_INCREMENT,
  msg VARCHAR(256) NOT NULL DEFAULT '' COMMENT '消息内容,可以json格式的数据',
  msg_order_id BIGINT NOT NULL DEFAULT 0 COMMENT '消息订单id',
  status SMALLINT NOT NULL DEFAULT 0 COMMENT '消息状态,0:待投递,1:已发送,2:取消发送'
) COMMENT '消息表';

DROP TABLE IF EXISTS t_msg_order;
CREATE TABLE t_msg_order(
  id INT PRIMARY KEY AUTO_INCREMENT,
  ref_type BIGINT NOT NULL DEFAULT 0 COMMENT '关联业务类型',
  ref_id VARCHAR(256) NOT NULL DEFAULT '' COMMENT '关联业务id(ref_type & ref_id 唯一)'
) COMMENT '消息订单表,放在业务库中';

alter table t_msg_order add UNIQUE INDEX idx1 (ref_type,ref_id);

3.2. 主要な Java コード

構成クラス MainConfig11

package com.javacode2018.tx.demo11;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

/**
 * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
 * <a href="http://www.itsoku.com">个人博客</a>
 */
@ComponentScan
@EnableTransactionManagement
public class MainConfig11 {
    
    
    @Bean
    public DataSource dataSource() {
    
    
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root123");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    //定义一个jdbcTemplate
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
    
    
        return new JdbcTemplate(dataSource);
    }

    //定义事务管理器transactionManager
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
    
    
        return new DataSourceTransactionManager(dataSource);
    }

}

メッセージモデル

package com.javacode2018.tx.demo11;

import lombok.*;

/**
 * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
 * <a href="http://www.itsoku.com">个人博客</a>
 */
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class MsgModel {
    
    
    private Long id;
    //消息内容
    private String msg;
    //消息订单id
    private Long msg_order_id;
    //消息状态,0:待投递,1:已发送,2:取消发送
    private Integer status;
}

MsgOrderModel

package com.javacode2018.tx.demo11;

import lombok.*;

/**
 * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
 * <a href="http://www.itsoku.com">个人博客</a>
 */
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MsgOrderModel {
    
    
    private Long id;
    //关联业务类型
    private Integer ref_type;
    //关联业务id(ref_type & ref_id 唯一)
    private String ref_id;
}

MsgOrderService

t_msg_order テーブルに対するいくつかの操作 (データ挿入用とクエリ用の 2 つのメソッド) を提供します。

package com.javacode2018.tx.demo11;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

/**
 * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
 * <a href="http://www.itsoku.com">个人博客</a>
 */
@Component
public class MsgOrderService {
    
    
    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 插入消息订单
     *
     * @param ref_type
     * @param ref_id
     * @return
     */
    @Transactional
    public MsgOrderModel insert(Integer ref_type, String ref_id) {
    
    
        MsgOrderModel msgOrderModel = MsgOrderModel.builder().ref_type(ref_type).ref_id(ref_id).build();
        //插入消息
        this.jdbcTemplate.update("insert into t_msg_order (ref_type,ref_id) values (?,?)",
                ref_type,
                ref_id
        );
        //获取消息订单id
        msgOrderModel.setId(this.jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class));
        return msgOrderModel;
    }

    /**
     * 根据消息id获取消息
     *
     * @param id
     * @return
     */
    public MsgOrderModel getById(Long id) {
    
    
        List<MsgOrderModel> list = this.jdbcTemplate.query("select * from t_msg_order where id = ? limit 1", new BeanPropertyRowMapper<MsgOrderModel>(MsgOrderModel.class), id);
        return Objects.nonNull(list) && !list.isEmpty() ? list.get(0) : null;
    }

}

メッセージサービス

メッセージ サービス。t_msg テーブルに対するいくつかの操作とメッセージ配信のいくつかの方法を提供します。

方法 説明する
追加メッセージ メッセージを追加すると、メッセージはライブラリに格納され、送信待ちの状態になります。
メッセージ送信の確認 必ずメッセージを配信してください。トランザクションが成功した後に呼び出すことができます。
キャンセルメッセージ送信 メッセージの投稿をキャンセルし、トランザクションのロールバックを呼び出すことができます

コード:

package com.javacode2018.tx.demo11;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

/**
 * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
 * <a href="http://www.itsoku.com">个人博客</a>
 */
@Component
public class MsgService {
    
    

    //添加一条消息(独立的事务中执行)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Long addMsg(String msg, Long msg_order_id, boolean isSend) {
    
    
        MsgModel msgModel = MsgModel.builder().msg(msg).msg_order_id(msg_order_id).status(0).build();
        //先插入消息
        Long msg_id = this.insert(msgModel).getId();
        if (isSend) {
    
    
            //如果需要投递,则调用投递的方法
            this.confirmSendMsg(msg_id);
        }
        return msg_id;
    }

    /**
     * 确认消息投递(不需要事务)
     *
     * @param msg_id 消息id
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void confirmSendMsg(Long msg_id) {
    
    
        MsgModel msgModel = this.getById(msg_id);
        //向mq中投递消息
        System.out.println(String.format("投递消息:%s", msgModel));
        //将消息状态置为已投递
        this.updateStatus(msg_id, 1);
    }

    /**
     * 取消消息投递(不需要事务)
     *
     * @param msg_id 消息id
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void cancelSendMsg(Long msg_id) {
    
    
        MsgModel msgModel = this.getById(msg_id);
        System.out.println(String.format("取消投递消息:%s", msgModel));
        //将消息状态置为取消投递
        this.updateStatus(msg_id, 2);
    }

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 插入消息
     *
     * @param msgModel
     * @return
     */
    private MsgModel insert(MsgModel msgModel) {
    
    
        //插入消息
        this.jdbcTemplate.update("insert into t_msg (msg,msg_order_id,status) values (?,?,?)",
                msgModel.getMsg(),
                msgModel.getMsg_order_id(),
                msgModel.getStatus());
        //获取消息id
        msgModel.setId(this.jdbcTemplate.queryForObject("SELECT LAST_INSERT_ID()", Long.class));
        System.out.println("插入消息:" + msgModel);
        return msgModel;
    }

    /**
     * 根据消息id获取消息
     *
     * @param id
     * @return
     */
    private MsgModel getById(Long id) {
    
    
        List<MsgModel> list = this.jdbcTemplate.query("select * from t_msg where id = ? limit 1", new BeanPropertyRowMapper<MsgModel>(MsgModel.class), id);
        return Objects.nonNull(list) && !list.isEmpty() ? list.get(0) : null;
    }

    /**
     * 更新消息状态
     *
     * @param id
     * @param status
     */
    private void updateStatus(long id, int status) {
    
    
        this.jdbcTemplate.update("update t_msg set status = ? where id = ?", status, id);
    }

}

メッセージ送信者MsgSender

メッセージ配信デバイスはビジネス側によって使用され、メッセージを送信するための内部メソッドは 1 つだけです。

コンテキスト内にトランザクションがない場合、メッセージはランディング直後に配信されます。トランザクションがある場合、メッセージ配信は 2 つのステップに分割されます。最初にメッセージがランディングされ、トランザクションが終了した後に配信するかどうかが決定されます。トランザクション拡張ポイントが使用されます: TransactionSynchronization、トランザクションの実行後、TransactionSynchronizationインターフェイスの afterCompletion メソッドをコールバックし、このメソッドでメッセージを配信するかどうかを決定します。トランザクション拡張ポイントTransactionSynchronizationに詳しくない場合は、まずこの記事を読んでください: Spring シリーズの記事 47: Spring トランザクションのソース コード分析

package com.javacode2018.tx.demo11;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;


/**
 * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
 * <a href="http://www.itsoku.com">个人博客</a>
 * 消息发送器,所有使用者调用send方法发送消息
 */
@Component
public class MsgSender {
    
    
    @Autowired
    private MsgOrderService msgOrderService;
    @Autowired
    private MsgService msgService;

    //发送消息
    public void send(String msg, int ref_type, String ref_id) {
    
    
        MsgOrderModel msgOrderModel = this.msgOrderService.insert(ref_type, ref_id);

        Long msg_order_id = msgOrderModel.getId();
        //TransactionSynchronizationManager.isSynchronizationActive 可以用来判断事务同步是否开启了
        boolean isSynchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
        /**
         * 若事务同步开启了,那么可以在事务同步中添加事务扩展点,则先插入消息,暂不发送,则在事务扩展点中添加回调
         * 事务结束之后会自动回调扩展点TransactionSynchronizationAdapter的afterCompletion()方法
         * 咱们在这个方法中确定是否投递消息
         */
        if (isSynchronizationActive) {
    
    
            final Long msg_id = this.msgService.addMsg(msg, msg_order_id, false);
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
    
    
                @Override
                public void afterCompletion(int status) {
    
    
                    //代码走到这里时,事务已经完成了(可能是回滚了、或者是提交了)
                    //看一下消息关联的订单是否存在,如果存在,说明事务是成功的,业务是执行成功的,那么投递消息
                    if (msgOrderService.getById(msg_order_id) != null) {
    
    
                        System.out.println(String.format("准备投递消息,{msg_id:%s}", msg_id));
                        //事务成功:投递消息
                        msgService.confirmSendMsg(msg_id);
                    } else {
    
    
                        System.out.println(String.format("准备取消投递消息,{msg_id:%s}", msg_id));
                        //事务是不:取消投递消息
                        msgService.cancelSendMsg(msg_id);
                    }
                }
            });
        } else {
    
    
            //无事务的,直接插入并投递消息
            this.msgService.addMsg(msg, msg_order_id, true);
        }
    }
}

3.3. テスト (3 つのシナリオ)

3.3.1、シナリオ 1: ビジネスの成功、メッセージ配信の成功

ユーザーサービス

次の register メソッドはトランザクションであり、ユーザー情報が内部に挿入され、メッセージが配信されます。

package com.javacode2018.tx.demo11;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
 * <a href="http://www.itsoku.com">个人博客</a>
 */
@Component
public class UserService {
    
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    //消息投递器
    @Autowired
    private MsgSender msgSender;

    /**
     * 模拟用户注册成功,顺便发送消息
     */
    @Transactional
    public void register(Long user_id, String user_name) {
    
    
        //先插入用户
        this.jdbcTemplate.update("insert into t_user(id,name) VALUES (?,?)", user_id, user_name);
        System.out.println(String.format("用户注册:[user_id:%s,user_name:%s]", user_id, user_name));
        //发送消息
        String msg = String.format("[user_id:%s,user_name:%s]", user_id, user_name);
        //调用投递器的send方法投递消息
        this.msgSender.send(msg, 1, user_id.toString());
    }

}
テストクラス
package com.javacode2018.tx.demo11;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
 * <a href="http://www.itsoku.com">个人博客</a>
 */
public class Demo11Test {
    
    

    private AnnotationConfigApplicationContext context;
    private UserService userService;
    private JdbcTemplate jdbcTemplate;

    @Before
    public void before() {
    
    
        this.context = new AnnotationConfigApplicationContext(MainConfig11.class);
        userService = context.getBean(UserService.class);
        this.jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
        jdbcTemplate.update("truncate table t_user");
        jdbcTemplate.update("truncate table t_msg");
        jdbcTemplate.update("truncate table t_msg_order");
    }

    @Test
    public void test1() {
    
    
        this.userService.register(1L, "路人");
    }

}
実行出力
用户注册:[user_id:1,user_name:路人]
插入消息:MsgModel(id=1, msg=[user_id:1,user_name:路人], msg_order_id=1, status=0)
准备投递消息,{msg_id:1}
投递消息:MsgModel(id=1, msg=[user_id:1,user_name:路人], msg_order_id=1, status=0)

3.3.2. シナリオ 2: ビジネスの失敗、メッセージのキャンセル

UserService にコードを追加する

手動で例外をスローし、トランザクションをロールバックさせます。

/**
 * 模拟用户注册失败,咱们通过弹出异常让事务回滚,结果也会导致消息发送被取消
 *
 * @param user_id
 * @param user_name
 */
@Transactional
public void registerFail(Long user_id, String user_name) {
    
    
    this.register(user_id, user_name);
    throw new RuntimeException("故意失败!");
}
Demo11テスト追加ユースケース
@Test
public void test2() {
    
    
    this.userService.registerFail(1L, "张三");
}
実行出力

例外がポップアップし、多くの情報があったため、重要な部分をインターセプトしました。次のように、トランザクションがロールバックされ、メッセージが配信されなかったことがわかります。

用户注册:[user_id:1,user_name:张三]
插入消息:MsgModel(id=1, msg=[user_id:1,user_name:张三], msg_order_id=1, status=0)
准备取消投递消息,{
    
    msg_id:1}
取消投递消息:MsgModel(id=1, msg=[user_id:1,user_name:张三], msg_order_id=1, status=0)

java.lang.RuntimeException: 故意失败!

 at com.javacode2018.tx.demo11.UserService.registerFail(UserService.java:44)
 at com.javacode2018.tx.demo11.UserService$$FastClassBySpringCGLIB$$5dd21f5c.invoke(<generated>)

3.3.3. ネストされたトランザクション

トランザクションの送信は現在のトランザクションの後に行われます。現在のトランザクションがコミットされている場合、メッセージは配信されます。現在のトランザクションがコミットされていない場合、メッセージはキャンセルされます。

ネストされたトランザクションのコードを見てみましょう

UserService にコードを追加する

次のメソッドのトランザクション伝播動作は REQUIRES_NEW であることに注意してください。現在トランザクションが存在する場合、トランザクションが再開されます。

//事务传播属性是REQUIRES_NEW,会在独立的事务中运行
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void registerRequiresNew(Long user_id, String user_name) {
    
    
    this.register(user_id, user_name);
}
UserService1 クラスを追加します
package com.javacode2018.tx.demo11;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
 * <a href="http://www.itsoku.com">个人博客</a>
 */
@Component
public class UserService1 {
    
    
    @Autowired
    private UserService userService;

    @Autowired
    private MsgSender msgSender;

    //嵌套事务案例
    @Transactional
    public void nested() {
    
    
        this.msgSender.send("消息1", 2, "1");
        //registerRequiresNew事务传播属性是REQUIRES_NEW:会在一个新事务中运行
        this.userService.registerRequiresNew(1L, "张三");
        //registerFail事务传播属性是默认的,会在当前事务中运行,registerFail弹出异常会导致当前事务回滚
        this.userService.registerFail(2L, "李四");
    }
}

nested は周辺メソッドです。このメソッドには @Transactional があります。実行すると、トランザクションが開きます。内部には 3 行のコードがあります:

@1: メッセージの送信は現在のトランザクション内で実行されます

@2: registerRequiresNew トランザクションの伝播動作は REQUIRES_NEW であるため、トランザクションは再開されます。

@3: registerFail トランザクションの伝播動作はデフォルトの REQUIRED であり、nested() によって開かれたトランザクションに参加します。 registerFail メソッド内で例外がスローされ、最終的には外部メソッドのトランザクションがロールバックされます。

上記のメソッドでは3つのメッセージを配信する必要があり、@1と@3が配信したメッセージはトランザクションのロールバックによりロールバックされますが、@2は独立したトランザクションで実行され、@2のメッセージは正常に配信されます。見てみましょう 実行結果は分析と一致していますか?

Demo11テスト追加ユースケース
@Test
public void test3() {
    
    
    UserService1 userService1 = this.context.getBean(UserService1.class);
    userService1.nested();
}
実行出力
插入消息:MsgModel(id=1, msg=消息1, msg_order_id=1, status=0)
用户注册:[user_id:1,user_name:张三]
插入消息:MsgModel(id=2, msg=[user_id:1,user_name:张三], msg_order_id=2, status=0)
准备投递消息,{
    
    msg_id:2}
投递消息:MsgModel(id=2, msg=[user_id:1,user_name:张三], msg_order_id=2, status=0)
用户注册:[user_id:2,user_name:李四]
插入消息:MsgModel(id=3, msg=[user_id:2,user_name:李四], msg_order_id=3, status=0)
准备取消投递消息,{
    
    msg_id:1}
取消投递消息:MsgModel(id=1, msg=消息1, msg_order_id=1, status=0)
准备取消投递消息,{
    
    msg_id:3}
取消投递消息:MsgModel(id=3, msg=[user_id:2,user_name:李四], msg_order_id=3, status=0)

java.lang.RuntimeException: 故意失败!

 at com.javacode2018.tx.demo11.UserService.registerFail(UserService.java:44)

分析と一致する結果を詳しく見てみましょう。

3.4 概要

トランザクション メッセージは 2 つのステップに分かれており、まずウェアハウスにドロップされます。このとき、メッセージは配信されます。トランザクションの実行後に、配信するかどうかが決定されます。技術的なポイントは、トランザクション拡張インターフェイス: TransactionSynchronization、トランザクションの実行後にインターフェイスを自動的にコールバックします。

残る 1 つの問題: メッセージ補償操作

トランザクション メッセージが到着したばかりのときは、配信中の状態であり、システムはダウンしたばかりです。システムが復元された後、このメッセージを処理するにはタイマーが必要です。メッセージ内の msg_order_id をビジネス データベースに取り込み、注文が存在するかどうかを確認し、存在する場合はメッセージを配信し、そうでない場合は配信をキャンセルします。これは全員に実装されます。

4. まとめ

さて、今日の内容はここまでです。マスターする必要のある内容をまとめて復習しましょう。

1. メッセージ配信の 5 つの方法を差し引いた、それぞれの長所と短所を上手に使いこなす必要がある

2. 方法 4 のトランザクション メッセージのコード実装は、全員が習得する必要があります。

メッセージ サービスは頻繁に使用されるため、通常はシステムの基本サービスとして使用されますが、独自のメッセージ サービスを開発して他のサービスに提供することもできます。

メッセージを残してご意見を共有してください。何か得たことがあれば、この記事を友達と共有してください。ありがとう!

5. ケースのソースコード

git地址:
https://gitee.com/javacode2018/spring-series

本文案例对应源码:
    spring-series\lesson-002-tx\src\main\java\com\javacode2018\tx\demo11 

通行人 A 今後、すべての Java ケース コードがこれに載せられる予定です。皆さんはそれを見て、引き続きダイナミクスに注目してください。

Vice.java:44)


大家细看一下结果,和分析的是一致的。

### 3.4、小结

事务消息分2步走,先落库,此时消息待投递,等到事务执行完毕之后,再确定是否投递,用到的关键技术点是事务扩展接口:TransactionSynchronization,事务执行完毕之后会自动回调接口中的afterCompletion方法。

**遗留的一个问题:消息补偿操作**

当事务消息刚落地,此时处于待投递状态,系统刚好down机了,此时系统恢复之后,需要有个定时器来处理这种消息,拿着消息中的msg_order_id去业务库查一下订单是否存在,如果存在,则投递消息,否则取消投递,这个留给大家去实现。

## 4、总结

好了,今天的内容就到此就讲完了,我们一块来总结回顾一下,你需要重点掌握的内容。

1、消息投递的5种方式的推演,要熟练掌握其优缺点

2、方式4中事务消息的代码实现,需要大家掌握

消息服务使用频率挺高的,通常作为系统中的基础服务使用,大家可以尝试一下开发一个独立的消息服务,提供给其他服务使用。

**欢迎留言和我分享你的想法,如果有收获,也欢迎你把这篇文章分享给你的朋友,谢谢!**

## 5、案例源码

Git アドレス:
https://gitee.com/javacode2018/spring-series

この記事のケースに対応するソースコード:
spring-series\lesson-002-tx\src\main\java\com\javacode2018\tx\demo11


**路人甲java所有案例代码以后都会放到这个上面,大家watch一下,可以持续关注动态。**

来源:https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648937788&idx=2&sn=21030dc8fff11dfdfb6d005cb8a8d526&scene=21#wechat_redirect

おすすめ

転載: blog.csdn.net/china_coding/article/details/130814620