seata It is a relatively troublesome configuration in the spring technology stack that the author has encountered, and it may not be successful according to the official case, and many errors have occurred. So record it here:
This article selects seata version 1.3.0, which is also the version that the project adds dependencies. Release v1.3.0 · seata/seata · GitHub
Configure seata server: Download seata-server and source code. Unzip the s ource code, find the config-center file in the script file, and modify config.txt
... service.vgroup-mapping.my_test_tx_group=default ... store.mode=db 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_server?useUnicode=true&serverTimezone=UTC store.db.user=root store.db.password=123456 ...
Note that changing vgroupMapping to vgroup-mapping is a problem with the seata version. new build nacos The namespace seata runs and writes to the configuration center:
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t seata_namespace_id -u nacos -w nacos
Open script->server->db and write mysql.sql into the seata_server database
Unzip the seata-server to find conf, configure file.conf and registry.conf according to the official website, mode db, type nacos, and the namespace is also seata_namespace_id.
Start the seata-server service.
Then configure the seata-client service. First of all, it is explained according to the official website configuration, which is not as simple and clear as other technology stacks. The author tried a variety of methods and there were many pitfalls to step on. In addition, he also combined the official cases: GitHub - seata/seata-samples: seata-samples
Find the springcloud-nacos-seata file inside. We can test its stock and order modules. We need to introduce the undo_log table in different libraries that need to use the seata service. The table creation statement is on the official website and the readme of the case. It can be easily found in .md. Unfortunately, according to the case configuration, an error will still be reported, which probably cannot match the latest dependency configuration, so according to the configuration method of the idea tool, the spring web/nacos/seata dependency is configured. An error will be reported at startup. This is because the seata version of the spring-cloud-starter-alibaba-seata package is not 1.3.0 or the dependency of the seata-all package is missing. The final imported dependency is:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <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> </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> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.example</groupId> <artifactId>mysql-config</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
All version issues are handled in the parent pom:
<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.7.RELEASE</spring-boot.version> <spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version> </properties> <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>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.1</version> </dependency> <!-- https://mvnrepository.com/artifact/io.seata/seata-all --> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>1.3.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.10.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> <scope>provided</scope> </dependency> </dependencies> </dependencyManagement>
Imported mysql-config module configuration:
@Configuration public class config { /** * @param sqlSessionFactory SqlSessionFactory * @return SqlSessionTemplate */ @Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } /** * Obtain attributes from the configuration file to construct datasource, pay attention to the prefix, druid is used here, configure according to your own situation, * The native datasource prefix takes "spring.datasource" * * @return */ @Bean @ConfigurationProperties(prefix = "spring.datasource.druid") public DataSource druidDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource; } /** * Construct a datasource proxy object to replace the original datasource * * @param druidDataSource * @return */ @Primary @Bean("dataSource") public DataSourceProxy dataSourceProxy(DataSource druidDataSource) { return new DataSourceProxy(druidDataSource); } @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception { MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); bean.setDataSource(dataSourceProxy); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); // bean.setConfigLocation(resolver.getResource("classpath:mybatis-config.xml")); bean.setMapperLocations(resolver.getResources("classpath*:mybatis/**/*-mapper.xml")); SqlSessionFactory factory = null; try { factory = bean.getObject(); } catch (Exception e) { throw new RuntimeException(e); } return factory; } /** * MP Comes with pagination plugin * * @return */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor page = new PaginationInterceptor(); page.setDialectType("mysql"); return page; } }
At the same time, exclude the default configuration of DataSourceAutoConfiguration on the startup class
@EnableFeignClients @EnableDiscoveryClient @SpringBootApplication(scanBasePackages = {"com.example"}, exclude = DataSourceAutoConfiguration.class) public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }
application.yml:
server: port: 8000 spring: application: name: order-service cloud: nacos: discovery: username: nacos password: nacos server-addr: 127.0.0.1:8848 group: SEATA_GROUP alibaba: seata: # seata service grouping, which should correspond to the suffix of service.vgroup_mapping in nacos-config.txt on the server side tx-service-group: my_test_tx_group datasource: druid: url: jdbc:mysql://localhost:3306/seata_order?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC driverClassName: com.mysql.cj.jdbc.Driver username: root password: 123456 logging: level: io: seata: debug seata: registry: type: nacos nacos: application: seata-server server-addr: 127.0.0.1:8848 group : "SEATA_GROUP" namespace: "seata_namespace_id" username: "nacos" password: "nacos" config: type: nacos nacos: server-addr: 127.0.0.1:8848 group: "SEATA_GROUP" namespace: "seata_namespace_id" username: "nacos" password: "nacos" application-id: ${spring.application.name}
StockService.java:
@Service public class StockService { @Resource private StockDAO stockDAO; /** * reduce inventory * * @param commodityCode * @param count */ @Transactional(rollbackFor = Exception.class) public void deduct(String commodityCode, int count) { QueryWrapper<Stock> wrapper = new QueryWrapper<>(); wrapper.setEntity(new Stock().setCommodityCode(commodityCode)); Stock stock = stockDAO.selectOne(wrapper); stock.setCount(stock.getCount() - count); stockDAO.updateById(stock); if (commodityCode.equals("product-2")) { throw new RuntimeException("abnormal:Simulate business exceptions:stock branch exception"); } } }
StockFeignClient.java:
@FeignClient(name = "stock-service") public interface StockFeignClient { @GetMapping("stock/deduct") Boolean deduct(@RequestParam("commodityCode") String commodityCode, @RequestParam("count") Integer count); }
OrderService.java:
@Service public class OrderService { @Resource private StockFeignClient stockFeignClient; @Resource private OrderDAO orderDAO; //Enable seata global transactions @GlobalTransactional @Transactional(rollbackFor = Exception.class) public void placeOrder(String userId, String commodityCode, Integer count) { BigDecimal orderMoney = new BigDecimal(count).multiply(new BigDecimal(5)); Order order = new Order().setUserId(userId).setCommodityCode(commodityCode).setCount(count).setMoney( orderMoney); orderDAO.insert(order); stockFeignClient.deduct(commodityCode, count); } }
OrderController.java:
@RestController @RequestMapping("order") public class OrderController { @Resource private OrderService orderService; /** * Order: insert order form, deduct inventory * * @return */ @RequestMapping("/placeOrder/commit") public Boolean placeOrderCommit() { orderService.placeOrder("1", "product-1", 1); return true; } /** * Order: Insert order table, deduct inventory, simulate rollback * * @return */ @RequestMapping("/placeOrder/rollback") public Boolean placeOrderRollback() { // product-2 simulated a business exception when deducting inventory, orderService.placeOrder("1", "product-2", 1); return true; } @RequestMapping("/placeOrder") public Boolean placeOrder(String userId, String commodityCode, Integer count) { orderService.placeOrder(userId, commodityCode, count); return true; } }
Test: 1. The distributed transaction is successful, simulating the normal order and deduction of inventory
localhost:8000/order/placeOrder/commit
2. Distributed transaction fails, simulates the success of placing an order, fails to deduct inventory, and finally rolls back at the same time
localhost:8000/order/placeOrder/rollback