我正在尝试实现一个多线程解决方案,以便我可以并行化我的业务逻辑,包括读取和写入数据库。
技术堆栈: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);
}
}
@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
}
}
执行此操作后,我得到:
找不到标记为传播的交易的现有交易&#39;强制性&#39;
我知道这是完全正常的,因为事务不会通过不同的线程传播。因此,一种解决方案是在每个线程as proposed in another similar question中手动创建事务。但这对我来说不够令人满意,所以我一直在寻找。
在Spring的论坛中,我找到了a discussion on the topic。一段我觉得非常有趣:
&#34;我可以想象一个人可以手动将事务上下文传播到另一个线程,但我认为你不应该尝试它。事务绑定到单个线程有一个原因 - 基本的底层资源 - jdbc连接 - 不是线程安全的。在多个线程中使用一个单独的连接会破坏基本的jdbc请求/响应合同,如果它可以用于更多简单的示例,那将是一个小小的奇迹。&#34;
所以第一个问题出现了:是否值得对数据库的读/写进行简化,这是否真的会损害数据库的一致性?
如果上述引用不正确,我怀疑,是否有办法实现以下目标:
MyIdPartitioner是Spring管理的 - 使用@Scope(&#34; prototype&#34;) - 并传递所需的递归调用参数,这样就可以将事务管理留给Spring了吗?
答案 0 :(得分:0)
这应该可以使用atomikos(http://www.atomikos.com),也可以选择使用嵌套事务。
如果这样做,那么如果同一根事务的多个线程写入数据库中的相同表,请注意避免死锁。
答案 1 :(得分:0)
在进一步阅读后,我设法解决了我的问题。有点(我现在看到它首先没有问题)。
由于我从数据库中读取的数据是块状的,并且我确信在此期间结果不会被编辑,我可以在事务之外进行。
在我的情况下写作也是安全的,因为我写的所有值都是唯一的,并且不会发生约束违规。所以我也从那里删除了交易。
我的意思是说&#34;我删除了交易&#34;只需在我的DAO中覆盖方法的传播模式,如:
@Repository
@Transactional(propagation = Propagation.MANDATORY)
public class IdsDao {
@Transactional(propagation = Propagation.SUPPORTS)
public MyData getData(List<UUID> list) {
// ...
}
}
或者,如果您出于某种原因决定需要该交易,那么您仍然可以通过将传播设置为REQUIRED
将事务管理留给Spring。
所以解决方案比我想象的要简单得多。
回答我的其他问题:
是否有必要简化对数据库的读/写操作,这是否真的会损害数据库的一致性?
是的,这是值得的。只要你有每个线程的交易,你就很酷。
有没有办法实现以下目标:MyIdPartitioner是Spring管理的 - 使用@Scope(&#34; prototype&#34;) - 并传递所需的参数以进行递归调用,然后离开事务管理到春天?
是的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个实例,但由于无法设置字段,因此您无法使用该对象。
所以简而言之 - 有一种方法,但我不需要在第一时间去做。