Chapter 14 of SpringCloud Alibaba, upgrade, distributed transaction solution, Seata
1, Overview of distributed transactions
1. What is distributed transaction
With the rapid development of the Internet, the software system has changed from the original single application to the distributed application.
The distributed system will split an application system into multiple services that can be deployed independently, so it needs remote cooperation between services to complete transaction operations. In this distributed system environment, the transaction completed by remote cooperation between different services through the network is called distributed transaction.
For example, user registration send points transaction, create order reduce inventory transaction, bank transfer transaction are distributed transactions.
1.1. The local transaction depends on the transaction features provided by the database itself to realize:
begin transaction; //1. Local database operation: Zhang San reduces the amount //2. Local database operation: Li Si increases the amount commit transation;
1.2. However, in a distributed environment, it will become the following:
begin transaction; //1. Local database operation: Zhang San reduces the amount //2. Remote call: let Li Si increase the amount commit transation;
It can be imagined that when the remote call makes Li Si increase the amount successfully, and the remote call fails to return due to network problems, the local transaction submission failure rolls back Zhang San's operation of reducing the amount, and the data of Zhang San and Li Si are inconsistent.
Therefore, on the basis of the distributed architecture, the traditional database transaction can not be used. The accounts of Zhang San and Li Si are not in a database or even an application system. To realize the transfer transaction, it needs to be called remotely. Because of the network problem, the distributed transaction problem will be caused.
2. Distributed transaction generation scenario:
2.1. A typical scenario is that microservices in microservice architecture complete transaction operations through remote calls. For example: order micro service and inventory micro service, when placing an order, the order micro service requests inventory micro service to reduce inventory.
2.2 when a single system needs to access multiple database instances, distributed transactions will be generated. For example, the user information and order information are stored in two MySQL instances respectively. When the user management system deletes the user information, it needs to delete the user information and the user order information respectively. Because the data is distributed in different data instances, it needs to operate the data through different database links. At this time, a distributed transaction is generated.
2.3. Multi service access to the same database instance, for example: even if the order micro service and inventory micro service access to the same database, they will generate distributed transactions. The reason is that the two micro services hold different database links for database operations across the JVM process. At this time, distributed transactions are generated.
2, Seata overview
1. Introduction
Seata is an open source distributed transaction solution dedicated to providing high-performance and easy-to-use distributed transaction services. Seata will provide users with AT, TCC, SAGA and XA transaction modes, and build a one-stop distributed solution for users.
2. Terminology
XID - Transaction ID, the only ID of a distributed transaction TC - Transaction Coordinator Maintain the state of global and branch transactions, and drive global transaction commit or rollback. TM - transaction manager Define the scope of global transaction: start global transaction, commit or roll back global transaction. RM - Resource Manager Manage the resources of branch transaction, talk with TC to register branch transaction and report the status of branch transaction, and drive branch transaction commit or rollback.
3. Seata process flow
1. TM applies to TC to open a global transaction. The global transaction is created successfully and a globally unique ID, XID, is generated 2. XID propagates across the context of the microservice call link 3. RM registers branch transactions with TC and puts them under the jurisdiction of global transactions corresponding to XID 4. TM initiates a global transaction commit or rollback resolution for XID to TC 5. TC schedules all branch transactions under XID to complete the commit or rollback request
other:
Official website: http://seata.io/ Download address: http://seata.io/zh-cn/blog/download.html Use: @ GlobalTransactional
3, Seata Server installation
1. Unzip seata-server-0.9.0.zip 2. Enter the conf directory to modify the file.conf file: user defined transaction group name, transaction storage mode, database connection information service module custom transaction group name: vgroup_mapping.my_test_tx_group = "default" Modify it to vgroup "mapping. My" test "TX" group = "seatatxgroup" The store module modifies the transaction storage mode: mode = "file" Modify to mode="db" Modify db module to your database connection: url = "jdbc:mysql://127.0.0.1:3306/seata" user = "root" password = "admin123" 3. Create the corresponding database and table according to the above database connection CREATE SCHEMA `seata` DEFAULT CHARACTER SET utf8 ; The SQL of the corresponding table is in the db_store.sql file in the conf Directory: Global table, branch table and lock table 4. Modify the registry.conf file under conf to register seata in the corresponding registry Change the type = "file" under the registration module to your corresponding registration center type="nacos" At the same time, modify the corresponding registry module: nacos { serverAddr = "localhost:8848" namespace = "" cluster = "seataTxGroup" ා ා - the corresponding transaction group name customized by seata } The file of the conf module is the file.conf file we modified. You can also modify the content of the conf registration information to nacos
Test:
1. Start nacos server with port number 8848 2. Start seata server Check whether the nacos service list has the injection of seata server. The corresponding port is 8901
4, Seata case
Case study: transactions among three microservices: order inventory user amount account
1. Create order 2. Modify the inventory corresponding to the goods purchased by the order, 3. And deduct the purchase amount from the user's account 4. Last modify order status
1. Create corresponding microservice table
Order form:
CREATE DATABASE seata_order; use seata_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 'Number', `money` DECIMAL(11,0) DEFAULT NULL COMMENT 'Amount of money', `status` INT(1) DEFAULT NULL COMMENT 'Order status: 0: Creating; 1: Closed' ) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; SELECT * FROM t_order;
an inventory statement:
CREATE DATABASE seata_storage; use seata_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 stock', `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 t_storage(`id`,`product_id`,`total`,`used`,`residue`) VALUES('1','1','100','0','100'); SELECT * FROM t_storage;
Account table:
CREATE DATABASE seata_account; use seata_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 'Used balance', `residue` DECIMAL(10,0) DEFAULT '0' COMMENT 'Remaining available quota' ) 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;
2. Create the corresponding rollback log table
##Each XID of swimsuit record needs to be set up in the order inventory account three databases ##The corresponding SQL is in DB undo log.sql under conf directory of seata server -- 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 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, `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;
3. Create corresponding micro service account
Create account micro service: seata-order-service-2003
<parent> <artifactId>cloud_2020</artifactId> <groupId>com.lee.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>seata-order-service-2003</artifactId>
POM
<?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"> <parent> <artifactId>cloud_2020</artifactId> <groupId>com.lee.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>seata-order-service-2003</artifactId> <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> <artifactId>seata-all</artifactId> <groupId>io.seata</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>0.9.0</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> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </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> </project>
application.yml
server: port: 2003 spring: application: name: seata-account-service cloud: alibaba: seata: tx-service-group: my_test_tx_group #It is consistent with the name of the user-defined transaction group in file.conf under the sea TA server conf directory nacos: discovery: server-addr: localhost:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_account username: root password: admin123 feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml
At the same time, copy the files of file.conf and register.conf under the Seata server conf directory to the resource directory
Entity class
@Data @AllArgsConstructor @NoArgsConstructor public class Account { private Long id; private Long userId; private BigDecimal total; private BigDecimal used; private BigDecimal residue; }
@Data @ToString @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); } }
Data layer
@Mapper public interface AccountDao { //Deduction account balance void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money); }
resource/mapper directory
<?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.lee.springcloud.dao.AccountDao"> <resultMap id="BaseResultMap" type="com.lee.springcloud.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>
Service layer
public interface AccountService { //Deduction account balance void decrease( Long userId,BigDecimal money); }
@Slf4j @Service public class AccountServiceImpl implements AccountService { @Resource private AccountDao accountDao; //Deduction account balance @Override public void decrease(Long userId, BigDecimal money) { log.info("------->account-service Start of the balance of the medium deduction account"); //try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } accountDao.decrease(userId,money); log.info("------->account-service Ending of balance of China deduction account"); } }
controller
@RestController public class AccountController { @Resource private AccountService accountService; //Deduction account balance @RequestMapping("/account/decrease") public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){ accountService.decrease(userId,money); return new CommonResult<>(200,"Deduction of account balance succeeded!"); } }
Configuration layer:
@Configuration @MapperScan({"com.lee.springcloud.dao"}) public class MybatisConfig { }
@Configuration public class DataSourceProxyConfig { @Value("${mybatis.mapperLocations}") private String mapperLocations; @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource(){ return new DruidDataSource(); } @Bean public DataSourceProxy dataSourceProxy(DataSource dataSource) { return new DataSourceProxy(dataSource); } @Bean public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations)); sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return sqlSessionFactoryBean.getObject(); } }
Main startup class
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableDiscoveryClient @EnableFeignClients public class SeataAccountMainApp2003 { public static void main(String[] args){ SpringApplication.run(SeataAccountMainApp2003.class, args); } }
4. Create corresponding micro service inventory
Create inventory microservice: seata-order-service-2002
<parent> <artifactId>cloud_2020</artifactId> <groupId>com.lee.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>seata-order-service-2002</artifactId>
POM
<?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"> <parent> <artifactId>cloud_2020</artifactId> <groupId>com.lee.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>seata-order-service-2002</artifactId> <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> <artifactId>seata-all</artifactId> <groupId>io.seata</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>0.9.0</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> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> </project>
application.yml
server: port: 2002 spring: application: name: seata-storage-service cloud: alibaba: seata: tx-service-group: my_test_tx_group #It is consistent with the name of the user-defined transaction group in file.conf under the sea TA server conf directory nacos: discovery: server-addr: localhost:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_storage username: root password: admin123 logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml
At the same time, copy the files of file.conf and register.conf under the Seata server conf directory to the resource directory
Entity class
@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); } }
@Data @ToString @AllArgsConstructor @NoArgsConstructor public class Storage { private Long id; // Product id private Long productId; //Total stock private Integer total; //Used inventory private Integer used; //Remaining inventory private Integer residue; }
Data layer:
@Mapper public interface StorageDao { //Deduct inventory information void decrease(@Param("productId") Long productId, @Param("count") Integer count); }
<?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.lee.springcloud.dao.StorageDao"> <resultMap id="BaseResultMap" type="com.lee.springcloud.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>
Service layer
public interface StorageService { // Deduct inventory void decrease(Long productId, Integer count); }
@Slf4j @Service public class StorageServiceImpl implements StorageService { @Resource private StorageDao storageDao; // Deduct inventory @Override public void decrease(Long productId, Integer count) { log.info("------->storage-service Start of inventory after deduction"); storageDao.decrease(productId,count); log.info("------->storage-service End of medium deduction inventory"); } }
controller
@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!"); } }
config
@Configuration @MapperScan({"com.lee.springcloud.dao"}) public class MybatisConfig { }
@Configuration public class DataSourceProxyConfig { @Value("${mybatis.mapperLocations}") private String mapperLocations; @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource(){ return new DruidDataSource(); } @Bean public DataSourceProxy dataSourceProxy(DataSource dataSource) { return new DataSourceProxy(dataSource); } @Bean public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations)); sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return sqlSessionFactoryBean.getObject(); } }
Main startup class:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableDiscoveryClient @EnableFeignClients public class SeataStorageServiceApplication2002 { public static void main(String[] args) { SpringApplication.run(SeataStorageServiceApplication2002.class, args); } }
5. Create corresponding microservice - order
Create corresponding order microservice: seata-order-service-2001
<parent> <artifactId>cloud_2020</artifactId> <groupId>com.lee.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>seata-order-service-2001</artifactId>
POM
<?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"> <parent> <artifactId>cloud_2020</artifactId> <groupId>com.lee.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>seata-order-service-2001</artifactId> <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> <artifactId>seata-all</artifactId> <groupId>io.seata</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>0.9.0</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> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </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> </project>
application.yml
server: port: 2001 spring: application: name: seata-order-service cloud: alibaba: seata: tx-service-group: my_test_tx_group #The user-defined transaction group name needs to correspond to that in sea TA server nacos: discovery: server-addr: localhost:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_order username: root password: admin123 feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml
At the same time, copy the files of file.conf and register.conf under the Seata server conf directory to the resource directory
Entity class
@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); } }
@Data @ToString @AllArgsConstructor @NoArgsConstructor public class Order{ private Long id; private Long userId; private Long productId; private Integer count; private BigDecimal money; private Integer status; //Order status: 0: creating; 1: closed }
Data layer
@Mapper public interface OrderDao{ //New order void create(Order order); //Change order status from zero to 1 void update(@Param("userId") Long userId, @Param("status") Integer status); }
<?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.lee.springcloud.dao.OrderDao"> <resultMap id="BaseResultMap" type="com.lee.springcloud.domain.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="create"> insert into t_order (id,user_id,product_id,count,money,status) values (null,#{userId},#{productId},#{count},#{money},0); </insert> <update id="update"> update t_order set status = 1 where user_id=#{userId} and status = #{status}; </update> </mapper>
Service layer
public interface OrderService { void create(Order order); }
@Service @Slf4j public class OrderServiceImpl implements OrderService { @Resource private OrderDao orderDao; @Resource private StorageService storageService; @Resource private AccountService accountService; /** * Create order - > call inventory service to deduct inventory - > call account service to deduct account balance - > Modify order status */ @Override //@GlobalTransactional(name = "test-create-order",rollbackFor = Exception.class) public void create(Order order){ log.info("----->Start new order"); //New order orderDao.create(order); //Deduct inventory log.info("----->Order micro service starts to call inventory and make deduction Count"); storageService.decrease(order.getProductId(),order.getCount()); log.info("----->Order micro service starts to call inventory and make deduction end"); //Deduction account log.info("----->Order micro service starts to call account and make deduction Money"); accountService.decrease(order.getUserId(),order.getMoney()); log.info("----->Order micro service starts to call account and make deduction end"); //Modify order status, from zero to 1 means completed log.info("----->Modify order status start"); orderDao.update(order.getUserId(),0); log.info("----->Modify order status end"); log.info("----->The order is over"); } }
feign
@FeignClient(value = "seata-storage-service") public interface StorageService{ @PostMapping(value = "/storage/decrease") CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count); }
@FeignClient(value = "seata-account-service") public interface AccountService{ @PostMapping(value = "/account/decrease") CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money); }
controller
@RestController public class OrderController{ @Resource private OrderService orderService; @GetMapping("/order/create") public CommonResult create(Order order) { orderService.create(order); return new CommonResult(200,"Order created successfully"); } }
Configuration class
@Configuration @MapperScan({"com.lee.springcloud.dao"}) public class MybatisConfig { }
@Configuration public class DataSourceProxyConfig { @Value("${mybatis.mapperLocations}") private String mapperLocations; @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource(){ return new DruidDataSource(); } @Bean public DataSourceProxy dataSourceProxy(DataSource dataSource) { return new DataSourceProxy(dataSource); } @Bean public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations)); sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return sqlSessionFactoryBean.getObject(); } }
Main startup class
@EnableDiscoveryClient @EnableFeignClients @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//Cancel configuration of data source auto creation public class SeataOrderMainApp2001{ public static void main(String[] args) { SpringApplication.run(SeataOrderMainApp2001.class, args); } }
6. Testing
1. Start Nacos server, Seata server, Seata order service-2001 \ 2002 \ 2003 2. At this time, the order microservice does not open the global transaction //@GlobalTransactional(name = "test-create-order",rollbackFor = Exception.class) The delay of the account micro service debit interface is not enabled //try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } business as usual Browser call: http: / / localhost: 2001 / order / create? Userid = 1 & productid = 1 & count = 10 & money = 100 result: {"code":200,"message": "order created successfully", "data":null} The tables corresponding to the database have been changed accordingly 3. Now we delay the account micro service debit interface, because the default call time of openFeign is 1s. If it exceeds 1s, the call fails Open the try {timeunit. Seconds. Sleep (20);} catch (interruptedexception E) {e.printstacktrace();} comment Browser access: http: / / localhost: 2001 / order / create? Userid = 1 & productid = 1 & count = 10 & money = 100 Error: java.net.socketimeoutexception: read timed out Table corresponding to database: Order table data is added, but status is 0 Inventory table data modified used10 reside90 Account table not moved 4. Now let's turn on global transactions @GlobalTransactional(name = "test-create-order",rollbackFor = Exception.class) Browser access: http: / / localhost: 2001 / order / create? Userid = 1 & productid = 1 & count = 10 & money = 100 Error: java.net.socketimeoutexception: read timed out Table corresponding to database: No change