1. Введение
Добро пожаловать в 5-е руководство из серии учебных пособий OpenMap. OpenMap — бесплатная библиотека Java GIS с открытым исходным кодом.
Вот список предыдущих уроков:
- В первом уроке мы создали базовое ГИС-приложение OpenMap, которое отображает карту с одним слоем фигур, загруженным из файловой системы, внутри
JFrame
. Этот учебник был основан наcom.bbn.openmap.app.example.SimpleMap
. - Во втором уроке мы расширили наше основное приложение для использования
MapHandler
. - В третьем руководстве мы увидели, как использовать преимущества технологии
BeanContext
для объявления наших классов в файлеopenmap.properties
и декларативного создания целого приложения. - Четвертый учебник объяснил слои карты.
В этом уроке мы поговорим о том, как создать 3-уровневое ГИС-приложение на основе OpenMap. Мы сделаем перерыв в изучении новых возможностей OpenMap и в основном рассмотрим то, что узнали в предыдущих уроках.
2. Требования и обзор архитектуры
Здесь идет ваш начальник или ваш клиент с некоторыми требованиями. В первом спринте (например, Scrum) приложение должно иметь возможность:
- чтение / запись данных из / в базу данных
- отобразить данные на карте ГИС
- взаимодействовать с данными и отображать их свойства
- перемещать географические данные в разные места и сохранять их обратно в базу данных
- создать / обновить / удалить данные карты
Проще говоря, вы можете сказать, и вы переходите к эскизу проекта 3-уровневой архитектуры вашего приложения:
3-уровневая архитектура следует архитектурному шаблону Model-View-Controller (MVC) . Модель создается из базы данных (бэкэнд). Наше представление — это ГИС-приложение OpenMap, которое мы создали в предыдущих уроках и которое может отображать данные в виде точек, линий, многоугольников и т. Д. И Контроллер все связывает.
Аналогичная архитектура — Model-View-ViewModel (MVVM), которую мы также кратко обсудим.
3. Технологии
3.1 Бэкэнд
Серверная часть — это, в основном, база данных, или, точнее, система управления базами данных (СУБД) . Здесь у вас есть выбор:
* Реляционные базы данных (Oracle, MySQL, Postgresql, MS SQL Server, Sqlite, Hsqldb, JavaDB и т. Д.) С или без геопространственных расширений. Существуют геопространственные расширения для MySQL , Postgresql , Oracle , SQLite ; MS SQL Server 2008 поставляется со встроенными пространственными расширениями.
* Объектно-ориентированные пространственные базы данных
* Базы данных без SQL с пространственной поддержкой (например, CassandraDB, CouchDB , MongoDB , Neo4j и т. Д.)
Пространственная база данных или база геоданных оптимизирована для хранения и запроса данных, которые представляют объекты, определенные в геометрическом пространстве. Большинство пространственных баз данных позволяют представлять простые геометрические объекты, такие как точки, линии и многоугольники, а также пространственные индексы в соответствии со спецификациями OpenGIS . Однако для создания ГИС-приложения вам не нужна база данных GeoSpatial, но ее использование имеет свои преимущества.
3.2 Модель
Как вы получаете доступ к базе данных для извлечения данных для использования в вашем приложении Java? Вот список возможных технологий, которые вы можете использовать:
- SQL-запросы к базе данных, например, Java Database Connectivity или JDBC . Это традиционный способ (но мы в 2016 году!). Вам нужно «говорить» на SQL, чтобы запрашивать вашу базу данных и получать ваши данные в
ResultSets
, что не очень удобно, когда ваше приложение следует объектно-ориентированной модели (если ваша база данных не является объектно-ориентированной или объектно-реляционной тоже). - Объектно-реляционное отображение, например, Java Persistence API (JPA) . Это современный способ отобразить таблицы базы данных на объекты Java. NetBeans предоставляет хороший мастер сопоставления JPA для вас.
- Функциональное картирование. Если вы парень из Java 8 и вам нравятся лямбды, то почему бы не использовать λ-выражения и Stream API вместо SQL-запросов или JPA? Speedment — это библиотека Java, которая делает эту мечту реальностью. Вот сравнение между SQL и Stream API для запроса данных.
3.3 Контроллер
Последний вопрос: как вы связываете вид с моделью? Ключевой проблемой здесь является слабая связь между различными компонентами. Слабая связь позволяет вам заменить любой из уровней вашего приложения другой технологией, не затрагивая (или с ограниченными изменениями) другие слои. Есть ряд решений, например:
- Java 6
ServiceLoader
- API поиска NetBeans
- Dukescript (MVVM) . Одним из преимуществ использования DukeScript для клиент-серверного приложения является повторное использование кода. Вы можете использовать одинаковые классы моделей на клиенте и сервере. Вот учебник, который отображает JPA и Dukescript.
4. Создайте наше приложение
Я не буду исследовать все эти технологии здесь. Не стесняйтесь смотреть на ссылки в конце этой статьи.
В этой статье мы увидим, как создать ГИС-приложение MVC с использованием JPA для модели и API-интерфейса поиска NetBeans для контроллера. В будущих статьях мы увидим альтернативные технологии, такие как Speedment для замены JPA и Dukescript для замены MVC на MVVM.
4.1 Наше мнение
Мы уже создали приложение OpenMap в моих предыдущих статьях. Давайте рассмотрим и проведем рефакторинг.
Наше приложение OpenMap состоит из следующей файловой иерархии:
-
openmap
-
DMSCoordInfoFormatter
-
DemoLayer
-
MyDrawingTool
-
OpenMap
-
-
openmap.properties
Давайте рефакторинг это так:
-
openmap
-
OpenMap.java
-
openmap.controller
-
openmap.model
-
openmap.view
-
DMSCoordInfoFormatter.java
-
DemoLayer.java
-
MyDrawingTool.java
-
-
-
openmap.properties
Не забудьте обновить пути и в openmap.properties
. Приведенная выше структура пакета изображает шаблон проектирования Model-View-Controller (MVC) .
В NetBeans (но также и в других средах разработки) вы можете легко применить рефакторинг (например, переместить файл или папку в другую папку или переименовать файл / папку), щелкнув правой кнопкой мыши файл / папку и выбрать рефакторинг в подменю Рефакторинг. ,
Добавьте слой городов (из оригинального openmap.properties
OpenMap):
Листинг 1 — openmap.properties — слой городов
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
# These layers are turned on when the map is first started. Order # does not matter here... openmap.startUpLayers=demo cities graticule shapePolitical # Layers listed here appear on the Map in the order of their names. openmap.layers=demo cities graticule shapePolitical ... ### # LocationLayer that holds cities. The palette for this layer lets # you turn on the names and declutter matrix, if you want. The # declutter matrix can get expensive at small scales. cities.class=com.bbn.openmap.layer.location.LocationLayer cities.prettyName=World Cities cities.locationHandlers=csvcities cities.useDeclutter=false cities.declutterMatrix=com.bbn.openmap.layer.DeclutterMatrix csvcities.class=com.bbn.openmap.layer.location.csv.CSVLocationHandler csvcities.prettyName=World Cities csvcities.locationFile=resources/map/cities.csv csvcities.csvFileHasHeader=true csvcities.locationColor=FF0000 csvcities.nameColor=008C54 csvcities.showNames=false csvcities.showLocations=true csvcities.nameIndex=0 csvcities.latIndex=5 csvcities.lonIndex=4 csvcities.csvFileHasHeader=true |
и не забудьте скопировать cities.csv
в resources/map
.
Запустите приложение еще раз, чтобы увидеть, что новый слой отображается.
4.2 Наша схема базы данных
Наша схема базы данных показана в следующем листинге. В основном он состоит из таблицы Supplier
. Мы хотим показать наших поставщиков как GeoPoint
s на карте.
Вот шаги для создания базы данных SQLite в NetBeans (вы можете выбрать любую понравившуюся СУБД):
- Щелкните правой кнопкой мыши по
Libraries
- Выберите Добавить JAR / Папка … из всплывающего меню
- Перейдите в папку, из которой вы скачали SQLite, и выберите
sqlite-jdbc-xxx.jar
- Выберите « Копировать в папку библиотек» и нажмите « Открыть» . Драйвер должен отображаться в разделе Библиотеки .
- Нажмите на меню Окно → Сервисы, чтобы открыть вкладку Сервисы .
- Разверните узел Базы данных
- Щелкните правой кнопкой мыши узел « Драйверы» и выберите « Новый драйвер».
- Нажмите на Добавить
- Перейдите в
sqlite-jdbc-xxxx.jar
вы загрузили файлsqlite-jdbc-xxxx.jar
с веб-сайта SQLite ; Класс драйвера должен бытьorg.sqlite.JDBC
и имя →SQLite
- Нажмите на ОК .
SQLite
должен быть указан в спискеDrivers
- Щелкните правой кнопкой мыши на Базы данных и выберите
New Connection…
- Выберите драйвер
SQLite
и нажмите « Далее» - Укажите URL-адрес JDBC, например,
jdbc:sqlite:C:\db\suppliers.sqlite-3
и нажмите кнопку « Готово» . Ваше соединение должно отображаться в разделе Базы данных . - Щелкните правой кнопкой мыши на нем и выберите «
Connect…
- Щелкните правой кнопкой мыши Таблицы и выберите
Execute Command…
- Введите следующую инструкцию SQL и нажмите кнопку
Run SQL
:
Листинг 2 — Таблица поставщиков
01
02
03
04
05
06
07
08
09
10
11
12
|
CREATE TABLE supplier ( SID INTEGER PRIMARY KEY , NAME VARCHAR2 (30) NOT NULL , CITY VARCHAR2 (30) NOT NULL , TYPE VARCHAR2 (10) NOT NULL CONSTRAINT TYPE CHECK (TYPE IN ( 'GROSS' , 'RETAIL' )), LATITUDE NUMBER (12,10) NOT NULL CONSTRAINT LATITUDE CHECK (LATITUDE BETWEEN -90.0000000000 AND 90.0000000000), LONGITUDE NUMBER (13,10) NOT NULL CONSTRAINT LONGITUDE CHECK (LONGITUDE BETWEEN -180.0000000000 AND 180.0000000000), CONSTRAINT UID UNIQUE (SID, NAME , LATITUDE, LONGITUDE) ) |
Убедитесь, что новая таблица была создана и перечислена в разделе Таблицы . Вы можете добавить те же данные образца в таблицу:
Листинг 3 — Пример данных
1
2
3
4
5
6
7
8
|
INSERT INTO supplier ( NAME , CITY, TYPE, LATITUDE, LONGITUDE) VALUES ( 'HP' , 'ATHENS' , 'GROSS' , 38.1216011, 23.65486336); INSERT INTO supplier ( NAME , CITY, TYPE, LATITUDE, LONGITUDE) VALUES ( 'DELL' , 'BRUSSELS' , 'RETAIL' , 50.83704758, 4.367612362); INSERT INTO supplier ( NAME , CITY, TYPE, LATITUDE, LONGITUDE) VALUES ( 'APPLE' , 'LONDON' , 'RETAIL' , 51.48791122, -0.177998126); INSERT INTO supplier ( NAME , CITY, TYPE, LATITUDE, LONGITUDE) VALUES ( 'TOSHIBA' , 'PARIS' , 'GROSS' , 48.88155365, 2.432832718); |
Не забудьте отключиться от базы данных, прежде чем продолжить. Поскольку SQLite является автономной базой данных, в основном это файл в файловой системе. Только одно приложение может получить к нему доступ одновременно. Если вы подключитесь к нему на вкладке « Службы » и попытаетесь одновременно получить к нему доступ из приложения OpenMap , вы получите исключение, что база данных заблокирована. Это не относится к «настоящим» СУБД, таким как Postgresql или MS SQL Server, где возможен одновременный доступ.
4.3 Постройте свою модель
Давайте построим модель JPA из приведенной выше схемы. NetBeans обеспечивает очень хорошую поддержку JPA:
- Щелкните правой кнопкой мыши на
openmap.model
- Выберите «
New → Other → Persistence → Entity Classes from Database
и нажмите « Далее». - Выберите соединение с базой данных (
suppliers.sqlite-3
) - Выберите таблицу
suppliers
из доступных таблиц и нажмите « Добавить», чтобы переместить ее в выбранные таблицы . - Нажмите на Далее .
- На шаге 3 отметьте только « Создать аннотации именованных запросов для постоянных полей» и нажмите « Далее» .
- На шаге 4 снимите все флажки и нажмите Готово .
Мастер создал новый класс Suppliers
и ненужный openmap.model
. Он также создал файл META-INF/persistence.xml
который содержит информацию о подключении к базе данных:
Листинг 4 — persistence.xml
01
02
03
04
05
06
07
08
09
10
11
12
13
|
<? xml version = "1.0" encoding = "UTF-8" ?> < persistence version = "2.1" xmlns = "http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd" > < persistence-unit name = "OpenMapPU" transaction-type = "RESOURCE_LOCAL" > < provider >org.eclipse.persistence.jpa.PersistenceProvider</ provider > < class >openmap.model.Supplier</ class > < properties > < property name = "javax.persistence.jdbc.url" value = "jdbc:sqlite:C:\db\suppliers.sqlite3" /> < property name = "javax.persistence.jdbc.user" value = "" /> < property name = "javax.persistence.jdbc.driver" value = "org.sqlite.JDBC" /> < property name = "javax.persistence.jdbc.password" value = "" /> </ properties > </ persistence-unit > </ persistence > |
Из-за схемы (см. Листинг 2), которая содержит определение первичного ключа, мастер создает класс SupplierPK
для первичного ключа. В этом нет необходимости, поэтому удалите этот класс и удалите это поле и его ссылки из класса Supplier
. Измените свой класс Supplier
, как показано в следующем листинге:
Листинг 5 — Поставщик.java
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
package openmap.model; import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; import javax.persistence.Transient; /** * * @author ikost */ @Entity @Table (name = "supplier" ) @NamedQueries ({ @NamedQuery (name = "Supplier.findAll" , query = "SELECT s FROM Supplier s" ), @NamedQuery (name = "Supplier.findBySid" , query = "SELECT s FROM Supplier s WHERE s.sid = :sid" ), @NamedQuery (name = "Supplier.findByName" , query = "SELECT s FROM Supplier s WHERE s.name = :name" ), @NamedQuery (name = "Supplier.findByCity" , query = "SELECT s FROM Supplier s WHERE s.city = :city" ), @NamedQuery (name = "Supplier.findByType" , query = "SELECT s FROM Supplier s WHERE s.type = :type" ), @NamedQuery (name = "Supplier.findByLatitude" , query = "SELECT s FROM Supplier s WHERE s.latitude = :latitude" ), @NamedQuery (name = "Supplier.findByLongitude" , query = "SELECT s FROM Supplier s WHERE s.longitude = :longitude" )}) public class Supplier implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue (strategy = GenerationType.IDENTITY) @Basic (optional = false ) @Column (name = "SID" ) private int sid; @Basic (optional = false ) @Column (name = "NAME" ) private String name; @Basic (optional = false ) @Column (name = "CITY" ) private String city; @Basic (optional = false ) @Column (name = "TYPE" ) @Enumerated (EnumType.STRING) private String type; @Basic (optional = false ) @Column (name = "LATITUDE" ) private double latitude; @Basic (optional = false ) @Column (name = "LONGITUDE" ) private double longitude; public enum TYPE {GROSS, RETAIL}; public Supplier() { } public Supplier( int id) { this .sid = id; } public Supplier( int id, String name, String city, TYPE type, double latitude, double longitude) { this .sid = id; this .name = name; this .city = city; this .type = type; this .latitude = latitude; this .longitude = longitude; } public int getSid() { return sid; } public void setSid( int sid) { this .sid = sid; } public String getName() { return name; } public void setName(String name) { this .name = name; } public String getCity() { return city; } public void setCity(String city) { this .city = city; } public TYPE getType() { return type; } public void setType(TYPE type) { this .type = type; } public double getLatitude() { return latitude; } public void setLatitude( double latitude) { this .latitude = latitude; } public double getLongitude() { return longitude; } public void setLongitude( double longitude) { this .longitude = longitude; } @Override public int hashCode() { return sid; } @Override public boolean equals(Object object) { if (!(object instanceof Supplier)) { return false ; } Supplier other = (Supplier) object; if ( this .sid != other.sid) { return false ; } return true ; } @Override public String toString() { return "openmap.model.Supplier[ sid =" + sid + " ]" ; } } |
JPA 2.1 обеспечивает поддержку отображения для перечислений (см. Поле type
в приведенном выше листинге).
4.4 Создайте свой контроллер
NetBeans также упрощает создание контроллера для вашей модели.
- Щелкните правой кнопкой мыши на
openmap.controller
- Выберите «
New → Other → Persistence → JPA Controller Classes from Entity Classes
и нажмите « Далее». - Выберите «
Supplier
из списка « Доступные классы объектов» и нажмите « Добавить», чтобы переместить его в список « Выбранные классы объектов» . - Нажмите на Далее .
- На шаге 3 исправьте пакет как
openmap.controller
. - Нажмите на Готово .
Мастер создал SupplierJpaController
а также 3 файла исключений. Теперь представление может получить доступ к этому контроллеру для выполнения действий с моделью.
Листинг 6 — SupplierJpaController.java
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
package openmap.controller; import java.io.Serializable; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Query; import javax.persistence.EntityNotFoundException; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; import openmap.controller.exceptions.NonexistentEntityException; import openmap.model.Supplier; /** * * @author ikost */ public class SupplierJpaController implements Serializable { public SupplierJpaController(EntityManagerFactory emf) { this .emf = emf; } private EntityManagerFactory emf = null ; public EntityManager getEntityManager() { return emf.createEntityManager(); } public void create(Supplier supplier) { EntityManager em = null ; try { em = getEntityManager(); em.getTransaction().begin(); em.persist(supplier); em.getTransaction().commit(); } finally { if (em != null ) { em.close(); } } } public void edit(Supplier supplier) throws NonexistentEntityException, Exception { EntityManager em = null ; try { em = getEntityManager(); em.getTransaction().begin(); supplier = em.merge(supplier); em.getTransaction().commit(); } catch (Exception ex) { String msg = ex.getLocalizedMessage(); if (msg == null || msg.length() == 0 ) { int id = supplier.getSid(); if (findSupplier(id) == null ) { throw new NonexistentEntityException( "The supplier with id " + id + " no longer exists." ); } } throw ex; } finally { if (em != null ) { em.close(); } } } public void destroy( int id) throws NonexistentEntityException { EntityManager em = null ; try { em = getEntityManager(); em.getTransaction().begin(); Supplier supplier; try { supplier = em.getReference(Supplier. class , id); supplier.getSid(); } catch (EntityNotFoundException enfe) { throw new NonexistentEntityException( "The supplier with id " + id + " no longer exists." , enfe); } em.remove(supplier); em.getTransaction().commit(); } finally { if (em != null ) { em.close(); } } } public List<Supplier> findSupplierEntities() { return findSupplierEntities( true , - 1 , - 1 ); } public List<Supplier> findSupplierEntities( int maxResults, int firstResult) { return findSupplierEntities( false , maxResults, firstResult); } private List<Supplier> findSupplierEntities( boolean all, int maxResults, int firstResult) { EntityManager em = getEntityManager(); try { CriteriaQuery cq = em.getCriteriaBuilder().createQuery(); cq.select(cq.from(Supplier. class )); Query q = em.createQuery(cq); if (!all) { q.setMaxResults(maxResults); q.setFirstResult(firstResult); } return q.getResultList(); } finally { em.close(); } } public Supplier findSupplier( int id) { EntityManager em = getEntityManager(); try { return em.find(Supplier. class , id); } finally { em.close(); } } public int getSupplierCount() { EntityManager em = getEntityManager(); try { CriteriaQuery cq = em.getCriteriaBuilder().createQuery(); Root<Supplier> rt = cq.from(Supplier. class ); cq.select(em.getCriteriaBuilder().count(rt)); Query q = em.createQuery(cq); return ((Long) q.getSingleResult()).intValue(); } finally { em.close(); } } } |
В этом примере у нас только один Supplier
доменных объектов, но в реальном приложении у вас будет много доменных объектов. Распространенным решением является создание Фасада, который будет отображать только методы, необходимые для представления, и скрывать остальные. Далее мы покажем, как создать слабосвязанный Facade IDBManager
с помощью API поиска NetBeans (см. Ссылки).
Листинг 7 — IDBManager.java
01
02
03
04
05
06
07
08
09
10
11
12
13
|
package openmap.controller; import java.util.List; import openmap.model.Supplier; /** * A facade of our controllers. * * @author ikost */ public interface IDBManager { List getSuppliers(); } |
Нажмите на BLOB- IDBManager
в левой части IDBManager
и выберите « Интерфейс реализации» . Измените реализацию DBManager
как DBManager
в следующем листинге, чтобы преобразовать ее в поставщика услуг:
Листинг 8 — DBManager.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package openmap.controller; import java.util.List; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import openmap.model.Supplier; import org.openide.util.lookup.ServiceProvider; @ServiceProvider (service = IDBManager. class ) public class DBManager implements IDBManager { private final EntityManagerFactory emf; private final SupplierJpaController suppliers; public DBManager() { emf = Persistence.createEntityManagerFactory( "OpenMapPU" ); suppliers = new SupplierJpaController(emf); } @Override public List getSuppliers() { return suppliers.findSupplierEntities(); } } |
Строка @ServiceProvider(service = IDBManager.class)
делает всю магию. Эта строка добавляет DBManager
к DBManager
по умолчанию . Мы увидим, как мы можем получить доступ к DBManager
из нашего представления в следующем разделе. Чтобы заставить его работать, вам нужно добавить зависимость в org-openide-util-lookup.jar
если NetBeans не добавил ее автоматически.
- Щелкните правой кнопкой мыши по
Libraries
- Выберите Добавить JAR / Папка … из всплывающего меню
- Найдите и выберите
<NetBeans_Installation>/platform/lib/org-openide-util-lookup.jar
- Выберите « Копировать в папку библиотек» и нажмите « Открыть» .
- Очистите и постройте свой проект, чтобы изменения вступили в силу.
Но что такое поиск? Поиск — это карта с объектами класса в качестве ключей и наборы экземпляров этих объектов класса в качестве значений, т.е.
Lookup = Map<Class, Set<Class>>
, например Map<String, Set<String>>
или Map<Provider, Set<Provider>>
. NetBeans предоставляет несколько методов для доступа к поиску по умолчанию:
1
2
|
Provider provider = Lookup.getDefault().lookup(Provider. class ); provider.aMethod(); |
или если у вас есть несколько реализаций Provider
:
1
2
|
Collection providers = Lookup.getDefault().lookupAll(Provider. class ); for (Provider provider : providers) { ... } |
Как видно из приведенных выше примеров кода, клиент не имеет представления о том, какую реализацию он использует; он знает только интерфейс. Слабая связь!
Приведенный выше код добавляет службы к поиску по умолчанию . Клиент ищет в интерфейсе поиск по умолчанию . По умолчанию это Lookup, который оценивает объявления META-INF/services
папке META-INF/services
. Он вызывается через метод Lookup.getDefault()
. Если вас интересует более подробная информация, Netbeans создает текстовый файл package.Provider
папке build/classes/META-INF/services/
которая содержит полные имена классов реализации. Запрашивая таким образом интерфейс службы, вы получаете примеры реализации классов, зарегистрированных в папке META-INF/services
.
Конечно, в NetBeans Rich Client Platform есть другие поиски вместо поиска по умолчанию , но они выходят за рамки данной статьи.
4.5 Создайте свой взгляд
Наконец, нам нужно создать новый слой, который будет отображать наших поставщиков на карте, и объявить его в openmap.properties
, чтобы добавить его на карту. Если вы следовали предыдущим урокам, то теперь это должно быть легко, как это сделать. Давайте создадим SupplierLayer.java
шаг за шагом:
Листинг 9 — SupplierLayer.java
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
package openmap.controller; public class SupplierLayer extends OMGraphicHandlerLayer { private static final String LOOKUP_OBJECT = "Lookup Object" ; public SupplierLayer() { // This is how to set the ProjectionChangePolicy, which // dictates how the layer behaves when a new projection is // received. setProjectionChangePolicy( new StandardPCPolicy( this , true )); setRenderPolicy( new BufferedImageRenderPolicy()); // Making the setting so this layer receives events from the // SelectMouseMode, which has a modeID of "Gestures". Other // IDs can be added as needed. setMouseModeIDsForEvents( new String[]{ "Gestures" }); } /** * Called from the prepare() method if the layer discovers that its * OMGraphicList is {@code null}. * * @return new {@code OMGraphicList} with {@code OMGraphics{ that you always * want to display and reproject as necessary. */ public OMGraphicList init() { final IDBManager dbManager = Lookup.getDefault().lookup(IDBManager. class ); final List suppliers = dbManager.getSuppliers(); // This layer keeps a pointer to an OMGraphicList that it uses // for painting. It's initially set to null, which is used as // a flag in prepare() to signal that the OMGraphcs need to be // created. The list returned from prepare() gets set in the // layer. // This layer uses the StandardPCPolicy for new // projections, which keeps the list intact and simply calls // generate() on it with the new projection, and repaint() // which calls paint(). OMGraphicList omList = new OMGraphicList(); // Add suppliers as OMPoints. for (Supplier supplier : suppliers) { OMPoint omSupplier = new OMPoint(supplier.getLatitude(), supplier.getLongitude(), 3 ); // radius omSupplier.putAttribute(OMGraphicConstants.LABEL, new OMTextLabeler(supplier.getName(), OMText.JUSTIFY_LEFT)); omSupplier.putAttribute(LOOKUP_OBJECT, supplier); omSupplier.setLinePaint(Color.BLUE); omSupplier.setSelectPaint(Color.ORANGE); omSupplier.setOval( true ); omList.add(omSupplier); } return omList; } /** * This is an important Layer method to override. The prepare method gets * called when the layer is added to the map, or when the map projection * changes. We need to make sure the OMGraphicList returned from this method * is what we want painted on the map. The OMGraphics need to be generated * with the current projection. We test for a null OMGraphicList in the * layer to see if we need to create the OMGraphics. This layer doesn't * change it's OMGraphics for different projections, if your layer does, you * need to clear out the OMGraphicList and add the OMGraphics you want for * the current projection. * * @return */ @Override public synchronized OMGraphicList prepare() { OMGraphicList list = getList(); // Here's a test to see if it's the first time that the layer has been // added to the map. This list object will be whatever was returned from // this method the last time prepare() was called. In this // example, we always return an OMGraphicList object, so if it's null, // prepare() must not have been called yet. if (list == null ) { list = init(); } /* * This call to the list is critical! OMGraphics need to be told where * to paint themselves, and they figure that out when they are given the * current Projection in the generate(Projection) call. If an * OMGraphic's location is changed, it will need to be regenerated * before it is rendered, otherwise it won't draw itself. You generally * know you have a generate problem when OMGraphics show up with the * projection changes (zooms and pans), but not at any other time after * something about the OMGraphic changes. * * If you want to be more efficient, you can replace this call to the * list as an else clause to the (list == null) check above, and call * generate(Projection) on all the OMGraphics in the init() method below * as you create them. This will prevent the * OMGraphicList.generate(Projection) call from making an additional * loop through all of the OMGraphics before they are returned. */ list.generate(getProjection()); return list; } /** * Query that an OMGraphic can be highlighted when the mouse moves over it. * If the answer is true, then highlight with this OMGraphics will be * called. * * @param omg * @return */ @Override public boolean isHighlightable(OMGraphic omg) { return true ; } /** * Query that an OMGraphic is selectable. Examples of handing selection are * in the EditingLayer. The default OMGraphicHandlerLayer behavior is to add * the OMGraphic to an OMGraphicList called selectedList. If you aren't * going to be doing anything in particular with the selection, then return * false here to reduce the workload of the layer. * * @param omg * @return * @see com.bbn.openmap.layer.OMGraphicHandlerLayer#select * @see com.bbn.openmap.layer.OMGraphicHandlerLayer#deselect */ @Override public boolean isSelectable(OMGraphic omg) { return true ; } /** * Query for what tooltip to display for an OMGraphic * the mouse is over. * * @param omg * @return */ @Override public String getToolTipTextFor(OMGraphic omg) { String ttText = null ; if (omg instanceof OMPoint) { OMPoint point = ((OMPoint) omg); Object attribute = point.getAttribute(OMGraphicConstants.LABEL); if (attribute != null && attribute instanceof OMTextLabeler) { OMTextLabeler labeler = (OMTextLabeler) attribute; ttText = labeler.getData(); } } return ttText; } @Override public Component getGUI() { JPanel panel = PaletteHelper.createPaletteJPanel( "Suppliers Layer" ); JCheckBox chkShowLabels = new JCheckBox( "Show/Hide Labels" , true ); chkShowLabels.addItemListener((ItemEvent e) -> { OMGraphicList omSuppliers = getList(); for (OMGraphic omSupplier : omSuppliers) { if (chkShowLabels.isSelected()) { omSupplier.putAttribute(OMGraphicConstants.LABEL, new OMTextLabeler( ((Supplier) omSupplier.getAttribute(LOOKUP_OBJECT)).getName(), OMText.JUSTIFY_LEFT)); } else { omSupplier.removeAttribute(OMGraphicConstants.LABEL); } } repaint(); }); panel.add(chkShowLabels); return panel; } } |
В методе init()
мы получаем список Supplier
из DBManager
используя DBManager
по умолчанию . Мы перебираем всех Supplier
и создаем OMPoint
из каждого из них. Метка точки создается путем установки атрибута OMGraphicConstants.LABEL
. Мы делаем хитрость, добавляя Supplier
поддержки на карту OMPoint
с помощью ключа "Lookup Object"
; это понадобится нам позже. (Не путайте его с «Уточняющим запросом» NetBean; мы просто назовем его аналогичным образом, чтобы показать, что он похож на «Уточняющий запрос NetBeans», но не имеет к нему никакого отношения; вы могли бы назвать его как-нибудь еще). Наконец, каждая точка добавляется в возвращенный OMGraphicList
.
Метод getGUI()
вызывается из диалогового окна « Слои » при нажатии кнопки « Инструменты» (см. Следующий рисунок):
Метод создает новую панель с флажком для отображения / скрытия меток слоя. Метка Supplier
— это имя Supplier
, которое извлекается из карты атрибутов OMPoint
, чтобы установить с ним атрибут OMGraphicConstants.LABEL
. Когда флажок установлен, метка видна, в противном случае атрибут удаляется. Слой repaint()
ed. К сожалению, repaint()
не работает на 100%; вам нужно увеличить или изменить размер карты, чтобы метки снова отображались.
Далее, мы хотим отобразить всплывающее меню, когда мы щелкаем правой кнопкой мыши по Supplier
, чтобы отобразить его свойства. Из предыдущего урока вы знаете, что нам нужно переопределить метод getItemsForOMGraphicMenu()
. Если вы хотите отобразить всплывающее меню при щелчке правой кнопкой мыши в любом месте слоя, переопределите следующий метод getItemsForMapMenu()
:
Листинг 10 — SupplierLayer.java (продолжение)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
@Override public List getItemsForOMGraphicMenu(OMGraphic omg) { final OMGraphic chosen = omg; List menuItems = new ArrayList<>(); JMenuItem mnuProperties = new JMenuItem( "Properties" ) mnuProperties.addActionListener((ActionEvent ae) -> { //... }); menuItems.add(mnuProperties); return menuItems; } /** * This method is called mnuCreate a right mouse click is detected over the map * and not over an OMGraphic. You can provide a List of components to be * displayed in a popup menu. You have to do the wiring for making the list * components do something, though. * * @param me * @return */ @Override public List getItemsForMapMenu(MapMouseEvent me) { List l = new ArrayList<>(); JMenuItem mnuCreate = new JMenuItem( "Create New Supplier" ); mnuCreate.addActionListener((ActionEvent ae) -> { fireRequestMessage( "Create New Supplier" ); }); l.add(mnuCreate); return l; } |
Нам не хватает формы для отображения данных. Мы сделаем быстрый взлом, чтобы показать вам другого мастера NetBeans, но не стесняйтесь создавать свое собственное диалоговое окно, используя Matisse или, как вы знаете.
- Создайте новый пакет
openmap.view.properties
- Щелкните правой кнопкой мыши на нем и выберите « Создать» → «Другое» → «Формы Swing GUI» → «Образец формы« Мастер / подробности »» и нажмите « Далее».
- Назовите его SuppliersPropertiesDialogBox и нажмите « Далее».
- Выберите соединение с базой данных , таблицу поставщиков и исключите поле
SID
из столбцов для включения . - Нажмите Готово .
Мастер создал форму мастера / детали, но нам нужна только деталь детали. Настройте его, как показано на следующем рисунке.
Выделите каждое из текстовых полей и кнопку « Удалить» , нажмите « Привязка» (область « Свойства» ) и удалите все ссылки на основную таблицу из enabled
и text
свойств.
Измените текстовое поле « Тип» на поле со списком, поскольку type
ограничен только значениями enum
'GROSS'
и 'RETAIL'
.
- Щелкните правой кнопкой мыши поле со списком и выберите «
Customize Code…
- Нажмите на второе поле со списком кодов по умолчанию и измените его на пользовательское свойство .
- Измените код на следующий и нажмите ОК :
1
|
cmbType.setModel( new javax.swing.DefaultComboBoxModel<>(Supplier.TYPE.values())); |
- Снова нажмите на поле со списком и в Свойствах нажмите на Код
- Установите параметры типа в значение
<Supplier.TYPE>
Добавьте метку ( lblStatus
) внизу. Сделайте это непрозрачным. Когда транзакция с базой данных будет успешной, она будет окрашена в зеленый, в противном случае — в красный. Это хороший отзыв для пользователя, чтобы убедиться, что его / ее изменения сохраняются.
Диалоговое окно связано с менеджером сущностей для извлечения данных для отображения, но в целом это плохой дизайн. Удалите все ссылки на менеджер сущностей, а также на главную таблицу и преобразуйте его в JDialog
. Чтобы избежать java.lang.IllegalArgumentException: GroupLayout can only be used with one Container at a time
, добавьте все компоненты в JPanel
- Изменить размер диалогового окна в представлении « Дизайн»
- Перетащите панель сверху из палитры (Swing Containers)
- Измените имя переменной
panel
наpanel
- Установите его макет в
Free Design
- Выберите все виджеты из навигатора и перетащите их на новую панель; их расположение должно остаться как прежде.
- Выберите
JDialog
в навигаторе и установите для его свойства размера значение[400, 230]
. Снимите флажок Изменить размер объекта.
Исходный код должен выглядеть так:
Листинг 11 — SuppliersPropertiesDialogBox.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
public class SuppliersPropertiesDialogBox extends JDialog { private final Supplier supplier; private final IDBManager dbManager; public SuppliersPropertiesDialogBox(Supplier s) { dbManager = Lookup.getDefault().lookup(IDBManager. class ); initComponents(); supplier = s; setData(supplier); } @SuppressWarnings ( "unchecked" ) private void btnCloseActionPerformed(java.awt.event.ActionEvent evt) { this .setVisible( false ); } private void btnDeleteActionPerformed(java.awt.event.ActionEvent evt) { try { dbManager.delete(supplier); lblStatus.setBackground(Color.green); } catch (Exception ex) { Logger.getLogger(SuppliersPropertiesDialogBox. class .getName()) .log(Level.SEVERE, null , ex); lblStatus.setBackground(Color.red); } } private void btnSaveActionPerformed(java.awt.event.ActionEvent evt) { try { dbManager.save(getData()); lblStatus.setBackground(Color.green); } catch (Exception ex) { Logger.getLogger(SuppliersPropertiesDialogBox. class .getName()) .log(Level.SEVERE, null , ex); lblStatus.setBackground(Color.red); } } public void setData(Supplier supplier) { txtName.setText(supplier.getName()); txtCity.setText(supplier.getCity()); txtLatitude.setText(String.valueOf(supplier.getLatitude())); txtLongitude.setText(String.valueOf(supplier.getLongitude())); cmbType.setSelectedItem(supplier.getType()); } public Supplier getData() { supplier.setName(txtName.getText()); supplier.setCity(txtCity.getText()); supplier.setLatitude(Double.valueOf(txtLatitude.getText())); supplier.setLongitude(Double.valueOf(txtLongitude.getText())); supplier.setType(Supplier.TYPE.valueOf(cmbType.getSelectedItem().toString())); return supplier; } // initComponents() generated method omitted ... } |
Как видите, мы обращаемся к DBManager
для обработки данных. Нам нужно добавить эти новые методы к нему:
Листинг 12 — IDBManager.java
1
2
3
4
5
|
public interface IDBManager { List getSuppliers(); void delete(Supplier supplier) throws Exception; void save(Supplier supplier) throws Exception; } |
и их реализации:
Листинг 13 — DBManager.java
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@Override public void delete(Supplier supplier) throws Exception { try { suppliers.destroy(supplier.getSid()); } catch (NonexistentEntityException ex) { Logger.getLogger(DBManager. class .getName()) .log(Level.SEVERE, null , ex); throw ex; } } @Override public void save(Supplier supplier) throws Exception { Supplier s = suppliers.findSupplier(supplier.getSid()); if (s == null ) { suppliers.create(supplier); } else { try { suppliers.edit(supplier); } catch (Exception ex) { Logger.getLogger(DBManager. class .getName()) .log(Level.SEVERE, null , ex); throw ex; } } } |
SupplierLayer
теперь может быть изменен как:
Листинг 14 — SupplierLayer.java
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@Override public List getItemsForOMGraphicMenu(OMGraphic omg) { List menuItems = new ArrayList<>(); JMenuItem mnuProperties = new JMenuItem( "Properties" ); mnuProperties.addActionListener((ActionEvent ae) -> { SuppliersPropertiesDialogBox dlgProperties = new SuppliersPropertiesDialogBox( (Supplier)omg.getAttribute(LOOKUP_OBJECT)); dlgProperties.setVisible( true ); }); menuItems.add(mnuProperties); return menuItems; } |
В заключение,
- Очистите и создайте приложение, а затем запустите его
- Нажмите на кнопку Layer Controls и сделайте слой World Cities невидимым; таким образом, щелчки мышью по карте не будут выбирать города, но поставщиков
- Щелкните правой кнопкой мыши на поставщике на карте и выберите « Свойства» во всплывающем меню;появится диалоговое окно следующего рисунка; Когда вы нажимаете Сохранить, вы получаете зеленую обратную связь, что ваши изменения были успешно сохранены.
Рисунок 5 — Диалоговое окно свойств поставщика
Отлично сработано! Вы создали большую часть функциональности, и ваш дизайн позволяет вам вносить изменения без необходимости менять все слои.
Вот список TODO, которые вы можете попробовать:
- Отформатируйте текстовые поля широты / долготы в вышеприведенном диалоговом окне, чтобы они были удобочитаемыми, например, в форме
xxºyy'zzz"N|S
,xxxºyy'zzz"E|W
- подсказки : используйте
DMSCoordInfoFormatter
то, что мы показали в предыдущей статье, чтобы отформатировать двойные значения широты / долготы; - Вы можете использовать отдельные текстовые поля для часов / минут / секунд широты и долготы, чтобы пользователь мог легко вводить новые значения, не путаясь со специальными символами;
- убедитесь, что
OMPoint
отображается в новой позиции после сохранения изменений; вам необходимо добавитьPropertyChangeListener
в системуSupplierLayer
для прослушивания измененийSupplier
:
Листинг 15 — SupplierLayer.java (продолжение)
- подсказки : используйте
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
private final PropertyChangeListener listener = (PropertyChangeEvent evt) -> { if (evt.getPropertyName().equals( "latitude" ) || evt.getPropertyName().equals( "longitude" )) { Supplier supplier = (Supplier) evt.getSource(); OMGraphicList list = getList(); for (OMGraphic omPoint : list) { if (omPoint.getAttribute(LOOKUP_OBJECT).equals(supplier)) { ((OMPoint) omPoint).set(supplier.getLatitude(), supplier.getLongitude()); break ; } } repaint(); } }; public OMGraphicList init() { // ... // Add suppliers as OMPoints. for (Supplier supplier : suppliers) { // ... supplier.addPropertyChangeListener(listener); omList.add(omSupplier); } // ... } |
Чтобы вышесказанное сработало, вам нужно преобразовать Supplier
его в наблюдаемое:
Листинг 16 — Supplier.java (продолжение)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public class Supplier implements Serializable { @Transient private final PropertyChangeSupport changeSupport = new PropertyChangeSupport( this ); // ... public void setSid( int sid) { int oldSid = this .sid; this .sid = sid; changeSupport.firePropertyChange( "sid" , oldSid, sid); } // ... public void setName(String name) { String oldName = this .name; this .name = name; changeSupport.firePropertyChange( "name" , oldName, name); } // ... public void setCity(String city) { String oldCity = this .city; this .city = city; changeSupport.firePropertyChange( "city" , oldCity, city); } // ... public void setType(TYPE type) { TYPE oldType = this .type; this .type = type; changeSupport.firePropertyChange( "type" , oldType, type); } // ... public void setLatitude( double latitude) { double oldLatitude = this .latitude; this .latitude = latitude; changeSupport.firePropertyChange( "latitude" , oldLatitude, latitude); } // ... public void setLongitude( double longitude) { double oldLongitude = this .longitude; this .longitude = longitude; changeSupport.firePropertyChange( "longitude" , oldLongitude, longitude); } // ... public void addPropertyChangeListener(PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } |
- Добавить функцию перетаскивания, т.е. пользователь должен иметь возможность перетаскивать поставщика на карте в новую позицию
- Используйте советы из предыдущей статьи;
- Реализовать интерфейс
DrawingToolRequestor
- Определите и инициализируйте экземпляр
DrawingTool
вfindAndInit()
- Переопределить
select()
иdrawingComplete()
методы
- Добавьте функциональность Создать нового поставщика (метод
getItemsForMapMenu()
вSupplierLayer
).SuppliersPropertiesDialogBox
Должна отображаться с широта / долгота полей уже заселенных с координатами , где пользователь нажал на карте; тогда пользователь должен заполнить другие поля и новый поставщик должен быть добавлен в базу данных - Когда вы нажимаете кнопку
Drawing Tool Launcher
, вы можете добавлять на слой множество типов графики, что может оказаться не тем, что вам нужно.Так как мы хотим, чтобы нашSupplier
слой отображал толькоOMPoint
s, измените,openmap.components
чтобы оставить только,omdrawingtool
иompointloader
как мы уже делали в предыдущей статье - Другая проблема, с которой вы можете столкнуться, заключается в том, что при щелчке правой кнопкой мыши на
OMPoint
всплывающем меню появляется другое, чем то, через которое вы создалиgetItemsForOMGraphicMenu()
.com.bbn.openmap.tools.drawing.OMDrawingTool
содержит строкуdt.setBehaviorMask(OMDrawingTool.QUICK_CHANGE_BEHAVIOR_MASK)
.OMDrawingTool
определяет количество масок поведения, как описано в предыдущем уроке. В качестве обходного пути мы создали свой собственныйOMDrawingTool
.
Вывод
В этом руководстве мы создали трехуровневое автономное приложение, которое извлекало данные из реляционной базы данных с использованием JPA и отображало их как слой OpenMap. Мы увидели, как свободно связать представление с контроллером с помощью Lookup
API-интерфейса NetBeans .
Вы должны были получить представление о том, как разрабатывать такие приложения, но не используйте этот код в реальных и особенно критических приложениях, как есть. Код содержит ошибки и не является ни эффективным, ни поточно-ориентированным (например, смотрите, com.bbn.openmap.layer.location.LayerLocationLayer
как извлечь данные из базы данных в другом потоке).
Существуют и другие технологии, которые вы можете использовать для замены различных уровней, например:
- Замените JPA на чистую лямбда-среду Java 8 для доступа к базе данных ( Speedment )
- Используйте DukeScript, чтобы приклеить JPA к вашему виду
Например, поскольку ваше представление зависит от IDBManager
конкретной реализации (а не EntityManager
от JPA), а не от каких-либо изменений модели (до тех пор, пока методы Supplier
s и IDBManager
s не меняются). Затем вы можете заменить JPA на Speedment, например, без каких-либо изменений в вашем представлении.
Если время и пространство позволяют, мы можем исследовать их в следующей статье.
использованная литература
- Руководство разработчика OpenMap
- OpenMap Разработчики Советы
- Бауэр и др. и др.(2016), Java Persistence with Hibernate , 2nd Ed., Manning.
- Coehlo H., Kiourtzoglou B., Мини-книга API персистентности Java , JavaCodeGeeks .
- Эппле Т. (2009), «Объяснение поисков NetBeans!», DZone
- Эппле Т. (2016), « JPA и Dukescript »
- Гонсалвес А. (2013), Начало Java EE 7 , Apress.
- Keith M. & Schincariol M. (2013), Pro JPA 2 — Освоение API персистентности Java ™ , 2-е изд., APress.
- Костарас И. Блог , « Слабая связь»