He pasado bastante tiempo jugando con Couchbase y aplicaciones web usando el impresionante Java SDK, pero ¿sabías que también puedes crear aplicaciones de escritorio que usen Couchbase para almacenar datos? Frameworks Java de escritorio como JavaFX pueden hacer uso del SDK Java de Couchbase, pero probablemente no sea una buena idea usar un SDK pensado para servidor en tu aplicación de cara al cliente. En su lugar, puedes utilizar la solución móvil de Couchbase para crear aplicaciones de escritorio orientadas al cliente. Utiliza las mismas APIs que Android, pero fue diseñado pensando en los ordenadores de sobremesa.
Vamos a echar un vistazo a la construcción de una sencilla aplicación de escritorio utilizando JavaFX, Couchbase Lite, e incluso Couchbase Sync Gateway para sincronizar estos datos entre ordenadores.
Requisitos
Hay algunos requisitos que deben cumplirse para que este proyecto tenga éxito.
- JDK 1.7+
- Maven
- Pasarela de sincronización Couchbase
Este proyecto utilizará Maven para reunir nuestras dependencias y construir un archivo JAR. Aunque Sync Gateway no es realmente un requisito, lo es si desea agregar soporte de sincronización a su aplicación.
Creación de un nuevo proyecto JavaFX con Maven
Necesitamos crear un proyecto Maven básico. Esto se puede hacer en un IDE de su elección, o se puede hacer manualmente. Esencialmente lo que necesitaremos es la siguiente estructura de archivos y directorios:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
src principal java com couchbase CouchbaseSingleton.java Principal.java Todo.java TodoFXController.java recursos TodoFX.fxml pom.xml |
Vamos a entrar en los detalles de lo que contiene cada archivo cuando empecemos a desarrollar. Por ahora tenemos que configurar nuestro Maven pom.xml para que obtenga las dependencias necesarias.
Abra el archivo pom.xml e incluya lo siguiente:
1 2 3 4 5 6 7 8 9 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
com.couchbase couchbase-javafx-ejemplo 4.0.0 Couchbase JavaFX Ejemplo tarro 1.0 com.couchbase.lite couchbase-lite-java 1.3.0 com.couchbase.lite couchbase-lite-java-sqlcipher 1.3.0 junit junit 4.12 prueba com.zenjava javafx-maven-plugin 8.5.0 com.couchbase.Principal org.apache.maven.plugins maven-compilador-plugin 3.3 1.7 1.7 maven-montaje-plugin 2.2.1 tarro-con-dependencias verdadero lib/ com.couchbase.Principal escriba a-montaje paquete solo |
Sin entrar en demasiados detalles innecesarios en el archivo Maven anterior, queremos prestar atención a algunas cosas en particular.
1 2 3 4 5 6 7 |
com.couchbase.lite couchbase-lite-java 1.3.0 |
La dependencia anterior dice que estamos incluyendo Couchbase Lite en nuestro proyecto. Recuerda, Couchbase Lite es una base de datos local que no se encuentra en un servidor en algún lugar. Los datos se almacenan localmente y el componente se incluye dentro de tu aplicación.
También queremos prestar atención al siguiente plugin:
1 2 3 4 5 6 7 8 9 10 |
com.zenjava javafx-maven-plugin 8.5.0 com.couchbase.Principal |
El plugin anterior es para crear un proyecto JavaFX. Por supuesto, la creación de este proyecto es mucho más fácil cuando se utiliza un IDE como IntelliJ, a pesar de que no es necesario.
Creación de la clase Singleton de Couchbase
Antes de dedicarnos a crear la interfaz de usuario y los controladores para nuestro proyecto JavaFX, preocupémonos de cómo se van a manejar los datos.
Por simplicidad, es una gran idea crear una clase singleton para gestionar los datos en todo nuestro proyecto. También funciona bastante bien a la hora de configurar escuchadores de datos para evitar tener que escribir consultas en todas partes de la aplicación. Vamos a seguir adelante y abrir el proyecto de src/main/java/com/couchbase/CouchbaseSingleton.java e incluya el siguiente código. Lo desglosaremos después.
1 2 3 4 5 6 7 8 9 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
paquete com.couchbase; importar com.couchbase.lite.*; importar com.couchbase.lite.replicador.Replicación; importar java.red.URL; importar java.util.ArrayList; importar java.util.HashMap; importar java.util.Iterador; importar java.util.Mapa; público clase CouchbaseSingleton { privado Director director; privado Base de datos base de datos; privado Replicación pushReplication; privado Replicación pullReplication; privado estático CouchbaseSingleton instancia = null; privado CouchbaseSingleton() { pruebe { este.director = nuevo Director(nuevo JavaContext("datos"), Director.OPCIONES_POR_DEFECTO); este.base de datos = este.director.getDatabase("proyecto fx"); Ver todoVer = base de datos.getView("todos"); todoVer.setMap(nuevo Mapeador() { @Anular público void mapa(Mapa<Cadena, Objeto> documento, Emisor emisor) { emisor.emite(documento.consiga("_id"), documento); } }, "1"); } captura (Excepción e) { e.printStackTrace(); } } público estático CouchbaseSingleton getInstance() { si(instancia == null) { instancia = nuevo CouchbaseSingleton(); } devolver instancia; } público Base de datos getDatabase() { devolver este.base de datos; } público void startReplication(URL pasarela, booleano continuo) { este.pushReplication = este.base de datos.createPushReplication(pasarela); este.pullReplication = este.base de datos.createPullReplication(pasarela); este.pushReplication.setContinuous(continuo); este.pullReplication.setContinuous(continuo); este.pushReplication.iniciar(); este.pullReplication.iniciar(); } público void stopReplication() { este.pushReplication.stop(); este.pullReplication.stop(); } público Todo guardar(Todo todo) { Mapa<Cadena, Objeto> propiedades = nuevo HashMap<Cadena, Objeto>(); Documento documento = este.base de datos.crearDocumento(); propiedades.poner("tipo", "todo"); propiedades.poner("título, todo.getTitle()); propiedades.poner("descripción", todo.getDescription()); pruebe { todo.setDocumentId(documento.putProperties(propiedades).getDocument().getId()); } captura (Excepción e) { e.printStackTrace(); } devolver todo; } público ArrayList consulta() { ArrayList resultados = nuevo ArrayList(); pruebe { Ver todoVer = este.base de datos.getView("todos"); Consulta consulta = todoVer.createQuery(); QueryEnumerator resultado = consulta.ejecute(); Documento documento = null; para (Iterador it = resultado; it.hasNext(); ) { Fila de consulta fila = it.siguiente(); documento = fila.getDocument(); resultados.añada(nuevo Todo(documento.getId(), (Cadena) documento.getProperty("título), (Cadena) documento.getProperty("descripción"))); } } captura (Excepción e) { e.printStackTrace(); } devolver resultados; } } |
Lo anterior era mucho para asimilar, pero era necesario para evitar confusiones.
Dentro de la CouchbaseSingleton
hay cuatro variables privadas. El gestor de base de datos nos permitirá tanto abrir nuestra base de datos como crearla. Los objetos de replicación se encargan de la sincronización en ambos sentidos.
En el constructor
creamos y abrimos una base de datos llamada proyecto fx y configuraremos una vista que utilizaremos a la hora de consultar datos. Esta vista llamada todos
emitirá un par clave-valor de id de documento y documento para cada documento almacenado en la base de datos local. La dirección constructor
es privado porque lo instanciamos a través del método estático getInstance
.
Aunque no veremos la sincronización hasta más adelante en la guía, es una buena idea sentar las bases. Esencialmente queremos definir que tendremos replicación continua hacia y desde una URL de puerta de enlace de sincronización particular. También queremos ser capaces de detener la replicación cuando la aplicación se cierre. Esto nos lleva a nuestros métodos para guardar y cargar datos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
público Todo guardar(Todo todo) { Mapa<Cadena, Objeto> propiedades = nuevo HashMap<Cadena, Objeto>(); Documento documento = este.base de datos.crearDocumento(); propiedades.poner("tipo", "todo"); propiedades.poner("título, todo.getTitle()); propiedades.poner("descripción", todo.getDescription()); pruebe { todo.setDocumentId(documento.putProperties(propiedades).getDocument().getId()); } captura (Excepción e) { e.printStackTrace(); } devolver todo; } |
Nuestra guardar
tomará un método Todo
y guardarlo en la base de datos. El resultado será un Todo
que contiene el identificador del documento que se devuelve al método de llamada. Este Todo
es sencilla. Acepta información básica como id, título y descripción, y tiene los métodos getter y setter correspondientes. Como referencia, se parece a lo siguiente y existe en el proyecto de src/main/java/com/couchbase/Todo.java archivo.
1 2 3 4 5 6 7 8 9 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 |
paquete com.couchbase; importar java.util.*; público clase Todo { privado Cadena documentId; privado Cadena título; privado Cadena descripción; Todo(Cadena documentId, Cadena título, Cadena descripción) { este.documentId = documentId; este.título = título; este.descripción = descripción; } Todo(Cadena título, Cadena descripción) { este.documentId = UUID.randomUUID().toString(); este.título = título; este.descripción = descripción; } público void setDocumentId(Cadena documentId) { este.documentId = documentId; } público Cadena getDocumentId() { devolver este.documentId; } público Cadena getTitle() { devolver este.título; } público Cadena getDescription() { devolver este.descripción; } } |
Esto nos deja con nuestra última función relacionada con los datos, la función consulta
función.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
público ArrayList consulta() { ArrayList resultados = nuevo ArrayList(); pruebe { Ver todoVer = este.base de datos.getView("todos"); Consulta consulta = todoVer.createQuery(); QueryEnumerator resultado = consulta.ejecute(); Documento documento = null; para (Iterador it = resultado; it.hasNext(); ) { Fila de consulta fila = it.siguiente(); documento = fila.getDocument(); resultados.añada(nuevo Todo(documento.getId(), (Cadena) documento.getProperty("título), (Cadena) documento.getProperty("descripción"))); } } captura (Excepción e) { e.printStackTrace(); } devolver resultados; } |
¿Recuerdas la vista que creamos? Esta vez vamos a consultarla. El conjunto de resultados se cargará en un array de Todo
objetos. Esto hace que nuestro capa de datos lo que nos permite centrarnos en el desarrollo real de la aplicación.
Diseño de la aplicación de escritorio
Aunque no es necesario, la aplicación JavaFX, SceneBuilderhace que sea muy sencillo crear una interfaz gráfica de usuario y su correspondiente clase de controlador. Si decide no utilizarlo, abra el archivo src/main/resources/TodoFX.fxml e incluya el siguiente marcado XML:
1 2 3 4 5 6 7 8 9 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 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 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
<!--?xml versión="1.0" codificación="UTF-8"?--> <!--?importar javafx.escena.control.*?--> <!--?importar java.lang.*?--> <!--?importar javafx.escena.diseño.*?--> <textarea> <Botón fx:id="fxSave" diseñoX="530.0" diseñoY="365.0" mnemonicParsing="falso" prefHeight="25.0" prefAncho="60.0" texto="Guardar" /> </niños> </Pane> </código> </pre> <p>En sobre marcado se dé us a INTERFAZ DE USUARIO que mira como el siguiente:</p> <p><img src="/wp-content/original-assets/2016/september/using-couchbase-in-a-javafx-desktop-application/todo-javafx-example.png" /></p> <p>Nada demasiado elegante en el sobre, correcto?</p> <p>Nosotros tienen a simple INTERFAZ DE USUARIO con a lista, dos entrada campos, y a guardar botón, como descrito en el XML marcado. En el marcado nosotros también referencia <código>com.couchbase.TodoFXController</código>. Este es el lógica que se sea encuadernado a el particular FX ver. Abrir el proyectosrc/main/java/com/couchbase/TodoFXController.java e incluye lo siguiente: <code> paquete com.couchbase; import com.couchbase.lite.*; import com.couchbase.lite.Database.ChangeListener; import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.*; import javafx.util.Callback; import java.net.URL; import java.util.*; public class TodoFXController implements Inicializable { privado CouchbaseSingleton couchbase; @FXML privado TextField fxTitle; @FXML privado TextArea fxDescription; @FXML privado ListView fxListView; @FXML privado Button fxSave; @Override public void initialize(URL fxmlFileLocation, ResourceBundle resources) { intentar { this.couchbase = CouchbaseSingleton.getInstance(); fxListView.getItems().addAll(this.couchbase.query()); this.couchbase.getDatabase().addChangeListener(new ChangeListener() { @Override public void changed(Evento Database.ChangeEvent) { for(int i = 0; i < event.getChanges().size(); i++) { final Document retrievedDocument = couchbase.getDatabase().getDocument(event.getChanges().get(i).getDocumentId()); Plataforma.runLater(new Runnable() { @Override public void run() { int documentIndex = indexOfByDocumentId(retrievedDocument.getId(), fxListView.getItems()); for(int j = 0; j -1) { fxListView.getItems().remove(documentIndex); } } else { if (documentIndex == -1) { fxListView.getItems().add(new Todo(retrievedDocument.getId(), (String) retrievedDocument.getProperty("title"), (String) retrievedDocument.getProperty("description"))); } else { fxListView.getItems().remove(documentIndex); fxListView.getItems().add(new Todo(retrievedDocument.getId(), (String) retrievedDocument.getProperty("title"), (String) retrievedDocument.getProperty("description"))); } } } }); } } }); } catch (Exception e) { e.printStackTrace(); } fxListView.setCellFactory(new Callback<ListView, ListCell>(){ @Override public ListCell call(ListView p) { ListCell cell = new ListCell(){ @Override protected void updateItem(Todo t, boolean bln) { super.updateItem(t, bln); if (t != null) { setText(t.getTitle()); } } }; devolver celda; } }); fxSave.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { if(!fxTitle.getText().equals("") && !fxDescription.getText().equals("")) { fxListView.getItems().add(couchbase.save(new Todo(fxTitle.getText(), fxDescription.getText()))); fxTitle.setText(""); fxDescription.setText(""); } else { Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setTitle("Falta información"); alert.setHeaderText(null); alert.setContentText("Tanto el título como la descripción son necesarios para este ejemplo"); alert.showAndWait(); } } }); } private int indexOfByDocumentId(String needle, ObservableList haystack) { int resultado = -1; for(int i = 0; i < pajar.tamaño(); i++) { if(haystack.get(i).getDocumentId().equals(needle)) { resultado = i; romper; } } Devuelve el resultado; } } Hay mucho que asimilar cuando se trata del código anterior, por lo que nos' re ir a a romper it abajo.</p><pre> <código> @FXML privado Campo de texto fxTítulo; @FXML privado Área de texto fxDescription; @FXML privado ListView fxListView; @FXML privado Botón fxSave; </código> </pre> <p>Recuerde el FXML archivo? Cada de el sobre variables son mapeado a el componentes de que archivo. Sin embargo, qué nosotros realmente atención acerca de es el utilice de el <código>Inicializable</código> interfaz que nosotrosque estamos implementando. Esto requiere una función initialize donde nos 'll configuración nuestra componente y base de datos oyentes.</p><pre> <código> fxListView.setCellFactory(nuevo Devolución de llamada<ListView<Todo>, ListaCelda<Todo>>(){ @Anular público ListaCelda<Todo> llame a(ListView<Todo> p) { ListaCelda<Todo> célula = nuevo ListaCelda<Todo>(){ @Anular protegido void updateItem(Todo t, booleano bln) { super.updateItem(t, bln); si (t != null) { setText(t.getTitle()); } } }; devolver célula; } }); </código> </pre> <p>Porque nosotros're utilizando una clase personalizada Todo en nuestra lista, tenemos que configurar cómo las filas de la lista muestran los datos. Por defecto son cadenas, pero en realidad queremos extraer el título de cualquiera de nuestros elementos de datos y mostrar eso en su lugar. . <code> this.couchbase = CouchbaseSingleton.getInstance(); fxListView.getItems().addAll(this.couchbase.query()); En lo anterior estamos obteniendo la instancia de base de datos abierta, realizando una consulta, y añadiendo todos los datos a la lista FX que está en la pantalla. Esto es una cosa de una sola vez que se hace cuando se carga la aplicación. Todas las futuras cargas de datos se realizan a través de un listener. El escuchador de cambios de la base de datos escuchará todos los cambios en los datos mientras la aplicación esté abierta. Los cambios pueden ocurrir en masa, así que hacemos un bucle a través de los cambios y recuperamos los documentos. Si el documento es nuevo, lo añadimos a la lista. Si el documento tiene un indicador de borrado, elimínalo de la lista. Si el documento es un cambio de un documento existente, elimina el antiguo y añade el nuevo a la lista. Todo esto se hace en un Por último tenemos nuestro botón de guardar que tiene su propio evento de clic. Si se hace clic, y los elementos de entrada se rellenan, guardar los datos en la base de datos. A partir de ahora, tenemos una aplicación de escritorio offline que guarda los datos en Couchbase Lite. Ahora podemos preocuparnos de la sincronización / replicación de datos. . Sincronización de datos entre el escritorio y el servidorLa siguiente parte es fácil gracias a Couchbase Mobile. Necesitarás Couchbase Sync Gateway instalado y funcionando antes de empezar a trastear con el código de la aplicación de escritorio. Con Sync Gateway instalado, crea el siguiente archivo de configuración básico: . <code> { "log":["CRUD+", "REST+", "Changes+", "Attach+"], "bases de datos": { "fx-example": { "servidor": "morsa:", "sync":` function (doc) { canal (doc.canales); } `, "usuarios": { "INVITADO": { "disabled": falso, "admin_channels": ["*"] } } } } } El archivo de configuración anterior es más o menos tan básico como se puede conseguir. Crear una partición <p>Rebotando volver en el JavaFX aplicación. Abrir el proyecto's src/main/java/com/couchbase/Main.java e incluye lo siguiente: <code> this.couchbase = CouchbaseSingleton.getInstance(); this.couchbase.startReplication(new URL("http://localhost:4984/fx-example/"), true); Lo anterior debe incluirse en el método Sync Gateway. ConclusiónAcabas de ver cómo crear una sencilla aplicación JavaFX que almacena datos en Couchbase Lite y sincroniza los datos. Para ejecutar esta aplicación usando Maven y la línea de comandos, ejecute lo siguiente: <code> mvn jfx:run Debido a que Couchbase es tan flexible, esta misma aplicación puede extenderse a móvil con muy pocos cambios en el código de la aplicación. Si usted' d como a descargar el completo proyecto en este guía, it puede sea encontrado en GitHub <a href="https://github.com/couchbaselabs/couchbase-lite-javafx-example">aquí</a>.</p></textarea> |