seata1.5.2集成sharding-jdbc4.1.1

116 阅读2分钟

大致流程如下:总共两个服务,一个住院服务,一个药房服务,药房集成sharding-jdbc进行分库分表,住院服务开启全局事务 1.住院扣费 2.药房锁库存

image.png

测试中发现药房分支事务不受seata全局事务控制,查看seata服务端日志也没有药房服务的分支注册日志及回滚日志,只有住院服务的分支注册日志和回滚日志;

image.png 查看shardingsphere官网得知,整合Seata AT事务时,需要把TM,RM,TC的模型融入到ShardingSphere 分布式事务的SPI的生态中。在数据库资源上,Seata通过对接DataSource接口,让JDBC操作可以同TC进行RPC通信。同样,ShardingSphere也是面向DataSource接口对用户配置的物理DataSource进行了聚合,因此把物理DataSource二次包装为Seata 的DataSource后,就可以把Seata AT事务融入到ShardingSphere的分片中。

image.png 最后,通过在药房服务的方法入口处加上即可正常注册分支事务和回滚

@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);

image.png 2.分支事务添加spring事务注解

image.png 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;
        }

    }
}