spring cloud+feign+mybatis中使用seata0.9实现分布式事务

  • A+
所属分类:Java

seata前身叫fescar,是阿里开源的实现分布式事务中间件。
官网地址:https://github.com/seata/seata

中文文档:https://github.com/seata/seata/wiki/Home_Chinese

原理就不说了,话不多说,直接分享代码:

首先需要安装seata服务,下载地址:
https://github.com/seata/seata/releases
里面都是linux、windows都同时可以用的绿色版安装包

1.启动seata服务

默认以file形式注册,直接可以启动,启动端口默认为8091

sh seata-server.sh -p 8091

加上-p参数可以修改seata启动端口

2.配置需要分布式事务项目的工程

a.加入maven依赖(我之前贴出来的忘记把spring-cloud-alibaba-seata的依赖贴出来了):

<seata.version>0.9.0</seata.version>
<!-- 分布式事务支持 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>2.1.0.RELEASE</version>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-all</artifactId>
    <version>${seata.version}</version>
</dependency>


b.将file.conf和registry.conf复制到需要开启分布式的spring boot工程配置目录下
如果以file形式注册,默认配置不用改

但是file.conf有一处要根据服务名来修改
vgroup_mapping.xxxx-fescar-service-group = "default"

xxxx表示你的spring.application.name


spring cloud+feign+mybatis中使用seata0.9实现分布式事务

c.在每个业务数据库中创建undo_log表,记住每个需要使用分布式事务的库都要创建,这张表是seata记录事务和回滚事务用的,必须创建,表名可以修改,file.conf中有配置,创建表脚本:

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;


d.配置代理数据源,需要将业务数据源放到代理数据源中,另外mybatis的SqlSessionFactory需要手动配置,将代理数据源写入mybatis的SqlSessionFactory中,代码如下:

package com.mcu.config;import javax.sql.DataSource;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;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 com.alibaba.druid.pool.DruidDataSource;import io.seata.rm.datasource.DataSourceProxy;import io.seata.spring.annotation.GlobalTransactionScanner;@Configurationpublic class DBConfig {	@Value("${spring.application.name}")	private String applicationId;	// mybatis配置	@Value("${mybatis.mapper-locations}")	private String mapperLocations;	@Value("${mybatis.type-aliases-package}")	private String typeAliasesPackage;	@Value("${mybatis.configuration.map-underscore-to-camel-case}")	private boolean mapUnderscoreToCamelCase;			@Bean    @ConfigurationProperties(prefix = "spring.datasource.druid")    public DataSource druidDataSource() {        DruidDataSource druidDataSource = new DruidDataSource();        return druidDataSource;    }	@Primary//@Primary标识必须配置在代码数据源上,否则本地事务失效    @Bean("dataSourceProxy")    public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {        return new DataSourceProxy(druidDataSource);    }    @Bean    public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();        factoryBean.setDataSource(dataSourceProxy);        factoryBean.setTypeAliasesPackage(typeAliasesPackage);        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()            .getResources(mapperLocations));        factoryBean.setTransactionFactory(new SpringManagedTransactionFactory());        factoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(mapUnderscoreToCamelCase);        return factoryBean.getObject();    }	/**	 * 初始化分布式全局事务扫描	 * 	 * @return	 */	@Bean	public GlobalTransactionScanner globalTransactionScanner() {		return new GlobalTransactionScanner(applicationId.toLowerCase(), applicationId.toLowerCase() + "-fescar-service-group");	}	}

以上代码可参考官方示例:

https://github.com/seata/seata-samples/tree/master/springboot-mybatis

mybatis配置,数据库的就不贴了,druid配置都差不多

mybatis:  mapper-locations: classpath:mapper/*.xml  #注意:一定要对应mapper映射xml文件的所在路径  type-aliases-package: com.jiachi.gcommon.entity.personnel  # 注意:对应实体类的路径  configuration:    mapUnderscoreToCamelCase: true

d.配置feign调用拦截器传递分布式事务xid:

import org.apache.commons.lang.StringUtils;import org.springframework.context.annotation.Configuration;import feign.RequestInterceptor;import feign.RequestTemplate;import io.seata.core.context.RootContext;//内部调用需要token校验,这边将页面端的token和平台属性转发给feign@Configurationpublic class FeignConfig2 implements RequestInterceptor {    @Override    public void apply(RequestTemplate requestTemplate) {        String xid = RootContext.getXID();        if (StringUtils.isNotBlank(xid)) {			System.out.println("feign 获得分布式事务xid:"+xid);		}        requestTemplate.header("fescar-XID", xid);    }}

e.启动类禁用数据源自动配置类

@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})//禁用默认的自动配置数据源类

3.调用分布式事务

调用分布式事务的service或controller的方法加上@GlobalTransactional注解就可以使用seata管理分布式事务了

但是注意:目前默认的分布式事务隔离级别为读未提交读,在回滚前是可以用其他工具查询到数据的,比如你的分布式事务执行到一半时,是可以用navicat工具查询到数据的,如果中间出了异常,会正常的回滚


还有一些遗留问题如下:

1.目前我还没遇到这个bug,不知道是不是我的场景测试不够,bug如下:
https://github.com/seata/seata/issues/883

2.目前seata不支持复合主键的表,会报错:

io.seata.common.exception.NotSupportYetException: Multi PK

所以需要将表改成单个主键,这要是遇到有表分区,直接就不能用了。。唉

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: