1. 版本:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata</artifactId>
<version>2.2.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.1.0</version>
</dependency>
<spring-boot.version>2.2.5.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
2.自定义seata-starter
文件结构如下:
3.上源码:
CqlivingSeataConfiguration.java
package org.cqliving.framework.cloud.seata;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ObjectUtils;
import io.seata.config.AbstractConfiguration;
import io.seata.config.ConfigurationChangeListener;
import io.seata.spring.boot.autoconfigure.util.SpringUtils;
public class CqlivingSeataConfiguration extends AbstractConfiguration{
private static final Logger logger = LoggerFactory.getLogger(CqlivingSeataConfiguration.class);
private static final String CONFIG_TYPE = "CqlivingSeataConfig";
private static final String PREFIX = "seata.";
private static volatile CqlivingSeataConfiguration instance;
private static final ConcurrentMap<String, Set<ConfigurationChangeListener>> LISTENER_SERVICE_MAP = new ConcurrentHashMap<>();
public static CqlivingSeataConfiguration getInstance() {
if (null == instance) {
synchronized (CqlivingSeataConfiguration.class) {
if (null == instance) {
instance = new CqlivingSeataConfiguration();
}
}
}
return instance;
}
private CqlivingSeataConfiguration() {}
private static final char LINE = '-';
@Override
public String getConfig(String dataId, String defaultValue, long timeoutMills) {
dataId = dataId.startsWith(PREFIX)?dataId: PREFIX.concat(formatCamel(dataId, LINE));
String value;
// 从系统变量获取
if ((value = getConfigFromSysPro(dataId)) != null) {
return value;
}
// 从配置文件获取
ApplicationContext applicationContext = SpringUtils.getApplicationContext();
if (null == applicationContext) {
return defaultValue;
}
value = applicationContext.getEnvironment().getProperty(dataId);
if(null == value) {
logger.error("properties null [{}] ", dataId);
return defaultValue;
}
return value;
}
private String formatCamel(String param, char sign) {
if (ObjectUtils.isEmpty(param)) {
return StringUtils.EMPTY;
}
int len = param.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = param.charAt(i);
if (Character.isUpperCase(c)) {
sb.append(sign);
sb.append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
@Override
public boolean putConfig(String dataId, String content, long timeoutMills) {
throw new UnsupportedOperationException();
}
@Override
public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeConfig(String dataId, long timeoutMills) {
throw new UnsupportedOperationException();
}
@Override
public void addConfigListener(String dataId, ConfigurationChangeListener listener) {
if (null == dataId || null == listener) {
return;
}
LISTENER_SERVICE_MAP.putIfAbsent(dataId, ConcurrentHashMap.newKeySet());
LISTENER_SERVICE_MAP.get(dataId).add(listener);
}
@Override
public void removeConfigListener(String dataId, ConfigurationChangeListener listener) {
if (!LISTENER_SERVICE_MAP.containsKey(dataId) || listener == null) {
return;
}
LISTENER_SERVICE_MAP.get(dataId).remove(listener);
}
@Override
public Set<ConfigurationChangeListener> getConfigListeners(String dataId) {
return LISTENER_SERVICE_MAP.get(dataId);
}
@Override
public String getTypeName() {
return CONFIG_TYPE;
}
}
CqlivingSeataConfigurationProvider.java
package org.cqliving.framework.cloud.seata;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.context.ApplicationContext;
import io.seata.common.loader.LoadLevel;
import io.seata.config.Configuration;
import io.seata.config.ConfigurationProvider;
import io.seata.config.ExtConfigurationProvider;
import io.seata.spring.boot.autoconfigure.util.SpringUtils;
@LoadLevel(name = "CqlivingSeataConfig", order = 1)
public class CqlivingSeataConfigurationProvider implements ExtConfigurationProvider, ConfigurationProvider {
private static final String GET ="get";
private static final String PREFIX ="seata.";
@Override
public Configuration provide(Configuration originalConfiguration) {
ApplicationContext applicationContext = SpringUtils.getApplicationContext();
return (Configuration)Enhancer.create(originalConfiguration.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {
if (method.getName().startsWith(GET) && args.length > 0) {
Object result = null;
String rawDataId = (String)args[0];
if (args.length == 1) {
result = applicationContext.getEnvironment().getProperty(PREFIX.concat(rawDataId));
}
if (null != result) {
return result;
}
}
return method.invoke(originalConfiguration, args);
}
});
}
@Override
public Configuration provide() {
return CqlivingSeataConfiguration.getInstance();
}
}
CustomProperties.java
@Component
@ConfigurationProperties(prefix = CustomProperties.PREFIX)
@Data
public class CustomProperties {
public static final String PREFIX = "seata.config.custom";
private String name = "CqlivingSeataConfig";
}
SeataAutoConfiguration.java
@Configuration
@PropertySource("classpath:seata.properties")
@EnableAutoDataSourceProxy
@EnableSeataSpringConfig
@EnableConfigurationProperties({CustomProperties.class})
public class SeataAutoConfiguration {
}
io.seata.config.ConfigurationProvider
org.cqliving.framework.cloud.seata.CqlivingSeataConfigurationProvider
io.seata.config.ExtConfigurationProvider
org.cqliving.framework.cloud.seata.CqlivingSeataConfigurationProvider
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.cqliving.framework.cloud.seata.SeataAutoConfiguration
seata.properties
seata.config.type=Custom
seata.config.custom.name=CqlivingSeataConfig
spring.cloud.alibaba.seata.tx-service-group=cqliving-seata-group
# ClientProperties.class
seata.client.rm.async-commit-buffer-limit=10000
seata.client.rm-report-retry-count=5
seata.client.rm.table-meta-check-enable=false
seata.client.rm-report-success-enable=false
seata.client.rm.sql-parser-type=druid
seata.client.tm-commit-retry-count=5
seata.client.tm-rollback-retry-count=5
# LockProperties.class
seata.client.rm.lock.lock-retry-interval=10
seata.client.rm.lock.lock-retry-times=30
seata.client.rm.lock.lock-retry-policy-branch-rollback-on-conflict=true
# LogProperties.class
seata.client.log.exception-rate=100
# ServiceProperties
seata.service.vgroupMapping.cqliving-seata-group=seata-server
seata.service.enable-degrade=false
seata.service.disable-global-transaction=false
# ShutdownProperties
seata.transport.shutdown.wait=3
# SpringProperties
seata.client.support.spring.datasource-autoproxy=true
# ThreadFactoryProperties
seata.transport.thread-factory.boss-thread-prefix=NettyBoss
seata.transport.thread-factory.worker-thread-prefix=NettyServerNIOWorker
seata.transport.thread-factory.server-executor-thread-prefix=NettyServerBizHandler
seata.transport.thread-factory.share-boss-worker=false
seata.transport.thread-factory.client-selector-thread-prefix=NettyClientSelector
seata.transport.thread-factory.client-selector-thread-size=1
seata.transport.thread-factory.client-worker-thread-prefix=NettyClientWorkerThread
seata.transport.thread-factory.boss-thread-size=1
seata.transport.thread-factory.worker-thread-size=8
# TransportProperties
seata.transport.type=TCP
seata.transport.server=NIO
seata.transport.heartbeat=true
seata.transport.serialization=seata
seata.transport.compressor=none
seata.transport.enable-client-batch-send-request=false
# UndoProperties
seata.client.undo.undo-data-validation=true
seata.client.undo.undo-log-serialization=jackson
seata.client.undo.undo-log-table=undo_log
# registry
seata.registry.type=eureka
seata.registry.eureka.application=${spring.application.name}
seata.registry.eureka.serviceUrl=${eureka.client.service-url.defaultZone}
seata.registry.eureka.weight=1
最后,创建几个微服务,注册到eureka,试试全局事物吧
注意:
1.每个服务数据库要创建一个表:undo_log,用于记录回滚日志
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) NOT NULL,
`context` text,
`rollback_info` text,
`log_status` varchar(32) DEFAULT NULL,
`log_created` datetime DEFAULT NULL,
`log_modified` datetime DEFAULT NULL,
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.配置文件不要跟着源码properties类里去定义,容易踩坑,要看运行时取的什么配置名,进org.cqliving.framework.cloud.seata.CqlivingSeataConfiguration.getConfig(String, String, long)里查看
3.注意配置:
seata.service.vgroupMapping.cqliving-seata-group=seata-server
这里的seata-server是seata注册到eureka的服务名,根据自己需要修改