Статьи

Слабосвязанные возможности удаления для приложений CRUD

После C (создать) , R (читать) и U (обновить) , мы теперь приходим к D (удалить). Как и в предыдущих частях, мы заинтересованы в отделении пользовательского интерфейса нашего приложения от кода доступа к данным. Короче говоря, мы хотим иметь ситуацию, в отличие от учебного руководства по приложениям CRUD NetBeans , где разработчикам пользовательского интерфейса вообще ничего не нужно знать о базовой реализации JPA. Более того, им не нужно будет знать, что используется JPA, и вообще ничего о уровне доступа к данным приложения.

При подключении функции «Создать» к приложению ( как описано здесь ) инженерам пользовательского интерфейса нужно знать только о « CreatableEntityCapability », который будет добавлен в объект «TripQuery» инженерами для доступа к данным (без нарушения интерфейс, поскольку используется композиция, а не наследование), и поэтому они смогут реализовать ее в реализации «NewType» платформы NetBeans. Мы сделаем нечто очень похожее с функциональностью «Удалить».

  1. Расширить 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();
    }

    }
  2. Определите DeletableEntityCapability. Вот абстракция поверх метода «delete (Trip)» в нашем DAO. То есть это то, что мы будем реализовывать в нашем объекте TripQuery, а затем извлекать в пользовательском интерфейсе нашего приложения для удаления поездок:

    public interface RemovableEntityCapability {

    public void remove(Trip trip) throws Exception;

    }
  3. Реализуйте возможность. Прекрасность «Поиска» теперь снова будет продемонстрирована. Поскольку мы не используем интерфейс, нам теперь не нужно переходить к нашему интерфейсу и определять новый метод, который затем необходимо будет реализовать всем реализациям. Вместо этого мы добавляем реализацию нашей новой возможности в список возможностей, которые предоставляет объект 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;
    }

    }
  4. Используйте возможность. Теперь пришло время менять шляпы. Теперь мы инженеры пользовательского интерфейса, а не инженеры баз данных. Команда разработчиков базы данных подготовила для нас возможность под названием «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» к панели инструментов «Файл» в приложении.

И хотя вышеперечисленное немного сложнее и тоньше, теперь у вас есть все функции удаления, показанные выше на скриншотах. Платформа NetBeans предоставляет все, что вам нужно, после того как вы включили это, как описано выше.