1, Environmental preparation
Existing environment:
mysql8.0.25
nacos
resources
Resource name | address | explain |
---|---|---|
nacos | https://github.com/alibaba/nacos/tags | Service discovery, registry, configuration center |
seata1.4.2 | https://seata.io/zh-cn/blog/download.html | Distributed transaction |
2, seata AT mode integration
1. nacos configuration
1.1. Unzip the downloaded nacos to the target directory
1.2. Persist nacos to MySQL and create a new nacos database locally,
Find the Nacos mysql.exe under nacos/conf SQL script execution
These tables will appear when the import is successful
1.3. Modify the application. In the conf directory Properties configuration file, as shown in the following figure:
1.4. Enter the bin directory to create a new start Bat file, open with Notepad, enter
startup.cmd -m standalone
Double click start Bat startup, access http://localhost:8848/nacos/#/login Just log in
Enter the nacos console. The default user name and password are all nacos
Click namespace, and then click new namespace
Create a new seata namespace
2. Seata installation configuration
2.1. Seata download address: https://seata.io/zh-cn/blog/download.html
Unzip after download
2.2. Modify the configuration file file under conf conf
2.3. Step 2: the SQL address required to initialize the database: https://github.com/seata/seata/tree/develop/script/server/db
Create database seata and import sql files
After successful import
2.4. Modify registry Registry configuration for conf
2.5. Import seata configuration into nacos
nacos-config.sh script preparation:
Download address https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh And put the file in the conf directory of Seata
2.6,config.txt, create a new config.txt in the same level directory of conf Txt file
transport.type=TCP transport.server=NIO transport.heartbeat=true transport.enableClientBatchSendRequest=false transport.threadFactory.bossThreadPrefix=NettyBoss transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler transport.threadFactory.shareBossWorker=false transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector transport.threadFactory.clientSelectorThreadSize=1 transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread transport.threadFactory.bossThreadSize=1 transport.threadFactory.workerThreadSize=default transport.shutdown.wait=3 #Modify my_test_tx_group is a custom service, Seata group service.vgroupMapping.my_test_tx_group=default service.default.grouplist=127.0.0.1:8091 service.enableDegrade=false service.disableGlobalTransaction=false client.rm.asyncCommitBufferLimit=10000 client.rm.lock.retryInterval=10 client.rm.lock.retryTimes=30 client.rm.lock.retryPolicyBranchRollbackOnConflict=true client.rm.reportRetryCount=5 client.rm.tableMetaCheckEnable=false client.rm.sqlParserType=druid client.rm.reportSuccessEnable=false client.rm.sagaBranchRegisterEnable=false client.tm.commitRetryCount=5 client.tm.rollbackRetryCount=5 client.tm.defaultGlobalTransactionTimeout=60000 client.tm.degradeCheck=false client.tm.degradeCheckAllowTimes=10 client.tm.degradeCheckPeriod=2000 store.mode=db store.file.dir=file_store/data store.file.maxBranchSessionSize=16384 store.file.maxGlobalSessionSize=512 store.file.fileWriteBufferCacheSize=16384 store.file.flushDiskMode=async store.file.sessionReloadReadSize=100 store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.cj.jdbc.Driver store.db.url=jdbc:mysql://127.0.0.1:3306/seata?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true store.db.user=root store.db.password=123456 store.db.minConn=5 store.db.maxConn=30 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000 store.redis.host=127.0.0.1 store.redis.port=6379 store.redis.maxConn=10 store.redis.minConn=1 store.redis.database=0 store.redis.password=null store.redis.queryLimit=100 server.recovery.committingRetryPeriod=1000 server.recovery.asynCommittingRetryPeriod=1000 server.recovery.rollbackingRetryPeriod=1000 server.recovery.timeoutRetryPeriod=1000 server.maxCommitRetryTimeout=-1 server.maxRollbackRetryTimeout=-1 server.rollbackRetryTimeoutUnlockEnable=false client.undo.dataValidation=true client.undo.logSerialization=jackson client.undo.onlyCareUpdateColumns=true server.undo.logSaveDays=7 server.undo.logDeletePeriod=86400000 client.undo.logTable=undo_log client.log.exceptionRate=100 transport.serialization=seata transport.compressor=none metrics.enabled=false metrics.registryType=compact metrics.exporterList=prometheus metrics.exporterPrometheusPort=9898
The finished directory looks like
2.7. In the conf directory, open git bash and enter sh nacos config sh -h localhost -p 8848 -g seata_ Group - t c9829d3b-5e7c-4d41-b0f1-00c4240c9c21 - u nacos - w nacos, where - t is the nacos namespace, which can be deleted (of course, it is recommended to create a separate namespace for seata, so that it can be found intuitively when there are many configurations in the future). This namespace is the id of the seata we just created. You can't enter it indiscriminately, otherwise the nacos configuration file doesn't exist
If you don't have git, you can download and install it, and the execution looks successful
Where config Txt is the configuration file, which is called nacos config The function of SH is to inject the attributes of the configuration file into nacos, so there is no need to inject the file Conf and registry Conf is put into our project. If config. Is not found Txt will be placed in the root directory of seata, and the configuration will be read directly from nacos. As shown in the figure:
Then go to the nacos console
You can see the successful loading
3. Project integration seata
- Database preparation:
Three data: seata_account,seata_order,seata_storage
sql statement:
CREATE DATABASE seata_order; CREATE DATABASE seata_storage; CREATE DATABASE seata_account;
When creating a table, pay attention to distinguishing between databases:
t_order:
CREATE TABLE t_order ( `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `user_id` BIGINT(11) DEFAULT NULL COMMENT 'user id', `product_id` BIGINT(11) DEFAULT NULL COMMENT 'product id', `count` INT(11) DEFAULT NULL COMMENT 'quantity', `money` DECIMAL(11,0) DEFAULT NULL COMMENT 'amount of money', `status` INT(1) DEFAULT NULL COMMENT 'Order status: 0: being created; 1: Closed' ) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; SELECT * FROM t_order;
t_storage:
CREATE TABLE t_storage ( `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `product_id` BIGINT(11) DEFAULT NULL COMMENT 'product id', `total` INT(11) DEFAULT NULL COMMENT 'Total inventory', `used` INT(11) DEFAULT NULL COMMENT 'Used inventory', `residue` INT(11) DEFAULT NULL COMMENT 'Remaining inventory' ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100'); SELECT * FROM t_storage;
t_account:
CREATE TABLE t_account ( `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id', `user_id` BIGINT(11) DEFAULT NULL COMMENT 'user id', `total` DECIMAL(10,0) DEFAULT NULL COMMENT 'Total amount', `used` DECIMAL(10,0) DEFAULT NULL COMMENT 'Balance used', `residue` DECIMAL(10,0) DEFAULT '0' COMMENT 'Remaining available limit' ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO seata_account.t_account(`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000'); SELECT * FROM t_account;
Finally, create undo under each database_ Log table:
undo_logo:
-- the table to store seata xid data -- 0.7.0+ add context -- you must to init this sql for you business databese. the seata server not need it. -- This script must be initialized in your current business database for AT pattern XID record. And server End independent (Note: business database) -- Note here 0.3.0+ Add unique index ux_undo_log DROP TABLE `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;
After successful construction:
- New order module seata-order-service2001
- pom dependency
<dependencies> <!--nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--seata--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <!-- Exclude default--> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.2</version> </dependency> <!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--web-actuator--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--mysql-druid--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
- View dependencies
- Modify application YML profile
server: port: 2001 spring: application: name: seata-order-service cloud: alibaba: # seata: # #The user-defined transaction group name needs to correspond to that in Seata server # tx-service-group: my_test_tx_group nacos: discovery: server-addr: localhost:8848 username: nacos password: nacos group: SEATA_GROUP # namespace: c9829d3b-5e7c-4d41-b0f1-00c4240c9c21 no namespace specified. Default public datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=NO&serverTimezone=Asia/Shanghai username: root password: 123456 feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml seata: tx-service-group: my_test_tx_group #Special attention should be paid here to maintaining the consistency with the configuration in nacos registry: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} #namespace: c9829d3b-5e7c-4d41-b0f1-00c4240c9c21 default public config: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} namespace: c9829d3b-5e7c-4d41-b0f1-00c4240c9c21 #2. The namespace ID in 2 is configured, but the default public space is not configured service: vgroup-mapping: my_test_tx_group: default # Here, we should pay special attention to maintaining the consistency with the configuration in nacos
So it's public, otherwise it will appear
No available service
- Entity classes Order and CommonResult
order
package com.zhubayi.springcloud.alibaba.doamin; import lombok.Data; import java.math.BigDecimal; /** * @author zhubayi */ @Data public class Order { private Long id; private Long userId; private Long productId; private Integer count; private BigDecimal money; private Integer status; //Order status: 0: being created; 1: Closed }
CommonResult
package com.zhubayi.springcloud.alibaba.doamin; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @author zhubayi */ @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T> { private Integer code; private String message; private T data; public CommonResult(Integer code, String message) { this(code,message,null); } }
- The dao layer creates the OrderDao interface
package com.zhubayi.springcloud.alibaba.dao; import com.zhubayi.springcloud.alibaba.doamin.Order; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; /** * @author zhubayi */ @Mapper public interface OrderDao { /** * Create order * @param order */ void createOrder(Order order); /** * Update order * @param userId * @param status */ void updateStatus(@Param("userId") Long userId, @Param("status") Integer status); }
- service layer: OrderService, StorageService, AccountService
OrderService
package com.zhubayi.springcloud.alibaba.service; import com.zhubayi.springcloud.alibaba.doamin.Order; /** * @author zhubayi */ public interface OrderService { /** * Create order * @param order */ void createOrder(Order order); }
StorageService
package com.zhubayi.springcloud.alibaba.service; import com.zhubayi.springcloud.alibaba.doamin.CommonResult; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import java.math.BigDecimal; /** * @author zhubayi */ @FeignClient(value = "seata-storage-service") public interface StorageService { @PostMapping(value = "/storage/decrease") CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count); }
AccountService
package com.zhubayi.springcloud.alibaba.service; import com.zhubayi.springcloud.alibaba.doamin.CommonResult; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import java.math.BigDecimal; @FeignClient(value = "seata-account-service") public interface AccountService { @PostMapping(value = "/account/decrease") CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money); }
Create the implementation class OrderServiceImpl of orderService
package com.zhubayi.springcloud.alibaba.service.impl; import com.zhubayi.springcloud.alibaba.dao.OrderDao; import com.zhubayi.springcloud.alibaba.doamin.Order; import com.zhubayi.springcloud.alibaba.service.AccountService; import com.zhubayi.springcloud.alibaba.service.OrderService; import com.zhubayi.springcloud.alibaba.service.StorageService; import io.seata.spring.annotation.GlobalTransactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * @author zhubayi */ @Service @Slf4j public class OrderServiceImpl implements OrderService { @Autowired private OrderDao orderDao; @Resource private StorageService storageService; @Resource private AccountService accountService; /** * Create order * * @param order */ @Override public void createOrder(Order order) { log.info("=====>{}","Start new order"); orderDao.createOrder(order); log.info("=====>{}","New order successfully created"); log.info("=====>{}","Inventory reduction"); storageService.decrease(order.getProductId(),order.getCount()); log.info("=====>{}","Inventory reduction succeeded"); log.info("=====>{}","Start reducing balance"); accountService.decrease(order.getUserId(),order.getMoney()); log.info("=====>{}","Balance reduction succeeded"); log.info("=====>{}","Start modifying order status"); orderDao.updateStatus(order.getUserId(),0); log.info("=====>{}","Order status modified successfully"); log.info("=====>{}","end"); } }
- Create OrderController
package com.zhubayi.springcloud.alibaba.controller; import com.zhubayi.springcloud.alibaba.doamin.CommonResult; import com.zhubayi.springcloud.alibaba.doamin.Order; import com.zhubayi.springcloud.alibaba.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author zhubayi */ @RestController @RequestMapping("order") public class OrderController { @Autowired private OrderService orderService; @GetMapping("create") public CommonResult create(Order order){ orderService.createOrder(order); return new CommonResult(200,"Created successfully"); } }
Create the mapper directory under the resources directory, and then create orderdao XML file
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zhubayi.springcloud.alibaba.dao.OrderDao"> <resultMap id="BaseMap" type="com.zhubayi.springcloud.alibaba.doamin.Order"> <id column="id" property="id" jdbcType="BIGINT"/> <result column="user_id" property="userId" jdbcType="BIGINT"/> <result column="product_id" property="productId" jdbcType="BIGINT"/> <result column="count" property="count" jdbcType="INTEGER"/> <result column="money" property="money" jdbcType="DECIMAL"/> <result column="status" property="status" jdbcType="INTEGER"/> </resultMap> <insert id="createOrder"> insert into t_order(id , user_id , product_id , count, money , status ) values (null,#{userId},#{productId},#{count},#{money},0) </insert> <update id="updateStatus"> update t_order set status=1 where user_id=#{userId} and status=#{status} </update> </mapper>
- Main startup class SeataOrderMainApp2001
package com.zhubayi.springcloud.alibaba; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; /** * @author zhubayi */ @EnableDiscoveryClient @EnableFeignClients @MapperScan(basePackages = {"com.zhubayi.springcloud.alibaba.dao"}) @SpringBootApplication public class SeataOrderMainApp2001 { public static void main(String[] args) { SpringApplication.run(SeataOrderMainApp2001.class, args); } }
The finished directory looks like:
- Create a new inventory module seata-storage-service2002
- pom file dependency
<dependencies> <!--nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--seata--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.2</version> </dependency> <!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
- Modify the configuration file application YML, which is almost the same as the previous order module seata-order-service2001. The port and name are different. The database is changed to seata_storage
server: port: 2002 spring: application: name: seata-storage-service cloud: alibaba: # seata: # tx-service-group: my_test_tx_group nacos: discovery: server-addr: localhost:8848 username: nacos password: nacos group: SEATA_GROUP datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_storage?useUnicode=true&characterEncoding=utf-8&useSSL=NO&serverTimezone=Asia/Shanghai username: root password: 123456 logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml seata: tx-service-group: my_test_tx_group #Special attention should be paid here to maintaining the consistency with the configuration in nacos registry: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} namespace: c9829d3b-5e7c-4d41-b0f1-00c4240c9c21 config: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} #namespace: ${spring.cloud.nacos.discovery.namespace} #2. The namespace ID in 2 is configured, but the default public space is not configured service: vgroup-mapping: my_test_tx_group: default # Special attention should be paid here to maintaining the consistency with the configuration in nacos
- Entity class Storage
Storage:
package com.zhubayi.springcloud.alibaba.domain; import lombok.Data; @Data public class Storage { private Long id; /** * Product id */ private Long productId; /** * Total inventory */ private Integer total; /** * Used inventory */ private Integer used; /** * Remaining inventory */ private Integer residue; }
CommonResult is the same as the order module
- service layer StorageService
package com.zhubayi.springcloud.alibaba.service; public interface StorageService { /** * Deduct inventory */ void decrease(Long productId, Integer count); }
The implementation class of StorageService is StorageServiceImpl
package com.zhubayi.springcloud.alibaba.service.impl; import com.zhubayi.springcloud.alibaba.dao.StorageDao; import com.zhubayi.springcloud.alibaba.service.StorageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class StorageServiceImpl implements StorageService { private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class); @Resource private StorageDao storageDao; /** * Deduct inventory */ @Override public void decrease(Long productId, Integer count) { LOGGER.info("------->storage-service Start of inventory deduction"); storageDao.decrease(productId,count); LOGGER.info("------->storage-service End of inventory deduction"); } }
- StorageDao interface:
package com.zhubayi.springcloud.alibaba.dao; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @Mapper public interface StorageDao { /** *Deduct inventory **/ void decrease(@Param("productId") Long productId, @Param("count") Integer count); }
StorageDao.xml file
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.zhubayi.springcloud.alibaba.dao.StorageDao"> <resultMap id="BaseResultMap" type="com.zhubayi.springcloud.alibaba.domain.Storage"> <id column="id" property="id" jdbcType="BIGINT"/> <result column="product_id" property="productId" jdbcType="BIGINT"/> <result column="total" property="total" jdbcType="INTEGER"/> <result column="used" property="used" jdbcType="INTEGER"/> <result column="residue" property="residue" jdbcType="INTEGER"/> </resultMap> <update id="decrease"> UPDATE t_storage SET used = used + #{count},residue = residue - #{count} WHERE product_id = #{productId} </update> </mapper>
- StorageController class
package com.zhubayi.springcloud.alibaba.controller; import com.zhubayi.springcloud.alibaba.domain.CommonResult ; import com.zhubayi.springcloud.alibaba.service.StorageService ; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class StorageController { @Autowired private StorageService storageService; /** * Deduct inventory */ @RequestMapping("/storage/decrease") public CommonResult decrease(Long productId, Integer count) { storageService.decrease(productId, count); return new CommonResult(200,"Inventory deduction succeeded!"); } }
- Main startup class: SeataStorageServiceApplication2002
package com.zhubayi.springcloud.alibaba; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.ForkJoinWorkerThread; /** * @auther zzyy * @create 2019-12-12 17:31 */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @MapperScan(basePackages = {"com.zhubayi.springcloud.alibaba.dao"}) public class SeataStorageServiceApplication2002 { public static void main(String[] args) { SpringApplication.run(SeataStorageServiceApplication2002.class, args); } }
After completion:
- Create Seata account service2003 user balance module
- pom file dependency
<dependencies> <!--nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--seata--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.2</version> </dependency> <!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
- New profile application yml
server: port: 2003 spring: application: name: seata-account-service cloud: alibaba: #seata: #tx-service-group: my_test_tx_group nacos: discovery: server-addr: localhost:8848 username: nacos password: nacos group: SEATA_GROUP datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=utf-8&useSSL=NO&serverTimezone=Asia/Shanghai username: root password: 123456 feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml seata: tx-service-group: my_test_tx_group #Special attention should be paid here to maintaining the consistency with the configuration in nacos registry: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} namespace: c9829d3b-5e7c-4d41-b0f1-00c4240c9c21 config: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} username: ${spring.cloud.nacos.discovery.username} password: ${spring.cloud.nacos.discovery.password} group: ${spring.cloud.nacos.discovery.group} #namespace: ${spring.cloud.nacos.discovery.namespace} #2. The namespace ID in 2 is configured, but the default public space is not configured service: vgroup-mapping: my_test_tx_group: default # Special attention should be paid here to maintaining the consistency with the configuration in nacos
Note the database name
- Entity class Account
package com.zhubayi.springcloud.alibaba.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.math.BigDecimal; @Data @AllArgsConstructor @NoArgsConstructor public class Account { private Long id; /** * User id */ private Long userId; /** * Total amount */ private BigDecimal total; /** * Used limit */ private BigDecimal used; /** * Remaining quota */ private BigDecimal residue; }
CommonResult is the same as before
- AccountDao interface
package com.zhubayi.springcloud.alibaba.dao; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Component; import org.springframework.stereotype.Repository; import java.math.BigDecimal; /** * @author zhubayi */ @Mapper public interface AccountDao { /** * Deduction of account balance * @param userId * @param money */ void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money); }
- AccountService interface
package com.zhubayi.springcloud.alibaba.service; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import java.math.BigDecimal; public interface AccountService { /** * Deduction of account balance * @param userId User id * @param money amount of money */ void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money); }
- Implementation class of AccountService interface AccountServiceImpl
package com.zhubayi.springcloud.alibaba.service.impl; import com.zhubayi.springcloud.alibaba.dao.AccountDao; import com.zhubayi.springcloud.alibaba.service.AccountService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; import java.util.concurrent.TimeUnit; /** * Account business implementation class */ @Service public class AccountServiceImpl implements AccountService { private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class); @Resource AccountDao accountDao; /** * Deduction of account balance */ @Override public void decrease(Long userId, BigDecimal money) { LOGGER.info("------->account-service Start of deduction of account balance in"); //Simulation timeout exception, global transaction rollback //Pause the thread for a few seconds // try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } accountDao.decrease(userId,money); LOGGER.info("------->account-service End of account balance deducted in"); } }
AccountController class
package com.zhubayi.springcloud.alibaba.controller; import com.zhubayi.springcloud.alibaba.domain.CommonResult; import com.zhubayi.springcloud.alibaba.service.AccountService; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.math.BigDecimal; @RestController public class AccountController { @Resource AccountService accountService; /** * Deduction of account balance */ @RequestMapping("/account/decrease") public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){ accountService.decrease(userId,money); return new CommonResult(200,"Account balance deducted successfully!"); } }
- Start class SeataAccountMainApp2003
package com.zhubayi.springcloud.alibaba; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; /** * @author zhubayi */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @MapperScan({"com.zhubayi.springcloud.alibaba.dao"}) public class SeataAccountMainApp2003 { public static void main(String[] args) { SpringApplication.run(SeataAccountMainApp2003.class, args); } }
AccountMapper.xml file
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.zhubayi.springcloud.alibaba.dao.AccountDao"> <resultMap id="BaseResultMap" type="com.zhubayi.springcloud.alibaba.domain.Account"> <id column="id" property="id" jdbcType="BIGINT"/> <result column="user_id" property="userId" jdbcType="BIGINT"/> <result column="total" property="total" jdbcType="DECIMAL"/> <result column="used" property="used" jdbcType="DECIMAL"/> <result column="residue" property="residue" jdbcType="DECIMAL"/> </resultMap> <update id="decrease"> UPDATE t_account SET residue = residue - #{money},used = used + #{money} WHERE user_id = #{userId}; </update> </mapper>
Start three modules as well as nacos and seata
021-07-05 11:48:22.383 INFO 12568 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2021-07-05 11:48:22.469 INFO 12568 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'Nacos-Watch-Task-Scheduler' 2021-07-05 11:48:22.820 INFO 12568 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator' 2021-07-05 11:48:22.895 INFO 12568 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 2001 (http) with context path '' 2021-07-05 11:48:22.899 INFO 12568 --- [ main] c.a.c.n.registry.NacosServiceRegistry : nacos registry, SEATA_GROUP seata-order-service 192.168.0.123:2001 register finished 2021-07-05 11:48:23.006 INFO 12568 --- [ main] c.z.s.alibaba.SeataOrderMainApp2001 : Started SeataOrderMainApp2001 in 4.239 seconds (JVM running for 4.756) 2021-07-05 11:48:23.395 INFO 12568 --- [)-192.168.0.123] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2021-07-05 11:48:23.395 INFO 12568 --- [)-192.168.0.123] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2021-07-05 11:48:23.400 INFO 12568 --- [)-192.168.0.123] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms `2021-07-05 11:49:20.607 INFO 12568 --- [eoutChecker_1_1] i.s.c.r.netty.NettyClientChannelManager : will connect to 192.168.0.123:8091 2021-07-05 11:49:20.608 INFO 12568 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory : NettyPool create channel to transactionRole:TMROLE,address:192.168.0.123:8091,msg:< RegisterTMRequest{applicationId='seata-order-service', transactionServiceGroup='my_test_tx_group'} > 2021-07-05 11:49:20.613 INFO 12568 --- [eoutChecker_1_1] i.s.c.rpc.netty.TmNettyRemotingClient : register TM success. client version:1.4.2, server version:1.4.2,channel:[id: 0xd7cc02ac, L:/192.168.0.123:54703 - R:/192.168.0.123:8091] 2021-07-05 11:49:20.613 INFO 12568 --- [eoutChecker_1_1] i.s.core.rpc.netty.NettyPoolableFactory : register success, cost 3 ms, version:1.4.2,role:TMROLE,channel:[id: 0xd7cc02ac, L:/192.168.0.123:54703 - R:/192.168.0.123:8091]`
Note that the configuration is correct only when the yellow part appears, and nacos is registered
Open the browser and enter: http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100
You can see that the order is created successfully, and then look at the database:
At this time, we manually create a timeout exception. By default, openfeign is called remotely for 1 second, and an error is reported if no data is returned
Visit again: http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100
Look at the console again:
The order module reports a timeout exception. There is an order in the database, but the status of the order has not changed. Distributed transactions are used here.
To solve this problem, we need to add @ GlobalTransactiona annotation to the method of distributed transaction
Then browse and visit: http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100
Take another look at the console:
It is also a timeout exception
Go check the database
The order table is not added, and the accout and storage tables are not changed. It shows that the problem of distributed transaction is solved
In order to see in more detail, let's make a breakpoint
Call again http://127.0.0.1:2001/order/create?userId=1&productId=1&count=10&money=100
- One more piece of data
Inventory deduction succeeded
View global lock, branch lock and table data lock
View seata library table lock status information
- View the order library, storage library and undo_log rollback log