Xin lỗi bạn đã đến muộn!
Như bất kỳ nhà phát triển swing nào khác, tôi đoán tất cả chúng ta đều gặp phải vấn đề này khi JPA được kết hợp với hy vọng sẽ xử lý tất cả các khía cạnh bền vững, bằng cách đóng gói tất cả logic đó trong một tầng đơn lẻ. mối quan tâm, tin rằng nó hoàn toàn miễn phí ... nhưng sự thật là nó chắc chắn là không.
Như bạn đã nói trước khi có sự cố với các thực thể tách rời khiến chúng tôi tạo giải pháp khắc phục để giải quyết vấn đề này. Vấn đề là không chỉ làm việc với các bộ sưu tập lười biếng, có một vấn đề làm việc với chính thực thể, trước hết, bất kỳ thay đổi nào chúng ta thực hiện cho thực thể của chúng ta phải được phản ánh vào kho lưu trữ (và với việc tách rời này sẽ không xảy ra). Tôi không phải là một chuyên gia về điều này .. nhưng tôi sẽ cố gắng để làm nổi bật những suy nghĩ của tôi về điều này và vạch trần một số giải pháp (nhiều người trong số họ đã được công bố trước đây bởi những người khác).
Từ tầng trình bày (có nghĩa là mã nằm ở tất cả giao diện người dùng và tương tác, điều này bao gồm các bộ điều khiển), chúng tôi truy cập tầng kho lưu trữ để thực hiện các hoạt động CRUD đơn giản, mặc dù kho lưu trữ cụ thể và bản trình bày cụ thể đây là một thực tế tiêu chuẩn được cộng đồng chấp nhận. [Tôi đoán đây là một khái niệm được viết bởi Robert Martin trong một trong những cuốn sách DDD]
Vì vậy, về cơ bản người ta có thể đi lang thang "nếu thực thể của tôi bị tách rời, tại sao tôi không để nó" với kho lưu trữ của tôi, tất cả các thay đổi được thực hiện cho thực thể sẽ được phản ánh "ngay lập tức" vào kho lưu trữ của tôi. Và vâng .... đó là nơi câu trả lời đầu tiên xuất hiện cho vấn đề này ..
1) Sử dụng một đối tượng người quản lý thực thể duy nhất và giữ nó mở từ đầu của ứng dụng đến cùng.
- Trong nháy mắt có vẻ như rất đơn giản (và nó là, chỉ cần mở một EntityManager và lưu trữ tham chiếu của nó trên toàn cầu và truy cập vào cùng một ví dụ ở khắp mọi nơi trong ứng dụng)
- Không khuyến cáo của cộng đồng vì nó không an toàn để giữ một người quản lý thực thể mở quá lâu. Kết nối kho lưu trữ (do đó session/entityManager) có thể giảm do nhiều lý do khác nhau.
Vì vậy, coi thường nó đơn giản, nó không phải là lựa chọn tốt nhất .... vì vậy hãy chuyển sang giải pháp khác được cung cấp bởi API JPA.
2) Sử dụng tải trọng các trường, vì vậy không cần phải được gắn vào kho lưu trữ.
- Điều này hoạt động tốt, nhưng nếu bạn muốn thêm hoặc xóa tập hợp thực thể hoặc sửa đổi trực tiếp giá trị trường, điều này sẽ không được phản ánh trong kho lưu trữ. thực thể bằng cách sử dụng một số phương pháp. Vì vậy, nếu bạn đang làm việc với ứng dụng đa tầng, từ tầng trình bày, bạn phải thêm một cuộc gọi bổ sung vào tầng kho lưu trữ, bạn đang làm nhiễm mã của tầng trình bày được đính kèm vào một kho lưu trữ cụ thể hoạt động với JPA (những gì xảy ra là kho lưu trữ chỉ là một bộ sưu tập của các thực thể trong bộ nhớ? ... không một kho lưu trữ bộ nhớ cần một cuộc gọi thêm để "cập nhật" một bộ sưu tập của một đối tượng ...Câu trả lời là không, vì vậy đây là thực hành tốt nhưng nó được thực hiện vì lợi ích của làm cho điều "cuối cùng" hoạt động)
- Ngoài ra bạn phải xem xét những gì xảy ra là đồ thị đối tượng lấy ra là quá lớn để được lưu trữ ở cùng một thời gian trong bộ nhớ, vì vậy nó có thể sẽ thất bại. (Chính xác như Craig đã nhận xét)
Một lần nữa .. điều này không giải quyết được sự cố.
3) Sử dụng mẫu thiết kế proxy, bạn có thể trích xuất Giao diện thực thể (hãy gọi nó là EntityInterface) và làm việc trong lớp trình bày của bạn với các giao diện đó (giả sử bạn thực sự có thể buộc máy khách mã của bạn) . Bạn có thể được mát mẻ và sử dụng proxy động hoặc tĩnh (thực sự không quan tâm) để tạo ra một ProxyEntity trong tầng kho lưu trữ để trả về đối tượng thực hiện giao diện đó. Đối tượng này trả về thực sự thuộc về một lớp có phương thức instance giống hệt nhau (ủy nhiệm các cuộc gọi đến đối tượng proxy) trừ các đối tượng làm việc với các bộ sưu tập cần phải được "đính kèm" vào repostory. ProxyEntity đó chứa một tham chiếu đến đối tượng proxy (bản thân thực thể) cần thiết cho các hoạt động CRUD trên kho lưu trữ.
- Điều này giải quyết vấn đề với chi phí buộc sử dụng Giao diện thay vì các lớp miền đơn giản. Không phải là một suy nghĩ xấu thực sự ... nhưng tôi cũng đoán là không và tiêu chuẩn. Tôi nghĩ tất cả chúng ta đều muốn sử dụng các lớp miền. Ngoài ra đối với mỗi đối tượng miền chúng ta phải viết một giao diện ... điều gì sẽ xảy ra nếu đối tượng đến .JAR ... aha! touche! Chúng ta không thể giải nén một giao diện một thời gian chạy: S, và trước đó chúng ta không thể tạo các proxy.
Theo mục đích của giải thích điều này tốt hơn tôi viết ra một ví dụ để làm điều này ...
Trên tầng miền (nơi lớp kinh doanh cốt lõi cư trú)
@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 là một đối tượng thực thể mô hình hóa một mục của một hóa đơn (một hóa đơn có thể chứa nhiều mục, một mục chỉ thuộc về một và chỉ một hóa đơn).
BillInterface đơn giản là giao diện khai báo tất cả các phương thức thanh toán.
Trên tầng kiên trì i đặt BillProxy ...
Các BillProxy có cái nhìn này:
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.
}
Bây giờ chúng ta hãy nhìn vào BillRepository (lỏng lẻo dựa trên một mẫu do NetBeans IDE)
lớp công khai DBBillRepository triển khai BillRepository { riêng 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;
}
}
như bạn thấy, các lớp được thực sự gọi là DBBillRepository ... đó là bởi vì có thể có một số kho (bộ nhớ, tập tin, net, ??) loại một từ khác tầng không cần phải biết từ loại kho tôi đang làm việc.
Ngoài ra còn có phương thức nội bộ ensureReference
được sử dụng để lấy đối tượng hóa đơn thực, chỉ cho trường hợp chúng tôi chuyển đối tượng proxy từ lớp trình bày. Và nói về lớp trình bày, chúng tôi chỉ sử dụng BillInterfaces thay vì Bill, tất cả sẽ hoạt động tốt.
Trong một số lớp điều khiển (hoặc một phương pháp gọi lại, trong trường hợp của một ứng dụng Swing), chúng ta có thể làm việc theo cách sau đây ...
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);
Đây là thêm một cách tiếp cận, với chi phí buộc sử dụng giao diện.
4) Cũng có thực sự là một điều nữa mà chúng ta có thể làm gì để tránh làm việc với giao diện ... sử dụng somekind của đối tượng proxy thoái hóa ...
Chúng tôi có thể viết một BillProxy theo cách này:
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();
}
}
}
}
Vì vậy, trong tầng trình bày, chúng tôi có thể sử dụng lớp Bill, cũng trong DBBillRepository mà không sử dụng giao diện, vì vậy chúng tôi nhận được một ràng buộc ít hơn :). Tôi không chắc chắn nếu điều này là tốt ... nhưng nó hoạt động, và cũng duy trì mã không bị ô nhiễm bằng cách thêm cuộc gọi bổ sung cho một loại kho cụ thể.
Nếu bạn muốn tôi có thể gửi cho bạn toàn bộ ứng dụng của tôi và bạn có thể tự mình xem.
Ngoài ra, có một số bài giải thích cùng một điều, rất thú vị để đọc.
Ngoài ra tôi sẽ chỉ định tài liệu tham khảo này mà tôi vẫn không đọc hoàn toàn, nhưng trông đầy hứa hẹn.
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
Vâng, chúng tôi đến cuối của câu trả lời ở đây ... tôi biết rằng đó là quá lâu và có lẽ somekind đau để đọc tất cả điều này: D (làm phức tạp hơn bởi lỗi ngữ pháp của tôi jeje) nhưng dù sao hy vọng nó sẽ giúp chúng ta tìm ra một giải pháp ổn định hơn cho một vấn đề mà chúng ta không thể xóa được jeje.
Chúc mừng.
Victor !!!
Thật không may, không sử dụng một số hình thức tải lười biếng là không khả thi trong môi trường này. Tôi có 10.000 khách hàng với hàng trăm nghìn thực thể liên quan trong cơ sở dữ liệu. Một số thực thể nhất thiết phải có dữ liệu lớn hoặc phức tạp liên quan đến chúng phải được một số phần của ứng dụng yêu cầu, nhưng không nhất thiết phải. Bài viết này trình bày một số vấn đề về thiết kế với việc sử dụng JPA/Hibernate/etc trong ứng dụng dành cho máy tính để bàn 2 tầng: http://blog.schauderhaft.de/2008/09/28/hibernate-sessions-in-two-tier -rich-client-applications/ ... và kết luận rằng, thực sự, không có bất kỳ giải pháp tốt nào. Tôi có khuynh hướng đồng ý. –
Trong trường hợp đó, điều duy nhất tôi có thể đề nghị là bạn xây dựng một lớp trên đầu trang của các thực thể của bạn sử dụng một cơ chế không đồng bộ để truy cập vào các thuộc tính được nạp lười. Vì vậy, bạn sẽ có một cái gì đó giống như một phương pháp có được nhưng bạn sẽ vượt qua một giao diện với nó mà sẽ nhận được thông báo về phản ứng khi nó đã sẵn sàng. Nếu bạn chỉ sử dụng lớp trên cùng này từ GUI của bạn, nó sẽ ngăn chặn việc chặn EDT và tránh GUI của bạn khỏi phải đối phó với quá trình kiểm tra và kết hợp trạng thái tài sản lộn xộn. Tuy nhiên, nó có lẽ sẽ yêu cầu viết lại đáng kể GUI. –