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
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.0.6.RELEASE
com.mei.dubbo
dubbo-order
0.0.1
dubbo-order
1.8
com.mei.dubbo
dubbo-common-api
0.0.1
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.springframework.boot
spring-boot-starter-test
test
io.seata
samples-common
1.0.0-SNAPSHOT
io.seata
seata-all
0.6.1
com.alibaba.boot
dubbo-spring-boot-starter
0.2.1.RELEASE
com.alibaba
dubbo
2.6.5
com.alibaba
dubbo-registry-nacos
0.0.2
com.alibaba.boot
nacos-config-spring-boot-starter
0.2.1
nacos-client
com.alibaba.nacos
com.alibaba.nacos
nacos-client
1.0.0
org.springframework.cloud
spring-cloud-starter-alibaba-nacos-discovery
0.2.1.RELEASE
nacos-client
com.alibaba.nacos
com.alibaba
druid
1.1.10
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-maven-plugin
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。。。。。
没有生成新的订单, 账户金额也没有减