persistence@glassfish.java.net

RE: Re: multiple threads trying to modfiy an entity

From: Mauro Almeida <malmeida_at_localmatters.com>
Date: Fri, 17 Apr 2009 07:50:05 -0600

Gordon ,

 

Thanks.. I shouldn't have used 'serialize' (poor choice of words). I meant locking. :-)

 

I will try what you mentioned below.. Hopefully it will take care of my problem.. I'll let you know..

 

Thanks .

 

 

Mauro Almeida

 

________________________________

From: Gordon Yorke [mailto:gordon.yorke_at_oracle.com]
Sent: Friday, April 17, 2009 8:45 AM
To: persistence_at_glassfish.dev.java.net
Subject: Re: multiple threads trying to modfiy an entity

 

You should not serialize the access that would prevent concurrent access to your application. What you need to ensure is that you are managing concurrent access through appropriate locking. If these conflicts occur rarely then I recommend using optimistic locking. Add @Version field to your entity (there are EclipseLink extensions to JPA if you can not update your entities). The @Version field will ensure any conflicts will be reported through an OptimisticLockException. If these conflicts are not rare then I recommend using pessimistic locking. JPA 1.0 did not provide support for pessimistic locking but if you switch your find to a JPQL query and add the hint. query.setHint(QueryHints.PESSIMISTIC_LOCK, PessimisticLock.Lock); Then each thread will have exclusive access to the corresponding row.
--Gordon

Mauro Almeida wrote:

In the error scenario the db has one row, that's updated everytime an event happens in the application.
 
basically, a counter is increment by one.
When the app sends this sessionbean, let's say 24 messages, to have it update it counter, some msg are 'lost'
 
Counter shows only 23, 14, etc.... instead of 24.
 
How can I serialize the access?
 
Another way to say is: how can several client updated concurrently the DB, based on it's previous state.,
Thanks !
 
 
yes s and s1 was just an was just an email issue..
 
 
 
-----Original Message-----
From: Gordon Yorke [mailto:gordon.yorke_at_oracle.com]
Sent: Thu 4/16/2009 3:33 PM
To: persistence_at_glassfish.dev.java.net
Subject: Re: multiple threads trying to modfiy an entity
 
If any number of threads perform a find within different transactions
then these threads will not find the Entity with the specified ID and
these threads will attempt to persist an Entity with that same ID and
all but the first to write the data to the database will fail. This is
expected behaviour. You will need to retry that transaction.
How many updates are lost? Can you provide specifics on what and when
the updates are lost.
 
I see your code also has a typo (s1 vs s) but this must be an email
issue as this code would not compile otherwise.
               if (s == null) {
 
                    Stats *s* =
createStat(id);
 
                    // Now increment the proper count, based on the type
o msg received
 
                    switch (type) {
 
                        case TOTAL:
 
                            *s1*.setTotal(*s1*.getTotal()+1);
 
                            break;
 
                        case PARTIAL:
 
                            *s1*.setPartial(*s1*.getPartial()+1);
 
                            break;
 
                    }
 
                    em.persist(s1);
 
                    em.flush();
 
                } else {
 
 
--Gordon
 
 
 
Mauro Almeida wrote:
  

        The App is the one creating the ID. It is not auto sequentially generated
         
         
         
        That's why I was first doing a em.find() to see if entry already
        exists. (99% of my cases, it will exist) than I just em.merge().
         
         
         
        If em.find returned me null I would try to em.persit(). As you can see
        in the snippet below.
         
         
         
        The problems are:
         
        When table is emptyl.. I get a duplicated entry (2 threads trying to
        persist the same id)
         
        When there are entries...... Some updates are lost. The counter in the
        DB do not add up to the number of messages received by the sessionbean
         
        Thanks!
         
         
         
         
         
        Mauro Almeida
         
        ------------------------------------------------------------------------
         
        *From:* Gordon Yorke [mailto:gordon.yorke_at_oracle.com]
        *Sent:* Thursday, April 16, 2009 3:03 PM
        *To:* persistence_at_glassfish.dev.java.net
        *Subject:* Re: multiple threads trying to modfiy an entity
         
         
         
        For the duplicate inserts who is creating the Id? If multiple threads
        attempt to insert the same PK then you should expect Unique Constraint
        violations as multiple threads would have attempted to insert the same
        object in different transaction.
        What are the problems you are still experiencing with this SessionBean?
        --Gordon
         
        Mauro Almeida wrote:
         
        Gordon,
         
         
         
        Thanks.. I think I'm on the right track but it still not working.
         
         
         
        So. I'm injecting the the emf using @PersistenceUnit into the class
         
        Every method call, emf.createEntityManager()
         
        Then I join em.joinTransaction() before I do my find(), merge(), persist()
         
         
         
        I still have some race condition. I also have a different façade to a
        different Entity, and the results a much more off (the updates seems
        to get lost more often)
         
        I continue to have cache turned off on persistence.xml
         
         
         
        Any Ideas...?? THANKS !
         
         
         
         
         
        Following the code
         
         
         
        @Stateless
         
        public class MyFacade implements IMyFacade ()
         
         
         
        @PersistenceUnit(unitName="myUnit")
         
        Private EntityManagerFactory emf;
         
         
         
            public void updateEntry (StatsType type, int id)
        {
         
         
         
                    EntityManager em = emf.CreateEntityManager()
         
        em.joinTransaction()
         
                try {
         
                        Stats s = em.find(Stats.class, id);
         
                        // if could not find entry in the DB, create it
         
                        if (s == null) {
         
                            Stats s =
        createStat(id);
         
                            // Now increment the proper count, based on the
        type o msg received
         
                            switch (type) {
         
                                case TOTAL:
         
                                    s1.setTotal(s1.getTotal()+1);
         
                                    break;
         
                                case PARTIAL:
         
                                    s1.setPartial(s1.getPartial()+1);
         
                                    break;
         
                            }
         
                            em.persist(s1);
         
                            em.flush();
         
                        } else {
         
                            switch (type) {
         
                                case TOTAL:
         
                                    s.setTotal(s.getTotal()+1);
         
                                    break;
         
                                case PARTIAL:
         
                                    s.setPartial(s.getPartial()+1);
         
                                    break;
         
                            }
         
                            em.merge(s);
         
                            em.flush();
         
                    }
         
                } catch (Exception ex) {
         
                    ex.printStackTrace();
         
                }
         
                    If (em.isOpen())
         
                                em.close();
         
            }
         
         
         
         
         
        }
         
         
         
        ------------------------------------------------------------------------
         
        *From:* Gordon Yorke [mailto:gordon.yorke_at_oracle.com]
        *Sent:* Thursday, April 16, 2009 8:53 AM
        *To:* persistence_at_glassfish.dev.java.net
        <mailto:persistence_at_glassfish.dev.java.net> <mailto:persistence_at_glassfish.dev.java.net>
        *Subject:* Re: multiple threads trying to modfiy an entity
         
         
         
        Stateless session beans can be used by more than one client. The best
        practise is to inject an EntityManagerFactory and then for each
        invocation create an EntityManager within the method. Otherwise
        multiple threads may be using the same EM.
        --Gordon
         
        Mauro Almeida wrote:
         
        Hi,
         
         
         
        I have the following problem.
         
         
         
        I have a facade (a stateless bean, CMT) that has an entity Manager and
        a method where several threads can call to have for an entry updated..
         
         
         
        What is happening is that the database is not updated properly.
         Sometimes the counter is not incremented, which seems to me something
        related to the attachment of the entity with the DB. Also, when the
        table is initially empty and all N updates come at the same to the
        same row, it tells me it has a duplicate key.
         
         
         
        I'm using sailfing build 60g. The DB is a JavaDB database
         
         
         
        The façade looks like:
         
        @Stateless
         
        @TransactionManagement(TransactionManagermentType.CONTAINER)
         
        public class MyFacade implements IMyFacade ()
         
         
         
            @PersistenceContext(unitName="myunit")
         
            private EntityManager em;
         
           
         
            @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
         
            public void updateEntry (StatsType type, int id)
        {
         
                try {
         
                        Stats s = em.find(Stats.class, id);
         
                        // if could not find entry in the DB, create it
         
                        if (s == null) {
         
                            Stats s =
        createStat(id);
         
                            // Now increment the proper count, based on the
        type o msg received
         
                            switch (type) {
         
                                case TOTAL:
         
                                    s1.setTotal(s1.getTotal()+1);
         
                                    break;
         
                                case PARTIAL:
         
                                    s1.setPartial(s1.getPartial()+1);
         
                                    break;
         
                            }
         
                            em.persist(s1);
         
                            em.flush();
         
                        } else {
         
                            switch (type) {
         
                                case TOTAL:
         
                                    s.setTotal(s.getTotal()+1);
         
                                    break;
         
                                case PARTIAL:
         
                                    s.setPartial(s.getPartial()+1);
         
                                    break;
         
                            }
         
                            em.merge(s);
         
                            em.flush();
         
                    }
         
                } catch (Exception ex) {
         
                    ex.printStackTrace();
         
                }
         
            }
         
         
         
        The transactionManagement, the em.flush and TransactionAttributes
        annotation I've put then after my original attempts failed.
         
         
         
        persistence.xml looks like:
         
        <?xml version="1.0" encoding="UTF-8"?>
         
        <persistence version="1.0"
        xmlns="http://java.sun.com/xml/ns/persistence" <http://java.sun.com/xml/ns/persistence>
        <http://java.sun.com/xml/ns/persistence> <http://java.sun.com/xml/ns/persistence> xmlns:xsi=*MailScanner has
        detected a possible fraud attempt from "www.w3.org" claiming to be*
        MailScanner has detected a possible fraud attempt from "www.w3.org" claiming to be "http://www.w3.org/2001/XMLSchema-instance" <http://www.w3.org/2001/XMLSchema-instance>
        <http://www.w3.org/2001/XMLSchema-instance> <http://www.w3.org/2001/XMLSchema-instance>
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence <http://java.sun.com/xml/ns/persistencehttp:/java.sun.com/xml/ns/persistence/persistence_1_0.xsd>
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" <http://java.sun.com/xml/ns/persistencehttp:/java.sun.com/xml/ns/persistence/persistence_1_0.xsd>
        <http://java.sun.com/xml/ns/persistencehttp:/java.sun.com/xml/ns/persistence/persistence_1_0.xsd> <http://java.sun.com/xml/ns/persistencehttp:/java.sun.com/xml/ns/persistence/persistence_1_0.xsd> >
         
          <persistence-unit name="myunit" transaction-type="JTA">
         
            <jta-data-source>jdbc/castats</jta-data-source>
         
            <properties>
         
                <property name="toplink.jdbc.user" value="username"/>
         
                <property name="toplink.jdbc.password" value="passed"/>
         
        <!-- <property name="toplink.cache.type.default"
        value="NONE"/> -->
         
            </properties>
         
          </persistence-unit>
         
        </persistence>
         
         
         
         
         
        Can someone point in the right direction? Why Do I have this race
        condition?
         
         
         
         
         
         
         
        Mauro Almeida