Извините, что опоздал!
Как и любой другой разработчик Swing, я думаю, что все мы столкнулись с такой проблемой, когда JPA включена, надеясь решить все аспекты постоянства, заключая всю эту логику в единый изолированный уровень, также способствуя более четкому разделению интересов. , веря, что это абсолютно бесплатно ... но правда в том, что это определенно нет.
Как вы заявили ранее, есть проблема с отсоединенными сущностями, которая заставляет нас создавать обходные пути для решения этой проблемы. Проблема заключается не только в работе с отложенными коллекциями, но и в работе с самой сущностью. Во-первых, любые изменения, которые мы вносим в нашу сущность, должны отражаться в хранилище (а в случае отсоединения этого не произойдет). Я не эксперт по этому вопросу .. но я постараюсь высказать свои мысли по этому поводу и раскрыть несколько решений (многие из них были ранее объявлены другими людьми).
Из уровня представления (то есть кода, в котором находится весь пользовательский интерфейс и взаимодействия, включая контроллеры) мы получаем доступ к уровню хранилища для выполнения простых операций CRUD, несмотря на конкретное хранилище и конкретную презентацию, я думаю, это это общепринятый факт, принятый сообществом. [Полагаю, это очень хорошо записано Робертом Мартином в одной из книг DDD]
Таким образом, в основном можно блуждать «если моя сущность отсоединена, почему я не оставляю ее присоединенной», делая это, она будет синхронизироваться с моим хранилищем, и все изменения, внесенные в сущность, будут отражаться «немедленно» в моем хранилище. И да .... вот где появляется первый ответ на эту проблему ..
1) Используйте один объект диспетчера сущностей и оставляйте его открытым от начала приложения до конца.
- На первый взгляд это кажется очень простым (и это просто, просто откройте EntityManager и сохраните его ссылку глобально и получите доступ к одному и тому же экземпляру везде в приложении)
- Сообществом не рекомендуется, так как небезопасно держать менеджера сущностей открытым слишком долго. Соединение с репозиторием (следовательно, session / entityManager) может прерваться по разным причинам.
Так что презирайте, что это просто, это не лучшие варианты ... поэтому давайте перейдем к другому решению, предоставленному JPA API.
2) Используйте активную загрузку полей, поэтому нет необходимости присоединяться к хранилищу.
- Это хорошо работает, но если вы хотите добавить или удалить коллекцию сущности или напрямую изменить какое-либо значение поля, это не будет отражено в хранилище. Вам придется вручную объединять или обновлять сущность с помощью используя какой-то метод. Таким образом, если вы работаете с многоуровневым приложением, где из уровня представления вы должны включить дополнительный вызов уровня репозитория, вы загрязняете код уровня представления для присоединения к конкретному репозиторию, который работает с JPA (что происходит с репозиторием). это просто набор сущностей в памяти? ... нужен ли хранилищу памяти дополнительный вызов, чтобы "обновить" коллекцию объекта ... ответ - нет, так что это хорошая практика, но это делается ради сделать вещь "наконец" работает)
- Также необходимо учитывать, что происходит, если получаемый граф объектов слишком велик для одновременного хранения в памяти, поэтому он, вероятно, потерпит неудачу. (Именно так, как прокомментировал Крейг)
Опять же .. это не решит проблему.
3) Используя шаблон проектирования прокси, вы можете извлечь Интерфейс сущности (назовем его EntityInterface) и работать на уровне представления с этими интерфейсами (предположим, что вы действительно можете заставить клиента своего кода сделать это). Вы можете быть крутым и использовать динамические прокси или статические (на самом деле все равно), чтобы создать ProxyEntity на уровне хранилища для возврата объекта, реализующего этот интерфейс. Этот возвращаемый объект фактически принадлежит классу, чей метод экземпляра точно такой же (делегирование вызовов проксируемому объекту), за исключением тех, которые работают с коллекциями, которые необходимо «прикрепить» к репозиторию. Этот proxyEntity содержит ссылку на прокси-объект (сам объект), необходимый для операций CRUD в хранилище.
- Это решает проблему за счет принудительного использования интерфейсов вместо простых классов домена. Неплохо подумать на самом деле ... но я также думаю, что ни то, ни другое. Я думаю, что мы все хотим использовать доменные классы. Также для каждого объекта домена мы должны написать интерфейс ... что произойдет, если объект вошел в .JAR ... ага! Touche! Мы не можем извлечь интерфейс во время выполнения: 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;
}
}
Предмет - это еще один объект-сущность, моделирующий предмет Билла (Билл может содержать много Предметов, Предмет принадлежит только одному и только одному Биллу).
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 ... потому что может быть несколько типов репозиториев (memory, file, net, ??) и других уровней, поэтому нет необходимости знать, из каких Я работаю с хранилищем.
Существует также внутренний метод 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.
Привет.
Victor !!!