Imprimir

Siguiendo los pasos anteriores de este tutorial, ya debes tener una aplicación Java creada en NetBeans con una ventana JavaFX diseñada con Scene Builder. Ahora trataremos de mostrar el contenido de la base de datos en esa ventana.

Conexión con la base de datos

Empezaremos estableciendo la conexión con la base de datos desde el código, partiendo del supuesto de que anteriormente se han creado las clases entidad y el archivo de unidad de persistencia como se explicó en pasos anteriores.

Como ya se explicó se necesita un objeto EntityManager que mantenga la información de la conexión con la base de datos. En este caso se declararán las variables del EntityManager y del EntityManagerFactory como propiedades de la clase (fuera de cualquier método) ya que necesitaremos tener acceso a ellos desde varios métodos. Todo esto se realizará en la clase principal del proyecto, para realizar la conexión en cuanto se ejecute la aplicación

public class Main extends Application {
    
    private EntityManagerFactory emf;
    private EntityManager em;

Dentro del método start deberemos obtener los objetos correspondientes para el EntityManager y el EntityManagerFactory correspondientes a la unidad de persistencia que se haya creado para la conexión a la base de datos. Este código puede añadirse, por ejemplo, una vez cargados los elementos de la ventana.

public void start(Stage primaryStage) throws IOException {
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ContactosView.fxml"));
    Parent root = fxmlLoader.load();

    emf = Persistence.createEntityManagerFactory("AgendaContactosPU");
    em = emf.createEntityManager();

Como ya se explicó en un artículo anterior de este tutorial, al finalizar la ejecución de la aplicación conviene cerrar correctamente la conexión con la base de datos. Dicho código se puede incluir en JavaFX dentro del método stop cuyo contenido se ejecutará precisamente cuando finalice la ejecución. Este método pertenece a la clase Application de JavaFX, la cual se está utilizando como extensión para la aplicación que estás creando, de manera que la clase que estás creando se tratará como la principal de una aplicación JavaFX. Puedes verlo en la cabecera de la clase como:

public class Main extends Application

Por tanto, hay que sobrecargar (override), ese método stop, es decir, añadirle nuevas funcionalidades, para que se encargue de cerrar la conexión. Puedes crear fácilmente dicho método posicionando el cursos fuera de cualquier método del código (por ejemplo, después del método start) y, tras abrir el menú contextual, seleccionar la opción Insert code > Override method > stop().

Sustituye el código generado por el correspondiente al cierre de la conexión, que ya se explicó en su momento. Comprueba que el nombre de la base de datos corresponde al que utilices.

@Override
public void stop() throws Exception {
    em.close(); 
    emf.close(); 
    try { 
        DriverManager.getConnection("jdbc:derby:BDAgendaContactos;shutdown=true"); 
    } catch (SQLException ex) { 
    }        
}

La llamada al método super.stop(), que aparece al generar dicho método, puede eliminarse. Esa llamada se encargaría de ejecutar el código original contenido en el método stop de la clase Application, pero según la documentación de esa clase, el método no realiza ninguna acción. Sólo sirve para que en las clases que extiendan a Application se incluya el código deseado, como se acaba de hacer.

Obtención del objeto asociado al controlador del archivo FXML

Al crear el archivo FXML se indicó que tuviera asociada una clase Java que hiciera las funciones de controlador (Controller) asociado a ese archivo. De esta manera podemos tener una clase Java donde se indiquen las acciones que deben realizar los elementos contenidos en el diseño de la ventana.

Para poder utilizar este controlador, necesitamos tener un objeto asociado a su clase. Esto se debe realizar con el siguiente código, que puedes incluir en cualquier lugar posterior a la carga del contenido del archivo FXML, por ejemplo, después de la conexión a la base de datos que se hizo antes.

ContactosViewController contactosViewController = (ContactosViewController) fxmlLoader.getController();                

Según el código anterior, la variable contactosViewController almacenará el objeto de la clase ContactosViewController que podrás usar para realizar las funciones de control de la ventana.

Paso del EntityManager al controlador

Para que en la ventana se puedan mostrar datos contenidos en la base de datos, el controlador debe tener acceso al objeto EntityManager que permite el acceso a los datos. Una manera de conseguirlo es pasarle el objeto EntityManager a través de un nuevo método set (setEntityManager) que hay que construir en la clase controladora. Por tanto, crea una propiedad EntityManager y su correspondiente método setEntityManager en la clase controladora:

public class ContactosViewController implements Initializable {

    private EntityManager entityManager;
    
    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

Ahora ya se podrá utilizar este método desde la clase principal de la aplicación, donde se había obtenido el objeto entityManager, pasando dicho objeto por parámetro:

// Después de obtener el objeto del controlador y del EntityManager:
contactosViewController.setEntityManager(em);

Tabla para mostrar los datos

La intención es mostrar en la ventana los registros contenidos en la tabla Persona de la base de datos. Los datos se van a mostrar en una tabla (TableView) donde aparezcan algunas de las columnas de Persona.

Añadir tabla en la estructura de la ventana (FXML)

Podemos empezar añadiendo un elemento TableView a la estructura de la ventana que se encuentra en el archivo FXML. Si no lo tienes abierto en Scene Builder, haz doble clic sobre él desde NetBeans para abrirlo.

Si aún tienes el Label que se colocó a modo de prueba elimínalo y arrastra un control de tipo TableView a la ventana.

Captura de pantalla de 2017 04 16 20 07 36 fed20

Añadir nueva columna

Inicialmente la tabla está formada por 2 columnas. En este ejemplo vamos a mostrar el nombre, apellidos y correo electrónico de cada contacto, por lo que vamos a ver cómo añadir una nueva columna a la tabla.

Selecciona el control TableColumn de la paleta del panel izquierdo, y arrástralo al interior de la tabla.

Captura de pantalla de 2017 04 16 20 22 22 e6239

Observarás que la tabla ya se ha quedado pequeña, por lo que deberías agrandarlo horizontalmente. Para ello, selecciona la tabla que habías añadidio, lo que puedes hacerlo fácilmente desde el panel Hierarchy (jerarquía) de la parte inferior izquierda, y arrastra el indicador derecho de la tabla hasta alcanzar el tamaño deseado.

Captura de pantalla de 2017 04 16 20 26 54 cb963

Puedes cambiar el ancho de cada columna de manera similar, seleccionando en cada caso la columna oportuna desde el panel Hierarchy o haciendo clic en la cabecera de la columna en la tabla. También podrás hacerlo sin tener que seleccionar cada columna, simplemente arrastrando el separador que hay entre cada columna en la vista de la tabla.

Captura de pantalla de 2017 04 16 20 33 46 7923d

Desde el panel Properties de la parte derecha, puedes cambiar el título de las columnas, usando la propiedad Text

Captura de pantalla de 2017 04 16 21 30 43 0a717

Asignar identificador (id) a la tabla y columnas

Para que desde la clase Java controladora se tenga acceso a la tabla y a sus columnas, se les debe asignar un identificador a cada uno de ellos. Esto puedes hacerlo desde el panel derecho, dentro de la sección Code, en la propiedad fx:id, seleccionando previamente el elemento al que le deseas asignar un identificador. Puedes hacer cómodamente la selección de cada elemento desde el panel Hierarchy.

En este ejemplo se han asignado los siguientes identificadores a la tabla y a cada columna: tableViewContactos, columnNombre, columnApellidos, columnEmail.

Guarda los cambios de Scene Builder, vuelve a NetBeans y sobre el archivo FXML abre el menú contextual para utilizar la opción Make Controller.

Captura de pantalla de 2017 04 16 20 39 43 a85ef

Esa acción crea en la clase controladora una propiedad para cada elemento del archivo FXML al que se le haya asignado un identificador. Por tanto, ahora en la clase controladora verás que se han creado automáticamente las siguientes propiedades:

@FXML
private TableView<?> tableViewContactos;
@FXML
private TableColumn<?, ?> columnNombre;
@FXML
private TableColumn<?, ?> columnApellidos;
@FXML
private TableColumn<?, ?> columnEmail;

Estas propiedades nos permitirán indicar posteriormente qué información de la base de datos se debe mostrar en cada columna.

Antes debemos cumplimentar los atributos que aparecen con interrogantes. En el caso del TableView se debe indicar el tipo de objetos que se van a mostrar en la tabla (en este caso <Persona>), y en las columnas se indicará también el tipo de objetos desde el que se obtiene el contenido a mostrar, junto con el tipo de dato concreto que corresponde con el dato a mostrar en la columna. Los datos que se quieren mostrar pertenecen todos a objetos Persona y todos ellos (nombre, apellidos y email) son de tipo String, por lo que se indicará <Persona, String>.

@FXML
private TableView<Persona> tableViewContactos;
@FXML
private TableColumn<Persona, String> columnNombre;
@FXML
private TableColumn<Persona, String> columnApellidos;
@FXML
private TableColumn<Persona, String> columnEmail;

Asociar columnas a propiedades de la clase entidad

Cada columna tiene que estar asociada a una de las propiedades de la clase entidad que se ha indicado para el TableView. En este ejemplo se va a usar la clase Persona, por lo que habrá que indicar qué propiedad de la clase Persona se mostrará en la columna columnNombre, y así sucesivamente con cada columna.

Esto se hará dentro del método initialize de la clase controladora de la siguiente manera:

@Override
public void initialize(URL url, ResourceBundle rb) {
    columnNombre.setCellValueFactory(new PropertyValueFactory<>("nombre"));
    columnApellidos.setCellValueFactory(new PropertyValueFactory<>("apellidos"));
    columnEmail.setCellValueFactory(new PropertyValueFactory<>("email"));
}    

Realizar consulta a la base de datos y rellenar el TableView

Para poder llenar el TableView con datos obtenidos desde la base de datos, previamente se le debe realizar una consulta. Una vez obtenidos los resultados de la consulta como un objeto de tipo List, se pasarán dichos resultados al TableView.

Por ejemplo, para mostrar todos los registros de la tabla Persona se puede utilizar la consulta predefinida "Persona.findAll" que se había generado automática en la clase entidad Persona (si no lo recuerdas abre la clase Persona y observa que se encuentra declarada esa consulta). En un paso anterior de este tutorial se explicó cómo se obtenía una lista de resultados desde la base de datos, por lo que sólo quedaría pasar la lista al TableView con el método setItems, que requiere convertir la lista de tipo List a al tipo ObservableArrayList, lo cual se hace fácilmente con el método FXCollections.observableArrayList.

Si todo eso se mete en un método (cargarTodasPersonas) dentro de la clase controladora, para que pueda ser utilizado posteriormente desde la clase principal, quedaría como:

public void cargarTodasPersonas() {
    Query queryPersonaFindAll = entityManager.createNamedQuery("Persona.findAll");
    List<Persona> listPersona = queryPersonaFindAll.getResultList();
    tableViewContactos.setItems(FXCollections.observableArrayList(listPersona));
}  

La llamada a este método deberás realizarla desde la clase principal, una vez que se ha obtenido el objeto asociado a la clase controladora, y después de pasar el EntityManager a la clase controladora (ya se había hecho anteriormente), por lo que puede quedar como:

ContactosViewController contactosViewController = (ContactosViewController) fxmlLoader.getController();                
contactosViewController.setEntityManager(em);
contactosViewController.cargarTodasPersonas();

Así ya puedes ejecutar la aplicación y obtener un resultado como este (procura tener algunos datos introducidos en la tabla como se explicó en artículos anteriores de este tutorial):

Captura de pantalla de 2017 04 16 21 33 16 a5e91 

Mostrar objetos de tabla relacionada

Según el diseño que se había hecho de la base de datos, la tabla Persona está relacionada con la tabla Provincia, de manera que la clase entidad Persona tiene una propiedad Provincia donde se almacena toda la información (id, código, nombre) referente a la provincia a la que pertenece una determinada persona.

Así que ahora vamos a ver que para mostrar en el TableView un dato que no sea directamente de tipo String o de algún tipo de dato básico, hay que escribir el código de manera diferente. En este caso, queremos mostrar en el TableView un dato de tipo Provincia, pero como un objeto Provincia está compuesto de varios datos, vamos a mostrar el nombre de dicha provincia.

En primer lugar asegúrate de crear en la tabla Provincia de la base de datos algunos registros con datos de provincias.

Screen Shot 2017 04 17 at 20.58.29 ec13a

Ahora indica en algunos registros de la tabla Persona cuál va a ser su provincia. Ten en cuenta que ambas tablas estás relacionadas por el ID de la Provincia, así que debes indicar el ID de la provincia.

Screen Shot 2017 04 17 at 21.01.54 c0e57

Desde Scene Builder edita el archivo FXML y añade una nueva columna al TableView para mostrar la Provincia. Inicialmente agranda bastante el ancho de esta columna para que veas cómo se muestra el objeto Provincia si intentamos mostrar su contenido igual que el resto de columnas.

Screen Shot 2017 04 17 at 21.04.41 a21dd

Recuerda siempre guardar los cambios en Scene Builder y hacer Make Controller en el archivo FXML para que aparezcan los identificadores en la clase controladora.

Asegúrate de que en la clase controladora ha aparecido el identificador de la columna para la provincia, y añade una nueva línea en el método initialize para que intente mostrar la propiedad provincia del objeto Persona que se muestre en cada fila del TableView.

@FXML
private TableColumn<Persona, String> columnProvincia;

@Override
public void initialize(URL url, ResourceBundle rb) {
    columnNombre.setCellValueFactory(new PropertyValueFactory<>("nombre"));
    columnApellidos.setCellValueFactory(new PropertyValueFactory<>("apellidos"));
    columnEmail.setCellValueFactory(new PropertyValueFactory<>("email"));
    columnProvincia.setCellValueFactory(new PropertyValueFactory<>("provincia"));
}

Si ejecutas ahora la aplicación verás que el dato de la provincia no aparece como nos gustaría, es decir, sólo con el nombre de la provincia. El contenido que aparece ahora mismo es el valor retornado por el método toString() que se ha generado automáticamente al crear la clase entidad Provincia

Screen Shot 2017 04 17 at 21.16.50 68c80

Por tanto, hay que cambiar el código correspondiente a la asociación de la propiedad provincia con la columna de la siguiente manera:

columnProvincia.setCellValueFactory(
    cellData -> {
        SimpleStringProperty property = new SimpleStringProperty();
        if (cellData.getValue().getProvincia() != null) {
            property.setValue(cellData.getValue().getProvincia().getNombre());
        }
        return property;
    });

Es ese código la llamada a cellData.getValue() obtiene el objeto Persona correspondiente una determinada fila del TableView. Por tanto, se utiliza seguidamente el método getProvincia() para obtener el objeto Provincia relacionado con dicho objeto Persona. Como nuestra intención es conocer el nombre de la provincia, se usa el método getNombre()  para obtener como String el nombre de la provincia, que será el dato que aparezca finalmente.

Observa que también se hace una comprobación por si una determinada persona no tiene asociada ninguna provincia. Si no se hiciera esa comprobación se obtendría un excepción de tipo NullPointerException, ya que la llamada a cellData.getValue().getProvincia() devolvería un valor Null y no se puede obtener así el nombre.

Screen Shot 2017 04 17 at 21.28.03 ec252

Código fuente 

Por si has perdido el hilo en algún apartado y has colocado mal alguna parte del código, aquí tienes el código fuente completo que se ha obtenido para esta parte del tutorial:

Clase principal

package es.javiergarciaescobedo.agendacontactos;

import java.io.IOException;
import java.sql.DriverManager;
import java.sql.SQLException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main extends Application {
    
    private EntityManagerFactory emf;
    private EntityManager em;
    
    @Override
    public void start(Stage primaryStage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ContactosView.fxml"));
        Parent root = fxmlLoader.load();

        emf = Persistence.createEntityManagerFactory("AgendaContactosPU");
        em = emf.createEntityManager();

        ContactosViewController contactosViewController = (ContactosViewController) fxmlLoader.getController();                
        contactosViewController.setEntityManager(em);
        contactosViewController.cargarTodasPersonas();
        
        Scene scene = new Scene(root, 300, 250);

        primaryStage.setTitle("Agenda Contactos");
        primaryStage.setScene(scene);
        primaryStage.show();                
    }

    @Override
    public void stop() throws Exception {
        em.close(); 
        emf.close(); 
        try { 
            DriverManager.getConnection("jdbc:derby:BDAgendaContactos;shutdown=true"); 
        } catch (SQLException ex) { 
        }        
    }

    public static void main(String[] args) {
        launch(args);
    }
    
}

Clase controladora

package es.javiergarciaescobedo.agendacontactos;

import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javax.persistence.EntityManager;
import javax.persistence.Query;

public class ContactosViewController implements Initializable {

    private EntityManager entityManager;
    @FXML
    private TableView<Persona> tableViewContactos;
    @FXML
    private TableColumn<Persona, String> columnNombre;
    @FXML
    private TableColumn<Persona, String> columnApellidos;
    @FXML
    private TableColumn<Persona, String> columnEmail;
    @FXML
    private TableColumn<Persona, String> columnProvincia;
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        columnNombre.setCellValueFactory(new PropertyValueFactory<>("nombre"));
        columnApellidos.setCellValueFactory(new PropertyValueFactory<>("apellidos"));
        columnEmail.setCellValueFactory(new PropertyValueFactory<>("email"));
        columnProvincia.setCellValueFactory(
            cellData -> {
                SimpleStringProperty property = new SimpleStringProperty();
                if (cellData.getValue().getProvincia() != null) {
                    property.setValue(cellData.getValue().getProvincia().getNombre());
                }
                return property;
            });		
    }    

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public void cargarTodasPersonas() {
        Query queryPersonaFindAll = entityManager.createNamedQuery("Persona.findAll");
        List<Persona> listPersona = queryPersonaFindAll.getResultList();
        tableViewContactos.setItems(FXCollections.observableArrayList(listPersona));
    }    
        
}

Archivo FXML

Contenido visto con formato XML del archivo editado con Scene Builder:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1" fx:controller="es.javiergarciaescobedo.agendacontactos.ContactosViewController">
   <opaqueInsets>
      <Insets />
   </opaqueInsets>
   <children>
      <TableView fx:id="tableViewContactos" prefHeight="268.0" prefWidth="499.0">
        <columns>
          <TableColumn fx:id="columnNombre" prefWidth="106.0" text="Nombre" />
          <TableColumn fx:id="columnApellidos" prefWidth="149.0" text="Apellidos" />
            <TableColumn fx:id="columnEmail" prefWidth="193.0" text="E-Mail" />
        </columns>
      </TableView>
   </children>
</AnchorPane>