# 安装tcc-transaction server和dashboard
参考这篇文章【https://changmingxie.github.io/zh-cn/docs/ops/server/deploy-alone.html】里面有mysql的建表脚本,先将数据库建好。
下载tcc-transaction
cd /chz/install/tcc-transaction
wget https://github.com/changmingxie/tcc-transaction/releases/download/2.1.0/tcc-transaction-server.zip
wget https://github.com/changmingxie/tcc-transaction/releases/download/2.1.0/tcc-transaction-dashboard.zip
解压
unzip tcc-transaction-server.zip -d server
unzip tcc-transaction-dashboard.zip -d dashboard
配置tcc-transaction-server
cd /chz/install/tcc-transaction/server
vim application.properties
<<<< 内容如下:
server:
port: 12332
servlet:
context-path: /${spring.application.name}
spring:
application:
name: tcc-transaction-server
tcc:
storage:
storage-type: memory
max-attempts: 1
recovery:
quartz-clustered: true
quartz-data-source-url: jdbc:mysql://192.168.44.228:3306/TCC_SERVER?useSSL=false&allowPublicKeyRetrieval=true
quartz-data-source-driver: com.mysql.jdbc.Driver
quartz-data-source-user: root
quartz-data-source-password: root
registry:
registry-types:
- direct
cluster-name: default
remoting:
listen-port: 2332
request-process-thread-queue-capacity: 1024
logging:
level:
root: info
>>>>
启动tcc-transaction-server
cd /chz/install/tcc-transaction/server
bin/startup.sh
配置tcc-transaction-dashboard
cd /chz/install/tcc-transaction/dashboard
vim application.properties
<<<< 修改后的内容如下
server:
servlet:
context-path: /${spring.application.name}
port: 22332
logging:
level:
root: info
spring:
application:
name: tcc-transaction-dashboard
resources:
static-locations: classpath:templates/
chain:
cache: false
freemarker:
enabled: true
cache: false
charset: UTF-8
suffix: .html
check-template-location: true
template-loader-path: classpath:/templates/
tcc:
dashboard:
userName: admin
password: 123456
connection-mode: server
registry:
registry-type: direct
registry-role: dashboard
direct:
server-addresses-for-dashboard: 192.168.44.228:12332
recovery:
recovery-enabled: false
quartz-clustered: true
quartz-data-source-url: jdbc:mysql://192.168.44.228:3306/TCC_SERVER?useSSL=false&allowPublicKeyRetrieval=true
quartz-data-source-driver: com.mysql.jdbc.Driver
quartz-data-source-user: root
quartz-data-source-password: root
feign:
path: /tcc-transaction-server
>>>>
配置tcc-transaction-dashboard的启动文件
cd /chz/install/tcc-transaction/dashboard
vim bin/startup.sh
<<<< 在文件里面【JAVA_OPT】附件添加这几句
JAVA_OPT="${JAVA_OPT} --add-opens=java.base/java.lang=ALL-UNNAMED"
JAVA_OPT="${JAVA_OPT} --add-opens=java.base/java.util=ALL-UNNAMED"
JAVA_OPT="${JAVA_OPT} --add-opens=java.base/java.text=ALL-UNNAMED"
>>>>
启动tcc-transaction-dashboard
cd /chz/install/tcc-transaction/dashboard
bin/startup.sh
访问【http://192.168.44.228:22332/tcc-transaction-dashboard】试试,用户名和密码是【admin/123456】
# 下面开始写测试程序
【pom.xml】
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mengyun</groupId>
<artifactId>tcc-transaction-http</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.mengyun</groupId>
<artifactId>tcc-transaction-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
【application.yaml】
server.port: 8082
spring.application.name: myTcc
server.servlet.context-path: /myTcc
spring.main.allow-bean-definition-overriding: true
spring.tcc.storage.storage-type: remoting
spring.tcc.storage.domain: TCC:FEIGN:CAPITAL
spring.tcc.recovery.recovery-enabled: false
spring.tcc.registry.registry-type: direct
spring.tcc.registry.cluster-name: default
spring.tcc.registry.direct.server-addresses: 192.168.44.228:2332
【logback.xml】
<configuration debug="false" xmlns="http://ch.qos.logback/xml/ns/logback"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback
https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ::: %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
【Test1Controller.java】
package com.chz.myTcc.controller;
@Slf4j
@RestController
@RequestMapping("/test1")
public class Test1Controller {
@Autowired
private Test2FeignClient test2FeignClient;
@Autowired
private Test3FeignClient test3FeignClient;
@RequestMapping(value = "/test1Success", method = RequestMethod.GET)
@Compensable(confirmMethod = "testConfirm", cancelMethod = "testCancel")
public String test1Success(@RequestParam("p1")String p1)
{
// 这个方法是测试全部正常的情况
log.info("Test1Controller::test1Success");
test2FeignClient.test1Success(p1);
test3FeignClient.test1Success(p1);
return "test1Success: success";
}
@RequestMapping(value = "/test1Fail", method = RequestMethod.GET)
@Compensable(confirmMethod = "testConfirm", cancelMethod = "testCancel")
public String test1Fail(@RequestParam("p1")String p1)
{
// 这个方法测试子调用全部正常,本方法抛出异常会不会全部回滚
log.info("Test1Controller::test1Fail");
test2FeignClient.test1Success(p1);
test3FeignClient.test1Success(p1);
throw new RuntimeException("test1Fail");
}
@RequestMapping(value = "/test2Fail", method = RequestMethod.GET)
@Compensable(confirmMethod = "testConfirm", cancelMethod = "testCancel")
public String test2Fail(@RequestParam("p1")String p1)
{
// 本方法测试子调用异常,但本方法忽略异常的情况下子方法会做何处理
log.info("Test1Controller::test2Fail");
test2FeignClient.test1Success(p1);
test3FeignClient.test1Fail(p1);
return "test2Fail: success";
}
public void testConfirm(@RequestParam("p1")String p1)
{
log.info("Test1Controller::testConfirm");
}
public void testCancel(@RequestParam("p1")String p1)
{
log.info("Test1Controller::testCancel");
}
}
【Test2Controller.java】
package com.chz.myTcc.controller;
@Slf4j
@RestController
@RequestMapping("/test2")
public class Test2Controller
{
@RequestMapping(value = "/test1Success", method = RequestMethod.GET)
@Compensable(confirmMethod = "testConfirm", cancelMethod = "testCancel")
public String test1Success(@RequestParam("p1")String p1)
{
log.info("Test2Controller::test1Success");
return "test1: success";
}
@RequestMapping(value = "/test1Fail", method = RequestMethod.GET)
@Compensable(confirmMethod = "testConfirm", cancelMethod = "testCancel")
public String test1Fail(@RequestParam("p1")String p1)
{
log.info("Test2Controller::test1Fail");
throw new RuntimeException("test1Fail");
}
public void testConfirm(@RequestParam("p1")String p1)
{
log.info("Test2Controller::testConfirm");
}
public void testCancel(@RequestParam("p1")String p1)
{
log.info("Test2Controller::testCancel");
}
}
【Test3Controller.java】
package com.chz.myTcc.controller;
@Slf4j
@RestController
@RequestMapping("/test3")
public class Test3Controller
{
@RequestMapping(value = "/test1Success", method = RequestMethod.GET)
@Compensable(confirmMethod = "testConfirm", cancelMethod = "testCancel")
public String test1Success(@RequestParam("p1")String p1)
{
log.info("Test3Controller::test1Success");
return "test1: success";
}
@RequestMapping(value = "/test1Fail", method = RequestMethod.GET)
@Compensable(confirmMethod = "testConfirm", cancelMethod = "testCancel")
public String test1Fail(@RequestParam("p1")String p1)
{
log.info("Test3Controller::test1Fail");
throw new RuntimeException("test1Fail");
}
public void testConfirm(@RequestParam("p1")String p1)
{
log.info("Test3Controller::testConfirm");
}
public void testCancel(@RequestParam("p1")String p1)
{
log.info("Test3Controller::testCancel");
}
}
【MyExceptionHandler.java】
package com.chz.myTcc.exceptions;
@Slf4j
@RestControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleException(Exception e){
log.error("MyExceptionHandler::handleException: " + e.getMessage());
return e.getClass().getSimpleName() + ": " + e.getMessage();
}
}
【Test2FeignClient.java】
package com.chz.myTcc.feign;
@FeignClient(contextId = "Test2FeignClient", name = "myTcc", url = "http://localhost:8082/myTcc/test2")
public interface Test2FeignClient {
@EnableTcc
@RequestMapping(value = "/test1Success", method = RequestMethod.GET)
String test1Success(@RequestParam("p1")String p1);
@EnableTcc
@RequestMapping(value = "/test1Fail", method = RequestMethod.GET)
String test1Fail(@RequestParam("p1")String p1);
}
注意上面是【@EnableTcc】非常重要,这是将主方法与子调用合并成一个事务进行处理的关键。
【Test3FeignClient.java】
package com.chz.myTcc.feign;
@FeignClient(contextId = "Test3FeignClient", name = "myTcc", url = "http://localhost:8082/myTcc/test3")
public interface Test3FeignClient {
@EnableTcc
@RequestMapping(value = "/test1Success", method = RequestMethod.GET)
String test1Success(@RequestParam("p1")String p1);
@EnableTcc
@RequestMapping(value = "/test1Fail", method = RequestMethod.GET)
String test1Fail(@RequestParam("p1")String p1);
}
注意上面是【@EnableTcc】非常重要,这是将主方法与子调用合并成一个事务进行处理的关键。
【MyTccTest.java】
package com.chz.myTcc;
@SpringBootApplication
@EnableFeignClients
public class MyTccTest {
public static void main(String[] args) {
SpringApplication.run(MyTccTest.class, args);
}
}
启动【MyTccTest.java】
# 下面开始测试
【测试1】:访问【http://localhost:8082/myTcc/test1/test1Success?p1=1】,查看日志如下:
很正常,跟预期的一样
【测试2】:访问【http://localhost:8082/myTcc/test1/test1Fail?p1=1】,查看日志如下:
虽然【Test1Controller::test2Fail】调用【Test2Controller::test1Success】和【Test3Controller::test1Success】都成功了,但是【Test1Controller::test1Fail】的最后一句主动抛了Exception,所以三个testCancel被触发全部进行了回滚。
【测试3】:访问【http://localhost:8082/myTcc/test1/test2Fail?p1=1】,查看日志如下:
虽然【Test1Controller::test1Fail】在访问【Test3Controller::test1Fail】的时候【Test3Controller::test1Fail】抛出了Exception,但是因为【Test1Controller::test1Fail】忽略了该异常,所以最后3个testConfirm被触发全部提交。
正常情况下是不会这样写代码的,正常情况下【Test3Controller::test1Fail】抛出了异常之后【Test1Controller::test1Fail】也是要抛出异常回滚全部事务的。代码这样写只是为了测试而已