原理介紹
TCC事務機制相對於傳統事務機制(X/Open XA Two-Phase-Commit),其特徵在於它不依賴資源管理器(RM)對XA的支持,而是通過對(由業務系統提供的)業務邏輯的調度來實現分佈式事務。主要由三步操作,Try: 嘗試執行業務、 Confirm:確認執行業務、 Cancel: 取消執行業務。
模式特點
- 該模式對代碼的嵌入性高,要求每個業務需要寫三種步驟的操作。
- 該模式對有無本地事務控制都可以支持使用面廣。
- 數據一致性控制幾乎完全由開發者控制,對業務開發難度要求高。
使用場景
沒有帶事務的中間件,如:redis
項目實戰
pom.xml和application.yml文與LCN的一致
模擬Tcc組合redis
項目組合:
- lcn-tm:事務管理者
- lcn-order:事務發起者
- lcn-pay:事務參與者
- cloud-eureka:註冊中心
事務發起者和事務參與者都是TCC
發起者代碼
package com.dandan.lcnorder.controller;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.codingapi.txlcn.tc.annotation.TccTransaction;
import com.dandan.lcnorder.dao.TblOrderDao;
import com.dandan.lcnorder.entity.TblOrder;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 模擬Tcc組合redis
*/
@RestController
public class OrderTccRedisController {
@Autowired
private TblOrderDao tblOrderDao;
@Autowired
private RestTemplate restTemplate;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@PostMapping("/add-order-tcc-redis")
@Transactional(rollbackFor = Exception.class)
@TccTransaction
public String add(@RequestBody TblOrder bean){
JSONObject date = new JSONObject();
date.put("payName",bean.getOrderName()+"pay");
restTemplate.postForEntity("http://lcn-pay/add-pay-tcc-redis",date,String.class);
TblOrder tblOrder = new TblOrder();
tblOrder.setId(1);
tblOrder.setOrderName("新");
BoundValueOperations<String, String> order = redisTemplate.boundValueOps("order");
order.set("order-value");
tblOrderDao.updateByPrimaryKey(tblOrder);
int i = 1/0;
return "新增訂單成功";
}
public String confirmAdd(TblOrder bean){
System.out.println("add 確認線程名:"+Thread.currentThread().getName());
System.out.println("order confirm ");
return "confirm 訂單成功";
}
//// private static Map<String,Integer> maps = new HashMap<>();
//
// private ThreadLocal<Integer> ids = new ThreadLocal<>();
public String cancelAdd(TblOrder bean){
System.out.println("add 取消線程名:"+Thread.currentThread().getName());
TblOrder tblOrder = new TblOrder();
tblOrder.setId(1);
tblOrder.setOrderName("舊");
tblOrderDao.updateByPrimaryKey(tblOrder);
redisTemplate.delete("order");
System.out.println("order cancel ");
return "cancel 訂單成功";
}
}
參與者代碼
package com.dandan.lcnpay.controller;
import com.codingapi.txlcn.tc.annotation.TccTransaction;
import com.dandan.lcnpay.dao.TblPayDao;
import com.dandan.lcnpay.entity.TblPay;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 模擬Tcc組合mysql,不建議用,代碼複雜度太高,使用LCN更簡單
*/
@RestController
public class PayTccController {
@Autowired
private TblPayDao tblPayDao;
@PostMapping("/add-pay-tcc")
@Transactional(rollbackFor = Exception.class)
@TccTransaction
public String addPay(@RequestBody TblPay bean){
tblPayDao.insert(bean);
Integer id = bean.getId();
maps.put("a",id);
// int i = 1/0;
return "新增支付成功";
}
public String confirmAddPay(TblPay bean){
System.out.println("pay confirm");
return "新增支付成功";
}
private static Map<String,Integer> maps = new HashMap<>();
/**
* 逆sql
* @param bean
* @return
*/
public String cancelAddPay(TblPay bean){
Integer a = maps.get("a");
System.out.println("a:"+a);
System.out.println("pay cancel");
tblPayDao.deleteByPrimaryKey(a);
return "取消支付成功";
}
}
事務發起者是LCN,參與者是TCC
發起者代碼
package com.dandan.lcnorder.controller;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.dandan.lcnorder.dao.TblOrderDao;
import com.dandan.lcnorder.entity.TblOrder;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 模擬Tcc組合redis,LCN組合sql
* client1使用LCN
* client2使用TCC
*/
@RestController
public class OrderTccRedisController2 {
@Autowired
private TblOrderDao tblOrderDao;
@Autowired
private RestTemplate restTemplate;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@PostMapping("/add-order-tcc-redis2")
@Transactional(rollbackFor = Exception.class)
@LcnTransaction
public String add(@RequestBody TblOrder bean){
JSONObject date = new JSONObject();
date.put("payName",bean.getOrderName()+"pay");
restTemplate.postForEntity("http://lcn-pay/add-pay-tcc-redis",date,String.class);
TblOrder tblOrder = new TblOrder();
tblOrder.setId(1);
tblOrder.setOrderName("新");
tblOrderDao.updateByPrimaryKey(tblOrder);
int i = 1/0;
return "新增訂單成功";
}
}
參與者代碼
package com.dandan.lcnpay.controller;
import com.dandan.lcnpay.dao.TblPayDao;
import com.dandan.lcnpay.entity.TblPay;
import com.dandan.lcnpay.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PayTccRedisController {
@Autowired
private TblPayDao tblPayDao;
@Autowired
private RedisService redisService;
@PostMapping("/add-pay-tcc-redis")
@Transactional(rollbackFor = Exception.class)
public String addPay(@RequestBody TblPay bean){
redisService.addPay(null);
// int i = 1/0;
return "新增支付成功";
}
}
RedisService代碼
package com.dandan.lcnpay.service;
import com.codingapi.txlcn.tc.annotation.TccTransaction;
import com.dandan.lcnpay.entity.TblPay;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.HashMap;
@Service
public class RedisService {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@TccTransaction
public String addPay(@RequestBody TblPay bean){
BoundValueOperations<String, String> pay = redisTemplate.boundValueOps("pay");
pay.set("pay-value");
// int i = 1/0;
return "新增支付成功";
}
public String confirmAddPay(TblPay bean){
System.out.println("pay confirm");
return "confirm 支付成功";
}
private static java.util.Map<String,Integer> maps = new HashMap<>();
/**
* 逆sql
* @param bean
* @return
*/
public String cancelAddPay(TblPay bean){
redisTemplate.delete("pay");
System.out.println("pay cancel");
return "取消支付成功";
}
}