【问题标题】:Spring cannot propagate transaction to ForkJoin's RecursiveActionSpring 无法将事务传播到 ForkJoin 的 RecursiveAction
【发布时间】:2015-10-23 18:50:09
【问题描述】:

我正在尝试实现一个多线程解决方案,以便我可以并行化我的业务逻辑,包括读取和写入数据库。

技术栈:Spring 4.0.2、Hibernate 4.3.8

这里有一些代码可以讨论:

配置

@Configuration
public class PartitionersConfig {

    @Bean
    public ForkJoinPoolFactoryBean forkJoinPoolFactoryBean() {
        final ForkJoinPoolFactoryBean poolFactory = new ForkJoinPoolFactoryBean();
        return poolFactory;
    }
}

服务

@Service
@Transactional
public class MyService {

    @Autowired
    private OtherService otherService;

    @Autowired
    private ForkJoinPool forkJoinPool;

    @Autowired
    private MyDao myDao;

    public void performPartitionedActionOnIds() {
        final ArrayList<UUID> ids = otherService.getIds();

        MyIdPartitioner task = new MyIdsPartitioner(ids, myDao, 0, ids.size() - 1);
        forkJoinPool.invoke(task);
    }
}

存储库/DAO

@Repository
@Transactional(propagation = Propagation.MANDATORY)
public class IdsDao {

    public MyData getData(List<UUID> list) {
        // ... 
    }
}

递归动作

public class MyIdsPartitioner extends RecursiveAction {

    private static final long serialVersionUID = 1L;
    private static final int THRESHOLD = 100;

    private ArrayList<UUID> ids;
    private int fromIndex;
    private int toIndex;

    private MyDao myDao;

    public MyIdsPartitioner(ArrayList<UUID> ids, MyDao myDao, int fromIndex, int toIndex) {
        this.ids = ids;
        this.fromIndex = fromIndex;
        this.toIndex = toIndex;
        this.myDao = myDao;
    }

    @Override
    protected void compute() {
        if (computationSetIsSamllEnough()) {
            computeDirectly();
        } else {
            int leftToIndex = fromIndex + (toIndex - fromIndex) / 2;
            MyIdsPartitioner leftPartitioner = new MyIdsPartitioner(ids, myDao, fromIndex, leftToIndex);
            MyIdsPartitioner rightPartitioner = new MyIdsPartitioner(ids, myDao, leftToIndex + 1, toIndex);

            invokeAll(leftPartitioner, rightPartitioner);
        }
    }

    private boolean computationSetIsSamllEnough() {
        return (toIndex - fromIndex) < THRESHOLD;
    }

    private void computeDirectly() {
        final List<UUID> subList = ids.subList(fromIndex, toIndex);
        final MyData myData = myDao.getData(sublist);
        modifyTheData(myData);
    }

    private void modifyTheData(MyData myData) {
        // ...
        // write to DB
    }
}

执行此操作后,我得到:

没有找到标记为传播“强制”的交易的现有交易

我知道这是完全正常的,因为事务不会通过不同的线程传播。所以一种解决方案是在每个线程as proposed in another similar question 中手动创建一个事务。但这对我来说还不够满意,所以我一直在寻找。

在 Spring 的论坛中,我找到了a discussion on the topic。有一段我觉得很有趣:

“我可以想象一个可以手动将事务上下文传播到另一个线程,但我认为你不应该真的尝试它。事务绑定到单个线程是有原因的 - 基本的底层资源 - jdbc 连接- 不是线程安全的。在多个线程中使用单个连接会破坏基本的 jdbc 请求/响应合同,并且它是否可以在更多琐碎的示例中工作也是一个小问题。”

所以第一个问题出现了:并行化对数据库的读/写是否值得,这真的会损害数据库的一致性吗?
如果上面的引用不是真的,我怀疑,有没有办法实现以下目标: MyIdPartitioner 由 Spring 管理 - 使用 @Scope("prototype") - 并将递归调用所需的参数传递给它,然后将事务管理留给 Spring?

【问题讨论】:

    标签: java multithreading spring database-management


    【解决方案1】:

    这应该可以通过 atomikos (http://www.atomikos.com) 和可选的嵌套事务实现。

    如果您这样做,请注意避免在同一根事务的多个线程写入数据库中的相同表时发生死锁。

    【讨论】:

    • 好吧,这看起来有点像,但我希望保持我的技术堆栈不变。这也不是我两个问题的完整答案。
    【解决方案2】:

    经过进一步阅读,我设法解决了我的问题。有点(我现在认为一开始没有问题)。

    由于我从数据库中读取数据是分块的,而且我确信在此期间不会对结果进行编辑,因此我可以在事务之外进行。

    在我的情况下,写入也是安全的,因为我写入的所有值都是唯一的,并且不会发生违反约束的情况。所以我也从那里删除了交易。

    我所说的“我删除了事务”只是在我的 DAO 中覆盖方法的传播模式,例如:

    @Repository
    @Transactional(propagation = Propagation.MANDATORY)
    public class IdsDao {
    
        @Transactional(propagation = Propagation.SUPPORTS)
        public MyData getData(List<UUID> list) {
            // ... 
        }
    }
    

    或者,如果您出于某种原因决定需要事务,那么您仍然可以通过将传播设置为 REQUIRED 来将事务管理留给 Spring。

    所以解决方案比我想象的要简单得多。

    并回答我的其他问题:

    并行化对数据库的读/写是否值得,这真的会损害数据库的一致性吗?

    是的,这是值得的。只要你有每个线程的事务,你就很酷。

    有没有办法实现以下目标:由 Spring 管理的 MyIdPartitioner - 使用 @Scope("prototype") - 并将递归调用所需的参数传递给它,然后将事务管理留给 Spring?

    是的,using pool 有办法(另一个 stackoverflow 问题)。或者您可以将您的 bean 定义为@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS),但是如果您需要为其设置参数,它将无法工作,因为实例的每次使用都会为您提供一个新实例。前任。

    @Autowire
    MyIdsPartitioner partitioner;
    
    public void someMethod() {
        ...
        partitioner.setIds(someIds);
        partitioner.setFromIndex(fromIndex);
        partitioner.setToIndex(toIndex);
        ...
    }
    

    这将创建 3 个实例,并且由于不会设置字段,因此您将无法使用有益的对象。

    简而言之 - 有一种方法,但我不需要一开始就去做。

    【讨论】:

      猜你喜欢
      • 2017-11-22
      • 2014-06-22
      • 1970-01-01
      • 1970-01-01
      • 2011-09-20
      • 2018-01-15
      • 1970-01-01
      • 2016-11-16
      • 1970-01-01
      相关资源
      最近更新 更多