項目地址
Github :https://github.com/alibaba/fescar
中文文檔:https://github.com/alibaba/fescar/wiki/Home_Chinese
demo:https://github.com/fescar-group/fescar-samples
dubbo官網使用fecar的案例
從Github上下載的fescar目測是沒有example的,所以需要從demo地址裏下載demo。關於官網的案例就不贅述了,直接開始運行步驟。
一、導入項目
1.導入案例demo
個人使用的是STS,解壓從第三個地址下載的fescar-samples壓縮包。然後用STS導入。
沒必要全部導入,我測試的是dubbo例子。springboot的例子還沒測,有興趣的自己動手。
2.導入server
從第一個地址下載的zip解壓後,導入該目錄下的所有工程。主要是server工程裏有依賴的版本號需要從父工程獲取,如果只導入server會提示Maven找不到依賴的。所以乾脆全導入即可。
二、創建數據庫
官網給的例子是在一個庫裏創建三張表,然後配置相同的數據源,追求簡單。我選擇創建三個庫,分別將賬戶、訂單、庫存的表放到各自對應的數據庫中,也是爲了更貼近實際生產的情況模擬。
1.每個庫先分別創建表undo_log
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_unionkey` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=159 DEFAULT CHARSET=utf8
UNDO_LOG 此表用於 Fescar 的AT模式。
2.創建庫存表storage_tbl
在fescar_storage庫創建表storage_tbl
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3.創建訂單表order_tbl
在fescar_order庫創建表order_tbl
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
4.創建賬戶表account_tbl
在fescar_account庫創建表account_tbl
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
至此,數據庫的準備完成
三、修改配置文件
如圖,修改數據庫配置文件。三個數據源對應的數據庫url以及賬號密碼。
四、啓動服務
1.啓動server
2.啓動業務服務
前三個服務的啓動順序隨意,啓動後最終啓動DubboBusinessTester接口運行。運行後程序會給賬戶表和庫存表各自初始化一條數據。
此時運行DubboBusinessTester的main方法後控制檯會報錯
原因是demo裏的purchase方法中官方拋出一個異常。
@Override
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
public void purchase(String userId, String commodityCode, int orderCount) {
LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
storageService.deduct(commodityCode, orderCount);
orderService.create(userId, commodityCode, orderCount);
throw new RuntimeException("xxx");
}
註釋掉後再運行就可以看到事務全部提交,三個庫的業務表都更新或插入了相關記錄。至此,官方demo運行完畢。
五、debug看事務提交及回滾情況
當代碼執行這一步之前,查看數據庫。
庫存表的count減少了2變爲98
賬戶表金額money減少了400變爲599
並且訂單表生成了一條記錄
由此證明,本地事務已經提交了。但是全局事務還是可以回滾的,繼續執行,會拋出事先寫好的異常。全局事務回滾,再看數據庫時,三張表的數據全部回滾爲初始狀態。
public class TransactionalTemplate {
/**
* Execute object.
*
* @param business the business
* @return the object
* @throws ExecutionException the execution exception
*/
public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException {
// 1. get or create a transaction
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
// 2. begin transaction開啓事務
try {
tx.begin(business.timeout(), business.name());
} catch (TransactionException txe) {
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.BeginFailure);
}
Object rs = null;
try {
// Do Your Business業務處理
rs = business.execute();
} catch (Throwable ex) {
// 3. any business exception, rollback.
try {
//回滾
tx.rollback();
// 3.1 Successfully rolled back
throw new TransactionalExecutor.ExecutionException(tx, TransactionalExecutor.Code.RollbackDone, ex);
} catch (TransactionException txe) {
// 3.2 Failed to rollback
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.RollbackFailure, ex);
} finally {
GlobalTransactionContext.clean();
}
}
// 4. everything is fine, commit.
try {
tx.commit();
} catch (TransactionException txe) {
// 4.1 Failed to commit
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.CommitFailure);
} finally {
GlobalTransactionContext.clean();
}
return rs;
}
}