抱歉迟到了!
作为任何其他摇摆开发人员,我想当 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)但无论如何希望它有所帮助* *我们要找到一个更稳定的解决方案来解决我们无法消除的问题。
您好。
维克多!!!