官网文档: https://seata.io/zh-cn/docs/user/configurations.html
官网示例代码: https://github.com/seata/seata-samples
1. 下载 : https://github.com/seata/seata/releases (版本1.4.2)
2. 安装: (建议docker)
3. 修改配置:
file.conf
改动如下:
mode = "db" url = "jdbc:mysql://192.168.186.122:3306/seata?rewriteBatchedStatements=true" user = "root" password = "root"
创建db seata ,初始化 db 脚本: 可以下载源码 https://github.com/seata/seata/releases ,然后在 /source_seata-1.4.2/script/server/db/mysql.sql
-- -------------------------------- The script used when storeMode is 'db' -------------------------------- -- the table to store GlobalSession data CREATE TABLE IF NOT EXISTS `global_table` ( `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `status` TINYINT NOT NULL, `application_id` VARCHAR(32), `transaction_service_group` VARCHAR(32), `transaction_name` VARCHAR(128), `timeout` INT, `begin_time` BIGINT, `application_data` VARCHAR(2000), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`xid`), KEY `idx_gmt_modified_status` (`gmt_modified`, `status`), KEY `idx_transaction_id` (`transaction_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; -- the table to store BranchSession data CREATE TABLE IF NOT EXISTS `branch_table` ( `branch_id` BIGINT NOT NULL, `xid` VARCHAR(128) NOT NULL, `transaction_id` BIGINT, `resource_group_id` VARCHAR(32), `resource_id` VARCHAR(256), `branch_type` VARCHAR(8), `status` TINYINT, `client_id` VARCHAR(64), `application_data` VARCHAR(2000), `gmt_create` DATETIME(6), `gmt_modified` DATETIME(6), PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; -- the table to store lock data CREATE TABLE IF NOT EXISTS `lock_table` ( `row_key` VARCHAR(128) NOT NULL, `xid` VARCHAR(128), `transaction_id` BIGINT, `branch_id` BIGINT NOT NULL, `resource_id` VARCHAR(256), `table_name` VARCHAR(32), `pk` VARCHAR(36), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`row_key`), KEY `idx_branch_id` (`branch_id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
/opt/seata-server-1.4.2/conf/registry.conf
改动如下 :
type = "eureka" eureka { serviceUrl = "http://icil:icil4icil@192.168.18.204:8761/eureka" application = "sea-seata" weight = "1" }
启动 即可
bin/seata-server.sh
###################### 使用 #########################
整合: springboot + euraka + seata + mysql
1. 创建项目
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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.sea</groupId> <artifactId>sea-goods-service</artifactId> <version>0.0.1-SNAPSHOT</version> <name>sea-goods-service</name> <description>Demo project for Spring Boot</description> <!-- <parent> <groupId>com.sea</groupId> <artifactId>shopsea</artifactId> <version>1.0-SNAPSHOT</version> <relativePath/> </parent>--> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.3.12.RELEASE</spring-boot.version> <spring-cloud.version>Hoxton.SR12</spring-cloud.version> <spring.admin.version>2.1.6</spring.admin.version> <!-- Log4j2漏洞--> <log4j2.version>2.15.0</log4j2.version> <apollo.version>1.5.0</apollo.version> <fastjson.version>1.2.76</fastjson.version> <swagger.version>2.9.2</swagger.version> <common.io.version>2.11.0</common.io.version> </properties> <dependencies> <!-- seata 依赖 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-seata</artifactId> <version>2.1.0.RELEASE</version> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> </exclusion> </exclusions> </dependency> <!-- https://mvnrepository.com/artifact/io.seata/seata-spring-boot-starter --> <!-- <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.2</version> </dependency>--> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>1.4.2</version> </dependency> <!-- <dependency> <groupId>com.sea</groupId> <artifactId>sea-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency>--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> <version>${spring.admin.version}</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--3rd part--> <!-- 缓存 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- ehcache --> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency> <dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.55</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${swagger.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${swagger.version}</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>${common.io.version}</version> </dependency> <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>${apollo.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> </plugin> </plugins> </build> </project>
application.properties
###### mysql ##### ### database sea_address configure jdbc.datasource.address.jdbc-url=jdbc:mysql://192.168.18.136:3306/sea_address jdbc.datasource.address.driverClassName=com.mysql.cj.jdbc.Driver #jdbc.datasource.address.driverClassName=com.mysql.jdbc.Driver jdbc.datasource.address.username=root jdbc.datasource.address.password=root jdbc.datasource.address.sql-script-encoding=UTF-8 ### database sea_user configure jdbc.datasource.user.jdbc-url=jdbc:mysql://192.168.18.136:3306/sea_user jdbc.datasource.user.driverClassName=com.mysql.cj.jdbc.Driver jdbc.datasource.user.username=root jdbc.datasource.user.password=root jdbc.datasource.user.sql-script-encoding=UTF-8
MySqlDataSourceConfig
//说明: XA 模式必须需要使用DatasourceProxy , 并且需要在当前业务数据库中 新建一个 undo_log 表, 用于保存需要回滚的数据
import io.seata.rm.datasource.DataSourceProxy; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; /*************************** *<pre> * @Project Name : seata_research * @Package : com.sea.shop.config * @File Name : MySqlDataSourceConfig * @Author : Sea * @Date : 7/29/22 10:01 AM * @Purpose : * @History : *</pre> ***************************/ @Configuration public class MySqlDataSourceConfig { @Bean(name = "addressDataSource") @Qualifier("addressDataSource") @ConfigurationProperties("jdbc.datasource.address") public DataSource addressDataSource() { return DataSourceBuilder.create().build(); }
//说明: XA 模式必须需要使用DatasourceProxy , 并且需要在当前业务数据库中 新建一个 undo_log 表, 用于保存需要回滚的数据 @Qualifier("addressDataSourceProxy") @Bean("addressDataSourceProxy") public DataSourceProxy addressDataSourceProxy( @Qualifier("addressDataSource") DataSource dataSource) { return new DataSourceProxy(dataSource); } @Bean(name = "addressJdbcTemplate") public JdbcTemplate addressJdbcTemplate(@Qualifier("addressDataSourceProxy") DataSourceProxy addressDataSourceProxy) { return new JdbcTemplate(addressDataSourceProxy); } //####################### user ################################3 @Bean(name = "userDataSource") @Qualifier("userDataSource") @ConfigurationProperties("jdbc.datasource.user") public DataSource userDataSource() { return DataSourceBuilder.create().build(); } @Qualifier("userDataSourceProxy") @Bean("userDataSourceProxy") public DataSourceProxy userDataSourceProxy( @Qualifier("userDataSource") DataSource dataSource) { return new DataSourceProxy(dataSource); } @Bean(name = "userJdbcTemplate") public JdbcTemplate userJdbcTemplate(@Qualifier("userDataSourceProxy") DataSourceProxy userDataSourceProxy) { return new JdbcTemplate(userDataSourceProxy); } }
在resource 下添加seata client 的配置文件 file.conf registry.conf (项目启动会自动加载) (注意里面的配置要和 seata-server 一致)
file.conf
transport { # tcp udt unix-domain-socket type = "TCP" #NIO NATIVE server = "NIO" #enable heartbeat heartbeat = true # the client batch send request enable enableClientBatchSendRequest = true #thread factory for netty threadFactory { bossThreadPrefix = "NettyBoss" workerThreadPrefix = "NettyServerNIOWorker" serverExecutorThread-prefix = "NettyServerBizHandler" shareBossWorker = false clientSelectorThreadPrefix = "NettyClientSelector" clientSelectorThreadSize = 1 clientWorkerThreadPrefix = "NettyClientWorkerThread" # netty boss thread size,will not be used for UDT bossThreadSize = 1 #auto default pin or 8 workerThreadSize = "default" } shutdown { # when destroy server, wait seconds wait = 3 } serialization = "seata" compressor = "none" } service { #transaction service group mapping vgroupMapping.my_test_tx_group = "sea-seata" #only support when registry.type=file, please don't set multiple addresses default.grouplist = "127.0.0.1:8091" #degrade, current not support enableDegrade = false #disable seata disableGlobalTransaction = false } client { rm { asyncCommitBufferLimit = 10000 lock { retryInterval = 10 retryTimes = 30 retryPolicyBranchRollbackOnConflict = true } reportRetryCount = 5 tableMetaCheckEnable = false reportSuccessEnable = false } tm { commitRetryCount = 5 rollbackRetryCount = 5 } undo { dataValidation = true logSerialization = "jackson" logTable = "undo_log" } log { exceptionRate = 100 } }
registry.conf
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "eureka" nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "" cluster = "default" username = "" password = "" } eureka { serviceUrl = "http://icil:icil4icil@192.168.18.204:8761/eureka" application = "sea-seata" weight = "1" } redis { serverAddr = "localhost:6379" db = 0 password = "" cluster = "default" timeout = 0 } zk { cluster = "default" serverAddr = "127.0.0.1:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = "" } consul { cluster = "default" serverAddr = "127.0.0.1:8500" aclToken = "" } etcd3 { cluster = "default" serverAddr = "http://localhost:2379" } sofa { serverAddr = "127.0.0.1:9603" application = "default" region = "DEFAULT_ZONE" datacenter = "DefaultDataCenter" cluster = "default" group = "SEATA_GROUP" addressWaitTime = "3000" } file { name = "file.conf" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "file" nacos { serverAddr = "127.0.0.1:8848" namespace = "" group = "SEATA_GROUP" username = "" password = "" dataId = "seataServer.properties" } consul { serverAddr = "127.0.0.1:8500" aclToken = "" } apollo { appId = "seata-server" ## apolloConfigService will cover apolloMeta apolloMeta = "http://192.168.1.204:8801" apolloConfigService = "http://192.168.1.204:8080" namespace = "application" apolloAccesskeySecret = "" cluster = "seata" } zk { serverAddr = "127.0.0.1:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = "" nodePath = "/seata/seata.properties" } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } }
2. 初始化业务数据库
CREATE SCHEMA `sea_user` ;
CREATE SCHEMA `sea_address` ;
创建表
CREATE TABLE sea_user.user ( `id` int(11) NOT NULL, `name` varchar(45) DEFAULT NULL, `age` varchar(45) DEFAULT NULL, `mark` varchar(45) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; SELECT * FROM sea_user.undo_log; CREATE TABLE sea_address.address ( `id` int(11) NOT NULL, `country` varchar(45) DEFAULT NULL, `provinces` varchar(45) DEFAULT NULL, `city` varchar(45) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
3.分别在两个数据库中 添加 undo_log 表
在业务相关的数据库中添加 undo_log 表,用于保存需要回滚的数据
CREATE TABLE `undo_log` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT, `branch_id` BIGINT(20) NOT NULL, `xid` VARCHAR(100) NOT NULL, `context` VARCHAR(128) 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 = 1 DEFAULT CHARSET = utf8
4. 测试分布式事务 分别像两个db 插入数据, 手动添加异常 ,看数据是否都保存了
另外 (feign) 调用, 在调用的service 层 方法加上 @GlobalTransactional 然后直接调用即可 (此处不贴代码)
业务代码:
@Service public class SeatesHandler { @Autowired @Qualifier("addressJdbcTemplate") private JdbcTemplate addressJdbcTemplate; @Autowired @Qualifier("userJdbcTemplate") public JdbcTemplate userJdbcTemplate; @Autowired private OrderServiceClient orderServiceClient; /** * @param no * @param isExp */ @GlobalTransactional public void insert(int no, Boolean isExp) { orderServiceClient.add(no); userJdbcTemplate.update("INSERT INTO user (id,name,age) VALUES (?,?,?)",no,"sea",21); addressJdbcTemplate.update ("INSERT INTO address (id,country,provinces,city) VALUES (?,?,?,?)",no,"cn","gd","sz"); if(isExp){ int i= 1/0; } List<Map<String, Object>> mapsUser = userJdbcTemplate.queryForList("select * from user"); List<Map<String, Object>> mapsAddress = addressJdbcTemplate.queryForList("select * from address"); System.err.println("user" + mapsUser); System.err.println("address" + mapsAddress); // int update = userJdbcTemplate.update("delete from user where id=?", 1); // int update1 = addressJdbcTemplate.update("delete from address where id=?", 1); } }
接口测试:
@Autowired SeatesHandler seatesHandler; @GetMapping("addToPd") public String addToOrder(@RequestParam Integer no,Boolean isExp) { seatesHandler.insert(no,isExp); return "ok"; }