У меня есть MDB (Message-bean-компонент), который получает сообщения со строкой, которые представляют слово. Также у меня есть таблица в базе данных. MDB должен хранить в таблице слова и количество раз, которое было получено каждое слово (счетчик).
Проблема в том, что для повышения производительности MDB запускается во многих случаях, и когда разные экземпляры получают одно и то же новое слово, они оба создают одну и ту же строку со счетчиком 1.
Чтобы решить эту проблему, я должен сделать поле слова уникальным, и тогда второй экземпляр потерпит неудачу при коммите, повторно передав сообщение, которое будет работать, но может быть проблематичным. Это хорошая практика?
Другим решением является объединение этих строк после суммирования счетчика. Но что, если другой экземпляр увеличит счетчик в середине обновления.
Что если два экземпляра попытаются увеличить счетчик? @Version
должно быть достаточно?
Я не уверен, какое здесь правильное решение. Как бы вы справились с такими случаями?
Также можете ли вы предложить несколько книг о методах параллелизма (не об использовании synchronized
, поскольку мне нужно поддерживать Java EE и может запускать кластер серверов приложений)?
Обновление:
Прочитав больше о EJB и JPA, я предполагаю, что хочу что-то вроде блокирующего объекта. Например, я могу создать новую таблицу, содержащую только столбцы id и key, а также такие данные:
ID | KEY
1 | WORDS_CREATE_LOCK
Так что, когда мне понадобится обработать новое слово, я сделаю что-то вроде этого (не точный код, не уверен, что оно даже скомпилируется):
// MAIN FUNCTION
public void handleWord(String wordStr) {
Word w = getWord(wordStr);
if (w == null)
w = getNewOrSychronizedWord(wordStr);
em.lock(w);
w.setCounter(w.getCounter() + 1);
em.unlock(w);
}
// Returns Word instance or null if not found
private Word getWord(String wordStr) {
Word w = null;
Query query = em.createQuery("select w from words as w where w.string = :wordStr order by w.id asc");
query.setParameter("wordStr", wordStr);
List<Word> words = query.getResultList();
if (words.getSize() > 0)
w = words.get(0);
return w;
}
// Handles locking to prevent duplicate word creation
private Word getNewOrSynchronizedWord(String wordStr) {
Word w = null;
Locks l = em.find(WORDS_CREATE_LOCK_ID, Locks.class);
em.lock(l);
Word w = getWord(wordStr);
if (w == null) {
w = new Word(wordStr);
em.persist(w);
}
em.unlock(l);
return w;
}
Итак, вопрос в том, будет ли это работать таким образом? И можно ли сделать то же самое без ведения таблицы БД с блокировкой строк? Может быть какой-то механизм блокировки контейнера Java EE?
Если это поможет, я использую JBoss 4.2.
У меня есть новая идея для этого. Я могу создать два MDB:
1-й MDB со многими разрешенными экземплярами, который будет обрабатывать все сообщения и, если слово не найдено, отправит слово во второй MDB
2-й MDB с разрешенным только одним экземпляром, будет обрабатывать сообщения последовательно и позволит создавать новое слово
Лучшая часть: нет полной блокировки таблицы / метода / процесса, только блокировка строки при обновлении счетчика
Насколько это хорошо?
Спасибо.