I have a MDB (Message driven bean) that receives messages with String which represent a word. Also I have a table in the database. The MDB should store in the table, the words and the number of times each word was received (counter).
The problem is that to get better performance the MDB started in many instances and when the same new word is received by different instances they both create the same row with count of 1.
To solve this I should make the word field unique and then the second instance will fail on commit, retransmitting the message, which will work, but may be problematic. Is it a good practice ?
Another solution is to merge these lines afterwards summing the counter. But what if another instance will increase the counter in the middle of the update.
What if two instances try to increase the counter ? @Version
should be enough?
I'm not sure what is the proper solution here. How would you handle such cases ?
Also can you suggest some books about concurrency practices (not about the use of synchronized
as I need to support J2EE and may run a cluster of application servers)?
Update: After reading more about EJB and JPA I suppose I want something like an locking entity. For example I can create a new table with only id and key columns and data like this:
ID | KEY
1 | WORDS_CREATE_LOCK
So that when I need to handle a new word I will do something like this (not exact code, not sure it will even compile):
// 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;
}
So the question is will it work that way? And can I do the same without maintaining a DB Table with locking rows? May be some J2EE container locking mechanism ?
If it helps I'm using JBoss 4.2.
I have a new idea for this. I can create two MDBs:
1st MDB with many instances allowed, that will handle all the messages and if the word is not found will send the word to the second MDB
2nd MDB with only one instance allowed, will handle the messages serially and will allow creation of new word
The best part: no entire table/method/process locking, only row locking on counter update
How good is that ?
Thanks.