Saltar a contenido

Proyecto de mantenimiento de Base de datos con JavaFX

Introducción

El objetivo será crear el mantenimiento de la siguiente base de datos en MySql

alt text

En el que un trabajador pertenece a un departamento y un departamento tiene un Jefe. Permitiremos que un trabajador no tenga departamento y que un departamento no tenga jefe dejando que puedan ser nulos dichos campos de ambas tablas.

Nota

En muchas ocasiones, para evitar trabajar con nulos, se decide crear una tupla general que represente la no pertenencia. Por ejemplo: Se crea la tupla en Departamento “Sin departamento”, y en trabajadores una persona llamada: “Sin Jefe

La aplicación permitirá acceder al mantenimiento de Empleados y Departamentos permitiendo filtrar la información para facilitar al usuario encontrar los datos que necesite.

Aprovecharemos las ventanas de mantenimientos para poder seleccionar la información por parte del usuario en otros mantenimientos donde sea necesaria.

Resultado final

Tenemos el código final en el repositorio

Proyecto JavaFX-MySQL

La aplicación final tiene el siguiente aspecto

alt text

El mantenimiento de Empleado permite la creación, borrado y edición y filtrar la información para su búsqueda

alt text

La ventana de Nuevo y Edición será la misma, y creará o modificará un Empleado. Facilitará al usuario la selección del Departamento al que pertenece

alt text

El mantenimiento de Departamento permite la creación, borrado y edición.

alt text

Permite seleccionar el jefe del departamento desde el mantenimiento de Empleado

alt text

Crear la base de datos

En MySql o MariaDB, crear una base de datos(la llamaremos empresa) y creamos las tablas y algunos registros iniciales para probar el funcionamiento de nuestro programa. El script puede ser el siguiente

CREATE TABLE IF NOT EXISTS Departamento (
     codigo INT AUTO_INCREMENT PRIMARY KEY,
     nombre VARCHAR(100),
     jefe INT UNIQUE
 );

 -- Crear tabla Empleado si no existe (sin clave foránea)
 CREATE TABLE IF NOT EXISTS Empleado (
     id INT AUTO_INCREMENT PRIMARY KEY,
     dni VARCHAR(9) NOT NULL UNIQUE,
     nombre VARCHAR(50),
     apellido VARCHAR(50),
     edad INT,
     departamento INT
 );

 -- Añadir clave foránea a Empleado si no existe
 ALTER TABLE Empleado
 ADD CONSTRAINT  fk_empleado_departamento
 FOREIGN KEY (departamento) REFERENCES Departamento(codigo)
     ON DELETE SET NULL ON UPDATE CASCADE;


 -- Añadir clave foránea a Departamento si no existe
 ALTER TABLE Departamento
 ADD CONSTRAINT fk_departamento_empleado
 FOREIGN KEY (jefe) REFERENCES Empleado(id)
     ON DELETE SET NULL ON UPDATE CASCADE; 

Añadimos unas tuplas de ejemplo para probar la aplicación

-- Insertar departamentos inicialmente sin jefe
INSERT INTO Departamento (codigo, nombre, jefe) VALUES
(1, 'Recursos Humanos', NULL),
(2, 'Tecnología', NULL),
(3, 'Marketing', NULL);

-- Insertar Empleados (jefes y empleados)
INSERT INTO Empleado (dni, nombre, apellido, edad, departamento) VALUES
('12345678A', 'Laura', 'Gómez', 40, 1),  -- jefe de RRHH
('23456789B', 'Carlos', 'López', 35, 2), -- jefe de Tecnología
('34567890C', 'Marta', 'Pérez', 42, 3),  -- jefe de Marketing
('45678901D', 'Juan', 'Sánchez', 38, 1), 
('56789012E', 'Ana', 'Martínez', 37, 2), 
('67890123F', 'Pedro', 'Ruiz', 28, 3),
('78901234G', 'Lucía', 'Fernández', 30, 2),
('89012345H', 'David', 'Torres', 32, 3),
('90123456I', 'Sofía', 'Ramírez', 26, 1),
('01234567J', 'Jorge', 'Hernández', 29, 2);
-- Asignar jefes a los departamentos
UPDATE Departamento SET jefe = 1 WHERE codigo = 1;
UPDATE Departamento SET jefe = 2 WHERE codigo = 2;
UPDATE Departamento SET jefe = 3 WHERE codigo = 3;             

Crear y configurar el proyecto JavaFX

En IntelliJ creamos el proyecto

alt text

Es necesario añadir en module-info.java lo siguiente

alt text

Para añadir la librería jdbc de MySQL al proyecto, lo haremos con Maven

alt text

Estructura del proyecto

Como ya vimos en Estructura de un proyecto

Vamos a organizar el proyecto en paquetes que se adapten al patrón MVC

Creamos los paquetes siguientes:

alt text

Refactoriza cambiando el nombre tanto del fichero fxml como el controles y moviéndolos a las carpetas correspondientes

alt text

Corrige la ubicación del fichero en la clase de inicio

alt text

Cada paquete nuevo es necesario registrarlo en el fichero module-info

alt text

Nota

Es posible que IntelliJ muestre un error que se corrige cuando creamos nuevas clases en los paquetes

Los recursos de la aplicación como las imágenes, páginas CSS, fuentes...las organizaremos en carpetas:

alt text

Probar que funciona correctamente la aplicación. Es posible que tengas que comentar parte de lo anterior

alt text

Importante

Recuerda descomentarlo cuando incluyas código en los paquetes

Conexión a la base de datos

Definimos la clase para la conexión a la base de datos en el en el paquete db como ya vimos en el proyecto.

DBConnection.java
public class DBConnection {
    // URL de conexión a la base de datos MySQL
    private static final String URL ="jdbc:mysql://localhost:3306/empresa"; 
    private static final String USERNAME = "root";
    private static final String PASSWORD = "password";

    private static Connection connection;

    // Constructor privado para evitar instancias directas
    private DBConnection() {}

    // Método estático para obtener la instancia única de la conexión
    public static Connection getConnection() {
        if (connection == null) {
            // Bloqueo sincronizado para evitar concurrencia
            synchronized (DBConnection.class) {
                if (connection == null) {
                    try {
                       // Establecer la conexión
                        connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);

                    } catch ( SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return connection;
    }
    // Método para cerrar la conexión
    public static void closeConnection() {
        if (connection != null) {
            try {
                connection.close();
                connection = null;
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

Mantenimiento de Empleado

Vamos a comenzar con el mantenimiento de Empleado.

Modelo Empleado (POJO Empleado)

Representará las necesidades de la aplicación para la tabla Empleado y su relación con Departamento. Decidimos mantener los datos de la tabla más el objeto Departamento al que pertenecen para facilitar mostrar la información al usuario

alt text

DAO Empleado

Tenemos que definir las sentencias SQL necesarias para la aplicación. Vamos a crear la clase DAO con las sentencias SQL que permitan el CRUD (Creación, Edición, Borrado y Selección) para la aplicación.

alt text

alt text

Creamos las sentencias SELECT que permitan obtener la información a mostrar al usuario incluyendo los filtros que utilizamos (no aplicamos los filtros en conjunto, se deja como ejercicio)

alt text

Vista Empleado: TableView

Como hemos indicado, vamos a mostrar un listado de los empleados donde podremos filtrar, crear, modificar y eliminar.

Creamos un nuevo fichero fxml y su correspondiente Controller en lo paquetes correspondientes

alt text

Nuestro primer objetivo tiene que ser poder mostrar la lista con todos los empleados

alt text

Para mostrar la lista utilizaremos un TableView de JavaFX

El TableView nos permite mostrar ObservableList que es muy parecido a un ArrayList, por lo que recuperaremos la lista de empleados de la base de datos utilizando DAO y lo cargaremos en la ObservableList y se lo asignaremos al TableView.

Para crear el TableView tenemos que darle un id y asignarle las columnas necesarias con la información que queremos mostrar en SceneBuilder. Es necesario asignar ids a las columnas también.

alt text

En el Controller, definimos la ObservableList y los tipos para cada columna que tiene que coincidir con los tipo de la clase Empleado.

alt text

En el método iniciaTableViewEmpleados, creamos la observableList, iniciamos la TableView, asociando a cada columna el nombre de la propiedad de Empleado a mostrar

alt text

Para las columnas que muestran la información del Departamento, tendremos que elegir el dato a mostrar

// Para mostrar el código del departamento, se utiliza una función lambda
// para acceder a la propiedad anidada 'departamento' y luego a 'codigo'.
tcidDep.setCellValueFactory(
        param -> new ReadOnlyObjectWrapper<>(
                param.getValue().getDepartamento() != null ? param.getValue().getDepartamento().getCodigo() : 0
        )
);
// Para mostrar el nombre del departamento, se utiliza una función lambda
// para acceder a la propiedad anidada 'departamento' y luego a 'nombre'.
tcDepartamento.setCellValueFactory(
        param -> new ReadOnlyObjectWrapper<>(
                param.getValue().getDepartamento() != null ? param.getValue().getDepartamento().getNombre() : "Sin departamento")
);

Leemos la lista de empleados de la base de datos desde el DAO

try {
    // Carga todos los empleados desde la base de datos y los añade a la lista observable.
    listaEmpleados.addAll(EmpleadoDAO.getInstance().listAllEmpleados());
} catch (SQLException e) {
    System.err.println(e.getMessage());
}

Desde el método Initialize, llamamos al método anterior, que si todo es correcto, mostrará la lista de empleados

@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
    iniciaTableViewEmpleados(); // Inicializa la TableView de empleados.    
}

Abrir en JavaFX una nueva ventana

Nuestra aplicación comienza en la ventana principal que permitirá abrir el mantenimiento de Empleados y Departamentos.

alt text

Para poder abrir Empleados al pulsar en el botón correspondiente, creamos una nuevo Stage desde el View creado anteriormente.

InicioController.java
 /**
 * Método invocado al hacer clic en el botón para acceder a la gestión de departamentos.
 * Carga y muestra la vista de departamentos en una nueva ventana modal.
 *
 * @param event El evento de acción.
 */
@FXML
void onClickDepartamentos(ActionEvent event) {
    // Es necesario el control de excepciones para manejar posibles errores al cargar la vista.
    try {
        // Cargamos la escena desde el archivo FXML que define la interfaz de departamentos.
        FXMLLoader loader = new FXMLLoader(EjemploDBJavaFx.class.getResource("views/departamentos-view.fxml"));
        Parent root = loader.load();

        Scene scene = new Scene(root);
        // Iniciamos un nuevo Stage (ventana) para mostrar la escena de departamentos.
        Stage stage = new Stage();
        stage.setTitle("IES Severos Ochoa/Departamentos"); // Establecemos el título de la ventana.
        stage.setResizable(false); // Deshabilitamos la posibilidad de redimensionar la ventana.
        stage.initModality(Modality.APPLICATION_MODAL); // Establecemos la modalidad de la ventana como modal de aplicación,
        // lo que bloquea la interacción con otras ventanas de la aplicación
        // hasta que esta se cierre.
        stage.setScene(scene); // Asignamos la escena al Stage.
        stage.showAndWait(); // Mostramos la ventana y esperamos a que el usuario la cierre.
    } catch (IOException e) {
        // En caso de error al cargar el archivo FXML, mostramos un mensaje de error en la consola.
        System.err.println(e.getMessage());
    }
}

Borrar Empleado

Para implementar el borrado, tenemos que:

  • saber el elemento de la tabla seleccionado
  • como es una acción que no tiene vuelta a atrás, queremos mostrar una alerta al usuario para que se asegure que quiere borrar el empleado

Tendremos un método que elimine el un empleado y actualice la TableView

EmpleadosController.java
private void borrarEmpleado(Empleado empleado) {
    try {
        EmpleadoDAO.getInstance().deleteEmpleado(empleado.getId());

        listaEmpleados = FXCollections.observableArrayList(
                EmpleadoDAO.getInstance().listAllEmpleados()
        );
        tvEmpleados.setItems(listaEmpleados);
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

Nota

No tenemos en cuenta los filtros actuales. Se deja como ejercicio.

Selección del empleado de la TableView

Para saber el elemento seleccionado de la TableView:

EmpleadosController.java
@FXML
    void onClickBorrar(ActionEvent event) {
        //obtenemos el empleado seleccionado
        Empleado empleado = tvEmpleados.getSelectionModel().getSelectedItem();

Alert: Avisos al usuario

Alert

Mostraremos un cuadro de dialogo al usuario antes de borrar y si decide que sí, llamamos al método borrar

alt text

EmpleadosController.java
@FXML
    void onClickBorrar(ActionEvent event) {
        //obtenemos el empleado seleccionado
        Empleado empleado = tvEmpleados.getSelectionModel().getSelectedItem();
        if (empleado != null) {
            //Abrimos un cuadro de diálogo para que confirme si borra o no
            Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
            alert.setTitle("Borrar Empleado");
            alert.setHeaderText("Borrar Empleado");
            alert.setContentText("¿Está seguro de borrar el empleado " + empleado.getNombre() + " " + empleado.getApellidos() + "?");
            alert.showAndWait().ifPresent(response -> {
                if (response == ButtonType.OK) {
                    borrarEmpleado(empleado);
                }
            });

        }

Filtros Empleados

Para programar los filtros, vamos a definir unos EditText para el filtrar por dni y por nombre y un ComboBox para los departamentos

alt text

Nota

Los filtros no funcionan en conjunto. Se deja como ejercicio el crear el DAO y lógica para hacerlos funcionar en conjunto.

Filtro: DNI y Nombre

Tenemos definido en el DAO los métodos que nos permiten obtener los empleados por DNI y por nombre

EmpleadoDAO.java
 /**
     * Recupera todos los empleados cuyo nombre o apellido coincida parcialmente
     * con la cadena de búsqueda proporcionada.
     *
     * @param nombre La cadena de búsqueda para el nombre o apellido del empleado.
     * @return Una lista de objetos {@link Empleado} que coinciden con la búsqueda.
     * Si no hay coincidencias, la lista estará vacía.
     * @throws SQLException Si ocurre un error al interactuar con la base de datos.
     */
    public ArrayList<Empleado> selectEmpleadosPorNombre(String nombre) throws SQLException {
        ArrayList<Empleado> lista = new ArrayList<>();
        String nombreLike = "%" + nombre + "%";
        String apellidoLike = "%" + nombre + "%";
        try (PreparedStatement ps = connection.prepareStatement(SELECT_EMPLEADOS_POR_NOMBRE)) {
            ps.setString(1, nombreLike);
            ps.setString(2, apellidoLike);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    Empleado empleado = resultSetToEmpleado(rs);
                    lista.add(empleado);
                }
            }
        }
        return lista;
    }

    /**
     * Recupera todos los empleados cuyo DNI coincida parcialmente con la cadena
     * de búsqueda proporcionada.
     *
     * @param dni La cadena de búsqueda para el DNI del empleado.
     * @return Una lista de objetos {@link Empleado} que coinciden con la búsqueda.
     * Si no hay coincidencias, la lista estará vacía.
     * @throws SQLException Si ocurre un error al interactuar con la base de datos.
     */
    public ArrayList<Empleado> selectEmpleadosPorDNI(String dni) throws SQLException {
        ArrayList<Empleado> lista = new ArrayList<>();
        String dniLike = "%" + dni + "%";
        try (PreparedStatement ps = connection.prepareStatement(SELECT_EMPLEADOS_POR_DNI)) {
            ps.setString(1, dniLike);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    Empleado empleado = resultSetToEmpleado(rs);
                    lista.add(empleado);
                }
            }
        }
        return lista;
    }

La búsqueda por nombre será tan simple como buscar la lista mediante el DAO y reasignarla a la TableView

EmpleadosController.java
 /**
     * Método invocado al hacer clic en el botón de buscar por nombre.
     * Realiza una búsqueda de empleados por el nombre introducido en el TextField.
     * Si el TextField está vacío, muestra todos los empleados.
     *
     * @param event El evento de acción.
     */
    @FXML
    void onClickBuscarPorNombre(ActionEvent event) {
        ArrayList<Empleado> empleados;
        try {
            String nombreBusqueda = tfNombre.getText();
            //si hay filtro, lo aplicamos
            if (!nombreBusqueda.isEmpty()) {
                empleados = EmpleadoDAO.getInstance().selectEmpleadosPorNombre(nombreBusqueda);
            } else {//no hay filtro, seleccionamos todos
                empleados = EmpleadoDAO.getInstance().listAllEmpleados();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        //asignamos la nueva lista a la TableView
        listaEmpleados = FXCollections.observableArrayList(empleados);
        tvEmpleados.setItems(listaEmpleados);
    }

La búsqueda por DNI es muy parecida

ComboBox: Filtro por Departamento

ComboBox: Queremos filtra los empleados según un departamento seleccionado mediante un ComboBox que muestre la lista de departamentos

alt text

alt text

Para ello hay que agregar mediante SceneBuilder un ComboBox y asignarle un id

alt text

Suponiendo que ya tenemos definido el DAO para Departamento y su Modelo

alt text

Cada elemento de la lista del ComboBox, mostrará el resultado del método toString de la clase Departamento

Para ello, definimos el tipo que se mostrará en el ComboBox en el Controller

EmpleadoController.java
 /**
     * ComboBox para filtrar empleados por departamento.
     */
    @FXML
    private ComboBox<Departamento> cbDepartamento;
El método que inicia el ComboBox:

  • Busca la lista de departamentos en la base de datos
  • Añade un elemento llamado 'Todos' para se puedan ver todos los empleado
  • Asigna los departamentos al ComboBox
  • Añade un listener para filtrar por departamento cuando se modifica el ComboBox

EmpleadosController.java
 /**
     * Inicializa el ComboBox de departamentos, cargando los departamentos desde la base de datos
     * y añadiendo un listener para filtrar la tabla de empleados según el departamento seleccionado.
     */
    private void iniciaCbDepartamentos() {
        ArrayList<Departamento> departamentos = null;
        try {
            //Buscamos todos los departamentos
            departamentos = DepartamentoDAO.getInstance().listAllDepartamentos();
            // Añade una opción "Todos" al principio del ComboBox para mostrar todos
            Departamento todos = new Departamento(0, "Todos", null);
            departamentos.add(0, todos);
            // Añade cada departamento al ComboBox.
            departamentos.forEach(
                    departamento -> cbDepartamento.getItems().add(departamento)
            );
            // Selecciona la opción "Todos" por defecto.
            cbDepartamento.getSelectionModel().select(0);
            // Añade un listener para detectar cambios en la selección del ComboBox.
            cbDepartamento.getSelectionModel().selectedItemProperty().addListener(
                    (observableValue, depAnterior, depSeleccionado) -> {
                        ArrayList<Empleado> empleados;
                        try {
                            // Si se selecciona "Todos", carga todos los empleados.
                            if (depSeleccionado.getCodigo() == 0) {
                                empleados = EmpleadoDAO.getInstance().listAllEmpleados();

                            } else {
                                // Si se selecciona un departamento específico, carga los empleados de ese departamento.
                                empleados = EmpleadoDAO.getInstance().empleadosDepartamento(depSeleccionado.getCodigo());
                            }
                        } catch (SQLException e) {
                            throw new RuntimeException(e);
                        }
                        // Actualiza la lista observable y la TableView con los empleados filtrados.
                        listaEmpleados = FXCollections.observableArrayList(empleados);
                        tvEmpleados.setItems(listaEmpleados);
                    }

            );
            // Define una acción al seleccionar un departamento (actualmente comentado).
            cbDepartamento.setOnAction(event -> {
                Departamento departamento = cbDepartamento.getValue();
                /* echo: por terminar*/
            });
        } catch (SQLException e) {
            System.err.println(e.getMessage());
        }

    }
Llamamos al método en Initialize

@Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        iniciaTableViewEmpleados(); // Inicializa la TableView de empleados.
        iniciaCbDepartamentos(); // Inicializa el ComboBox de departamentos.

    }

Crear y Editar Empleado

Como indicamos, la ventana de Nuevo y Edición será la misma, y creará o modificará un Empleado. Facilitará al usuario la selección del Departamento al que pertenece.

alt text

Para conseguirlo, cuando abrimos la ventana de Empleado, tenemos que saber si estamos creando un empleado o nos han pasado un Empleado para editarlo.

Una vez que hemos definido el diseño de EmpleadoView con SceneBuilder, en EmpleadoController tenemos una propiedad Empleado y un método que carga los datos del empleado en los Controles de la pantalla

EmpleadoController.java
public class EmpleadoController implements Initializable {
    Empleado empleado;

    /**
     * Muestra los datos del empleado actual en los campos de texto y selecciona
     * su departamento en el ComboBox. Si el empleado es {@code null},
     * se configura la vista para la creación de un nuevo empleado.
     */
    private void iniciaEmpleado() {
        // Si el empleado es null, se está creando un nuevo empleado.
        if (empleado == null) {
            lbTitulo.setText("Nuevo Empleado");
        } else {
            // Si hay un empleado para editar, se llenan los campos de texto con sus datos.
            tfDni.setText(empleado.getDni());
            tfNombre.setText(empleado.getNombre());
            tfApellidos.setText(empleado.getApellidos());
            tfEdad.setText(String.valueOf(empleado.getEdad()));
            // Selecciona el departamento del empleado en el ComboBox.
            cbDepartamento.setValue(empleado.getDepartamento());
            // Actualiza el título de la ventana para indicar que se está editando un empleado.
            lbTitulo.setText("Editar: " + empleado.getNombre() + " " + empleado.getApellidos());
        }
    }

Definimos un método que permitirá iniciar la ventana pasándole un empleado si estamos editando o null si es nuevo

EmpleadoController.java
public void initialize(Empleado empleado) {
    // Inicializa el ComboBox con los departamentos.
    iniciaCbDepartamentos();
    // Guarda la instancia del empleado a editar.
    this.empleado = empleado;
    // Muestra los datos del empleado en los campos de texto y el ComboBox.
    iniciaEmpleado();
    // Limpia cualquier mensaje de error que pudiera estar visible.
    lbError.setText("");
}

Para llamar y abrir la ventana de edición de Empleado, lo haremos asignado el empleado previamente desde este método

En el Controller de Empleados

EmpleadosController.java
private void abrirEmpleado(Empleado empleado) {
        try {
            // Carga la escena desde el recurso FXML.
            FXMLLoader loader = new FXMLLoader(EjemploDBJavaFx.class.getResource("views/empleado-view.fxml"));
            Parent root = loader.load();

            // Obtiene el controlador de la ventana de edición de empleado.
            EmpleadoController empleadoController = loader.getController();
            // Inicializa el controlador con el empleado a editar (si es null, se prepara para una alta).
            empleadoController.initialize(empleado);

            Scene scene = new Scene(root);
            // Crea un nuevo Stage para la ventana modal.
            Stage stage = new Stage();
            if (empleado == null)
                stage.setTitle("Alta de Empleado");
            else
                stage.setTitle("Empleado: " + empleado.getNombre() + " " + empleado.getApellidos());
            // Establece la modalidad de la ventana como modal, bloqueando la interacción con la ventana padre.
            stage.initModality(Modality.WINDOW_MODAL);
            // Establece la ventana padre de la ventana modal.
            stage.initOwner(btBuscarPorNombre.getScene().getWindow());
            stage.setResizable(false);
            // Define un evento que se ejecuta al cerrar la ventana de edición.
            stage.setOnHidden(event -> {
                // Al cerrar la ventana, se actualiza la lista de empleados en la TableView.
                try {
                    tvEmpleados.setItems(FXCollections.observableArrayList(EmpleadoDAO.getInstance().listAllEmpleados()));
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            });
            // Asigna la escena al Stage y muestra la ventana modal, esperando a que se cierre.
            stage.setScene(scene);
            stage.showAndWait();

        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
    }

Abrimos la venta si es nuevo o edición en el onClick de los botones correspondientes

alt text

EmpleadosController.java
 @FXML
void onClickbtAlta(ActionEvent event) {
    abrirEmpleado(null);
}


@FXML
void onClickEditar(ActionEvent event) {
    //obtenemos el empleado seleccionado de la TableView
    Empleado empleado = tvEmpleados.getSelectionModel().getSelectedItem();
    if (empleado != null)
        abrirEmpleado(empleado);
}

Devolver un resultado de una ventana

El mantenimiento de Departamento es muy parecido al de Empleados, pero para seleccionar el jefe, utilizaremos el mantenimiento de Empleados

alt text

Para conseguir el objetivo, la ventana que devuelve el dato, tiene que definir una Interface con el método que devuelve el dato. Los pasos son los siguiente:

Definir la Interface en EmpleadosController

Esta Interface contiene un método que tiene como parámetro el Empleado seleccionado

EmpleadosController.java
public interface SeleccionEmpleadoCallback {
        /**
         * Método llamado cuando un empleado es seleccionado.
         *
         * @param empleado El empleado seleccionado.
         */
        void onEmpleadoSelected(Empleado empleado);
    }
Definimos una propiedad con una instancia del tipo anterior

EmpleadosController.java
  private SeleccionEmpleadoCallback seleccionEmpleadoCallback;
Definimos el Set que permite asignar el la instancia anterior

EmpleadosController.java
public void setSeleccionEmpleadoCallback(SeleccionEmpleadoCallback seleccionEmpleadoCallback) {
    this.seleccionEmpleadoCallback = seleccionEmpleadoCallback;
    //mostramos el botón que permite mostrar el botón de selección
    btSeleccionEmpleado.setVisible(true);
}

Ejecutar la acción que devuelve el Empleado

Cuando se pulse en seleccionar llamamos al método anterior del CallBack y le pasamos el empleado

EmpleadosController.java
@FXML
void onClickSeleccion(ActionEvent event) {
    Empleado empleado=tvEmpleados.getSelectionModel().getSelectedItem();
    if (empleado!=null){
        if (seleccionEmpleadoCallback!=null){
            //devolvemos el empleado seleccionado
            seleccionEmpleadoCallback.onEmpleadoSelected(empleado);
        }
        //cerramos la ventana
        Stage stage=(Stage) tvEmpleados.getScene().getWindow();
        stage.close();
    }
}

Asignar la instancia de la Interface

Al abrir la ventana de Empleados desde Departamento, le asignamos una instancia del tipo de la Interface anterior que permitirá asignar el empleado seleccionado al campo correspondiente

DepartamentoController.java
@FXML
    void onClickBuscarJefe(ActionEvent event) {
        // Intenta cargar la vista de empleados para seleccionar un jefe.
        try {
            FXMLLoader loader = new FXMLLoader(EjemploDBJavaFx.class.getResource("views/empleados-view.fxml"));
            Parent root = loader.load();

            // Obtiene el controlador de la vista de empleados.
            EmpleadosController empleadosController = loader.getController();
            // Establece un callback para recibir el empleado seleccionado como jefe.
            empleadosController.setSeleccionEmpleadoCallback(empleado -> {
                //si se ha seleccionar un empleado
                if (empleado != null) {
                    jefe = empleado;
                    lbJefe.setText(jefe.getNombreCompleto()); // Muestra el nombre completo del jefe seleccionado.
                }
            });
            Scene scene = new Scene(root);
            // Crea y muestra una nueva ventana modal para la selección de empleados (jefes).
            Stage stage = new Stage();
            stage.setTitle("Ies Ochoa/Empleados");
            stage.setResizable(false);
            stage.initModality(Modality.APPLICATION_MODAL); // Bloquea la interacción con la ventana padre.
            stage.setScene(scene);
            stage.showAndWait(); // Espera hasta que la ventana de selección de empleados se cierre.

        } catch (IOException e) {
            // Imprime un mensaje de error si no se puede cargar la vista de empleados.
            System.err.println(e.getMessage());
        }
    }