Spring トランザクションとトランザクション伝播メカニズム

1. 事業レビュー

1. トランザクションとは

トランザクション定義: 一連の操作を実行ユニットにカプセル化します (操作をまとめてカプセル化します)。すべて成功するかすべて失敗します。

2. なぜトランザクションを使用するのでしょうか?

決まり文句の例を使ってみましょう。

張三は李斯に送金します。 

張三のアカウント-100;

John Doe のアカウント +100; 

Zhang San のアカウントが -100 のときに例外が発生し、この時点で Li Si のアカウント +100 の操作が完了していない場合、Zhang San のアカウントのお金が理由もなく消えてしまいますが、トランザクションを使用するとこれを解決できます。一連の操作は、一緒に成功するか、一緒に失敗するかのどちらかです。

3. MySQL でのトランザクションの使用法 

--オープントランザクション
トランザクションを開始します。
--業務 執行
--トランザクションを送信する
専念;
--ロールバック トランザクション
ロールバック;

2. 春の取引

0.準備

1. まずデータベースを作成します

        -- 创建数据库
        drop database if exists mycnblog;
        create database mycnblog default character set utf8mb4;
        -- 使⽤数据数据
        use mycnblog;
        -- 创建表[⽤户表]
        drop table if exists userinfo;
        create table userinfo(
        id int primary key auto_increment,
        username varchar(100) not null,
        password varchar(32) not null,
        photo varchar(500) default '',
        createtime datetime default now(),
        updatetime datetime default now(),
        `state` int default 1
        ) default charset 'utf8mb4';

        -- 添加⼀个⽤户信息
        insert into `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`,
        `createtime`, `updatetime`, `state`) values
        (1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1)
        ;
        -- 添加⼀个用户日志表
        DROP TABLE IF EXISTS userlog;
        CREATE TABLE userlog(
        id INT PRIMARY KEY AUTO_INCREMENT,
        username VARCHAR(100) NOT NULL,
        createtime DATETIME DEFAULT NOW( ),
        updatetime DATETIME DEFAULT NOW()
        ) DEFAULT CHARSET 'utf8mb4';

2. エンティティクラス

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
    private String photo;
    private Date createtime;
    private Date updatetime;
}
@Data
public class UserLog {
    private Integer id;
    private String username;
    private Date cratetime;
    private Date updatetime;

    public UserLog() {
    }

    public UserLog(String username) {
        this.username = username;
    }
}

3.マッパー

@Mapper
public interface UserMapper {
    @Select("select * from userinfo")
    List<User> selectAll();
     @Insert("insert into userinfo(username,password) values (#{username},#{password}))")
    Integer insert(User user);
}
@Mapper
public interface UserLogMapper {
    @Insert("insert into userlog(username) values (#{username})")
    Integer insert(UserLog userLog);
}

4.サービス

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;

    public List<User> selectAll() {
        return userMapper.selectAll();
    }

    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
}
@Service
public class UserLogService {
    @Autowired
    UserLogMapper userLogMapper;

    public Integer addUser(UserLog userLog) {
        return userLogMapper.insert(userLog);
    }
}
Spring のトランザクション操作は 2 つのカテゴリに分類されます。
1. プログラムによるトランザクション (トランザクションを操作するためのコードを手動で作成します)。
2. 宣言型トランザクション (アノテーションを使用してトランザクションを自動的に開き、送信します)。
通常の加算操作
@RestController
@Slf4j
@RequestMapping("/trans")
public class TransactionalController {
    @Autowired
    UserService userService;

    @RequestMapping("/add")
    public Integer addUser() {
        User user = new User();
        user.setName("zxn123");
        user.setPwd("123456");
        return userService.addUser(user);

    }
}

 正常に追加されたことがわかります

1. マニュアル

 トランザクション参加後のロールバック

@RestController
@Slf4j
@RequestMapping("/trans")
public class TransactionalController {
    @Autowired
    private UserService userService;
    //数据库事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    //事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;

    @RequestMapping("/add")
    public Integer addUser(String username, String password) {
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        Integer insert = userService.addUser(user);
        log.info("影响了" + insert + "行");
        dataSourceTransactionManager.rollback(transaction);
        return insert;

    }
}

上記の操作を引き続き実行しますが、データベースは変更されておらず、データがロールバックされたことを示しています。

 トランザクション参加後にロールバックログを実行する

  トランザクションに参加した後にログをコミットする

 2. 注釈

@Transactional アノテーションを追加する

@RestController
@Slf4j
@RequestMapping("/trans2")
public class TransactionalController2 {
    @Autowired
    private UserService userService;
    @Transactional
    @RequestMapping("/add")
    public Integer addUser(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        Integer insert = userService.addUser(user);
        log.info("影响了" + insert + "行");
        return insert;

    }
}

1. 例外なく自動送信

2. 例外が発生した場合は、自動的にロールバックします。

 送信操作がないことがわかります。

3. @トランザクションスコープ

@Transactional はメソッドまたはクラスを変更するために使用できます。
メソッドを変更する場合: パブリック メソッドにのみ適用できることに注意してください。それ以外の場合は有効になりません。この使い方がおすすめです。
クラスを変更する場合: アノテーションがクラス内のすべてのパブリック メソッドに対して有効であることを示します。

4. @Transactionalパラメータの説明

パラメータ 効果
価値 複数のトランザクション マネージャーが構成されている場合、このプロパティを使用して、どのトランザクション マネージャーが選択されるかを指定できます。
トランザクションマネージャー 複数のトランザクション マネージャーが構成されている場合、このプロパティを使用して、どのトランザクション マネージャーが選択されるかを指定できます。
伝搬 トランザクション伝播動作、デフォルト値 Propagation.REQUIRED
分離 トランザクションの分離レベル。デフォルト値は lsolation.DEFAULT です。
タイムアウト トランザクションのタイムアウト期間。デフォルト値は -1 です。制限時間を超えてもトランザクションが完了していない場合、トランザクションは自動的にロールバックされます。
読み取り専用
 
トランザクションが読み取り専用トランザクションかどうかを指定します。デフォルト値は false です。データの読み取りなど、トランザクションを必要としないメソッドを無視するには、読み取り専用を true に設定します。
ロールバック用 トランザクションのロールバックをトリガーできる例外タイプを指定するために使用されます。複数の例外タイプを指定できます。
クラス名のロールバック トランザクションのロールバックをトリガーできる例外タイプを指定するために使用されます。複数の例外タイプを指定できます。
noRollbackFor トランザクションをロールバックせずに、指定された例外タイプをスローします。複数の例外タイプを指定することもできます。
noRollbackForClassName
 
トランザクションをロールバックせずに、指定された例外タイプをスローします。複数の例外タイプを指定することもできます。

デモのロールバックについて

    @Transactional(noRollbackFor = ArithmeticException.class)
    @RequestMapping("/add")
    public Integer addUser(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        Integer insert = userService.addUser(user);
        log.info("影响了" + insert + "行");
        int a = 10 / 0;
        return insert;

    }

この時点で、ArithmeticException エラーが発生していることがわかります。データを追加します。

 正常に追加されたことがわかります 

@Transactional のデフォルトでは、ランタイム例外とエラーが発生した場合にのみロールバックされます。非ランタイム例外はロールバックされません。つまり、
RuntimeException とそのサブクラスを除く、Exception のサブクラス内ではロールバックされません。

 したがって、すべての例外をロールバックするように自分で設定できます。

 @Transactional(rollbackFor = Exception.class)

5. アノテーションに関する注意事項

@Transactional 例外がキャッチされても、トランザクションは自動的にロールバックされません。

    @RequestMapping("/add2")
    @Transactional
    public Integer add2(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        // 插⼊数据库
        int result = userService.addUser(user);
        try {
            // 执⾏了异常代码(0不能做除数)
            int i = 10 / 0;
        } catch (Exception e) {
            log.info(e.getMessage());
        }
        return result;
    }

 この時点では、追加がまだ成功していることがわかります。

3. トランザクション分離レベル

1. 取引の特徴

  1. アトミック性 アトミック性とは、トランザクションが分割不可能な作業単位であり、トランザクション内の操作がすべて発生するか、まったく発生しないかのいずれかであることを意味します。

  2. 整合性トランザクションは、データベースをある整合性状態から別の整合性状態に変換する必要があります。

  3. 分離:トランザクションの分離とは、トランザクションの実行が他のトランザクションによって干渉されないことを意味します。つまり、トランザクション内で使用される操作とデータは他の同時トランザクションから分離され、同時に実行されるトランザクションは互いに干渉できません。(詳細については、トランザクションの分離レベルを参照してください)

  4. 永続性 (Durability)永続性とは、トランザクションがコミットされると、データベース内のデータに対する変更が永​​続的であり、その後の他の操作やデータベース障害がトランザクションに影響を与えないことを意味します。

2. トランザクション分離レベル

  • ダーティ リード: 2 つのトランザクション T1、T2 の場合、T1 は、T2 によって更新されたがまだコミットされていないフィールドを読み取ります。その後、T2 がロールバックすると、T1 によって読み取られたコンテンツは一時的なものになり、無効になります。

  • 非反復読み取り: 2 つのトランザクション T1、T2 の場合、T1 がフィールドを読み取り、その後 T2 がフィールドを更新します。その後、T1 が同じフィールドを再度読み取りますが、値は異なります。

  • ファントム読み取り: 2 つのトランザクション T1、T2 の場合、T1 はテーブルからフィールドを読み取り、次に T2 がテーブルにいくつかの新しい行を挿入します。その後、T1 が同じテーブルを再度読み取ると、さらにいくつかの行が存在します。

トランザクション分離レベル ダーティリード 反復不可能な読み取り 幻の読書
コミットされていない読み取り
コミットされた読み取り バツ
反復読み取り バツ バツ
シリアル化可能 バツ バツ バツ

3. Springトランザクション分離レベル

Spring のトランザクション分離レベルには次の 5 種類があります。
  1. Isolation.DEFAULT: 接続されたデータベースのトランザクション分離レベルに基づきます。
  2.  Isolation.READ_UNCOMMITTED: コミットされていない読み取り、コミットされていないトランザクションを読み取ることができ、ダーティ リードが存在します。
  3.  Isolation.READ_COMMITTED: 読み取りはコミットされ、コミットされたトランザクションのみを読み取ることができ、ダーティ読み取りは解決され、反復不可能な読み取りが存在します。
  4. Isolation.REPEATABLE_READ: 反復可能な読み取りは反復不可能な読み取りを解決しますが、ファントム読み取りが存在します (MySQL のデフォルトレベル)。
  5. Isolation.SERIALIZABLE: シリアル化はすべての同時実行の問題を解決できますが、パフォーマンスが低すぎます。
Springのトランザクション分離レベルは@Transactionalにisolation属性を設定するだけでよく、具体的な実装コードは以下の通りです。

 @Transactional(isolation = Isolation.READ_COMMITTED)

4. Springトランザクション伝播メカニズム

1. トランザクション伝播メカニズムとは何ですか?

Spring のトランザクション伝播メカニズムでは、トランザクションを含む複数のメソッドが定義されており、相互に呼び出した場合に、これらのメソッド間でトランザクションがどのように渡されるかが決まります。
トランザクション分離レベルは、複数のトランザクションの同時実行の制御性 (安定性) を保証し、トランザクション伝播メカニズムは、複数の呼び出しメソッド間のトランザクションの制御性 (安定性) を保証します。
トランザクション伝播メカニズムは、複数のノード間でトランザクションが送信される問題を解決します (メソッド)

2. トランザクションの伝達メカニズム

Springのトランザクション伝播機構には以下の7種類があります。
  1. Propagation.REQUIRED: デフォルトのトランザクション伝播レベル。これは、トランザクションが現在存在する場合はトランザクションに参加し、現在トランザクションが存在しない場合は新しいトランザクションを作成することを意味します。
  2. Propagation.SUPPORTS: 現在トランザクションがある場合はトランザクションに参加し、現在トランザクションがない場合は非トランザクションで実行を継続します。
  3. Propagation.MANDATORY: (必須: 必須) 現在トランザクションがある場合はトランザクションに参加し、現在のトランザクションがない場合は例外をスローします。
  4. Propagation.REQUIRES_NEW: 新しいトランザクションの作成を示します。現在トランザクションが存在する場合は、現在のトランザクションを一時停止します。つまり、外部メソッドがトランザクションをオープンするかどうかに関係なく、Propagation.REQUIRES_NEW によって変更された内部メソッドは新たに独自のトランザクションをオープンし、オープンされたトランザクションは互いに独立しており、互いに干渉しません。
  5. Propagation.NOT_SUPPORTED: 非トランザクション モードで実行します。現在トランザクションが存在する場合、現在のトランザクションは一時停止されます。
  6. Propagation.NEVER: 非トランザクション モードで実行され、トランザクションが現在存在する場合は例外をスローします。
  7. Propagation.NESTED: トランザクションが現在存在する場合は、現在のトランザクションのネストされたトランザクションとして実行するトランザクションを作成します。現在トランザクションが存在しない場合、この値は PROPAGATION_REQUIRED と同等です。

上記のトランザクション伝播メカニズムは、次の 3 つのカテゴリに分類できます。

 

3.Springのトランザクション伝播機構の利用と各種シナリオのデモ 

1.現在のトランザクションのサポート (必須)

エラーのないシミュレーション

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer addUserLog(UserLog userLog) {
        return userLogMapper.insert(userLog);
    }
@RestController
@Slf4j
@RequestMapping("/trans3")
public class TransactionalController3 {
    @Autowired
    private UserService userService;
    @Autowired
    private UserLogService userLogService;
    @Transactional
    @RequestMapping("/addUser")
    public boolean addUser(String username, String password) {
        User user = new User();
        user.setName(username);
        user.setPwd(password);
        //插入用户表
        userService.addUser(user);
        UserLog userLog = new UserLog(username);
        //插入日志表
        userLogService.addUserLog(userLog);
        return true;

    }
}

  

 エラー発生時のシミュレーション

    @Transactional(propagation = Propagation.REQUIRED)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

 

 データが変わっていないことがわかります

2.現在のトランザクション (REQUIRES_NEW) はサポートされていません 

分離レベルを変更する

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

 userinfo は、追加が成功したことを示していることがわかります。

3.現在のトランザクションはサポートされていないため、例外はスローされません。

    @Transactional(propagation = Propagation.NEVER)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.NEVER)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

実行すると以下のエラーログが出力されます。 

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

    @Transactional(propagation = Propagation.NESTED)
    public Integer addUser(User user) {
        return userMapper.insert(user);
    }
    @Transactional(propagation = Propagation.NESTED)
    public Integer addUserLog(UserLog userLog) {
        int i = 10 / 0;
        return userLogMapper.insert(userLog);
    }

 例外をキャッチする

    @Transactional(propagation = Propagation.NESTED)
    public Integer addUserLog(UserLog userLog) {
        userLogMapper.insert(userLog);
        try {
            int i = 10 / 0;
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return 1;
    }

 userinfo は追加が成功したことを示し、userlog は追加が失敗したことを示していることがわかります。

これにより、一部のトランザクションのみがロールバックされます。必要に応じて、すべてのトランザクションがロールバックされます。 

ネストされたトランザクション (NESTED) と結合トランザクション (REQUIRED) の違い:

  • トランザクション全体が正常に実行された場合、両方の結果は同じになります。
  • トランザクションが実行途中で失敗した場合、トランザクションへの参加後にトランザクション全体がロールバックされますが、ネストされたトランザクションは部分的にロールバックされ、前のメソッドの実行結果には影響しません。
     

おすすめ

転載: blog.csdn.net/qq_64580912/article/details/131996805