2010-07-22 15 views
12

Tôi đang vật lộn với việc sử dụng JPA (Hibernate, EclipseLink, v.v.) trong thế giới thực trong ứng dụng máy tính để bàn Swing.Làm thế nào để tránh chặn EDT với JPA tải chậm trong ứng dụng máy tính để bàn Swing

JPA có vẻ như là một ý tưởng tuyệt vời, nhưng dựa vào tải chậm để có hiệu quả. Tải trọng chậm đòi hỏi trình quản lý thực thể tồn tại trong suốt thời gian của các bean thực thể và không kiểm soát luồng nào được sử dụng để tải hoặc bất kỳ cách nào để thực hiện tải trong nền trong khi EDT được bật với các thứ khác. Việc truy cập vào một thuộc tính có thể được tải nhẹ nhàng trên EDT sẽ chặn giao diện người dùng của ứng dụng của bạn khi truy cập cơ sở dữ liệu, thậm chí không có cơ hội để đặt con trỏ bận. Nếu ứng dụng đang chạy trên wifi/3G hoặc Internet chậm có thể khiến ứng dụng bị lỗi.

Để tránh việc tải chậm trì hoãn EDT, tôi phải làm việc với các thực thể tách rời. Sau đó, nếu tôi thực sự cần giá trị của một thuộc tính lười biếng, tất cả các thành phần của tôi (ngay cả những thành phần được cho là không thể biết về cơ sở dữ liệu) phải được chuẩn bị để xử lý các trường hợp tải lười hoặc sử dụng PersistenceUtil để kiểm tra trạng thái của thuộc tính. Họ phải gửi các thực thể trở lại luồng công nhân cơ sở dữ liệu để được hợp nhất và có các thuộc tính được nạp trước khi được tách ra và trả về một lần nữa.

Để làm cho hiệu quả, các thành phần của tôi cần phải biết trước những thuộc tính của hạt sẽ được yêu cầu. Vì vậy, bạn sẽ thấy tất cả các hướng dẫn sáng bóng này minh họa cách tạo một ứng dụng CRUD đơn giản trên Nền tảng NetBeans, Eclipse RCP, Swing App Framework, vv bằng cách sử dụng JPA, nhưng trên thực tế các phương pháp đã chứng minh vi phạm các thực hành Swing cơ bản (không chặn EDT) và hoàn toàn không khả thi trong thế giới thực.

(Chi tiết trong write-up ở đây: http://soapyfrogs.blogspot.com/2010/07/jpa-and-hibernateeclipselinkopenjpaetc.html)

Có một số câu hỏi liên quan đến những đáp ứng phần nào hữu ích, nhưng không ai trong số họ thực sự bao gồm các vấn đề quản lý quản lý đời edt chặn/lười biếng tải/đơn vị với nhau.

Lazy/Eager loading strategies in remoting cases (JPA)

Làm cách nào để giải quyết vấn đề này? Tôi có đang sủa cây sai bằng cách cố gắng sử dụng JPA trong ứng dụng dành cho máy tính để bàn không? Hoặc có những giải pháp rõ ràng tôi đang thiếu? Làm cách nào để tránh việc chặn EDT và giữ ứng dụng của bạn đáp ứng trong khi sử dụng JPA để truy cập cơ sở dữ liệu trong suốt?

Trả lời

3

Tôi đã gặp phải vấn đề tương tự. Giải pháp của tôi là vô hiệu hóa tải chậm và đảm bảo rằng tất cả các thực thể được khởi tạo đầy đủ trước khi chúng được trả về từ lớp cơ sở dữ liệu. Các tác động của điều này là bạn cần phải cẩn thận thiết kế các thực thể của bạn để chúng có thể được nạp theo khối. Bạn phải giới hạn số lượng các liên kết x-nhiều, nếu không bạn sẽ chỉ lấy một nửa cơ sở dữ liệu trên mỗi lần tìm nạp.

Tôi không biết đây có phải là giải pháp tốt nhất hay không nhưng nó hoạt động. JPA đã được thiết kế chủ yếu cho một ứng dụng không có yêu cầu phản hồi. Nó vẫn còn rất hữu ích trong một ứng dụng Swing stateful - nó làm cho chương trình của bạn di động với nhiều cơ sở dữ liệu và tiết kiệm rất nhiều mã boilerplate.Tuy nhiên, bạn phải cẩn thận hơn nhiều khi sử dụng nó trong môi trường đó.

+0

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 ý. –

+1

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

6

Tôi chỉ sử dụng JPA với cơ sở dữ liệu được nhúng, trong đó độ trễ trên EDT không phải là vấn đề. Trong ngữ cảnh JDBC, tôi đã sử dụng SwingWorker để xử lý xử lý nền bằng thông báo GUI. Tôi đã không thử nó với JPA, nhưng đây là một JDBC tầm thường example.

Phụ lục: Nhờ @Ash đề cập đến điều này SwingWorkerbug. Giải pháp thay thế là build from source đã được submitted.

+3

Để tham khảo, phiên bản SwingWorker trong Sun/Oracle JVMs từ JDK6u17 dường như có lỗi khá lớn: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6880336. Có một số cách giải quyết ở phần cuối của các bình luận. – Ash

+0

Trên thực tế, việc xử lý nền không bị phiền phức. Vấn đề nảy sinh, với JPA, bởi vì bạn không biết * khi nào * xử lý nền đó là bắt buộc, vì nó tất cả đều "trong suốt" với tải chậm thông qua các proxy cglib. Khi bạn đang làm việc với mã nhận biết cơ sở dữ liệu và tải nền rõ ràng, thật dễ dàng để biết khi nào nên gọi vào nhân viên cơ sở dữ liệu nền của bạn bằng cách gọi lại Runnable, sử dụng SwingWorker hoặc bất kỳ thứ gì. Với JPA, mặc dù, bạn không biết bạn cần tải gì đó cho đến khi giao diện người dùng đã bị chặn đang chờ tải. –

+1

Quay lại lỗi SwingWorker: Tôi thường sử dụng hệ thống Executor trực tiếp, với một nhân viên cơ sở dữ liệu nền xử lý các runnables với các cuộc gọi lại hoàn thành. Nhưng cảm ơn về tiền boa. –

1

Chúng tôi bao gồm mọi hoạt động quan trọng vào SwingWorkers, điều này có thể kích hoạt tải các đối tượng đơn lẻ hoặc bộ sưu tập. Điều này là khó chịu, nhưng không thể được giúp đỡ.

1

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 !!!