大致流程如下:总共两个服务,一个住院服务,一个药房服务,药房集成sharding-jdbc进行分库分表,住院服务开启全局事务 1.住院扣费 2.药房锁库存
测试中发现药房分支事务不受seata全局事务控制,查看seata服务端日志也没有药房服务的分支注册日志及回滚日志,只有住院服务的分支注册日志和回滚日志;
查看shardingsphere官网得知,整合
Seata AT事务时,需要把TM,RM,TC的模型融入到ShardingSphere 分布式事务的SPI的生态中。在数据库资源上,Seata通过对接DataSource接口,让JDBC操作可以同TC进行RPC通信。同样,ShardingSphere也是面向DataSource接口对用户配置的物理DataSource进行了聚合,因此把物理DataSource二次包装为Seata 的DataSource后,就可以把Seata AT事务融入到ShardingSphere的分片中。
最后,通过在药房服务的方法入口处加上即可正常注册分支事务和回滚
@Transactional
@ShardingTransactionType(TransactionType.BASE)
然而,分支事务提交或回滚时,报以下错误
Commit exception overridden by rollback exception
java.lang.NullPointerException: null
at org.apache.shardingsphere.transaction.base.seata.at.SeataATShardingTransactionManager.commit(SeataATShardingTransactionManager.java:110)
at org.apache.shardingsphere.shardingjdbc.jdbc.core.connection.ShardingConnection.commit(ShardingConnection.java:155)
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doCommit(DataSourceTransactionManager.java:329)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:743)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:711)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:633)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:386)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)
at net.jqsoft.stock.drug.controller.ITestService$$EnhancerBySpringCGLIB$$2fbebced.testSeata(<generated>)
在github上找到相似问题 github.com/apache/shar… 1.在shardingDaTaSource.getConnection()之前设置事务类型 TransactionTypeHolder.set(TransactionType.BASE);
2.分支事务添加spring事务注解
3.在spring mvc拦截器中初始化SeataATShardingTransactionManager,然后在每一次请求中,判断是否包含xid,有的话绑定xid到RootContext,并执行SeataATShardingTransactionManager.begin()方法(ps:seata分支事务状态是根据http status来判断的,我们项目中的状态是根据ajaxResult的code来判断接口是否正常,所以需要调用方主动判断分支事务是否正常,以便提交和回滚)
@Configuration
public class SeataFeignInterceptorConfiguration implements WebMvcConfigurer, EnvironmentAware {
private final Map<String, DataSource> dataSourceMap = new LinkedHashMap<>();
private final String jndiName = "jndi-name";
private SeataATShardingTransactionManager transactionManager;
@PostConstruct
public void init(){
transactionManager = new SeataATShardingTransactionManager();
transactionManager.init(new DMDatabaseType(),getResourceDataSources(dataSourceMap));
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SeataFeignInterceptor(transactionManager)).addPathPatterns("/**");
}
private Collection<ResourceDataSource> getResourceDataSources(final Map<String, DataSource> dataSourceMap) {
List<ResourceDataSource> result = new LinkedList<>();
for (Map.Entry<String, DataSource> entry : dataSourceMap.entrySet()) {
result.add(new ResourceDataSource(entry.getKey(), entry.getValue()));
}
return result;
}
@Override
public final void setEnvironment(final Environment environment) {
String prefix = "spring.shardingsphere.datasource.";
for (String each : getDataSourceNames(environment, prefix)) {
try {
dataSourceMap.put(each, getDataSource(environment, prefix, each));
} catch (final ReflectiveOperationException ex) {
throw new ShardingSphereException("Can't find datasource type!", ex);
} catch (final NamingException namingEx) {
throw new ShardingSphereException("Can't find JNDI datasource!", namingEx);
}
}
}
private List<String> getDataSourceNames(final Environment environment, final String prefix) {
StandardEnvironment standardEnv = (StandardEnvironment) environment;
standardEnv.setIgnoreUnresolvableNestedPlaceholders(true);
return null == standardEnv.getProperty(prefix + "name")
? new InlineExpressionParser(standardEnv.getProperty(prefix + "names")).splitAndEvaluate() : Collections.singletonList(standardEnv.getProperty(prefix + "name"));
}
@SuppressWarnings("unchecked")
private DataSource getDataSource(final Environment environment, final String prefix, final String dataSourceName) throws ReflectiveOperationException, NamingException {
Map<String, Object> dataSourceProps = PropertyUtil.handle(environment, prefix + dataSourceName.trim(), Map.class);
Preconditions.checkState(!dataSourceProps.isEmpty(), "Wrong datasource properties!");
if (dataSourceProps.containsKey(jndiName)) {
return getJndiDataSource(dataSourceProps.get(jndiName).toString());
}
DataSource result = DataSourceUtil.getDataSource(dataSourceProps.get("type").toString(), dataSourceProps);
DataSourcePropertiesSetterHolder.getDataSourcePropertiesSetterByType(dataSourceProps.get("type").toString()).ifPresent(
dataSourcePropertiesSetter -> dataSourcePropertiesSetter.propertiesSet(environment, prefix, dataSourceName, result));
return result;
}
private DataSource getJndiDataSource(final String jndiName) throws NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setResourceRef(true);
bean.setJndiName(jndiName);
bean.setProxyInterface(DataSource.class);
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}
public static class SeataFeignInterceptor implements HandlerInterceptor {
private SeataATShardingTransactionManager transactionManager;
public SeataFeignInterceptor(SeataATShardingTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String xid = request.getHeader(RootContext.KEY_XID);
if (!StringUtils.isBlank(xid)) {
RootContext.bind(xid);
SeataTransactionHolder.set(GlobalTransactionContext.getCurrentOrCreate());
transactionManager.begin();
}
return true;
}
}
}