Я пытаюсь реализовать многопоточное решение, чтобы распараллелить свою бизнес-логику, включающую чтение и запись в базу данных.
Стек технологий: 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
}
}
После выполнения этого я получаю:
Существующая транзакция не найдена для транзакции, помеченной как "обязательная" для распространения.
Я понял, что это совершенно нормально, поскольку транзакция не распространяется по разным потокам. Поэтому одним из решений является создание транзакции вручную в каждом потоке как предложено в другом подобном вопросе. Но меня это мало удовлетворило, и я продолжил поиски.
На форуме Spring я нашел обсуждение темы. Один абзац я нахожу очень интересным:
"Я могу представить, что можно вручную передать контекст транзакции в другой поток, но я не думаю, что вам стоит пытаться это делать. threadsafe. Использование одного единственного соединения в нескольких потоках нарушило бы основные контракты запроса/ответа jdbc, и было бы неудивительно, если бы это работало в более чем тривиальных примерах."
Итак, возникает первый вопрос: Стоит ли парализировать чтение/запись в базу данных и может ли это действительно повредить согласованности БД?
Если приведенная выше цитата не соответствует действительности, в чем я сомневаюсь, есть ли способ добиться следующего: MyIdPartitioner должен управляться Spring - с помощью @Scope("prototype") - и передавать ему необходимые аргументы для рекурсивных вызовов и, таким образом, оставить управление транзакциями Весна?