【问题标题】:How to avoid blocking EDT with JPA lazy loading in Swing desktop apps如何避免在 Swing 桌面应用程序中使用 JPA 延迟加载阻塞 EDT
【发布时间】:2011-03-19 08:22:02
【问题描述】:

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

JPA 似乎是个好主意,但依赖延迟加载来提高效率。延迟加载要求实体管理器在实体 bean 的整个生命周期内都存在,并且无法控制用于加载的线程或在 EDT 处理其他事情时在后台执行加载的任何方式。访问恰好在 EDT 上延迟加载的属性将阻止应用程序的 UI 访问数据库,甚至没有机会设置繁忙的光标。如果应用在 wifi/3G 或网速较慢的情况下运行,可能会使其看起来像是崩溃了。

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

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

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

(在此处撰写更多详细信息:http://soapyfrogs.blogspot.com/2010/07/jpa-and-hibernateeclipselinkopenjpaetc.html

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

Lazy/Eager loading strategies in remoting cases (JPA)

其他人是如何解决这个问题的?我是否通过尝试在桌面应用程序中使用 JPA 来找错树?还是我缺少明显的解决方案?在使用 JPA 进行透明数据库访问时,您如何避免阻塞 EDT 并保持应用响应?

【问题讨论】:

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


    【解决方案1】:

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

    附录:感谢@Ash 提到这个SwingWorkerbug。解决方法是将build from source 改为submitted

    【讨论】:

    • 作为参考,从 JDK6u17 开始的 Sun/Oracle JVM 中的 SwingWorker 版本显然有一个相当大的错误:bugs.sun.com/bugdatabase/view_bug.do?bug_id=6880336。 cmets 末尾有一些解决方法。
    • 其实做后台处理是没有问题的。使用 JPA 会出现问题,因为您不知道 何时 需要后台处理,因为通过 cglib 代理的延迟加载都是“透明的”。当您使用数据库感知代码和显式后台加载时,很容易知道何时使用回调 Runnable、使用 SwingWorker 或其他方式调用后台数据库工作程序。但是,使用 JPA,您不知道需要加载某些内容,直到 UI 已被阻止等待加载。
    • 关于 SwingWorker 错误:无论如何,我通常直接使用 Executor 系统,后台数据库工作者处理带有完成回调的可运行文件。不过谢谢你的提示。
    • @trashgod 谢谢你的例子......我正在使用 Spring Data 实现一个设计以实现持久性,并且控制器架构对于 SwingWorkers 来说有点挑战......
    【解决方案2】:

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

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

    【讨论】:

    • 不幸的是,在这种环境下不使用某种形式的延迟加载是不可行的。我有 10,000 个客户,数据库中有数十万个相关实体。某些实体必然具有与其关联的大量或复杂数据,这些数据是应用程序的某些部分(但不是大多数)所需的。本文介绍了在 2 层桌面应用程序中使用 JPA / Hibernate / 等的一些设计问题:blog.schauderhaft.de/2008/09/28/… ... 并得出结论,实际上,没有任何好的解决方案。我倾向于同意。
    • 在这种情况下,我唯一可以建议的是,您在实体之上构建一个层,该层使用异步机制来访问延迟加载的属性。所以你会有类似 get 方法的东西,但是你会传递一个接口给它,当它准备好时会得到响应的通知。如果您只在您的 GUI 中使用此顶层,它将防止 EDT 阻塞并避免您的 GUI 不得不处理混乱的属性状态检查和合并过程。但是,它可能需要对 GUI 进行大量重写。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      抱歉迟到了!

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

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

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

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

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

      • 乍一看似乎非常简单(实际上,只需打开一个 EntityManager 并全局存储其引用并在应用程序的任何地方访问相同的实例)
      • 社区不推荐,因为将实体管理器保持打开时间过长是不安全的。由于各种原因,存储库连接(因此 session/entityManager)可能会断开。

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

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

      • 这很好用,但是如果你想在实体集合中添加或删除,或者直接修改某些字段值,这将不会反映在存储库中。你必须手动合并或更新实体使用某种方法。因此,如果您正在使用多层应用程序,从表示层您必须包括对存储库层的额外调用,您正在污染表示层的代码以附加到与 JPA 一起使用的具体存储库(发生的是存储库只是内存中实体的集合?...内存存储库是否需要额外调用来“更新”对象的集合...答案是否定的,所以这是一种很好的做法,但这样做是为了让事情“最终”起作用)
      • 您还必须考虑会发生什么情况,即检索到的对象图太大而无法同时存储在内存中,因此它可能会失败。 (正如 Craig 评论的那样)

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

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

      • 这以强制使用接口而不是普通域类为代价解决了问题。不错的想法实际上......但我想也不是标准的。我想我们都想使用域类。同样对于每个域对象,我们必须编写一个接口......如果对象进入.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 是另一个实体对象,它对 Bill 的一个 item 进行建模(一个 Bill 可以包含许多 Item,一个 Item 只属于一个并且只属于一个 Bill)。

      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 { 私有 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(由于我的语法错误而变得更加复杂,jeje)但无论如何希望它有所帮助* *我们要找到一个更稳定的解决方案来解决我们无法消除的问题。

      您好。

      维克多!!!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-11-21
        • 1970-01-01
        • 2012-01-02
        • 1970-01-01
        • 2020-11-23
        • 1970-01-01
        • 1970-01-01
        • 2011-01-23
        相关资源
        最近更新 更多