代码之家  ›  专栏  ›  技术社区  ›  Aaron Digulla

如何用EJB3.0实现一个正确的计数器bean?

  •  3
  • Aaron Digulla  · 技术社区  · 16 年前

    我试着根据目前得到的答案修改代码。我正在使用JBoss6.0.0M2和Hypersonic(只需下载它并调用run.bat)。

    testCounterMitLock*() 循环500次。因此,一个成功的测试应该打印“Anzahl eingetragene Zeilen:1500”(3*500)。

    我试过:

            CounterTestVersion ct = manager.find(CounterTestVersion.class, 1);
            manager.lock(ct, LockModeType.WRITE);
            int wert = ct.getWert();
    

            CounterTestVersion ct = manager.find(CounterTestVersion.class, 1);
            manager.lock(ct, LockModeType.WRITE);
            manager.refresh (ct);
            int wert = ct.getWert();
    

    refresh() 应该给我当前值,隐式查询也应该确保对象现在被锁定。没有这样的运气。让我们试试JPA2.0:

            CounterTestVersion ct = manager.find(CounterTestVersion.class, 1, LockModeType.WRITE);
            int wert = ct.getWert();
    

    那也不行。也许锁不够?

            CounterTestVersion ct = manager.find(CounterTestVersion.class, 1, LockModeType.PESSIMISTIC_WRITE);
            int wert = ct.getWert();
    

            CounterTestVersion ct = manager.find(CounterTestVersion.class, 1, LockModeType.PESSIMISTIC_WRITE);
            manager.flush();
            manager.refresh (ct);
            int wert = ct.getWert();
    

    可以。。。有人能解释为什么什么都不管用吗?我没有主意了。

    [EDIT2]PS:雪上加霜的是,这是最后一个运行线程的最后一个输出:

    commit/rollback: 441/62
    

    (441+62 = 503)...

    这是完整的代码。首先是豆子:

    package server.kap15;
    
    import java.rmi.RemoteException;
    
    import javax.ejb.*;
    import javax.persistence.*;
    
    @Stateful
    public class CounterTestBean implements CounterTestRemote, SessionSynchronization {
        @PersistenceContext(unitName = "JavaEE")
        EntityManager manager;
    
        private int commit = 0;
    
        private int rollback = 0;
    
        public void initDatenbank() {
            manager.createNamedQuery("CounterTest.deleteAll").executeUpdate();
            manager.createNamedQuery("TestTabelle.deleteAll").executeUpdate();
            CounterTestVersion ct = new CounterTestVersion();
            ct.setNr(1);
            ct.setVersion(1);
            ct.setWert(1);
            manager.persist(ct);
        }
    
        public boolean testCounterOhneLock() {
            try {
                CounterTest ct = manager.find(CounterTest.class, 1);
                int wert = ct.getWert();
                ct.setWert(wert + 1);
                TestTabelle tt = new TestTabelle();
                tt.setNr(wert);
                manager.persist(tt);
                manager.flush();
                return true;
            } catch (Throwable t) {
                return false;
            }
        }
    
        public boolean testCounterMitLock() {
            try {
                CounterTestVersion ct = manager.find(CounterTestVersion.class, 1);
                manager.lock(ct, LockModeType.WRITE);
                int wert = ct.getWert();
                ct.setWert(wert + 1);
                TestTabelle tt = new TestTabelle();
                tt.setNr(wert);
                manager.persist(tt);
                manager.flush();
                return true;
            } catch (Throwable t) {
                return false;
            }
        }
    
        public boolean testCounterMitLock2() {
            try {
                CounterTestVersion ct = manager.find(CounterTestVersion.class, 1);
                manager.lock(ct, LockModeType.WRITE);
                manager.refresh (ct);
                int wert = ct.getWert();
                ct.setWert(wert + 1);
                TestTabelle tt = new TestTabelle();
                tt.setNr(wert);
                manager.persist(tt);
                manager.flush();
                return true;
            } catch (Throwable t) {
                return false;
            }
        }
    
        public boolean testCounterMitLock3() {
            try {
                CounterTestVersion ct = manager.find(CounterTestVersion.class, 1, LockModeType.WRITE);
                int wert = ct.getWert();
                ct.setWert(wert + 1);
                TestTabelle tt = new TestTabelle();
                tt.setNr(wert);
                manager.persist(tt);
                manager.flush();
                return true;
            } catch (Throwable t) {
                return false;
            }
        }
    
        public boolean testCounterMitLock4() {
            try {
                CounterTestVersion ct = manager.find(CounterTestVersion.class, 1, LockModeType.PESSIMISTIC_WRITE);
                int wert = ct.getWert();
                ct.setWert(wert + 1);
                TestTabelle tt = new TestTabelle();
                tt.setNr(wert);
                manager.persist(tt);
                manager.flush();
                return true;
            } catch (Throwable t) {
                return false;
            }
        }
    
        public boolean testCounterMitLock5() {
            try {
                CounterTestVersion ct = manager.find(CounterTestVersion.class, 1, LockModeType.PESSIMISTIC_WRITE);
                manager.flush();
                manager.refresh (ct);
                int wert = ct.getWert();
                ct.setWert(wert + 1);
                TestTabelle tt = new TestTabelle();
                tt.setNr(wert);
                manager.persist(tt);
                manager.flush();
                return true;
            } catch (Throwable t) {
                return false;
            }
        }
    
        public boolean testCounterMitVersion() {
            try {
                CounterTestVersion ctv = manager.find(CounterTestVersion.class, 1);
                int wert = ctv.getWert();
                ctv.setWert(wert + 1);
                manager.flush();
                TestTabelle tt = new TestTabelle();
                tt.setNr(wert);
                manager.persist(tt);
                manager.flush();
                return true;
            } catch (OptimisticLockException e) {
                System.out.println(">>> Versionskonflikt !");
                return false;
            } catch (Throwable t) {
                System.out.println(t.getMessage());
                return false;
            }
        }
    
        public long anzTestZeilen() {
            Query query = manager.createNamedQuery("TestTabelle.anzZeilen");
            Long anzahl = (Long) query.getSingleResult();
            return anzahl;
        }
    
        public void afterBegin() throws EJBException, RemoteException {
        }
    
        public void beforeCompletion() throws EJBException, RemoteException {
        }
    
        public void afterCompletion(boolean committed) throws EJBException,
        RemoteException {
            if (committed)
                commit++;
            else
                rollback++;
            System.out.println("commit/rollback: " + commit + "/" + rollback);
        }
    }
    

    远程接口:

    package server.kap15;
    
    import javax.ejb.Remote;
    
    @Remote
    public interface CounterTestRemote {
        public void initDatenbank();
    
        public boolean testCounterOhneLock();
    
        public boolean testCounterMitLock();
        public boolean testCounterMitLock2();
        public boolean testCounterMitLock3();
        public boolean testCounterMitLock4();
        public boolean testCounterMitLock5();
    
        public boolean testCounterMitVersion();
    
        public long anzTestZeilen();
    }
    

    persistence.xml文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
            http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
        version="1.0">
        <persistence-unit name="JavaEE">
            <jta-data-source>java:DefaultDS</jta-data-source>
        </persistence-unit>
    </persistence>
    

    测试客户:

    package client.kap15;
    
    import java.util.Properties;
    import javax.naming.*;
    import javax.rmi.PortableRemoteObject;
    import server.kap15.CounterTestRemote;
    
    public class CounterTestMitLock extends Thread {
        CounterTestRemote ctr;
    
        public static void main(String[] args) {
            try
            {
                testMitLock();
                testMitLock2();
                testMitLock3();
                testMitLock4();
                testMitLock5();
            }
            catch (Exception e)
            {
                e.printStackTrace ();
            }
        }
    
        static int N = 3;
        static CounterThread[] ct = new CounterThread[N];
    
        private static void testMitLock () throws InterruptedException
        {
            System.out.println("--- Counter Test MIT Lock ----------------------");
            System.out.println("Testinstanzen erzeugen...");
            for (int i=0; i<N; i++)
                ct[i] = new CounterThreadMitLock();
    
            runTest ();
        }
    
        private static void testMitLock2 () throws InterruptedException
        {
            System.out.println("--- Counter Test MIT Lock2 ----------------------");
            System.out.println("Testinstanzen erzeugen...");
            for (int i=0; i<N; i++)
                ct[i] = new CounterThreadMitLock2();
    
            runTest ();
        }
    
        private static void testMitLock3 () throws InterruptedException
        {
            System.out.println("--- Counter Test MIT Lock3 ----------------------");
            System.out.println("Testinstanzen erzeugen...");
            for (int i=0; i<N; i++)
                ct[i] = new CounterThreadMitLock3();
    
            runTest ();
        }
    
        private static void testMitLock4 () throws InterruptedException
        {
            System.out.println("--- Counter Test MIT Lock4 ----------------------");
            System.out.println("Testinstanzen erzeugen...");
            for (int i=0; i<N; i++)
                ct[i] = new CounterThreadMitLock4();
    
            runTest ();
        }
    
        private static void testMitLock5 () throws InterruptedException
        {
            System.out.println("--- Counter Test MIT Lock5 ----------------------");
            System.out.println("Testinstanzen erzeugen...");
            for (int i=0; i<N; i++)
                ct[i] = new CounterThreadMitLock5();
    
            runTest ();
        }
    
        private static void runTest () throws InterruptedException
        {
            System.out.println("Datenbank initialisieren...");
            ct[0].ctr.initDatenbank();
    
            System.out.println("Test durchführen...");
            for (int i=0; i<N; i++)
                ct[i].start();
    
            System.out.println("Auf Ende warten...");
            for (int i=0; i<N; i++)
                ct[i].join();
    
            System.out.println("Anzahl eingetragene Zeilen: " + ct[0].ctr.anzTestZeilen());
        }
    
        private static CounterTestRemote verbinden() {
            try {
                Properties p = new Properties();
                p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
                p.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
                p.put(Context.PROVIDER_URL, "jnp://localhost:1099");
                Context ctx = new InitialContext(p);
    
                Object ref = ctx.lookup("CounterTestBean/remote");
                CounterTestRemote ctr = (CounterTestRemote) PortableRemoteObject.narrow(ref, CounterTestRemote.class);
    
                return ctr;
            } catch (NamingException e) {
                System.out.println("ERROR - NamingException!");
                System.exit(-1);
            }
            return null;
        }
    
        public abstract static class CounterThread extends Thread
        {
            protected CounterTestRemote ctr;
    
            public CounterThread ()
            {
                this.ctr = verbinden ();
            }
    
            public void run() {
                for (int i = 0; i < 500; i++)
                    test ();
            }
    
            public abstract void test ();
        }
    
        public static class CounterThreadMitLock extends CounterThread
        {
            @Override
            public void test ()
            {
                this.ctr.testCounterMitLock();
            }
    
        }
    
        public static class CounterThreadMitLock2 extends CounterThread
        {
            @Override
            public void test ()
            {
                this.ctr.testCounterMitLock2();
            }
    
        }
    
        public static class CounterThreadMitLock3 extends CounterThread
        {
            @Override
            public void test ()
            {
                this.ctr.testCounterMitLock3();
            }
    
        }
    
        public static class CounterThreadMitLock4 extends CounterThread
        {
            @Override
            public void test ()
            {
                this.ctr.testCounterMitLock4();
            }
    
        }
    
        public static class CounterThreadMitLock5 extends CounterThread
        {
            @Override
            public void test ()
            {
                this.ctr.testCounterMitLock5();
            }
    
        }
    }
    
    4 回复  |  直到 16 年前
        1
  •  2
  •   Community Mohan Dere    9 年前

    因为所有的锁定模式都不起作用,我试着 ewernli SELECT ... FOR UPDATE . 这给了一个有趣的例外:“意外的代币”。所以我查看了数据库。

    JBoss的安装 Hypersonic 默认为1.8(HSQLDB),不支持行锁定。亲爱的JBoss开发人员:当不支持锁定模式时,JPA实现应该抛出异常。

    所以我添加了一个Oracle数据源并更改了persistence.xml。之后,两个测试开始工作:

            CounterTestVersion ct = manager.find(CounterTestVersion.class, 1, LockModeType.PESSIMISTIC_WRITE);
            int wert = ct.getWert();
    

        Query query = manager.createNativeQuery ("select * from COUNTER_TEST where NR = 1 for update", CounterTestVersion.class);
        CounterTestVersion ct = (CounterTestVersion)query.getSingleResult ();
        int wert = ct.getWert ()+1;
    

    这很有趣。它应该与 LockModeType.PESSIMISTIC_FORCE_INCREMENT

    ORA-00054: resource busy and acquire with NOWAIT specified
    

    manager.find() . 我不明白为什么这两个在加载阶段表现不同。可能是JBoss或Hibernate中的bug。

        2
  •  1
  •   Pascal Thivent    16 年前

    • 您当前正在使用乐观锁定,但我没有看到任何 @Version 实体上的字段。我觉得这样不行。
    • OptimisticLockingException )但悲观的锁定。
    • Throwable 是真的错了,你要容器做他的工作(但我猜你知道)。

    所以,这里我用这个来代替:

    manager.lock(ct, LockModeType.READ);
    

    catch (Throwable t) .

    我现在不能测试这个,但是我会使用这样的东西(代码的其余部分是不变的):

    public boolean testCounterWithLock() {
        CounterTest ct = manager.find(CounterTest.class, 1);
        manager.lock(ct, LockModeType.READ);
        int counter = ct.getCounter();
        ct.setCounter(counter + 1);
        manager.flush();
        return true;
    }
    

    我真的怀疑这是否行得通。首先,读锁不会阻止其他线程更新行。其次,另一个线程可以更新find()和getCounter()之间的行

    SELECT FOR UPDATE 语义)。不知怎么的,我忘记了这一点,对阅读模式造成了很大的混乱。我的错。谢谢你指出这一点。

    我认为您必须使用LockModeType.WRITE,但也许您可以在lock()之后使用em.refresh()来确保实体不过时?

    使用时 LockModeType.WRITE @版本 已添加到 WHERE 合同条款 UPDATE 并发性检查是在 更新 :

    UPDATE COUNTERTEST SET COUNTER = ?, OPT_LOCK = ? 
    WHERE ((ID = ?) AND (OPT_LOCK = ?))
    

    如果 哪里 OptimisticLockException .

    换句话说,在 lock() 不会更改任何内容,另一个线程仍可以在另一个线程修改计数器时刷新同一实体。以自动化方式处理乐观锁定的唯一方法是实现重试机制。

    但是当 flush() 抛出 PersitenceException (以下情况除外: NoResultException NonUniqueResultException ),当前事务被标记为回滚,因此不能用于事务目的。因此,每次重试都必须使用一个新事务来执行。在无状态bean中,可以进行递归远程调用,但我认为这在有状态bean中没有意义,因此必须从客户端处理。

        3
  •  1
  •   Bill the Lizard    14 年前

    即使有 LockModeType.READ LockModeType.WRITE ,JPA1.0只支持 乐观锁定 . 锁获取仍然可以推迟到提交时间,因此您遇到了问题。

    JPA 2.0 concurrency and locking :

    PA 1.0仅支持乐观读取 或乐观写锁定。JPA 2.0版 支持乐观和悲观 锁定

    EJB3 performance Pessimist Locking with JPA

    拥有真正的 悲观锁定 对于JPA1.0,您将需要依赖于特定于数据库或实现的扩展。例如。:

    JPA2.0(HibernateAPI也有类似的功能)

    Account acc = em.find( Account.class, id, PESSIMISTIC );
    

    JPA 1.0版

    Query query = em.createNativeQuery("SELECT * ... FOR UPDATE"); // works with most db
    Account acc = (Account) query.getSingleResult();
    

    lock 没有按预期工作。

    (注意:当乐观异常发生时,还可以实现重试逻辑。但这很复杂,因为事务是由应用程序管理的。服务器。你需要使用 @TRANSACTION_NEW 暂停当前事务并启动新事务等。。。我觉得太复杂了!)

        4
  •  0
  •   djna    16 年前

    您不会显示如何处理testCounterWithLock的返回值。我的猜测是,您得到了乐观的锁定失败和返回值有时是假的。

    当冲突在实践中可能很少发生时,最优锁定是一个合理的模型,调用方可以合理地重做工作。所以,如果你得到乐观的失败,你可以再试一次。