SpringCloud与Seata分布式事务初体验

在本篇文章中我们在SpringCloud环境下通过使用Seata来模拟用户购买商品时由于用户余额不足导致本次订单提交失败,来验证下在MySQL数据库内事务是否会回滚

本章文章只涉及所需要测试的服务列表以及Seata配置部分。

用户提交订单购买商品大致分为以下几个步骤:

  1. 减少库存
  2. 扣除金额
  3. 提交订单

1. 准备环境

  • Seata Server

    如果对Seata Server部署方式还不了解,请访问:http://blog.yuqiyu.com/seata-init-env.html

  • Eureka Server

    服务注册中心,如果对Eureka Server部署方式还不了解,请访问http://blog.yuqiyu.com/eureka-server.html

2. 准备测试服务

为了方便学习的同学查看源码,我们本章节源码采用Maven Module(多模块)的方式进行构建。

我们用于测试的服务所使用的第三方依赖都一致,各个服务的pom.xml文件内容如下所示:

 

<dependencies><!--Web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--openfeign接口定义--><dependency><groupId>org.minbox.chapter</groupId><artifactId>openfeign-service</artifactId><version>0.0.1-SNAPSHOT</version></dependency><!--公共依赖--><dependency><groupId>org.minbox.chapter</groupId><artifactId>common-service</artifactId><version>0.0.1-SNAPSHOT</version></dependency><!--seata--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--Eureka Client--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>org.minbox.framework</groupId><artifactId>api-boot-starter-mybatis-enhance</artifactId></dependency>
</dependencies>

2.1 Openfeign接口定义模块

由于我们服务之间采用的Openfeign方式进行相互调用,所以创建了一个模块openfeign-service来提供服务接口的定义

  • 账户服务提供的接口定义

账户服务对外所提供的Openfeign接口定义如下所示:

 

/*** 账户服务接口** @author 恒宇少年*/
@FeignClient(name = "account-service")
@RequestMapping(value = "/account")
public interface AccountClient {/*** 扣除指定账户金额** @param accountId 账户编号* @param money     金额*/@PostMappingvoid deduction(@RequestParam("accountId") Integer accountId, @RequestParam("money") Double money);
}
  • 商品服务提供的接口定义

    商品服务对外所提供的Openfeign接口定义如下所示:

 

  /*** 商品服务接口定义** @author 恒宇少年*/@FeignClient(name = "good-service")@RequestMapping(value = "/good")public interface GoodClient {/*** 查询商品基本信息** @param goodId {@link Good#getId()}* @return {@link Good}*/@GetMappingGood findById(@RequestParam("goodId") Integer goodId);/*** 减少商品的库存** @param goodId {@link Good#getId()}* @param stock  减少库存的数量*/@PostMappingvoid reduceStock(@RequestParam("goodId") Integer goodId, @RequestParam("stock") int stock);}

2.2 公共模块

公共模块common-service内所提供的类是共用的,各个服务都可以调用,其中最为重要的是将Seata所提供的数据源代理(DataSourceProxy)实例化配置放到了这个模块中,数据库代理相关配置代码如下所示:

 

/*** Seata所需数据库代理配置类** @author 恒宇少年*/
@Configuration
public class DataSourceProxyAutoConfiguration {/*** 数据源属性配置* {@link DataSourceProperties}*/private DataSourceProperties dataSourceProperties;public DataSourceProxyAutoConfiguration(DataSourceProperties dataSourceProperties) {this.dataSourceProperties = dataSourceProperties;}/*** 配置数据源代理,用于事务回滚** @return The default datasource* @see DataSourceProxy*/@Primary@Bean("dataSource")public DataSource dataSource() {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl(dataSourceProperties.getUrl());dataSource.setUsername(dataSourceProperties.getUsername());dataSource.setPassword(dataSourceProperties.getPassword());dataSource.setDriverClassName(dataSourceProperties.getDriverClassName());return new DataSourceProxy(dataSource);}
}

该配置类在所需要的服务中使用@Import注解进行导入使用。

2.3 账户服务

  • 服务接口实现

    账户服务用于提供接口的服务实现,通过实现openfeign-service内提供的AccountClient服务定义接口来对应提供服务实现,实现接口如下所示:

 

  /*** 账户接口实现** @author 恒宇少年*/@RestControllerpublic class AccountController implements AccountClient {/*** 账户业务逻辑*/@Autowiredprivate AccountService accountService;@Overridepublic void deduction(Integer accountId, Double money) {accountService.deduction(accountId, money);}}
  • 服务配置(application.yml)

 

  # 服务名spring:application:name: account-service# seata分组cloud:alibaba:seata:tx-service-group: minbox-seata# 数据源datasource:url: jdbc:mysql://localhost:3306/testusername: rootpassword: 123456type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver# eurekaeureka:client:service-url:defaultZone: http://service:nodev2@10.180.98.83:10001/eureka/

通过spring.cloud.alibaba.seata.tx-service-group我们可以指定服务所属事务的分组,该配置非必填,默认为spring.application.name配置的内容加上字符串-fescar-service-group,如:account-service-fescar-service-group,详见com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration配置类源码。

在我本地测试环境的Eureka Server10.180.98.83服务器上,这里需要修改成你们自己的地址,数据库连接信息也需要修改成你们自己的配置。

  • 导入Seata数据源代理配置

 

  /*** @author 恒宇少年*/@SpringBootApplication@Import(DataSourceProxyAutoConfiguration.class)public class AccountServiceApplication {/*** logger instance*/static Logger logger = LoggerFactory.getLogger(AccountServiceApplication.class);public static void main(String[] args) {SpringApplication.run(AccountServiceApplication.class, args);logger.info("账户服务启动成功.");}}

通过@Import导入我们common-service内提供的Seata数据源代理配置类DataSourceProxyAutoConfiguration

2.4 商品服务

  • 服务接口实现

    商品服务提供商品的查询以及库存扣减接口服务,实现openfeign-service提供的GoodClient服务接口定义如下所示:

 

  /*** 商品接口定义实现** @author 恒宇少年*/@RestControllerpublic class GoodController implements GoodClient {/*** 商品业务逻辑*/@Autowiredprivate GoodService goodService;/*** 查询商品信息** @param goodId {@link Good#getId()}* @return*/@Overridepublic Good findById(Integer goodId) {return goodService.findById(goodId);}/*** 扣减商品库存** @param goodId {@link Good#getId()}* @param stock  减少库存的数量*/@Overridepublic void reduceStock(Integer goodId, int stock) {goodService.reduceStock(goodId, stock);}}
  • 服务配置(application.yml)

 

  spring:application:name: good-servicecloud:alibaba:seata:tx-service-group: minbox-seatadatasource:url: jdbc:mysql://localhost:3306/testusername: rootpassword: 123456type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Drivereureka:client:service-url:defaultZone: http://service:nodev2@10.180.98.83:10001/eureka/server:port: 8081
  • 导入Seata数据源代理配置

 

  /*** @author 恒宇少年*/@SpringBootApplication@Import(DataSourceProxyAutoConfiguration.class)public class GoodServiceApplication {/*** logger instance*/static Logger logger = LoggerFactory.getLogger(GoodServiceApplication.class);public static void main(String[] args) {SpringApplication.run(GoodServiceApplication.class, args);logger.info("商品服务启动成功.");}}

2.5 订单服务

  • 服务接口

    订单服务提供了下单的接口,通过调用该接口完成下单功能,下单接口会通过Openfeign调用account-servicegood-service所提供的服务接口来完成数据验证,如下所示:

 

  /*** @author 恒宇少年*/@RestController@RequestMapping(value = "/order")public class OrderController {/*** 账户服务接口*/@Autowiredprivate AccountClient accountClient;/*** 商品服务接口*/@Autowiredprivate GoodClient goodClient;/*** 订单业务逻辑*/@Autowiredprivate OrderService orderService;/*** 通过{@link GoodClient#reduceStock(Integer, int)}方法减少商品的库存,判断库存剩余数量* 通过{@link AccountClient#deduction(Integer, Double)}方法扣除商品所需要的金额,金额不足由account-service抛出异常** @param goodId    {@link Good#getId()}* @param accountId {@link Account#getId()}* @param buyCount  购买数量* @return*/@PostMapping@GlobalTransactionalpublic String submitOrder(@RequestParam("goodId") Integer goodId,@RequestParam("accountId") Integer accountId,@RequestParam("buyCount") int buyCount) {Good good = goodClient.findById(goodId);Double orderPrice = buyCount * good.getPrice();goodClient.reduceStock(goodId, buyCount);accountClient.deduction(accountId, orderPrice);Order order = toOrder(goodId, accountId, orderPrice);orderService.addOrder(order);return "下单成功.";}private Order toOrder(Integer goodId, Integer accountId, Double orderPrice) {Order order = new Order();order.setGoodId(goodId);order.setAccountId(accountId);order.setPrice(orderPrice);return order;}}
  • 服务配置(application.yml)

 

  spring:application:name: order-servicecloud:alibaba:seata:tx-service-group: minbox-seatadatasource:url: jdbc:mysql://localhost:3306/testusername: rootpassword: 123456type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Drivereureka:client:service-url:defaultZone: http://service:nodev2@10.180.98.83:10001/eureka/server:port: 8082
  • 启用Openfeign & 导入Seata数据源代理配置

 

  /*** @author 恒宇少年*/@SpringBootApplication@EnableFeignClients(basePackages = "org.minbox.chapter.seata.openfeign")@Import(DataSourceProxyAutoConfiguration.class)public class OrderServiceApplication {/*** logger instance*/static Logger logger = LoggerFactory.getLogger(OrderServiceApplication.class);public static void main(String[] args) {SpringApplication.run(OrderServiceApplication.class, args);logger.info("订单服务启动成功.");}}

我们仅在order-service调用了其他服务的Openfeign接口,所以我们只需要在order-service内通过@EnableFeignClients注解启用Openfeign接口实现代理。

3. 服务连接Seata Server

服务想要连接到Seata Server需要添加两个配置文件,分别是registry.conffile.conf

  • registry.conf

    注册到Seata Server的配置文件,里面包含了注册方式、配置文件读取方式,内容如下所示:

 

  registry {# file、nacos、eureka、redis、zk、consultype = "file"file {name = "file.conf"}}config {type = "file"file {name = "file.conf"}}
  • file.conf

    该配置文件内包含了使用file方式连接到Eureka Server的配置信息以及存储分布式事务信息的方式,如下所示:

 

  transport {# tcp udt unix-domain-sockettype = "TCP"#NIO NATIVEserver = "NIO"#enable heartbeatheartbeat = true#thread factory for nettythread-factory {boss-thread-prefix = "NettyBoss"worker-thread-prefix = "NettyServerNIOWorker"server-executor-thread-prefix = "NettyServerBizHandler"share-boss-worker = falseclient-selector-thread-prefix = "NettyClientSelector"client-selector-thread-size = 1client-worker-thread-prefix = "NettyClientWorkerThread"# netty boss thread size,will not be used for UDTboss-thread-size = 1#auto default pin or 8worker-thread-size = 8}}## transaction log storestore {## store mode: file、dbmode = "file"## file storefile {dir = "sessionStore"# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptionsmax-branch-session-size = 16384# globe session size , if exceeded throws exceptionsmax-global-session-size = 512# file buffer size , if exceeded allocate new bufferfile-write-buffer-cache-size = 16384# when recover batch read sizesession.reload.read_size = 100# async, syncflush-disk-mode = async}## database storedb {datasource = "druid"db-type = "mysql"driver-class-name = "com.mysql.jdbc.Driver"url = "jdbc:mysql://10.180.98.83:3306/iot-transactional"user = "dev"password = "dev2019."}}service {vgroup_mapping.minbox-seata = "default"default.grouplist = "10.180.98.83:8091"enableDegrade = falsedisable = false}client {async.commit.buffer.limit = 10000lock {retry.internal = 10retry.times = 30}}

配置文件内service部分需要注意,我们在application.yml配置文件内配置了事务分组为minbox-seata,在这里需要进行对应配置vgroup_mapping.minbox-seata = "default",通过default.grouplist = "10.180.98.83:8091"配置Seata Server的服务列表。

将上面两个配置文件在各个服务resources目录下创建。

4. 编写下单逻辑

在前面说了那么多,只是做了准备工作,我们要为每个参与下单的服务添加对应的业务逻辑。

  • 账户服务

    account-service内添加账户余额扣除业务逻辑类,AccountService如下所示:

 

  /*** 账户业务逻辑处理** @author 恒宇少年*/@Service@Transactional(rollbackFor = Exception.class)public class AccountService {@Autowiredprivate EnhanceMapper<Account, Integer> mapper;/*** {@link EnhanceMapper} 具体使用查看ApiBoot官网文档http://apiboot.minbox.io/zh-cn/docs/api-boot-mybatis-enhance.html** @param accountId {@link Account#getId()}* @param money     扣除的金额*/public void deduction(Integer accountId, Double money) {Account account = mapper.selectOne(accountId);if (ObjectUtils.isEmpty(account)) {throw new RuntimeException("账户:" + accountId + ",不存在.");}if (account.getMoney() - money < 0) {throw new RuntimeException("账户:" + accountId + ",余额不足.");}account.setMoney(account.getMoney().doubleValue() - money);mapper.update(account);}}
  • 商品服务

    good-service内添加查询商品、扣减商品库存的逻辑类,GoodService如下所示:

 

  /*** 商品业务逻辑实现** @author 恒宇少年*/@Service@Transactional(rollbackFor = Exception.class)public class GoodService {@Autowiredprivate EnhanceMapper<Good, Integer> mapper;/*** 查询商品详情** @param goodId {@link Good#getId()}* @return {@link Good}*/public Good findById(Integer goodId) {return mapper.selectOne(goodId);}/*** {@link EnhanceMapper} 具体使用查看ApiBoot官网文档http://apiboot.minbox.io/zh-cn/docs/api-boot-mybatis-enhance.html* 扣除商品库存** @param goodId {@link Good#getId()}* @param stock  扣除的库存数量*/public void reduceStock(Integer goodId, int stock) {Good good = mapper.selectOne(goodId);if (ObjectUtils.isEmpty(good)) {throw new RuntimeException("商品:" + goodId + ",不存在.");}if (good.getStock() - stock < 0) {throw new RuntimeException("商品:" + goodId + "库存不足.");}good.setStock(good.getStock() - stock);mapper.update(good);}}

5. 提交订单测试

我们在执行测试之前在数据库内的seata_accountseata_good表内对应添加两条测试数据,如下所示:

 

-- seata_good
INSERT INTO `seata_good` VALUES (1,'华为Meta 30',10,5000.00); -- seata_account
INSERT INTO `seata_account` VALUES (1,10000.00,'2019-10-11 02:37:35',NULL);

增加SEATA恢复表

DROP SCHEMA IF EXISTS zeroa;CREATE SCHEMA zeroa;USE zeroa;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;DROP SCHEMA IF EXISTS zerob;CREATE SCHEMA zerob;USE zerob;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;

5.1 启动服务

将我们本章所使用good-serverorder-serviceaccount-service三个服务启动。

5.2 测试点:正常购买

我们添加的账户余额测试数据够我们购买两件商品,我们先来购买一件商品验证下接口访问是否成功,通过如下命令访问下单接口:

 

~ curl -X POST http://localhost:8082/order\?goodId\=1\&accountId\=1\&buyCount\=1
下单成功.

通过我们访问/order下单接口,根据响应的内容我们确定商品已经购买成功。

通过查看order-service控制台内容:

 

2019-10-11 16:52:15.477  INFO 13142 --- [nio-8082-exec-4] i.seata.tm.api.DefaultGlobalTransaction  : [10.180.98.83:8091:2024417333] commit status:Committed
2019-10-11 16:52:16.412  INFO 13142 --- [atch_RMROLE_2_8] i.s.core.rpc.netty.RmMessageListener     : onMessage:xid=10.180.98.83:8091:2024417333,branchId=2024417341,branchType=AT,resourceId=jdbc:mysql://localhost:3306/test,applicationData=null
2019-10-11 16:52:16.412  INFO 13142 --- [atch_RMROLE_2_8] io.seata.rm.AbstractRMHandler            : Branch committing: 10.180.98.83:8091:2024417333 2024417341 jdbc:mysql://localhost:3306/test null
2019-10-11 16:52:16.412  INFO 13142 --- [atch_RMROLE_2_8] io.seata.rm.AbstractRMHandler            : Branch commit result: PhaseTwo_Committed

我们可以看到本次事务已经成功Committed

再去验证下数据库内的账户余额商品库存是否有所扣减。

5.3 测试点:库存不足

测试商品添加了10个库存,在之前测试已经销售掉了一件商品,我们测试购买数量超过库存数量时,是否有回滚日志,执行如下命令:

 

~ curl -X POST http://localhost:8082/order\?goodId\=1\&accountId\=1\&buyCount\=10
{"timestamp":"2019-10-11T08:57:13.775+0000","status":500,"error":"Internal Server Error","message":"status 500 reading GoodClient#reduceStock(Integer,int)","path":"/order"}

在我们good-service服务控制台已经打印了商品库存不足的异常信息:

 

java.lang.RuntimeException: 商品:1库存不足.at org.minbox.chapter.seata.service.GoodService.reduceStock(GoodService.java:42) ~[classes/:na]....

我们再看order-service的控制台打印日志:

 

Begin new global transaction [10.180.98.83:8091:2024417350]
2019-10-11 16:57:13.771  INFO 13142 --- [nio-8082-exec-5] i.seata.tm.api.DefaultGlobalTransaction  : [10.180.98.83:8091:2024417350] rollback status:Rollbacked

通过日志可以查看本次事务进行了回滚

由于库存的验证在账户余额扣减之前,所以我们本次并不能从数据库的数据来判断事务是真的回滚。

5.4 测试点:余额不足

既然商品库存不足我们不能直接验证数据库事务回滚,我们从账户余额不足来下手,在之前成功购买了一件商品,账户的余额还够购买一件商品,商品库存目前是9件,我们本次测试购买5件商品,这样就会出现购买商品库存充足余额不足的应用场景,执行如下命令发起请求:

 

~ curl -X POST http://localhost:8082/order\?goodId\=1\&accountId\=1\&buyCount\=5
{"timestamp":"2019-10-11T09:03:00.794+0000","status":500,"error":"Internal Server Error","message":"status 500 reading AccountClient#deduction(Integer,Double)","path":"/order"}

我们通过查看account-service控制台日志可以看到:

 

java.lang.RuntimeException: 账户:1,余额不足.at org.minbox.chapter.seata.service.AccountService.deduction(AccountService.java:33) ~[classes/:na]

已经抛出了余额不足的异常。

通过查看good-serviceorder-serivce控制台日志,可以看到事务进行了回滚操作。

接下来查看seata_account表数据,我们发现账户余额没有改变,账户服务的事务回滚验证成功

查看seata_good表数据,我们发现商品的库存也没有改变,商品服务的事务回滚验证成功

6. 总结

本章主要来验证分布式事务框架SeataMySQL下提交与回滚有效性,是否能够完成我们预期的效果,Seata作为SpringCloud Alibaba的核心框架,更新频率比较高,快速的解决使用过程中遇到的问题,是一个潜力股,不错的选择。

由于本章设计的代码比较多,请结合源码进行学习。

7. 本章源码

请访问<a href="https://gitee.com/hengboy/spring-cloud-chapter" target="_blank">https://gitee.com/hengboy/spring-cloud-chapter</a>查看本章源码,建议使用git clone https://gitee.com/hengboy/spring-cloud-chapter.git将源码下载到本地。

  • Gitee:https://gitee.com/hengboy/spring-boot-chapter



 

坑点一
如果你的项目采用是spring-cloud-alibaba-seata 0.9.0版本或以下的话,它集成了fescar-spring的0.4.2版本,如果你的seata-server服务端是采用0.5.0以上建议还是降低版本,采用0.4.2版本。因为0.4.2版本解压是fescar-server名字,意不意外。这就是坑。而且项目引入seata依赖会与旧版本的fescar依赖冲突。

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-alibaba-seata</artifactId>
                <version>0.9.0.RELEASE</version>
            </dependency>
 


如果你的项目采用是spring-cloud-alibaba-seata 0.9.1(这个的seata为0.5.2)版本以上的话,那恭喜你。你可以使用seata-server的0.5.2以上的版本了。只需要在依赖这样引入

       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-alibaba-seata</artifactId>
            <version>0.9.1.BUILD-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>seata-server对应的版本</version>
        </dependency>
 


坑点二
需要在每个服务中的resources文件中添加两个文件file.conf和registry.conf


具体以seata-server中的file.conf和registry.conf为准。

坑点三
如果你的项目采用是spring-cloud-alibaba-seata 0.9.0版本或以下的话,因为它集成了fescar-spring的0.4.2版本,如果你是使用nacos来配置参数的话,建议使用seata-server 0.4.2,不然引入seata0.5.0以上的版本依赖会混淆,容易报以下错误

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'globalTransactionScanner' defined in class path resource [org/springframework/cloud/alibaba/seata/GlobalTransactionAutoConfiguration.class]: Invocation of init method failed; nested exception is java.lang.ExceptionInInitializerError
Caused by: java.lang.ExceptionInInitializerError: null
Caused by: java.lang.NullPointerException: Name is null
    at java.lang.Enum.valueOf(Enum.java:236) ~[na:1.8.0_201]
    at com.alibaba.fescar.core.rpc.netty.TransportProtocolType.valueOf(TransportProtocolType.java:25) ~[fescar-core-0.4.2.jar:na]
    at com.alibaba.fescar.core.rpc.netty.NettyBaseConfig.<clinit>(NettyBaseConfig.java:114) ~[fescar-core-0.4.2.jar:na]
    ... 23 common frames omitted
 


坑点四
数据源需要引入druid依赖

/**
 * @author lgt
 */
@Configuration
public class DatabaseConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource druidDataSource() {
        return new DruidDataSource();
    }

    /**
     * 需要将 DataSourceProxy 设置为主数据源,否则事务无法回滚
     *
     * @param druidDataSource The DruidDataSource
     * @return The default datasource
     */
    @Primary
    @Bean("dataSource")
    public DataSource dataSource(DruidDataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }

}

ps:上面是 seata 数据源的配置,数据源采用 druid 的DruidDataSource,但实际 jdbcTemplate 执行时并不是用该数据源,而用的是 seata 对DruidDataSource的代理DataSourceProxy,所以,与 RM 相关的代码逻辑基本上都是从DataSourceProxy这个代理数据源开始的。

坑点五
0.6.1及之前版本的启动命令是:sh seata-server.sh 8091 file 127.0.0.1
0.7.0 及之后版本的启动命令是:sh seata-server.sh -p 8091 -h 127.0.0.1 -m file
0.5.2及之前的版本的数据库和之后版本数据库是不一样,详细以github的文件一致
————————————————
版权声明:本文为CSDN博主「sbit_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sbit_/article/details/96112393

作者:恒宇少年
链接:https://www.jianshu.com/p/0a92b7c97c65
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/386607.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

想学IT的必看!今年Android面试必问的这些技术面,架构师必备技能

第一次观看我文章的朋友&#xff0c;可以关注、点赞、转发一下&#xff0c;每天分享各种干货技术和程序猿趣事 前言 职场的金三银四跳槽季又来了&#xff0c;不同的是今年比往年「冷」一些&#xff0c;形式更加严峻一些&#xff0c;大家多多少少可能都听到或看到一些信息&…

springboot集成redis使用redis作为session报错ClassNotFoundException类RememberMeServices

springboot 集成redis使用redis作为缓存&#xff0c;会报错的问题。 错误信息&#xff1a; java.lang.IllegalStateException: Error processing condition on org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration.taskSchedulerat org.springframew…

阿里巴巴分布式事务利器Seata环境准备

阿里巴巴自从跟SpringCloud共同发起创建微服务开源社区时&#xff0c;开启了SpringCloud Alibaba分支&#xff0c;而且在生态内提供了一款适用于分布式应用程序&#xff08;Dubbo、SpringCloud等&#xff09;的事务框架Seata&#xff0c;该框架经过多个大版本的发布&#xff0c…

对于‘敲什么都队’自主开发的《校园服务》软件的使用体验

信1805-1 边信哲 20183694 在六月十三日我系组织的2017级软件工程交流大会中&#xff0c;我为第十一组敲什么都队’自主开发的《校园服务》软件投出了我的一票&#xff0c;在为数众多的校园服务类软件中&#xff0c;《校园服务》最吸引我的地方就是他们的软件能完成数据…

阿里P7大牛亲自教你!BAT这种大厂履历意味着什么?积累总结

金九银十过后各大网络平台都是各种面经分享&#xff0c;包括已收offer&#xff0c;或面试失败的都有&#xff0c;相信大部分人都拿到了自己心仪的大厂offer&#xff0c;不过也有没有少数没能进到自己内心向往的大厂而懊恼的&#xff0c;那么到底如何才能进大厂&#xff0c;该准…

启动mac版docker自带的k8s

最近准备好好学习下k8s&#xff0c;为了图方便&#xff0c;直接使用docker集成的k8s&#xff0c;但是网上找了一些教程但都没能一次性成功&#xff0c;只好自己从头跑一遍&#xff0c;顺手写个教程可以方便有类似需求的同学参考。 话不多说&#xff0c;直接上步骤。 1.下载doc…

yum安装mysql

在CentOS7中默认安装有MariaDB&#xff0c;这个是MySQL的分支&#xff0c;但为了需要&#xff0c;还是要在系统中安装MySQL&#xff0c;而且安装完成之后可以直接覆盖掉MariaDB。 1. 下载并安装MySQL官方的 Yum Repository 1[rootBrianZhu /]# wget -i -c http://dev.mysql.com…

springboot很多以来jar包是在外部当时候,如何打dockerfile到阿里云

首先保证springboot与各种jar包文件夹在同一目录 dockerfile如下内容 FROM frolvlad/alpine-oraclejdk8 VOLUME /usr/cloud ADD lib /lib/ ADD lib_attachment /lib_attachment/ ADD lib_bigdata /lib_bigdata/ ADD lib_bpm /lib_bpm/ ADD lib_deploy /lib_deploy/ ADD lib_el…

阿里P7大牛手把手教你!一眼就能看懂的Android自学手册,真香!

前言 曾听过很多人说Android学习很简单&#xff0c;做个App就上手了&#xff0c;工作机会多&#xff0c;毕业后也比较容易找工作。这种观点可能是很多Android开发者最开始入行的原因之一。 在工作初期&#xff0c;工作主要是按照业务需求实现App页面的功能&#xff0c;按照设…

【VScode】使用VScode 来写markdown时序图

准备工作在VScode中下载插件Markdown Preview Enhanced插件创建一个.md文件在VScode中打开文件&#xff0c;界面内点击右键可以看到Open preview to the side(还有很多方法外面都能搜到)&#xff0c;可以进行实时预览效果开始markdown第一行主标题&#xff08;次标题依次加#&am…

阿里P7大牛整理!BAT大厂面试基础题集合,成功入职字节跳动

都说大厂面试必问源码&#xff0c;可很多人看完MMKV 源码、Handler 源码、Binder 源码、OkHttp 源码等源码记不住&#xff0c;是脑子有问题吗&#xff1f;当然不是&#xff01;是因为你没有掌握学习源码的技巧。 我的朋友子路&#xff0c;很多人都叫他路神&#xff0c;称他为“…

大项目之网上书城(八)——数据库大改添加图书

目录 大项目之网上书城&#xff08;八&#xff09;——数据库大改&添加图书主要改动1.数据库新增表代码2.数据库新增触发器3.其他对BookService和BookDao的修改代码4.addBook.jsp代码效果图5.AddNewBookServlet代码大项目之网上书城&#xff08;八&#xff09;——数据库大…

hping3工具DOS攻击实验

需要两台机器&#xff0c;一台扮演攻击源&#xff0c;另一做目标源。 攻击源地址:10.0.40.4 被攻击机器地址:10.0.40.246 2 被攻击的机器上安装tcpdump&#xff0c;tcpdump主要是用来抓包的&#xff0c;看看网络数据包是否到达。 $ yum install tcpdump -y 3 首先开启tcp…

腾讯T2亲自讲解!搞懂开源框架设计思想真的这么重要吗?系列篇

Java相关 无论什么级别的Android从业者&#xff0c;Java作为Android开发基础语言。不管是工作还是面试中&#xff0c;Java都是必考题。如果不懂Java的话&#xff0c;薪酬会非常吃亏&#xff08;美团尤为重视Java基础&#xff09; 详细介绍了Java泛型、注解、并发编程、数据传…

解决Docker容器内访问宿主机MySQL数据库服务器的问题

懒得描述太多,总归是解决了问题,方法简要记录如下,虽然简要,但是完整,一来纪念处理该问题耗费的大半天时间,二来本着共享精神帮助其他遇到该问题的哥们儿,当然这个方法并不一定能解决你们的问题,但是多少能提供些解决思路. 第一,先检查防火墙,通常应该没什么问题 (问题解决之后…

阿里P7亲自教你!我的头条面试经历分享,完整PDF

前言 转眼间&#xff0c;2020 年已过去一大半了&#xff0c;2020 年很难&#xff0c;各企业裁员的消息蛮多的&#xff0c;降职&#xff0c;不发年终奖等等。2020 年确实是艰难的一年。然而生活总是要继续&#xff0c;时间不给你丧的机会&#xff01;如果我们能坚持下来&#x…

Java多线程 ——线程基础和锁锁锁

Java多线程(一) 一、线程的定义二、Synchronize线程同步三、偏向锁、自旋锁、重量级锁四、volatile关键字 4.1.普通变量运算的物理意义4.2.有无解决的方案4.3.volatile的几个特性&#xff08;参考https://www.cnblogs.com/kubidemanong/p/9505944.html&#xff09;五、Compare …

阿里P7级别面试经验总结,进阶学习资料!

一、前言 本人面试已经很久之前了&#xff0c;分享一下我做美团面试官的经历吧。 美团上海面试&#xff0c;2-1及以下美团是不社招的&#xff0c;校招和2-2~2-3社招一般是三面&#xff0c;格外优秀3-1及以上会加签面试。初面技术基础&#xff0c;二面业务感知和技术项目&#…

C 预处理指令

0. Overview C的预处理指令格式为#name&#xff0c;均以#开头&#xff0c;#和指令名之间不可有空白字符&#xff0c;#前可以有空字符&#xff0c;但为增强可读性&#xff0c;一般应从第一列开始 #name不能由宏展开得来&#xff0c;name也不能由宏展开得来&#xff0c;如 // Wro…

Windows NAT端口映射

Windows本身命令行支持配置端口映射&#xff0c;条件是已经安装了IPV6&#xff0c;启不启用都无所谓&#xff0c;我在win7和server2008上是可以的。xp&#xff0c;2003装了ipv6协议也是可以的。 CMD下操作 增加端口映射&#xff0c;将10.10.10.10的8080映射到10.10.10.11的80…