我正在努力在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并保持应用程序响应的?
答案 0 :(得分:6)
我只使用JPA和嵌入式数据库,其中EDT的延迟不是问题。在JDBC上下文中,我使用SwingWorker
来处理GUI通知的后台处理。我没有尝试过JPA,但这里有一个简单的JDBC example。
附录:感谢@Ash提及此SwingWorker
bug。解决方法是build from source已submitted。
答案 1 :(得分:3)
我遇到了同样的问题。我的解决方案是禁用延迟加载,并确保所有实体在从数据库层返回之前已完全初始化。这样做的含义是您需要仔细设计您的实体,以便它们可以以块的形式加载。您必须限制x-to-many关联的数量,否则最终会在每次提取时检索一半数据库。
我不知道这是否是最佳解决方案,但确实有效。 JPA主要用于请求 - 响应无状态应用程序。它在有状态的Swing应用程序中仍然非常有用 - 它使您的程序可移植到多个数据库并节省大量样板代码。但是,在该环境中使用它必须更加小心。
答案 2 :(得分:1)
我们将每个重要操作都包装到SwingWorkers中,这可能会触发单个对象或集合的延迟加载。这很烦人,但无法帮助。
答案 3 :(得分:1)
抱歉迟到了!
正如任何其他摇摆开发人员一样,我想我们都会在JPA被纳入时遇到这种问题,希望通过将所有逻辑封装在单个隔离层中来处理所有持久性方面,同时促进更清晰的关注点分离,相信它是完全自由的...但事实是,它绝对不是。
正如您之前所说,分离的实体存在问题,这使得我们创建解决此问题的解决方法。问题不仅在于使用惰性集合,使用实体本身存在问题,首先,我们对实体所做的任何更改都必须反映到存储库(并且使用分离的这不会发生)。我不是这方面的专家......但我会尝试强调我对此的看法,并揭示一些解决方案(其中许多已经被其他人宣布过)。
从表示层(即驻留所有用户界面和交互的代码,包括控制器),我们访问存储库层以执行简单的CRUD操作,尽管特定的存储库和特定的表示,我认为这是社区接受的标准事实。 [我想这是罗伯特·马丁在其中一本DDD书中写得非常好的概念]
所以,基本上一个人可以徘徊"如果我的实体是分离的,为什么我不留下它附加"这样做,它将与我的存储库保持同步,对实体所做的所有更改都将立即反映出来#34;立即"到我的存储库。是的....这是这个问题的第一个答案。
1)使用单个实体管理器对象,并在应用程序开始到结束时保持打开状态。
所以鄙视它很简单,它不是最好的选择....所以让我们转向JPA API提供的另一个解决方案。
2)使用热切的字段加载,因此无需附加到存储库。
再次......这不能解决问题。
3)使用代理设计模式,您可以提取实体的接口(让它称之为EntityInterface)并使用这些接口在您的表示层中工作(假设您实际上可以强制您的代码的客户端对此)。您可以很酷并使用动态代理或静态代理(实际上并不关心)在存储库层中创建ProxyEntity以返回实现该接口的对象。返回的这个对象实际上属于一个类,它的实例方法完全相同(委托对代理对象的调用),除了那些使用需要"附加"到了repostory。该proxyEntity包含对存储库上CRUD操作所必需的代理对象(实体本身)的引用。
为了更好地解释这个,我写下了这样做的一个例子......
在域层(核心业务类所在的位置)
@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(由于我的语法错误使得更复杂)但无论如何希望它有帮助* *我们为一个我们无法抹去的问题找到一个更稳定的解决方案。
问候。
维克多!!!