1、pom.xml配置
<dependency>
<groupId>com.jinnjo.txgroupId>
<artifactId>spring-boot-starter-narayana-lraartifactId>
<version>2.0.9version>
dependency>
<dependency>
<groupId>org.eclipsegroupId>
<artifactId>yassonartifactId>
<version>1.0version>
dependency>
<dependency>
<groupId>org.glassfishgroupId>
<artifactId>javax.jsonartifactId>
<version>1.0.4version>
dependency>
2、修改openfeign依赖
<dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-openfeignartifactId> <exclusions><exclusion> <groupId>om.sun.jerseygroupId> <artifactId>jersey-clientartifactId> exclusion> <exclusion><groupId>javax.ws.rsgroupId> <artifactId>jsr311-apiartifactId>exclusion> exclusions> dependency>
3、启动类扫描包配置
示例代码:"com.jinnjo.tx.lra"
引入类配置
启动类增加事务调度器的配置
/** * 事务调度器地址 */ @Value("${lra.http.host}") private String lraHost; /** * 事务调度器端口 */ @Value("${lra.http.port}") private int lraPort; @Bean public LRAClient lraClient() throws URISyntaxException { return new NarayanaLRAClient(lraHost,lraPort); }
Properties配置
##事务调度器地址
lra.http.host=localhost
##事务调度器端口
lra.http.port=8080
4、业务开发
被调用方(创建lra事务处理controller)
示例代码:
/** * 卡包消费 */ @RestController @Api(tags = "卡包消费", description = "卡包消费(TCC事务处理)") @Validated @LRA(LRA.Type.SUPPORTS) public class CardLraController extends RestControllerBase { @Autowired private CardPackageService cardPackageService; /** * 使用卡包消费 * @param auth 授权码 * @param amount 金额 * @param orderNo 订单号 * @param lraId 事务ID * @return */ @RequestMapping(value = "card-lra/notify", method = RequestMethod.POST) @ApiOperation(value = "使用卡包消费", notes = "使用卡包消费") @LRA(LRA.Type.REQUIRED) public ResponseEntitypayNotify( @ApiIgnore @AuthenticationPrincipal Authentication auth, @ApiParam(name = "amount", value = "消费金额(元)", required = true) @RequestParam BigDecimal amount, @ApiParam(name = "orderNo", value = "消费单号", required = true) @RequestParam String orderNo, @RequestHeader(value= LRA_HTTP_HEADER) String lraId){ UserVO user = UserUtil.getUserForAuthentication(auth); if(user!=null && StringUtils.isNotEmpty(user.getUserId())) { Boolean result = cardPackageService.payNotify(user.getUserId(), amount, orderNo, lraId); if(result){ return ResponseEntity.ok().build(); } } return ResponseEntity.notFound().build(); } /** * 使用卡包消费成功后确认 * @param lraId 事务ID * @return */ @RequestMapping(value="card-lra/complete", method = RequestMethod.PUT) @ApiOperation(value = "使用卡包消费成功后确认", notes = "使用卡包消费成功后确认", hidden = true) @Complete public ResponseEntitycompleteCard( @RequestHeader(LRA_HTTP_HEADER) String lraId){ Boolean result = cardPackageService.completeCard(lraId); if(result){ return ResponseEntity.ok().build(); } return ResponseEntity.notFound().build(); } /** * 使用卡包消费失败后回滚 * @param lraId 事务ID * @return */ @RequestMapping(value="card-lra/compensate", method = RequestMethod.PUT) @ApiOperation(value = "使用卡包消费失败后回滚", notes = "使用卡包消费失败后回滚", hidden = true) @Compensate public ResponseEntitycompensateCard( @RequestHeader(LRA_HTTP_HEADER) String lraId){ Boolean result = cardPackageService.compensateCard(lraId); if(result){ return ResponseEntity.ok().build(); } return ResponseEntity.notFound().build(); } }
说明:创建事务处理controller,创建TCC方法(T:try尝试/占有,C:confirm确认/提交,C:cancel补偿/回滚)
T:尝试(占用)资源,通过事务ID为唯一标识
C:确认成功(Complete),通过事务ID获取占有的资源进行submit
C:事务回滚/补偿(Compensate),通过事务ID获取占有的资源进行rowback
调用方(创建lra事务处理controller)
示例代码:
@RestController @RequestMapping(value="/card-pay") @LRA(LRA.Type.SUPPORTS) public class CardPayController { static final Logger logger = LoggerFactory.getLogger(CardPayController.class); @Autowired private OrderInfoService orderInfoService; @Autowired private LRAClient lraClient; @Autowired private CardClient cardClient; @Autowired private ResultPayAssembler resultPayAssembler; @Autowired private OrderUnifiedRepository orderUnifiedRepository; @Autowired private OrderInfoRepository orderInfoRepository; /** * 卡包支付 事务开启接口 * @param id * @param auth * @return */ @RequestMapping(value = "/pay/{id}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) @LRA(delayClose = true, join = true) @PreAuthorize(OrderConstants.AUTH_SQR_QYG_USER) public ResponseEntity paymentOrder(@PathVariable String id, @ApiIgnore @AuthenticationPrincipal Authentication auth) { //查询订单信息 OrderUnified orderUnified = orderUnifiedRepository.findOrderUnifiedByBigOrder(id); if (orderUnified == null) orderUnified = orderUnifiedRepository.findOrderUnifiedByOrderInfoListContains(orderInfoRepository.findOrderInfoByOrderId(id)); String lraId = lraClient.getCurrent().toString(); //预占资源 try { cardClient.saveAmountLRA(orderUnified.getPayAmount().doubleValue(),orderUnified.getBigOrder(),lraId); }catch (Exception e){ e.printStackTrace(); throw new DataConflictException(HttpStatus.CONFLICT.value(), "金额不足!", new HashSet() {}); } orderInfoService.successOrder(orderUnified.getBigOrder()); //cardClient.saveAmountLRA(orderUnified.getPayAmount().doubleValue(),orderUnified.getBigOrder(),lraId); //修改订单状态 ResultPayVO vo = new ResultPayVO(); vo.setMsg(OrderConstants.ORDER_PAY_TYPE_CARDBAGPAY_SUCCESS); vo.setCode("0"); return Optional.ofNullable(vo).map(resultPayAssembler::toResource).map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build()); } /** * 事务回滚 * @param lraId * @return * @throws MalformedURLException */ @RequestMapping(value = "/{lraId}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) @LRA(LRA.Type.NOT_SUPPORTED) public ResponseEntity compensateTrade(@PathVariable(value = "lraId") String lraId) throws MalformedURLException { //为了使luraId作为路径参数传递,需要对lraId进行Base64编码,此处相应做解码处理。 try { lraId = URLDecoder.decode(lraId, "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } try { String status = lraClient.cancelLRA(new URL(lraId)); return ResponseEntity.ok(status); }catch (GenericLRAException ex){ if(ex.getStatusCode()==404){ return ResponseEntity.notFound().build(); } throw ex; } } /** * 事务确认 * @param lraId * @return * @throws MalformedURLException */ @RequestMapping(value = "/{lraId}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE) @LRA(LRA.Type.NOT_SUPPORTED) public ResponseEntity confirmTrade(@PathVariable(value = "lraId") String lraId) throws MalformedURLException { //为了使luraId作为路径参数传递,需要对lraId进行Base64编码,此处相应做解码处理。 try { lraId = new String(Base64.getUrlDecoder().decode(lraId), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } try { String status = lraClient.closeLRA(new URL(lraId)); return ResponseEntity.ok(status); }catch (GenericLRAException ex){ if(ex.getStatusCode()==404){ return ResponseEntity.notFound().build(); } throw ex; } } }
说明:创建事务处理controller,创建TCC方法(T:try尝试/占有,C:confirm确认/提交,C:cancel补偿/回滚)
T:尝试(占用)资源,通过事务ID为唯一标识
C:确认成功(Complete),通过事务ID获取占有的资源进行submit
C:事务回滚/补偿(Compensate),通过事务ID获取占有的资源进行rowback
事务开启接口说明:delayClose如果为true,需要自己调用确认或回滚的方法,适用场景:如果分布式事务中有一方不可控,则需要开发者选择在什么时候调用确认或回滚的方法;如果delayClose设置为false,事务开启接口如果出现异常事务调度器会自动调用回滚操作,如果执行成功则会自动调用确认接口使用场景:所有分布式事务都提供了对应的TCC方法。
cancelOn可设置Response.Status,根据返回code来决定调用确认或回滚的接口。设置示例代码:cancelOn = {INTERNAL_SERVER_ERROR,SERVICE_UNAVAILABLE}
注:通过分布式调用多个模块时,如果有一个返回失败(失败:异常或者返回错误提示),自己程序处理不要再调用其他模块