実際のプロジェクトでは、分散トランザクションを避けるように努めます。 ただし、場合によってはサービスを分割する必要があり、分散トランザクションの問題が発生することがあります。
同時に、分散取引では面接で市場についても質問されるので、このケースで練習して、面接で 123 について話すことができます。
ここはビジネス上の問題です。ユーザーがクーポンを受け取ると、ユーザーがクーポンを受け取った回数を差し引く必要があり、その後、ユーザーがクーポンを受け取った記録が作成されます。記録された。
当初はメッセージを使用できましたここではキュー モードで、非同期を使用して新しいユーザー コレクション レコードを追加します。ただし、ここでの要件は、ユーザーが収集記録を受け取った後すぐに表示できる必要があることであるため、分散トランザクションの問題を実装するためにAmitikos を導入しました。
分散トランザクションは、複数のコンピュータまたはデータベースにまたがるトランザクションを指します。ネットワークの遅延、障害、またはこれらのコンピュータまたはデータベース間の不一致が考えられます。分散トランザクションでは、データの正確性と整合性を確保するために、すべての操作の原子性、一貫性、分離性、耐久性を確保する必要があります。
2PC は現在最も一般的に使用されている分散トランザクション プロトコルであり、そのプロセスは準備段階と送信段階の 2 つの段階に分かれています。準備フェーズでは、トランザクション コーディネーターがすべての参加者に準備リクエストを発行し、参加者はローカル トランザクションを実行して準備状態にし、準備結果をトランザクション コーディネーターに返します。コミット フェーズでは、すべての参加者が正常に実行された場合、トランザクション コーディネーターはすべての参加者にコミット リクエストを発行し、参加者はローカル トランザクションをコミットします。それ以外の場合、トランザクション コーディネーターはすべての参加者にロールバック リクエストを発行し、参加者はローカル トランザクションをロールバックします。取引のロールです。
3PC は 2PC の改良版で、2PC に基づいて準備と提出の段階が追加されています。送信準備フェーズでは、コーディネーターが参加者に送信できるかどうかを尋ねます。参加者が同意を返した場合は送信フェーズで直接送信され、そうでない場合は送信フェーズでロールバックされます。
javax.transaction.TransactionManager
。これは、トランザクションの開始、送信、引き出し、およびその他の操作を定義します。XA 仕様を満たすリソース定義インターフェイス
javax.transaction.xa.XAResource
XAResourceInterface を作成し、このインターフェイスで定義された 2 フェーズ送信関連のインターフェイスを実装します。
JTA インターフェースを使用してトランザクションを実装するアプリケーションがある場合、アプリケーションの実行中には、JTA を実装するコンテナーが必要になります。通常、これは JBoss、Websphere、その他のアプリケーション サーバーなどの J2EE コンテナーです。
ただし、JTA を実装する独立したフレームワークもいくつかあります。たとえば、Amitikos や bitronix は JTA 実装フレームワークを jar パッケージの形式で提供しています。このようにして、JTA を使用して Tomcat や Jetty などのサーバー上でトランザクションを実装するアプリケーション システムを実行できます。
上記のローカル トランザクションと外部トランザクションの違いで述べたように、JTA トランザクションは外部トランザクションであり、複数のリソースのトランザクション性を実装するために使用できます。各リソースによって実装された XAResource
を通じて、2 フェーズの送信を正確に制御します。興味のある学生は、このインターフェースのメソッドを見てみることができます。コミット、ロールバックなどのメソッドに加えて、end()
、forget()
、isSameRM もあります。 ()
、prepare()
など。これらのインターフェイスだけから、2 フェーズ トランザクションの実装における JTA の複雑さが想像できます。
XA は、X/Open 組織によって提案された分散トランザクション アーキテクチャ (またはプロトコル) です。 XA アーキテクチャは主に、(グローバル) トランザクション マネージャーと (ローカル) リソース マネージャー間のインターフェイスを定義します。 XA インターフェイスは、トランザクション マネージャーと 1 つ以上のリソース マネージャー間の通信ブリッジを形成する双方向システム インターフェイスです。つまり、XAに基づくトランザクションでは、システムが複数のデータベースにアクセスしたり、データベースとメッセージミドルウェアなどのリソースの両方にアクセスしたりするなど、複数のリソースに対するトランザクション管理が可能となります。このようにして、送信されたすべてのトランザクションまたはキャンセルされたすべてのトランザクションを複数のデータベースおよびメッセージ ミドルウェアに直接実装できます。 XA仕様はJava仕様ではなく汎用仕様であり、現在、さまざまなデータベースや多くのメッセージミドルウェアがXA仕様をサポートしています。
JTA は XA 仕様を満たす仕様であり、Java 開発に使用されます。したがって、JTA を使用して分散トランザクションを実装すると言うとき、実際には、JTA 仕様を使用して、システム内の複数のデータベース、メッセージ ミドルウェア、およびその他のリソースとのトランザクションを実装することを意味します。
Atomikos は非常に人気のあるオープンソース トランザクション マネージャーであり、Spring Boot アプリケーションに組み込むことができます。 Tomcat アプリケーション サーバーは JTA 仕様を実装していません。Tomcat をアプリケーション サーバーとして使用する場合は、サードパーティのトランザクション マネージャー クラスをグローバル トランザクション マネージャーとして使用する必要があります。これを行うのは Amitikos フレームワークであり、トランザクション管理をアプリケーションに統合します。アプリケーションサーバーに依存しません。
たくさんの理論について話すのは無駄です。コードを見せてください。
テクノロジー スタック:Spring Boot MyBatis Atomikos MySQL
この記事のコードに従う場合は、mysql のバージョンに注意してください。
最初に 2 つのデータベース (my-db_0 と my-db_1) を構築し、次に各データベースにテーブルを作成します。
データベース my-db_0 内:
CREATE TABLE `t_user_0` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `age` int NOT NULL, `gender` int NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
データベース my-db_1 内:
CREATE TABLE `t_user_1` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `age` int NOT NULL, `gender` int NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
これは分散トランザクションを説明するためだけのものであり、テーブル。
("classpath*:com/tian/mapper/user1/*.xml")
mapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.tian.mapper.user0.User0Mapper"> <!-- --> <cache eviction="LRU" flushInterval="10000" size="1024" /> <resultMap id="BaseResultMap" type="com.tian.entity.User0"> <id column="id" jdbcType="BIGINT" property="id" /> <result column="user_name" jdbcType="VARCHAR" property="userName" /> <result column="age" jdbcType="INTEGER" property="age" /> <result column="gender" jdbcType="INTEGER" property="gender" /> </resultMap> <sql id="Base_Column_List"> id, user_name, age, gender </sql> <insert id="insert" parameterType="com.tian.entity.User0"> insert into t_user_0 (id, user_name,age, gender) values (#{id,jdbcType=BIGINT}, #{userName,jdbcType=VARCHAR},#{age,jdbcType=INTEGER},#{gender,jdbcType=INTEGER}) </insert> </mapper>
public interface User0Mapper { int insert(User0 record); }
/** * @author tianwc 公众号:java后端技术全栈、面试专栏 * @version 1.0.0 * @date 2023年05月11日 19:38 * 博客地址:<a href="http://woaijava.cc/">博客地址</a> * <p> * 模拟三种场景:正常、制造异常、数据库异常 */ @Service public class UserServiceImpl implements UserService { @Resource private User0Mapper user0Mapper; @Resource private User1Mapper user1Mapper; /** * 正常逻辑 同时对两个数据库进行 插入数据 */ @Transactional @Override public int transaction1() throws Exception { User1 user1 = new User1(); user1.setUserName("22222"); user1.setAge(11); user1.setGender(0); user1Mapper.add(user1); System.out.println("---------------------------"); // sit(数据源1) User0 user0 = new User0(); user0.setUserName("111111"); user0.setAge(11); user0.setGender(0); user0Mapper.insert(user0); return 1; } /** * 正常逻辑 同时对两个数据库进行 插入数据 * 数据插入完后 出现异常 */ @Transactional @Override public int transaction2() throws Exception { User1 user1 = new User1(); user1.setUserName("22222"); user1.setAge(11); user1.setGender(0); user1Mapper.add(user1); System.out.println("---------------------------"); // sit(数据源1) User0 user0 = new User0(); user0.setUserName("111111"); user0.setAge(11); user0.setGender(0); user0Mapper.insert(user0); //认为制造一个异常 int a=1/0; return 1; } /** * 第一个数据插入成功 第二个数据插入失败 */ @Transactional @Override public int transaction3() throws Exception { User1 user1 = new User1(); user1.setUserName("22222"); user1.setAge(11); user1.setGender(0); user1Mapper.add(user1); System.out.println("---------------------------"); // sit(数据源1) User0 user0 = new User0(); //故意搞长点,模拟插入失败 让前面的数据回滚 user0.setUserName("111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"); user0.setAge(11); user0.setGender(0); user0Mapper.insert(user0); return 1; } }
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; /** * @author tianwc 公众号:java后端技术全栈、面试专栏 * @version 1.0.0 * @date 2023年05月11日 19:38 * 博客地址:<a href="http://woaijava.cc/">博客地址</a> * <p> * 项目启动类 */ @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) //@ComponentScan(basePackages = {"com.tian"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
結果: 除数をゼロにすることはできないという例外がスローされ、新しいデータはどちらのデータベースにも追加されません。
http://localhost:9001/user/test3
結果: データ フィールド値が長すぎる例外がスローされ、新しいデータはどちらのデータベースにも追加されません。
以上がSpring Boot + MyBatis + Atomikos + MySQL (ソースコード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。