Springboot はトランザクション メソッドを開き、トランザクションなしでメソッドを呼び出します。
提示:上方标题是一个很笼统的场景,详情展开如下,先说结论:
概要:
メソッド A がメソッド B を呼び出す:
シナリオ 1 メソッド A と B が同じクラスにある場合:
A に @Transactional の注釈が付けられ、B に @Transactional の有無にかかわらず注釈が付けられ、トランザクションが有効な場合、AB は同じクラスにあります。取引。
A が @Transactional アノテーションを追加せず、B が @Transactional アノテーションを追加した場合、トランザクションは無効になります。( 但是如果使用代理类来进行b方法调用,那么B会开启事务,A不会开启事务
)
シナリオ 2 A と B が同じクラスにない場合:
A に @Transactional アノテーションが付けられ、B に @Transactional アノテーションが付いているかどうかに関係なく、トランザクションは有効です。
A が @Transactional アノテーションを追加せず、B が @Transactional アノテーションを追加した
場合、B のみがトランザクションを持ちます。A が @Transactional アノテーションを追加せず、B が @Transactional アノテーションを追加しなかった場合、A と B の両方にはトランザクションがありません。
または、次のように理解します。
1. A が @Transactional アノテーションを追加した場合、それがクラス内であるかどうか、B がアノテーションを追加するかどうかに関係なく、AB は同じトランザクション内にあります。 2. A が @Transactional アノテーションを追加しない場合、B のみが追加されます
。 @Transactional を追加します。注: AB メソッドは同じクラスに属しており、トランザクションは無効です (プロキシ クラスを使用すると、B メソッドにはトランザクションがあります)。AB は別のクラスに属し、B のみにトランザクションがあります。3. A が存在しない場合
@Transactional アノテーションを追加し、B が @Transactional アノテーションを追加しない場合、トランザクションは存在しません。
理由: A メソッドに @Transactional アノテーションが付いているため、Spring が管理するとプロキシ クラスが生成されます。実際に A メソッドが呼び出される際には、実際にプロキシ クラス内のメソッドが実行されます。プロキシ クラス内のメソッドには、既にB メソッド。呼び出しはメソッドになりました。したがって、トランザクションは有効です。
注:コード内の TestUserServiceImpl のupdate方法
合計はupdateOther方法
、全文では A メソッドと呼ばれます。
シナリオ 1 A メソッドと B メソッドは同じクラスにあります。
まずコントローラー、サービス、マッパーを自分で作成します。コードは以下のように表示されます
@RestController
@RequestMapping("/testUser")
public class TestShiWuController {
@Autowired
private ITestService testService;
@GetMapping("/update")
public void update(String param) {
testService.update(param);
}
}
@Service
public class TestUserServiceImpl implements ITestService {
@Autowired
private TestUserMapper testUserMapper;
@Override
@Transactional(rollbackFor =Exception.class)
public int update( String param) {
testUserMapper.updateUserById("a",1L);
b(param);
c(param);
return 0;
}
// @Transactional(rollbackFor =Exception.class)
public void b(String s) {
TestUser testUser = testUserMapper.selectById(1L);
System.out.println(testUser);
testUserMapper.updateUserById("b",2L);
if (s.equals("b")) {
int a = 1/0;
}
}
// @Transactional(rollbackFor =Exception.class)
public void c(String s) {
TestUser testUser = testUserMapper.selectById(2L);
System.out.println(testUser);
testUserMapper.updateUserById("c",3L);
if (s.equals("c")) {
int a = 1/0;
}
}
}
@Mapper
public interface TestUserMapper extends BaseMapper<TestUser> {
@Update("update testUser set name = #{name} where id=#{id}")
public int updateUserById(String name, Long id );
}
1.1 如果A加@Transactional注解,B不加@Transactional注解:
param=b の場合、リクエスト後の結果は次のようになります。
注意
: Q: アイデア コンソールによると、メソッド b は id=1 のレコードをクエリしていることがわかりますが、なぜ name=1 ではなく name=a なのでしょうか? 。
回答: この時点でクエリ レコードの name=a となっているのは、b が a のトランザクション内にあり、同一事务内数据是可见共享的
この時点では name=a がデータベースに登録されていないためです。为了验证此处
メソッドの最後に Thread.Sleep(10000) を追加して 10 秒間スリープし、インターフェイスを呼び出してこの 10 秒間にパラメータ param=a を渡します。navicat に移動して、id=1 のレコードをクエリします。データベースで、それが 1 であるかどうかを確認してください。1 であれば、上記が正しいことが証明されます。
原因分析:
ヒント: メソッド b と c がメソッド a のメイン トランザクションに追加されていることがわかります。つまり、3 つのメソッドは同じトランザクション内にあります。
数据都变成了初始值
分析: param=b を指定してインターフェイスを呼び出した後、直感的に、つまりロールバックを確認できます。(実際には、@Transional アノテーションで b メソッドが追加されているかどうかに関係なく、トランザクションは有効になります。つまり、両方のメソッドが同じトランザクション内にあり、ブロガーが個人的にテストしました)
1.2 如果update方法不加@Transactional注解,B加@Transactional注解:
コードを次のように変更します。
param=b の場合、リクエスト後の結果は次のようになります。
注意
此时name=a和上方1.1标题的name=a有什么区别吗
質問: アイデア コンソールによると、メソッド b が id=1 、?のレコードをクエリしたことがわかります。。
回答: 今回クエリされたレコード名 = a は、更新メソッドの変更がすでにデータベースに反映されているためであり、トランザクションとは関係ありません。为了验证此处
メソッドの最後に Thread.Sleep(10000) を追加して 10 秒間スリープし、インターフェイスを呼び出してこの 10 秒間にパラメータ param=a を渡します。navicat に移動して、id=1 のレコードをクエリします。データベースを調べて、名前が a であるかどうかを確認し、10 秒以内に Name=a であれば、上記が正しいことが証明されます。
1.3 如果update方法不加@Transactional注解,B加@Transactional注解,并且使用代理类调用B方法:
コードを次のように変更します。
図に示すように、param=b の場合、リクエスト後の結果は次のようになります。
結論: このクラスでは、トランザクションをオープンしないメソッド A が次のようになります。
1. トランザクションを開始したメソッド B を直接呼び出します。AB はトランザクションを開始しません。
2. ただし、A メソッドがプロキシ オブジェクトを使用して B メソッドを呼び出す場合、B メソッドはトランザクションを開きますが、A メソッドにはトランザクションがありません。
シナリオ 1 の要約分析:
分析:
シナリオ 1.2 param=b の場合、id=1 と id=2 のレコードが観察されます并未回滚
。update方法没有开启事务,b方法的事务也失效了。
シナリオ 1.3 では、param=b の場合、id=1 のレコードが観察できることがわかります并未回滚
。id=2 のレコードにはデータがロールバックされています。このことから、A方法没有开启事务,B方法的开启了事务。
上記の状況がなぜ起こるのかということが考えられます。
回答: このクラスでは、メソッド A がメソッド B を直接呼び出すことは、this.b() を使用するのと同等であるため、詳細については、以下のテクスチャ クラスのコンパイル ファイルを参照してください。@Transitional に含まれるトランザクション原則は AOP に基づいているため、トランザクションを有効にするには、プロキシ オブジェクトを通じてメソッドを呼び出す必要があります。この呼び出しは、ネイティブ オブジェクト呼び出しと同等です (トランザクション プロキシによって拡張されていません)。
備考: 私の開発環境ではspringboot 2.3版本,AOP默认代理是Cglib
、プロキシ オブジェクトは、implements インターフェイスを使用せずに、このクラスのメソッドを直接呼び出すことができ、ServiceImpl の強制型変換も直接実行できます。(jdkエージェントの場合はエラーが報告されます) もちろん自己注入自己
Bメソッドを呼び出すこともできますが、必ずImplementsメソッドを使用する必要があります。
シナリオ 2 A メソッドと B メソッドは同じクラスにありません。
注意:
TestUserServiceImpl的updateOther 就是A方法
GoodsServiceImpl的updateGoodsById 就是B方法
まず、比較方法に対応するトランザクションの具体的な状況 (トランザクション伝播レベル) を観察するために、別の操作クラスを独自に作成します。コードは以下のように表示されます
@Service
public class TestUserServiceImpl implements ITestService {
@Autowired
private TestUserMapper testUserMapper;
@Autowired
private IGoodsService goodsService;
@Override
@Transactional(rollbackFor = Exception.class)
public int updateOther(String param) throws InterruptedException {
// 该testUser表记录,修改前 name = '1'
testUserMapper.updateUserById("a", 1L);
// 该goods表记录,修改前 name = '1'
goodsService.updateGoodsById("product1", 1L);
return 1;
}
}
@Service
public class GoodsServiceImpl implements IGoodsService {
@Autowired
private GoodsMapper goodsMapper;
@Autowired
private TestUserMapper testUserMapper;
// @Transactional(rollbackFor = Exception.class)
@Override
public int updateGoodsById(String name, Long id) {
TestUser testUser = testUserMapper.selectById(1L);
System.out.println("GoodsServiceImpl.updateGoodsById()查询的user结果: " + testUser.toString());
goodsMapper.updateGoodsById(name, id); // 修改前 name = '1'
int i = 1 / 0;
return 0;
}
}
@Mapper
public interface GoodsMapper extends BaseMapper<Goods> {
@Update("update goods set name = #{name} where id=#{id}")
public int updateGoodsById(String name, Long id );
}
2.1 如果A方法加@Transactional注解,b方法不加@Transactional注解:
リクエスト後の結果は次のようになります。
2.1 現象の分析: param=b の場合、mysql 内のデータは変更されておらず、両方がトランザクション データをロールバックしたことを示し、両方がトランザクションを開始したことを示します。そして、メソッド a のデータ変更はメソッド b でクエリされ、2 つが同じトランザクションであることが示されます。
結論: 異なる種類のメソッドが呼び出される場合 (A が B を呼び出す)、メソッド A に @Transional が追加され、メソッド B に @Transional が追加されない場合、メソッド B はメソッド A のトランザクションに追加されます。
(B メソッドが @Transional を追加すると、Spring のトランザクションのデフォルトの伝播レベルにより、AB メソッドも同じトランザクション内になります)
2.2 如果A方法不加@Transactional注解,B加@Transactional注解:
リクエスト後の結果は次のようになります。
注意
此时name=a和上方2.1标题的name=a有什么区别吗
質問: アイデア コンソールによると、メソッド b が id=1 、?のレコードをクエリしたことがわかります。。
回答:
2.2 シナリオ b のメソッド クエリの name=a は、メソッド A でトランザクションがオープンされておらず、データ更新がデータベースに直接格納されているためです。このとき、クエリされた name=a は、データベース。
2.1 シナリオ b のメソッド クエリの name=a は、2 つが同じトランザクション内にあり、データが共有されており、ある種のスナップショット読み取りのようです (詳細は忘れましたが、mvcc とは何ですか)。
2.2 現象の分析: メソッド b の例外のため、mysql の商品テーブルのみにデータがロールバックされており、メソッド B のみがトランザクションを開始したことを示しています。
結論: 異なるクラスでは、A は @Transitional メソッドを追加するために B を呼び出す @Transitional メソッドを追加せず、A はトランザクションを開始せず、B はトランザクションを開始します。