Извините за опоздание!
Как и любой другой разработчик Swing, я полагаю, что все мы пришли к подобной проблеме, когда был включен JPA, надеясь справиться со всеми аспектами персистентности, инкапсулируя всю эту логику на одном изолированном уровне, а также способствуя более четкому разделению проблем, полагая, что это совершенно бесплатно ... но правда в том, что это определенно не так.
Как вы заявили ранее, существует проблема с отдельными объектами, которая заставляет нас создавать обходные пути для решения этой проблемы. Проблема не только в работе с ленивыми коллекциями, есть проблема в работе с самой сущностью, во-первых, любые изменения, которые мы делаем с нашей сущностью, должны отражаться в репозитории (а с отсоединением этого не произойдет). Я не эксперт в этом вопросе ... но я постараюсь выделить свои мысли по этому поводу и представить несколько решений (многие из них были ранее анонсированы другими людьми).
С уровня представления (то есть кода, в котором находится весь пользовательский интерфейс и взаимодействия, включая контроллеры) мы получаем доступ к уровню репозитория для выполнения простых операций CRUD, несмотря на конкретный репозиторий и конкретную презентацию, я думаю, что это стандарт факт, принятый сообществом. [Я думаю, это понятие очень хорошо записано Робертом Мартином в одной из книг DDD]
Таким образом, в основном можно блуждать «если моя сущность отсоединена, почему я не оставляю ее прикрепленной», делая это, она останется синхронизированной с моим репозиторием, и все изменения, внесенные в сущность, будут «немедленно» отражены в моем репозитории. И да .... вот где появляется первый ответ на эту проблему ..
1) Используйте единый объект диспетчера сущностей и держите его открытым от начала приложения до конца.
- На первый взгляд это кажется очень простым (и это так, просто откройте EntityManager и сохраните его ссылку глобально и получите доступ к одному и тому же экземпляру повсюду в приложении)
- Не рекомендуется сообществом, так как держать менеджера объекта открытым слишком долго небезопасно. Соединение с репозиторием (следовательно, session / entityManager) может разорваться по разным причинам.
Так что презирайте это просто, это не самые лучшие варианты ... так что давайте перейдем к другому решению, предоставляемому JPA API.
2) Используйте активную загрузку полей, чтобы не было необходимости прикрепляться к репозиторию.
- Это работает хорошо, но если вы хотите добавить или удалить в коллекцию объекта, или изменить какое-либо значение поля напрямую, это не будет отражено в репозитории .. вам придется вручную объединить или обновить объект, используя какой-либо метод . Следовательно, если вы работаете с многоуровневым приложением, где из уровня представления вы должны включить дополнительный вызов уровня репозитория, вы загрязняете код уровня представления, который нужно прикрепить к конкретному репозиторию, который работает с JPA (происходит то, что репозиторий это просто набор объектов в памяти? ... требуется ли репозиторию памяти дополнительный вызов для "обновления" коллекции объекта ... ответ отрицательный, так что это хорошая практика, но это делается ради заставить вещь "наконец-то" заработать)
- Также вы должны учитывать, что происходит, если полученный граф объектов слишком велик для одновременного сохранения в памяти, поэтому он, вероятно, не удастся. (Точно так, как прокомментировал Крейг)
Опять же .. это не решает проблему.
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 (Bill может содержать много элементов, 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 (основанный на шаблоне, предоставленном IDE NetBeans)
открытый класс 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 (усложнено моими грамматическими ошибками, jeje), но в любом случае надеюсь, что это поможет ** нам найти более стабильное решение проблемы, которую мы просто не можем стереть jeje.
Привет.
Виктор!!!
person
Victor
schedule
11.01.2013