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

时间:2010-07-22 03:29:06

标签: java swing jpa desktop-application lazy-loading

我正在努力在Swing桌面应用程序中实际使用JPA(Hibernate,EclipseLink等)。

JPA似乎是个好主意,但依靠延迟加载来提高效率。延迟加载需要实体管理器在实体bean的生命周期中存在,并且无法控制用于加载的线程或在EDT开始使用其他东西时在后台进行加载的任何方式。访问恰好在EDT上延迟加载的属性将阻止应用程序在数据库访问时使用UI,甚至无法设置忙碌光标。如果应用程序在wifi / 3G或慢速互联网上运行,可能会使其看起来已经崩溃。

为了避免延迟加载停止EDT,我必须使用分离的实体。然后,如果我真的需要一个惰性属性的值,我所有的组件(甚至应该是那些应该不知道数据库的组件)必须准备好处理延迟加载异常或使用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)

其他人如何解决这个问题?我是否试图在桌面应用程序中使用JPA来吠叫错误的树?或者我有没有明显的解决方案?在使用JPA进行透明数据库访问时,您是如何避免阻止EDT并保持应用程序响应的?

4 个答案:

答案 0 :(得分:6)

我只使用JPA和嵌入式数据库,其中EDT的延迟不是问题。在JDBC上下文中,我使用SwingWorker来处理GUI通知的后台处理。我没有尝试过JPA,但这里有一个简单的JDBC example

附录:感谢@Ash提及此SwingWorker bug。解决方法是build from sourcesubmitted

答案 1 :(得分:3)

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

我不知道这是否是最佳解决方案,但确实有效。 JPA主要用于请求 - 响应无状态应用程序。它在有状态的Swing应用程序中仍然非常有用 - 它使您的程序可移植到多个数据库并节省大量样板代码。但是,在该环境中使用它必须更加小心。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

抱歉迟到了!

正如任何其他摇摆开发人员一样,我想我们都会在JPA被纳入时遇到这种问题,希望通过将所有逻辑封装在单个隔离层中来处理所有持久性方面,同时促进更清晰的关注点分离,相信它是完全自由的...但事实是,它绝对不是。

正如您之前所说,分离的实体存在问题,这使得我们创建解决此问题的解决方法。问题不仅在于使用惰性集合,使用实体本身存在问题,首先,我们对实体所做的任何更改都必须反映到存储库(并且使用分离的这不会发生)。我不是这方面的专家......但我会尝试强调我对此的看法,并揭示一些解决方案(其中许多已经被其他人宣布过)。

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

所以,基本上一个人可以徘徊"如果我的实体是分离的,为什么我不留下它附加"这样做,它将与我的存储库保持同步,对实体所做的所有更改都将立即反映出来#34;立即"到我的存储库。是的....这是这个问题的第一个答案。

1)使用单个实体管理器对象,并在应用程序开始到结束时保持打开状态。

  • 一目了然看起来非常简单(只需打开一个EntityManager并全局存储它的引用并在应用程序的任何地方访问相同的实例)
  • 社区不推荐,因为让实体经理长时间保持开放是不安全的。由于各种原因,存储库连接(因此会话/实体管理器)可能会丢失。

所以鄙视它很简单,它不是最好的选择....所以让我们转向JPA API提供的另一个解决方案。

2)使用热切的字段加载,因此无需附加到存储库。

  • 这很好用,但是如果要添加或删除实体的集合,或直接修改某个字段值,这将不会反映在存储库中..您必须手动合并或更新实体用一些方法。因此,如果您正在使用多层应用程序,那么从表示层开始,您必须包含对存储库层的额外调用,这会污染表示层的代码以附加到与JPA一起使用的具体存储库(存储库会发生什么)只是内存中实体的集合?...内存存储库需要额外调用"更新"对象的集合......答案是否定的,所以这是一个很好的做法,但它是为了制造东西而做了#34;最后"工作)
  • 此外,您必须考虑所检测到的对象图形太大而无法同时存储在内存中,因此它可能会失败。 (正如克雷格评论的那样)

再次......这不能解决问题。

3)使用代理设计模式,您可以提取实体的接口(让它称之为EntityInterface)并使用这些接口在您的表示层中工作(假设您实际上可以强制您的代码的客户端对此)。您可以很酷并使用动态代理或静态代理(实际上并不关心)在存储库层中创建ProxyEntity以返回实现该接口的对象。返回的这个对象实际上属于一个类,它的实例方法完全相同(委托对代理对象的调用),除了那些使用需要"附加"到了repostory。该proxyEntity包含对存储库上CRUD操作所必需的代理对象(实体本身)的引用。

  • 这解决了这个问题,代价是强制使用Interfaces而不是普通的域类。实际上还不错......但我猜也不是标准。我想我们都想使用域类。此外,对于每个域对象,我们必须编写一个接口......如果对象进来,会发生什么... .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;
    }
}

项目是另一个对比特项目建模的实体对象(比尔可以包含许多项目,项目只属于一个且只有一个比尔)。

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.
}

现在让我们来看看BillRepository(松散地基于NetBeans IDE提供的模板)

公共类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内部方法用于获取真实账单对象,仅用于我们从表示层传递代理对象的情况。谈到表示层,我们只使用BillInterfaces而不是Bill,一切都会运行良好。

在某些控制器类(或回调方法,如果是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();
            }
        }
    }

}

因此在表示层中我们可以使用Bill类,也可以在DBBillRepository中使用而不使用接口,因此我们得到一个约束:)。我不确定这是否合适......但是它有效,并且还通过向特定存储库类型添加额外调用来维护未受污染的代码。

如果你想我可以发送给你我的整个应用程序,你可以亲自看看。

另外,有几篇文章解释了相同的内容,这些内容非常有趣。

此外,我将指定这些参考文献,我仍然没有完全阅读,但看起来很有希望。

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(由于我的语法错误使得更复杂)但无论如何希望它有帮助* *我们为一个我们无法抹去的问题找到一个更稳定的解决方案。

问候。

维克多!!!