1、部署 nacos-server 见https://blog.csdn.net/u013792404/article/details/98479190
2、部署 seata-server
下载seata-server , https://github.com/seata/seata/releases
修改配置文件:全部使用file
registry.conf
registry {
# type配置为file
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "file"
.................
}
config {
# type配置为file
# file、nacos 、apollo、zk、consul、etcd3
type = "file"
................
file {
name = "file.conf"
}
}
file.conf
#其他不用改动
store {
## store mode: file、db
mode = "file"
..........
3、数据库
-- ----------------------------
-- Table structure for undo_log
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
-- ----------------------------
DROP TABLE IF EXISTS `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`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8;
创建订单表和账户表 , 账户表添加初始数据
4、java代码
dubbo-common-api 存放domain, dubbo接口,VO等, dubbo-bussiness 调用 dubbo-account 和 dubbo-order , 生成订单后,减少账户金额。
5、dubbo-order
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.mei.dubbo</groupId>
<artifactId>dubbo-order</artifactId>
<version>0.0.1</version>
<name>dubbo-order</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.mei.dubbo</groupId>
<artifactId>dubbo-common-api</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--jsp支持 -->
<!-- servlet 依赖. -->
<!-- <dependency> -->
<!-- <groupId>javax.servlet</groupId> -->
<!-- <artifactId>javax.servlet-api</artifactId> -->
<!-- <scope>provided</scope> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>javax.servlet</groupId> -->
<!-- <artifactId>jstl</artifactId> -->
<!-- </dependency> -->
<!-- tomcat 的支持. -->
<!-- <dependency> -->
<!-- <groupId>org.springframework.boot</groupId> -->
<!-- <artifactId>spring-boot-starter-tomcat</artifactId> -->
<!-- </dependency> -->
<!-- <dependency> -->
<!-- <groupId>org.apache.tomcat.embed</groupId> -->
<!-- <artifactId>tomcat-embed-jasper</artifactId> -->
<!-- <scope>provided</scope> -->
<!-- </dependency> -->
<!-- ====================seata=========================== -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>samples-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.6.1</version>
</dependency>
<!-- ====================seata=========================== -->
<!-- ====================dubbo=========================== -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.5</version>
</dependency>
<!-- ====================dubbo =========================== -->
<!-- ====================nacos =========================== -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>0.0.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>0.2.1</version>
<exclusions>
<exclusion>
<artifactId>nacos-client</artifactId>
<groupId>com.alibaba.nacos</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.1.RELEASE</version>
<exclusions>
<exclusion>
<artifactId>nacos-client</artifactId>
<groupId>com.alibaba.nacos</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- ====================nacos =========================== -->
<!-- 数据库 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
DubboOrderApplication.java
package com.mei.dubbo.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
@SpringBootApplication(scanBasePackages = "com.mei.dubbo.order")
@EnableDiscoveryClient
@EnableDubbo(scanBasePackages = "com.mei.dubbo.order")
public class DubboOrderApplication {
public static void main(String[] args) {
SpringApplication.run(DubboOrderApplication.class, args);
}
}
application.properties
server.port=8101
spring.application.name=dubbo-order
#====================================Dubbo config===============================================
dubbo.application.id= dubbo-order
dubbo.application.name= dubbo-order
dubbo.protocol.id=dubbo
dubbo.protocol.name=dubbo
dubbo.registry.id=dubbo-order-registry
dubbo.registry.address=nacos://127.0.0.1:8848
dubbo.protocol.port=20881
dubbo.application.qosEnable=false
#===================================registry config==========================================
#Nacos\u6CE8\u518C\u4E2D\u5FC3
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
management.endpoints.web.exposure.include=*
#====================================mysql =============================================
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/dubbo?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=mysql
#=====================================mybatis\u914D\u7F6E======================================
mybatis.mapper-locations=classpath*:/mapper/*.xml
file.conf 和 registry.conf 可以从seata-server的conf中复制。
SeataAutoConfig.java seata需用到代理数据源 , new GlobalTransactionScanner("dubbo-order", "my_test_tx_group") ,其中txServiceGroup名称需要和seata-server中配置的相同才行。applicationId经测试随便写没问题。
package com.mei.dubbo.order.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.spring.annotation.GlobalTransactionScanner;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
/**
* @Author: heshouyou
* @Description seata global configuration
* @Date Created in 2019/1/24 10:28
*/
@Configuration
public class SeataAutoConfig {
/**
* autowired datasource config
*/
@Autowired
private DataSourceProperties dataSourceProperties;
/**
* init durid datasource
*
* @Return: druidDataSource datasource instance
*/
@Bean
@Primary
public DruidDataSource druidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(dataSourceProperties.getUrl());
druidDataSource.setUsername(dataSourceProperties.getUsername());
druidDataSource.setPassword(dataSourceProperties.getPassword());
druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
druidDataSource.setInitialSize(0);
druidDataSource.setMaxActive(180);
druidDataSource.setMaxWait(60000);
druidDataSource.setMinIdle(0);
druidDataSource.setValidationQuery("Select 1 from DUAL");
druidDataSource.setTestOnBorrow(false);
druidDataSource.setTestOnReturn(false);
druidDataSource.setTestWhileIdle(true);
druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
druidDataSource.setMinEvictableIdleTimeMillis(25200000);
druidDataSource.setRemoveAbandoned(true);
druidDataSource.setRemoveAbandonedTimeout(1800);
druidDataSource.setLogAbandoned(true);
return druidDataSource;
}
/**
* init datasource proxy
* @Param: druidDataSource datasource bean instance
* @Return: DataSourceProxy datasource proxy
*/
@Bean
public DataSourceProxy dataSourceProxy(DruidDataSource druidDataSource){
return new DataSourceProxy(druidDataSource);
}
/**
* init mybatis sqlSessionFactory
* @Param: dataSourceProxy datasource proxy
* @Return: DataSourceProxy datasource proxy
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSourceProxy);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/*.xml"));
factoryBean.setTransactionFactory(new JdbcTransactionFactory());
return factoryBean.getObject();
}
/**
* init global transaction scanner
*
* @Return: GlobalTransactionScanner
*/
@Bean
public GlobalTransactionScanner globalTransactionScanner(){//order-gts-fescar-example
return new GlobalTransactionScanner("dubbo-order", "my_test_tx_group");
}
}
OrderDubboServiceImpl.java
package com.mei.dubbo.order.dubbo;
import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.dubbo.config.annotation.Service;
import com.mei.dubbo.common.order.domain.Order;
import com.mei.dubbo.common.response.ServiceResponse;
import com.mei.dubbo.common.service.OrderDubboService;
import com.mei.dubbo.order.service.OrderService;
import io.seata.core.context.RootContext;
@Service(version = "1.0.0",protocol = "${dubbo.protocol.id}",
application = "${dubbo.application.id}",registry = "${dubbo.registry.id}",
timeout = 3000)
public class OrderDubboServiceImpl implements OrderDubboService {
@Autowired
private OrderService orderService;
@Override
public ServiceResponse createOrder(Order order) {
System.out.println("全局事务id :" + RootContext.getXID());
return orderService.createOrder(order);
}
}
OrderService 使用OrderMapper完成数据相关操作。
6、dubbo-account
pom.xml和 dubbo-order相似 。application.properties 内容相似,连接同一个数据库,只需修改应用端口和dubbo协议端口。
Seata配置类和启动主类相似,修改扫描包即可。file.conf 和 registry.conf相同
AccountDubboServiceImpl.java
package com.mei.dubbo.account.dubbo;
import org.springframework.beans.factory.annotation.Autowired;
import com.alibaba.dubbo.config.annotation.Service;
import com.mei.dubbo.account.service.AccountService;
import com.mei.dubbo.common.account.domain.Account;
import com.mei.dubbo.common.response.ServiceResponse;
import com.mei.dubbo.common.service.AccountDubboService;
import io.seata.core.context.RootContext;
@Service(version = "1.0.0",protocol = "${dubbo.protocol.id}",
application = "${dubbo.application.id}",registry = "${dubbo.registry.id}",
timeout = 3000)
public class AccountDubboServiceImpl implements AccountDubboService {
@Autowired
private AccountService accountService;
@Override
public ServiceResponse decreaseAccount(Account account) {
System.out.println("全局事务id :" + RootContext.getXID());
return accountService.decreaseAccount(account);
}
}
7、dubbo-bussiness
pom.xml和 dubbo-order相似 ,不需要连接数据库,去掉数据库相关依赖即可。application.properties 内容相似,去掉数据库相关配置,需修改应用端口和dubbo协议端口。file.conf 和 registry.conf相同
Seata配置类和启动主类相似,修改扫描包即可。
BussinessService.java , 经测试 @GlobalTransactional(timeoutMills = 300000) 其中没有加上name也可以
package com.mei.dubbo.bussiness.service;
import java.util.UUID;
import org.springframework.stereotype.Service;
import com.alibaba.dubbo.config.annotation.Reference;
import com.mei.dubbo.common.account.domain.Account;
import com.mei.dubbo.common.order.domain.Order;
import com.mei.dubbo.common.response.ServiceResponse;
import com.mei.dubbo.common.service.AccountDubboService;
import com.mei.dubbo.common.service.OrderDubboService;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
@Service
public class BussinessService {
@Reference(version = "1.0.0")
private AccountDubboService accountDubboService;
@Reference(version = "1.0.0")
private OrderDubboService orderDubboService;
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-seata-example")
public void buy(String str) {
System.out.println("开始全局事务,XID = " + RootContext.getXID());
Order order = new Order();
order.setOrderId(UUID.randomUUID().toString().replace("-", ""));
order.setGoodsName("商品1");
order.setNum(2);
order.setPrice("60");
order.setAccountId("1001");
String amount = "120";// 2*60
Account account = new Account();
account.setAccountId("1001");
account.setName("张三");
account.setAmount(amount);
ServiceResponse orderResponse = orderDubboService.createOrder(order);
ServiceResponse amountResponse = accountDubboService.decreaseAccount(account);
if(ServiceResponse.SUCCESS.equals(orderResponse.getCode()) && ServiceResponse.SUCCESS.equals(amountResponse.getCode())) {
System.out.println("事务成功。。。。。");
} else {
System.out.println("事务回滚1。。。。。");
throw new RuntimeException("事务回滚1。。。。。") ;
}
if("1".equals(str)) {
System.out.println("事务回滚2。。。。。");
throw new RuntimeException("事务回滚2。。。。。") ;
}
}
}
BussinessController.java
package com.mei.dubbo.bussiness.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.mei.dubbo.bussiness.service.BussinessService;
@Controller
public class BussinessController {
@Autowired
private BussinessService bussinessService ;
@ResponseBody
@RequestMapping("/buy")
public String test(String str) {
try {
bussinessService.buy(str) ;
return "OK" ;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return "false" ;
}
}
server.port=8104
spring.application.name=dubbo-business
#============================dubbo config==============================================
dubbo.application.id=dubbo-business-example
dubbo.application.name=dubbo-business-example
dubbo.protocol.id=dubbo
dubbo.protocol.name=dubbo
dubbo.protocol.port=20884
dubbo.registry.id=dubbo-business-example-registry
dubbo.registry.address=nacos://127.0.0.1:8848
dubbo.provider.version=1.0.0
dubbo.application.qosEnable=false
#==================================nacos==============================================
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
management.endpoints.web.exposure.include=*
8、测试
根据dubbo依赖关系,先启动dubbo-account ,dubbo-order ; 在启动dubbo-bussiness .
访问dubbo-bussiness : http://localhost:8104/buy?str=0 , 返回“OK”
开始全局事务,XID = 192.168.5.167:8091:2018715902
事务成功。。。。。
2019-08-05 15:55:27.367 INFO 8332 --- [nio-8104-exec-1] i.seata.tm.api.DefaultGlobalTransaction : [192.168.5.167:8091:2018715902] commit status:Committed
访问: http://localhost:8104/buy?str=1 返回“false”
事务回滚2。。。。。
2019-08-05 15:57:36.056 INFO 8332 --- [nio-8104-exec-4] i.seata.tm.api.DefaultGlobalTransaction : [192.168.5.167:8091:2018715905] rollback status:Rollbacked
事务回滚2。。。。。
没有生成新的订单, 账户金额也没有减