0

I have a situation where I need a set of operations be enclosed into a single transaction and be thread safe from a MDB.

If thread A executes the instruction 1, do not want other threads can read, at least not the same, data that thread A is processing. In the code below since IMAGE table contains duplicated data, coming from different sources, this will lead in a duplicated INFRANCTION. Situation that needs to be avoided.

The actual solution that I found is declaring a new transaction for each new message and synchronize the entire transaction. Simplifying the code:

@Stateless
InfranctionBean{
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    checkInfranction(String plate){
        1. imageBean.getImage(plate); // read from table IMAGE
        2. infranctionBean.insertInfranction(String plate); // insert into table INFRANCTION
        3. imageBean.deleteImage(String plate); //delete from table IMAGE
    }
}

@MessageDriven
public class ImageReceiver {

    private static Object lock = new Object();

    public void onMessage(Message msg){
        String plate = msg.plate;

        synchronized (lock) {
            infanctionBean.checkInfranction(plate);
        }
    }
}

I am aware that using synchronized blocks inside the EJB is not recommanded by EJB specification. This can lead even in problems if the applicaton server runs in two node cluster.

Seems like EE6 has introduced a solution for this scenario, which is the EJB Singleton. In this case, my solution would be something like this:

@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@Singleton
InfranctionBean{

    @Lock(LockType.WRITE)
    checkInfranction(String plate){
        1...
        2...
        3...
    }
}

And from MDB would not be neccessary the usage of synchronized block since the container will handle the concurrency. With @Lock(WRITE) the container guarantees the access of single thread inside checkInfranction().

My queston is: How can I handle this situation in EE5? There is a cleaner solution without using synchronized block?

Environment: Java5,jboss-4.2.3.GA,Oracle10.

ACTUAL SOLUTION

@Stateless
InfranctionBean{
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    checkInfranction(String plate){    
        1. imageBean.lockImageTable(); // lock table IMAGE in exclusive mode
        2. imageBean.getImage(plate); // read from table IMAGE
        3. infranctionBean.insertInfranction(String plate); // insert into table INFRANCTION
        4. imageBean.deleteImage(String plate); //delete from table IMAGE
    }
}

@MessageDriven
public class ImageReceiver {
    public void onMessage(Message msg){
        infanctionBean.checkInfranction(msg.plate);
    }
}

On 20.000 incoming messages (half of them simultaneously) seems the application works ok.

Ermal
  • 441
  • 5
  • 19

1 Answers1

3

@Lock(WRITE) is only a lock within a single application/JVM, so unless you can guarantee that only one application/JVM is accessing the data, you're not getting much protection anyway. If you're only looking for single application/JVM protection, the best solution in EE 5 would be a ReadWriteLock or perhaps a synchronized block. (The EJB specification has language to dissuade applications from doing this to avoid compromising the thread management of the server, so take care that you don't block indefinitely, that you don't ignore interrupts, etc.)

If you're looking for a more robust cross-application/JVM solution, I would use database locks or isolation levels rather than trying to rely on JVM synchronized primitives. That is probably the best solution regardless of the EJB version being used.

Brett Kail
  • 33,593
  • 2
  • 85
  • 90
  • Thanks for the reply Brett. What do you mean with database locks? You mean do something like `LOCK TABLE IMAGE IN EXCUSIVE MODE` as first step of atomic block? checkInfranction() method in my case. – Ermal Apr 18 '16 at 20:11
  • I'm not much of a database expert, but I'm aware of two general approaches. First, you could use database locks similar to synchronizing on a lock object. Alternatively, you can use strong isolation levels like read committed, repeatable reads, or serializable, which basically cause the database to implicitly take locks depending on how the two threads are reading/updating data to ensure that they don't read data being updated by a different transaction in another thread. – Brett Kail Apr 18 '16 at 20:55
  • This is what I need Brett. An isolation level SERIALIZABLE, so when enter in checkInfranction() I don't want other threads can read IMAGE table. My question was more from implementation prospective. Anyway your answer made me go through the solution so I am going to accept the answer as solved. Thank you! – Ermal Apr 22 '16 at 15:11