SpringCloudAlibaba之Seata微服务之间调用(七)
上一节介绍seata的安装。本节主要讲述使用seata实现分布式事务。
创建库和表
创建一个业务需要的数据库,例如seata_order。接下来是是创建表。
为了演示业务,创建三种表分别是order(订单表)、storage(商品库存表)、user_account(用户账号信息表)。
-- seata_order.user_account definitionCREATE TABLE `user_account` ( `userid` bigint(20) NOT NULL AUTO_INCREMENT, `account` bigint(20) DEFAULT NULL, `name` varchar(64) DEFAULT NULL, PRIMARY KEY (`userid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- seata_order.storage definitionCREATE TABLE `storage` ( `commoditycode` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(64) DEFAULT NULL, `count` bigint(20) DEFAULT NULL, `price` bigint(20) DEFAULT NULL, PRIMARY KEY (`commoditycode`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- seata_order.`order` definitionCREATE TABLE `order` ( `orderid` bigint(20) NOT NULL AUTO_INCREMENT, `commoditycode` bigint(20) DEFAULT NULL, `userId` bigint(20) DEFAULT NULL, `num` bigint(20) DEFAULT NULL, `money` bigint(20) DEFAULT NULL, `status` bigint(20) DEFAULT NULL, PRIMARY KEY (`orderid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
storage(商品库存表)和user_account(用户账号信息表)造几条数据备用。自己可以根据自己的喜好自己造,一下是我创建的几条数据,仅供参考。
这里还需要一张seata相关的表,跟业务表放在同一个数据库下,表结构如下
-- 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.-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)-- 注意此处0.3.0+ 增加唯一索引 ux_undo_logdrop 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;
商品微服务Goods
pom核心内容
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.google.code.findbugs jsr305 org.hdrhistogram HdrHistogram org.checkerframework checker-qual com.google.errorprone error_prone_annotations com.alibaba.cloud spring-cloud-starter-alibaba-seata org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis.springboot.version} org.springframework.boot spring-boot-starter-logging mysql mysql-connector-java com.alibaba druid-spring-boot-starter ${druid.springboot.version}
application.yml相关配置
server: port: 7002spring: profiles: active: dev application: name: GOODS cloud: nacos: discovery: namespace: ec6004af-f122-4ed1-b6d6-5d77ea5d5c94 group: SEATA_GROUP server-addr: 192.168.43.85:8845,192.168.43.229:8846,192.168.43.251:8847 alibaba: seata: tx-service-group: my_test_tx_groupmybatis: type-aliases-package: com.juwusheng.goods.model mapper-locations: classpath:mapper/*.xmlribbon: eager-load: enabled: true#spring cloud alibaba 2.1.4 之后支持yml中配置seata属性,可以用来替换registry.conf文件seata: # seata 服务分组,要与服务端nacos-config.txt中service.vgroup_mapping的后缀对应 tx-service-group: my_test_tx_group registry: # 指定nacos作为注册中心 type: nacos nacos: server-addr: 192.168.43.85:8845,192.168.43.229:8846,192.168.43.251:8847 application: seata-server group: SEATA_GROUP namespace: ec6004af-f122-4ed1-b6d6-5d77ea5d5c94 config: # 指定nacos作为配置中心 type: nacos nacos: server-addr: 192.168.43.85:8845,192.168.43.229:8846,192.168.43.251:8847 namespace: ec6004af-f122-4ed1-b6d6-5d77ea5d5c94 group: SEATA_GROUP service: vgroup-mapping: my_test_tx_group: default disable-global-transaction: false client: rm: report-success-enable: false
上述主要是配置关于seata客户端注册到nacos先关配置。这里还增加了一个application-dev.yml的配置,主要配置数据库。
spring: datasource: username: 账号 password: 密码 url: jdbc:mysql://127.0.0.1:3306/seata_order?characterEncoding=utf8&useSSL=false driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource druid: initial-size: 5 min-idle: 5 max-wait: 60000 max-active: 20 validation-query: SELECT 1 stat-view-servlet: #启动控制平台 login-username: druid login-password: druid enabled: true filter: stat: slow-sql-millis: 1 log-slow-sql: true log4j2: enabled: true exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*" filters: stat,wall,log4j2
这里使用阿里druid的连接池,数据账号密码以及连接请更改。
主函数SpringBootApplication注解中剔除DataSourceAutoConfiguration。如下
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
手动配置数据库启动类MybatisConfig
package com.juwusheng.goods.config;import com.alibaba.druid.pool.DruidDataSource;import io.seata.rm.datasource.DataSourceProxy;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.annotation.MapperScan;import org.mybatis.spring.transaction.SpringManagedTransactionFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.core.io.support.ResourcePatternResolver;import javax.sql.DataSource;@Configuration@MapperScan("com.juwusheng.goods.dao")public class MybatisConfig { @Value("${mybatis.mapper-locations:classpath:mapper/*.xml}") String mybatisMapperLocation; / * 从配置文件获取属性构造datasource,注意前缀,这里用的是druid,根据自己情况配置, * 原生datasource前缀取"spring.datasource" */ @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource; }// /// * 构造datasource代理对象,替换原来的datasource// * @param druidDataSource// @Primary// @Bean("dataSource")// public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {// return new DataSourceProxy(druidDataSource);// }*/ @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactoryBean(DataSource druidDataSource) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); //设置代理数据源 factoryBean.setDataSource(new DataSourceProxy(druidDataSource)); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); factoryBean.setMapperLocations(resolver.getResources(mybatisMapperLocation)); org.apache.ibatis.session.Configuration configuration=new org.apache.ibatis.session.Configuration(); //使用jdbc的getGeneratedKeys获取数据库自增主键值 configuration.setUseGeneratedKeys(true); //使用列别名替换列名 configuration.setUseColumnLabel(true); //自动使用驼峰命名属性映射字段,如userId ---> user_id configuration.setMapUnderscoreToCamelCase(true); //更改默认的事务 factoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); factoryBean.setConfiguration(configuration); return factoryBean.getObject(); }}
因涉及三个微服务,代码较多,忽略部分非核心代码,比如数据库表对应的java的model对象类。
新增一个Controller的类,增加请求代码。
@PostMapping("/goods/deductStorage") public String deductStorage(@RequestParam(value="id",defaultValue = "0") String id,@RequestParam(value="count",defaultValue = "0") String count) throws Exception { boolean isSuccess=goodsService.deductStorage(id,count); Map map= new HashMap(8); map.put("code",isSuccess?"ok":"fail"); map.put("msg",isSuccess?"成功":"失败"); return jsonUtil.objectToJson(map); }
goodsService的核心代码
public boolean deductStorage(String id, String count) throws Exception { logger.info("事务XID:{}", RootContext.getXID()); Storage storage = storageMapper.selectByPrimaryKey(Long.parseLong(id)); if (storage.getCount() 0 ? true: false; }
Mapper.xml中减少商品库存核心的sql
update storage set count = count - #{count,jdbcType=BIGINT} where commoditycode = #{id,jdbcType=BIGINT}
由于篇幅过长,下一节说明剩余的两个微服务的代码。