Spring Cloud Alibaba整合seata 1.4.0(一)中整合了seata,但是项目中并没有使用到seata的功能,这篇博客介绍项目中如何使用seata。
使用seata是极其简单的,配置确实极其复杂的。
项目背景:
my-service中调用provider-service,调用provider-service后my-service中出现了RuntimeException或者超时异常。如果使用传统的@Transactional注解,只能保证my-service中所作的修改被回滚,不能保证provider-service中所作的修改,导致数据不一致。这时候就需要使用seata来代理数据源来控制分布式事务,从而保证数据一致性。
注意:provider-service和my-service使用不同的数据库。
伪代码
MyService{
function(){
changeDB();
provider-service.changeDB();
// 下面的代码逻辑出现了运行时异常或者超时异常
doSomething...
}
}
在Spring Cloud Alibaba整合seata 1.4.0(一)的基础上作出如下配置和编码。
步骤一、创建provider-service项目
provider-service的项目创建和my-service的创建基本一致。
添加依赖、引入file.conf、引入registry.conf不再赘述
需要特别注意的几点:
1、file.conf文件中修改
service {
#transaction service group mapping
vgroupMapping.provider-service-group = "default"
#only support when registry.type=file, please don't set multiple addresses
default.grouplist = "127.0.0.1:8091"
#degrade, current not support
enableDegrade = false
#disable seata
disableGlobalTransaction = false
}跟my-service中的配置类似
2、修改config.txt文件
在seata-server-1.4.0\seata目录下的config.txt中添加如下配置:
service.vgroupMapping.my-service-group=default
service.vgroupMapping.provider-service-group=default
并且将config.txt中的配置添加到nacos配置管理(上一篇文章步骤四有说明)
seata/conf目录下打开git bash执行如下命令
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t cadb1d46-f1fa-417f-bcc7-504822fd55b3 -u nacos -w nacos
yml配置
server:
port: 9005
servlet:
context-path: /provider
spring:
application:
name: provider-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
alibaba:
seata:
tx-service-group: ${spring.application.name}-group
datasource:
#type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_storage?userUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: provider.domain
业务逻辑代码忽略,提供一个API接口供my-service通过open-feign方式调用即可。
使用Seata对数据源进行代理
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
/**
* 使用seata对数据源进行代理
*/
@Configuration
public class DataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource datasource) {
return new DataSourceProxy(datasource);
}
/**
* 将原有的DataSource对象替换为DataSourceProxy
*/
@Bean
public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy)throws Exception{
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:/mapper/*.xml"));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
步骤二、修改my-service项目
yml修改
server:
port: 9004
servlet:
context-path: /my-service
spring:
application:
name: my-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
alibaba:
seata:
tx-service-group: my-service-group
datasource:
#type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order?userUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
修改file.conf文件
添加如下内容
store {
## store mode: file、db、redis
mode = "db"
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "root"
password = "root"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
启动类注解修改
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源的自动创建
使用Seata对数据源进行代理,见创建provider-service项目中的DataSourceConfiguration
添加@GlobalTransactional注解
在需要分布式事务的业务方法上添加@GlobalTransaction注解
@GlobalTransactional(name = "txName", rollbackFor = Exception.class)
步骤三、创建undo_log表
分别为my-service和provider-service所使用的数据创建undo_log表
README-zh.md文件中已经准备好了
https://github.com/seata/seata/tree/develop/script/client下载mysql.sql
分别在两个数据库下执行mysql.sql即可。
操作回滚时,会在log中出现以下日志信息:
Seata原理详见这里
具体项目代码见github,相关源码。
不当之处,欢迎指出