После C (создать) , R (читать) и U (обновить) , мы теперь приходим к D (удалить). Как и в предыдущих частях, мы заинтересованы в отделении пользовательского интерфейса нашего приложения от кода доступа к данным. Короче говоря, мы хотим иметь ситуацию, в отличие от учебного руководства по приложениям CRUD NetBeans , где разработчикам пользовательского интерфейса вообще ничего не нужно знать о базовой реализации JPA. Более того, им не нужно будет знать, что используется JPA, и вообще ничего о уровне доступа к данным приложения.
При подключении функции «Создать» к приложению ( как описано здесь ) инженерам пользовательского интерфейса нужно знать только о « CreatableEntityCapability », который будет добавлен в объект «TripQuery» инженерами для доступа к данным (без нарушения интерфейс, поскольку используется композиция, а не наследование), и поэтому они смогут реализовать ее в реализации «NewType» платформы NetBeans. Мы сделаем нечто очень похожее с функциональностью «Удалить».
- Расширить DAO. Как и прежде, в центре внимания этой серии лежит не создание наилучшего из возможных DAO, а просто то, что работает, чтобы наши возможности могли быть продемонстрированы на его основе. Итак, ниже, DAO немного уродлив, и предложения по улучшению приветствуются. Единственное отличие DAO от предыдущей части заключается в том, что в строку 45–49 добавлен метод «remove (Trip)»:
import client.Person;
import client.Trip;
import client.Triptype;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.persistence.Query;
public final class TripSearchDAO {
public List<Trip> search() {
createTransactionalEntityManager();
List<Trip> trips = new ArrayList<Trip>();
List<Trip> resultList = query.getResultList();
for (Trip c : resultList) {
trips.add(c);
}
return trips;
}
public void save(Trip trip) {
createTransactionalEntityManager();
em.merge(trip);
closeTransactionalEntityManager();
}
public void create(Trip trip) {
Random generator = new Random();
createTransactionalEntityManager();
trip.setPersonid(new Person(generator.nextInt()));
Triptype tt = new Triptype();
tt.setDescription("Bla");
tt.setTriptypeid(generator.nextInt());
tt.setName("Bla bla");
tt.setLastupdated(new Date());
trip.setTriptypeid(tt);
trip.setTripid(generator.nextInt());
em.persist(trip);
closeTransactionalEntityManager();
}
public void remove(Trip trip) {
createTransactionalEntityManager();
em.createQuery("DELETE FROM Trip t WHERE t.tripid = " + trip.getTripid()).executeUpdate();
closeTransactionalEntityManager();
}
private EntityManager em;
private Query query;
private void createTransactionalEntityManager() {
em = Persistence.createEntityManagerFactory("TripPU").createEntityManager();
query = em.createQuery("SELECT t FROM Trip t");
em.getTransaction().begin();
}
private void closeTransactionalEntityManager() {
em.getTransaction().commit();
em.close();
}
} - Определите DeletableEntityCapability. Вот абстракция поверх метода «delete (Trip)» в нашем DAO. То есть это то, что мы будем реализовывать в нашем объекте TripQuery, а затем извлекать в пользовательском интерфейсе нашего приложения для удаления поездок:
public interface RemovableEntityCapability {
public void remove(Trip trip) throws Exception;
} - Реализуйте возможность. Прекрасность «Поиска» теперь снова будет продемонстрирована. Поскольку мы не используем интерфейс, нам теперь не нужно переходить к нашему интерфейсу и определять новый метод, который затем необходимо будет реализовать всем реализациям. Вместо этого мы добавляем реализацию нашей новой возможности в список возможностей, которые предоставляет объект TripQuery. Это также хорошо, потому что объект TripQuery может контролировать свои собственные возможности; то есть снаружи ничего не говорит объекту TripQuery, что он может делать, вместо этого он определяет свои возможности для себя, что является довольно мощным.
Итак, ниже, в строке 54 — 60, наша реализация RemovableEntityCapability состоит из вызова метода JPA «remove (Trip)» с последующим удалением Trip из списка Trips, доступного для остальной части приложения:
import org.my.api.capabilities.ReloadableEntityCapability;
import org.my.api.capabilities.SaveableEntityCapability;
import org.my.api.capabilities.CreatableEntityCapability;
import org.my.api.capabilities.RemovableEntityCapability;
import client.Trip;
import java.util.ArrayList;
import java.util.List;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Lookup;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
public final class TripQuery implements Lookup.Provider {
private List<Trip> trips;
private Lookup lookup;
private InstanceContent instanceContent;
private TripSearchDAO dao = new TripSearchDAO();
public TripQuery() {
trips = new ArrayList<Trip>();
// Create an InstanceContent to hold abilities...
instanceContent = new InstanceContent();
// Create an AbstractLookup to expose InstanceContent contents...
lookup = new AbstractLookup(instanceContent);
// Add a "Reloadable" ability to this entity
instanceContent.add(new ReloadableEntityCapability() {
@Override
public void reload() throws Exception {
ProgressHandle handle = ProgressHandleFactory.createHandle("Loading...");
handle.start();
List<Trip> result = dao.search();
for (Trip trip : result) {
if (!getTrips().contains(trip)) {
getTrips().add(trip);
}
}
handle.finish();
}
});
instanceContent.add(new SaveableEntityCapability() {
@Override
public void save(Trip trip) throws Exception {
dao.save(trip);
}
});
instanceContent.add(new CreatableEntityCapability() {
@Override
public void create(Trip trip) throws Exception {
dao.create(trip);
}
});
instanceContent.add(new RemovableEntityCapability() {
@Override
public void remove(Trip trip) throws Exception {
dao.remove(trip);
getTrips().remove(trip);
}
});
}
@Override
public Lookup getLookup() {
return lookup;
}
public List<Trip> getTrips() {
return trips;
}
} - Используйте возможность. Теперь пришло время менять шляпы. Теперь мы инженеры пользовательского интерфейса, а не инженеры баз данных. Команда разработчиков базы данных подготовила для нас возможность под названием «DeletableEntityCapability», и они задокументировали ее, чтобы мы знали, для чего она нужна, хотя нам не нужно заботиться о том, что она на самом деле делает под капотом. Нам просто нужно извлечь эту возможность из Lookup объекта «TripQuery», а затем передать ему «Поездку» всякий раз, когда мы хотим удалить «Поездку».
Давайте предположим, что мы, т.е. инженеры пользовательского интерфейса, хотим создать этот пользовательский интерфейс для удаления поездок:
Как вы можете видеть, пользователь может щелкнуть правой кнопкой мыши поездку, чтобы удалить ее, или использовать кнопку «Удалить» (это кнопка с красным крестиком на панели инструментов). Если щелкнуть любой из элементов «Удалить», это диалоговое окно отображается перед удалением:
И, когда несколько поездок выбраны для удаления …
… нажатие «Удалить» приводит к появлению в этом диалоговом окне:
… с последующим удалением всех выбранных поездок. Хорошей новостью является то, что ни один из этих элементов пользовательского интерфейса (т. Е. Пункт меню, кнопка и диалоги) не нужно создавать вручную. Вместо этого платформа NetBeans предоставляет все это из коробки. Это ваша работа, чтобы включить эту функцию и подключить ваш собственный код, и это немного сложно. Но, как только все будет настроено, вы будете удивлены, насколько много новых функций у вас есть.
- Включите функцию «Удалить» в вашем TopComponent. В «TripViewerTopComponent» добавьте действие «удалить» в ActionMap:
...
...
...
ActionMap map = getActionMap();
map.put("delete", ExplorerUtils.actionDelete(em, true)); // NOI18N
associateLookup(ExplorerUtils.createLookup(em, map)); - Прослушайте удаление узлов, а затем обновите иерархию. В «RootNodeChildFactory» внедрите «org.openide.nodes.NodeListener». Затем вам нужно реализовать несколько методов в ChildFactory, и только «nodeDestroyed» относится к этому примеру:
@Override
public void nodeDestroyed(NodeEvent ev) {
refresh(true);
}
@Override public void propertyChange(PropertyChangeEvent evt) {}
@Override public void childrenAdded(NodeMemberEvent ev) {}
@Override public void childrenRemoved(NodeMemberEvent ev) {}
@Override public void childrenReordered(NodeReorderEvent ev) {} - Включите функцию «Удалить» в «TripNode» и используйте «RemoveableEntityCapability». Теперь мы, наконец, в «TripNode», то есть это дочерний узел.
@Override
public Action[] getActions(boolean context) {
return new Action[]{(SystemAction.get(DeleteAction.class))};
}
@Override
public boolean canDestroy() {
return true;
}
@Override
public void destroy() throws IOException {
Trip trip = getLookup().lookup(Trip.class);
TripQuery query = getLookup().lookup(TripQuery.class);
RemovableEntityCapability cec = query.getLookup().lookup(RemovableEntityCapability.class);
try {
cec.remove(trip);
} catch (Exception e) {
}
//Notify the NodeListener in the RootNodeChildFactory,
//where nodeDestroyed will call refresh on the ChildFactory:
fireNodeDestroyed();
}Как видите, нам совершенно не нужно знать, что происходит на уровне базы данных. Нам нужно только знать, что существует «RemoveableEntityCapability», мы передаем ему наш объект Trip, и затем мы закончим. Мы уведомляем ChildFactory о том, что Node был уничтожен, что в результате вызывает «обновление» и воссоздание иерархии Node в ChildFactory.
- Зарегистрируйте «DeleteAction» и покажите его на панели инструментов. И теперь мы переходим к файлу layer.xml (у меня этот файл находится в отдельном модуле брендинга) и там мы регистрируем «DeleteAction», как показано ниже, под «NewAction». («NewAction» загружается в контекстное меню корневого отключения, поскольку оно строит свои меню из папки «Actions / RootTrip», которая относится только к корневому узлу, как объяснено в предыдущей части этой серии.)
<folder name="Actions">
<folder name="RootTrip">
<file name="org-openide-actions-NewAction.instance">
<attr methodvalue="org.openide.awt.Actions.context" name="instanceCreate"/>
<attr name="delegate" newvalue="org.openide.actions.NewAction"/>
<attr name="selectionType" stringvalue="EXACTLY_ONE"/>
<attr boolvalue="false" name="noIconInMenu"/>
<attr name="type" stringvalue="org.openide.util.datatransfer.NewType"/>
<attr name="iconBase" stringvalue="org/my/branding/record-new.png"/>
<attr name="displayName" stringvalue="New Trip"/>
</file>
</folder>
<folder name="LeafTrip">
<file name="org-openide-actions-DeleteAction.instance">
<attr name="instanceCreate" methodvalue="org.openide.awt.Actions.callback"/>
<attr name="key" stringvalue="delete"/>
<attr name="iconBase" stringvalue="org/my/branding/delete.png"/>
<attr name="displayName" stringvalue="Delete Trip"/>
<attr name="noIconInMenu" boolvalue="false"/>
</file>
</folder>
</folder>Как вы можете видеть, мы связали ключ «delete» в ActionMap TopComponent с «DeleteAction». Мы используем действие «CallBack», как описано здесь в javadoc API NetBeans.
<folder name="Toolbars">
<folder name="File">
<file name="org-openide-actions-DeleteAction.shadow">
<attr name="originalFile" stringvalue="Actions/LeafTrip/org-openide-actions-DeleteAction.instance"/>
<attr name="position" intvalue="20"/>
</file>
...
...
...И, как вы можете видеть выше, мы привязываем «DeleteAction» к панели инструментов «Файл» в приложении.
- Включите функцию «Удалить» в вашем TopComponent. В «TripViewerTopComponent» добавьте действие «удалить» в ActionMap:
И хотя вышеперечисленное немного сложнее и тоньше, теперь у вас есть все функции удаления, показанные выше на скриншотах. Платформа NetBeans предоставляет все, что вам нужно, после того как вы включили это, как описано выше.