代码之家  ›  专栏  ›  技术社区  ›  Craig Ringer

如何避免在Swing桌面应用程序中使用JPA延迟加载阻塞EDT

  •  12
  • Craig Ringer  · 技术社区  · 14 年前

    我正在为在Swing桌面应用程序中实际使用JPA(Hibernate、EclipseLink等)而挣扎。

    为了避免延迟加载使EDT停滞,我必须处理分离的实体。然后,如果我真的需要lazy属性的值,我的所有组件(甚至那些应该能够不知道数据库的组件)都必须准备好处理延迟加载异常或使用PersistenceUtil测试属性状态。它们必须将实体分派回要合并的数据库工作线程,并在分离和再次返回之前加载属性。

    为了提高效率,我的组件需要知道 需要bean的哪些属性。

    因此,您将看到所有这些闪亮的教程演示如何使用JPA在NetBeans平台、Eclipse RCP、Swing app Framework等上快速生成一个简单的CRUD应用程序,但实际上演示的方法违反了基本的Swing实践(不要阻止EDT),在现实世界中完全不可行。

    (更多详情请写在这里: http://soapyfrogs.blogspot.com/2010/07/jpa-and-hibernateeclipselinkopenjpaetc.html

    有一些相关问题的回答有些帮助,但没有一个真正涵盖了edt阻塞/延迟加载/实体管理器生存期管理问题。

    Lazy/Eager loading strategies in remoting cases (JPA)

    4 回复  |  直到 7 年前
        1
  •  3
  •   Russ Hayward    14 年前

    我也遇到了同样的问题。我的解决方案是禁用延迟加载,并确保所有实体在从数据库层返回之前都已完全初始化。这意味着您需要仔细地设计实体,以便它们可以成堆地加载。您必须限制x-to-many关联的数量,否则每次获取时都会检索到一半的数据库。

    我不知道这是不是最好的解决办法,但确实有效。JPA主要是为请求-响应无状态应用程序设计的。它在有状态的Swing应用程序中仍然非常有用—它使您的程序可移植到多个数据库,并保存了大量样板代码。但是,在这种环境中使用它时必须更加小心。

        2
  •  6
  •   trashgod    14 年前

    我只在嵌入式数据库中使用JPA,EDT上的延迟不是问题。在JDBC上下文中,我使用 SwingWorker example .

    荡秋千的 bug . 解决方法是 build from source 已经 submitted .

        3
  •  1
  •   sola    14 年前

    我们将每个重要的操作包装到swingworker中,这可能会触发单个对象或集合的延迟加载。这很烦人,但也没办法。

        4
  •  1
  •   Community Mr_and_Mrs_D    7 年前

    抱歉迟到了!

    和其他任何swing开发人员一样,我想我们都遇到了这样的问题,当JPA被合并时,我们希望处理所有的持久性方面,通过将所有的逻辑封装在一个单独的层中,同时促进更干净的关注点分离,相信它是完全免费的…但事实是,它绝对不是。

    如您之前所述,分离实体存在一个问题,这使得我们需要创建解决此问题的变通方法。问题不仅在于使用惰性集合,还在于使用实体本身,首先,我们对实体所做的任何更改都必须反映到存储库中(使用分离的集合,这是不会发生的)。我不是这方面的专家。。但我将努力强调我对这一点的想法,并公开几个解决方案(其中许多方案以前已经被其他人宣布)。

    从表示层(即驻留所有用户界面和交互的代码,这包括控制器)我们访问存储库层来执行简单的CRUD操作,尽管有特定的存储库和特定的表示,我认为这是社区接受的标准事实[我想这是罗伯特·马丁在DDD的一本书中写得很好的一个概念]

    因此,基本上人们可以徘徊“如果我的实体是分离的,为什么我不让它附加”这样做,它将保持与我的存储库同步,对实体所做的所有更改都将“立即”反映到我的存储库中。是的。。。。这就是这个问题的第一个答案。。

    1) 使用单个实体管理器对象,并使其从应用程序的开始一直保持打开状态。

    • 乍一看,它似乎非常简单(实际上,只需打开EntityManager并全局存储其引用,并在应用程序中的任何地方访问相同的实例)

    所以轻视它的简单,这不是最好的选择。。。。因此,让我们转到jpaapi提供的另一个解决方案。

    • 您还必须考虑到所检索到的对象图太大,无法同时存储在内存中,因此可能会失败(正如克雷格所说)

    再一次。。这并不能解决问题。

    • 这以强制使用接口而不是普通域类为代价来解决问题。其实想得不错。。。但我也猜两者都不是标准的。我想我们都想使用域类。对于每个域对象,我们都必须编写一个接口。。。如果物体进入了。JAR。。。啊哈!触摸!我们不能在运行时提取接口:S,因此我们不能创建代理。

    为了更好地解释这一点,我写了一个这样做的例子。。。

    在域层(核心业务类所在的位置)

    @Entity
    public class Bill implements Serializable, BillInterface
    {
        private static final long serialVersionUID = 1L;
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
        @OneToMany(fetch=FetchType.LAZY, cascade = {CascadeType.ALL}, mappedBy="bill")
        private Collection<Item> items = new HashSet<Item> ();
    
        @Temporal(javax.persistence.TemporalType.DATE)
        private Date date;
    
        private String descrip;
    
        @Override
        public Long getId()
        {
            return id;
        }
    
        public void setId(Long id)
        {
            this.id = id;
        }
    
        public void addItem (Item item)
        {
            item.setBill(this);
            this.items.add(item);
        }
    
        public Collection<Item> getItems()
        {
            return items;
        }
    
        public void setItems(Collection<Item> items)
        {
            this.items = items;
        }
    
        public String getDescrip()
        {
            return descrip;
        }
    
        public void setDescrip(String descrip)
        {
            this.descrip = descrip;
        }
    
        public Date getDate()
        {
            return date;
        }
    
        public void setDate(Date date)
        {
            this.date = date;
        }
    
        @Override
        public int hashCode()
        {
            int hash = 0;
            hash += (id != null ? id.hashCode() : 0);
            return hash;
        }
    
        @Override
        public boolean equals(Object object)
        {
            // TODO: Warning - this method won't work in the case the id fields are not set
            if (!(object instanceof Bill))
            {
                return false;
            }
            Bill other = (Bill) object;
            if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id)))
            {
                return false;
            }
            return true;
        }
    
        @Override
        public String toString()
        {
            return "domain.model.Bill[ id=" + id + " ]";
        }
    
        public BigDecimal getTotalAmount () {
            BigDecimal total = new BigDecimal(0);
            for (Item item : items)
            {
                total = total.add(item.getAmount());
            }
            return total;
        }
    }
    

    Item是另一个实体对象,它对账单的一个项目进行建模(一个账单可以包含许多项目,一个项目只属于一个并且只属于一个账单)。

    BillInterface只是一个声明所有Bill方法的接口。

    在持久层上,我放置BillProxy。。。

    BillProxy的外观如下:

    class BillProxy implements BillInterface
    {
        Bill bill; // protected so it can be used inside the BillRepository (take a look at the next class)
    
        public BillProxy(Bill bill)
        {
            this.bill = bill;
            this.setId(bill.getId());
            this.setDate(bill.getDate());
            this.setDescrip(bill.getDescrip());
            this.setItems(bill.getItems());
        }
    
        @Override
        public void addItem(Item item)
        {
            EntityManager em = null;
            try
            {
                em = PersistenceUtil.createEntityManager();
                this.bill = em.merge(this.bill); // attach the object
                this.bill.addItem(item);
            }
            finally
            {
                if (em != null)
                {
                    em.close();
                }
            }
        }
    
    
    
        @Override
        public Collection<Item> getItems()
        {
            EntityManager em = null;
            try
            {
                em = PersistenceUtil.createEntityManager();
                this.bill = em.merge(this.bill); // attach the object
                return this.bill.getItems();
            }
            finally
            {
                if (em != null)
                {
                    em.close();
                }
            }
        }
    
        public Long getId()
        {
            return bill.getId(); // delegated
        }
    
        // More setters and getters are just delegated.
    }
    

    公共类DBBillRepository实现BillRepository private EntityManagerFactory emf=null;

        public DBBillRepository(EntityManagerFactory emf)
        {
            this.emf = emf;
        }
    
        private EntityManager createEntityManager()
        {
            return emf.createEntityManager();
        }
    
        @Override
        public void create(BillInterface bill)
        {
            EntityManager em = null;
            try
            {
                em = createEntityManager();
                em.getTransaction().begin();
                bill = ensureReference (bill);
                em.persist(bill);
                em.getTransaction().commit();
            }
            finally
            {
                if (em != null)
                {
                    em.close();
                }
            }
        }
    
        @Override
        public void update(BillInterface bill) throws NonexistentEntityException, Exception
        {
            EntityManager em = null;
            try
            {
                em = createEntityManager();
                em.getTransaction().begin();
                bill = ensureReference (bill);
                bill = em.merge(bill);
                em.getTransaction().commit();
            }
            catch (Exception ex)
            {
                String msg = ex.getLocalizedMessage();
                if (msg == null || msg.length() == 0)
                {
                    Long id = bill.getId();
                    if (find(id) == null)
                    {
                        throw new NonexistentEntityException("The bill with id " + id + " no longer exists.");
                    }
                }
                throw ex;
            }
            finally
            {
                if (em != null)
                {
                    em.close();
                }
            }
        }
    
        @Override
        public void destroy(Long id) throws NonexistentEntityException
        {
            EntityManager em = null;
            try
            {
                em = createEntityManager();
                em.getTransaction().begin();
                Bill bill;
                try
                {
                    bill = em.getReference(Bill.class, id);
                    bill.getId();
                }
                catch (EntityNotFoundException enfe)
                {
                    throw new NonexistentEntityException("The bill with id " + id + " no longer exists.", enfe);
                }
                em.remove(bill);
                em.getTransaction().commit();
            }
            finally
            {
                if (em != null)
                {
                    em.close();
                }
            }
        }
    
        @Override
        public boolean createOrUpdate (BillInterface bill) 
        {
            if (bill.getId() == null) 
            {
                create(bill);
                return true;
            }
            else 
            {
                try
                {
                    update(bill);
                    return false;
                }
                catch (Exception e)
                {
                    throw new IllegalStateException(e.getMessage(), e);
                }
            }
        }
    
        @Override
        public List<BillInterface> findEntities()
        {
            return findBillEntities(true, -1, -1);
        }
    
        @Override
        public List<BillInterface> findEntities(int maxResults, int firstResult)
        {
            return findBillEntities(false, maxResults, firstResult);
        }
    
        private List<BillInterface> findBillEntities(boolean all, int maxResults, int firstResult)
        {
            EntityManager em = createEntityManager();
            try
            {
                Query q = em.createQuery("select object(o) from Bill as o");
                if (!all)
                {
                    q.setMaxResults(maxResults);
                    q.setFirstResult(firstResult);
                }
                List<Bill> bills = q.getResultList();
                List<BillInterface> res = new ArrayList<BillInterface> (bills.size());
                for (Bill bill : bills)
                {
                    res.add(new BillProxy(bill));
                }
                return res;
            }
            finally
            {
                em.close();
            }
        }
    
        @Override
        public BillInterface find(Long id)
        {
            EntityManager em = createEntityManager();
            try
            {
                return new BillProxy(em.find(Bill.class, id));
            }
            finally
            {
                em.close();
            }
        }
    
        @Override
        public int getCount()
        {
            EntityManager em = createEntityManager();
            try
            {
                Query q = em.createQuery("select count(o) from Bill as o");
                return ((Long) q.getSingleResult()).intValue();
            }
            finally
            {
                em.close();
            }
        }
    
        private Bill ensureReference (BillInterface bill) {
            if (bill instanceof BillProxy) {
                return ((BillProxy)bill).bill;
            }
            else
                return (Bill) bill;
        }
    
    }
    

    正如您所注意到的,这个类实际上被称为DBBillRepository。。。这是因为可以有多个存储库(内存、文件、网络、?)类型和其他层的存储库,而不需要知道我在使用哪种类型的存储库。

    ensureReference 用于获取真实bill对象的内部方法,仅适用于从表示层传递代理对象的情况。谈到表示层,我们只使用Bill接口,而不是Bill an all。

    在某些控制器类(或者在SWING应用程序中的回调方法)中,我们可以按以下方式工作。。。

    BillInterface bill = RepositoryFactory.getBillRepository().find(1L); 
    bill.addItem(new Item(...)); // this will call the method of the proxy
    Date date = bill.getDate(); // this will deleagte the call to the proxied object "hidden' behind the proxy.
    bill.setDate(new Date()); // idem before
    RepositoryFactory.getBillRepository().update(bill);
    

    4) 实际上还有一件事我们可以避免使用接口。。。使用某种退化的代理对象。。。

    我们可以这样写BillProxy:

    class BillProxy extends Bill
    {
        Bill bill;
    
        public BillProxy (Bill bill)
        {
            this.bill = bill;
            this.setId(bill.getId());
            this.setDate(bill.getDate());
            this.setDescrip(bill.getDescrip());
            this.setItems(bill.getItems());
        }
    
        @Override
        public void addItem(Item item)
        {
            EntityManager em = null;
            try
            {
                em = PersistenceUtil.createEntityManager();
                this.bill = em.merge(this.bill);
                this.bill.addItem(item);
            }
            finally
            {
                if (em != null)
                {
                    em.close();
                }
            }
        }
    
    
    
        @Override
        public Collection<Item> getItems()
        {
            EntityManager em = null;
            try
            {
                em = PersistenceUtil.createEntityManager();
                this.bill = em.merge(this.bill);
                return this.bill.getItems();
            }
            finally
            {
                if (em != null)
                {
                    em.close();
                }
            }
        }
    
    }
    

    还有,有几个帖子解释了同样的事情,读起来很有趣。

    此外,我将任命这个参考,我仍然没有完全阅读,但看起来很有希望。

    http://javanotepad.blogspot.com/2007/08/managing-jpa-entitymanager-lifecycle.html http://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/transactions.html

    好吧,我们到了答案的结尾。。。我知道读这些东西太长了,可能有点痛苦:D(因为我的语法错误而变得更复杂了jeje),但无论如何,我希望它能帮助我们找到一个更稳定的解决方案,解决一个我们无法抹去jeje的问题。

    问候语。

    胜利者!!!