Spring Boot Vaadin Hibernate JPA

Seguimos trabajando sobre la integración del Framework Vaadin a Spring Boot, esta vez vamos a integrar la tecnología de persistencia Hibernate JPA, para este tutorial agregaremos la capa de datos a nuestra aplicación demostrativa y aprenderemos a mostrar los datos obtenidos en un elemento Grid<T> de la UI, también añadiremos un formulario que nos permitirá agregar, editar o eliminar un determinado elemento.

Anteriormente vimos cómo integrar Vaadin a Spring Boot, usaremos dicho tutorial como base, adicionalmente aplicaremos los conocimientos adquiridos en el curso de acceso a datos usando Spring Data con Hibernate JPA, para desarrollar la aplicación utilizaremos el IDE Netbeans 8.2 y una base de datos HSQLDB.

Tutorial Vaadin Spring Data JPA

Agregando las dependencias requeridas por el proyecto, archivo pom.xml.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>com.vaadin</groupId>
    <artifactId>vaadin-spring-boot-starter</artifactId>
</dependency> 

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <scope>runtime</scope>
</dependency>

Establecer la configuración de acceso a datos con la clase SpringDataConfiguration.

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("carmelo.data.repo")
public class SpringDataConfiguration {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.HSQL)
                .generateUniqueName(true).build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setDataSource(dataSource());
        factoryBean.setJpaVendorAdapter(jpaVendorAdapter());
        factoryBean.setPackagesToScan("carmelo.data.model");
        factoryBean.setPersistenceUnitName("spring-vaadin-punit");     
        return factoryBean;
    }

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setGenerateDdl(true);
        jpaVendorAdapter.setDatabase(Database.HSQL);
        return jpaVendorAdapter;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    }
}

Nuestro modelo de datos estará definido por la clase Cliente.

@Entity
public class Cliente {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    private String nombre;
    private String apellido;
    private String email;
    private LocalDate fecha;
    private Boolean activo;
    private String Descripcion;

    // ...
}

Finalmente definimos el repositorio JPA que nos permitirá lanzar consultas sobre nuestro modelo, lo llamaremos ClienteRepository.

public interface ClienteRepository extends JpaRepository<Cliente, Long> {
    // ...
}

Nuestro proyecto usará la interface ClienteService de la capa de servicio, para utilizar este repositorio.

Crear la UI Vaadin

Imaginemos que tenemos en nuestra base de datos una lista de clientes, cómo hacemos para visualizar dicho listado, Vaadin nos proporciona un elemento llamado Grid<T> que se utiliza para mostrar un conjunto de datos en formato tabla o rejilla, veamos como se utiliza.

@Theme("valo")
@Title("Mis Clientes")
@SpringUI(path = "/clientes")
public class ClienteUI extends UI {
    
    @Autowired
    private ClienteService clientes;

    @Override
    protected void init(VaadinRequest request) {
        
        VerticalLayout layout = new VerticalLayout();
        
        Label title = new Label("Estos son mis clientes");
        
        Grid<Cliente> grid = new Grid<>(Cliente.class);
        grid.setColumns("nombre", "apellido", "email", "fecha", "descripcion");
        grid.setItems(clientes.findAll());
        grid.setSizeFull();

        layout.addComponent(title);
        layout.addComponent(grid);
        
        setContent(layout);
    }
}

Las anotaciones @Theme, @Title y @SpringUI definen, el tema, titulo que se aplica a la página y la URL a la que se asocia la misma.

El VerticalLayout es un componente que nos permite organizar los elementos de manera vertical, le hemos agregado un Label y un Grid<T>, el primero lo utilizamos para mostrar una etiqueta de texto, el segundo mostrará el conjunto de datos.

Grid<Cliente> grid = new Grid<>(Cliente.class);
grid.setColumns("nombre", "apellido", "email", "fecha", "descripcion");
grid.setItems(clientes.findAll());
grid.setSizeFull();

Al crear el objeto debemos indicar el tipo de datos que contendrá, usamos el método setColumns(...) para indicar las columnas que deseamos mostrar, cada una de ellas corresponde a las propiedades de la clase, con setItems(...) establecemos el conjunto de datos y setSizeFull() indica que el componente debe ocupar todo el ancho de la página.

Ejecutamos la aplicación y vamos a la dirección: http://localhost:8080/clientes

tutorial spring boot vaadin hibernate jpa

Si no indicamos las columnas que deseamos visualizar se mostrarán todas, es posible reordenar y cambiar el tamaño de la columnas usando arrastrar y soltar del ratón.

Agregar, Editar, Eliminar Datos

Al hacer clic sobre una fila de la tabla podremos seleccionar la misma, lo siguiente que realizaremos será crear un formulario que nos permita editar el objeto seleccionado, este formulario también lo utilizaremos para agregar un nuevo dato.

Para crear el formulario de clientes extenderemos la clase FormLayout, este contenedor organiza sus elementos en dos columnas la primera muestra una etiqueta y la segunda el control que nos permitirá editar un determinado campo.

public class ClienteForm extends FormLayout {

    private final Binder<Cliente> binder = new Binder<>();
    private Cliente cliente;

    public ClienteForm() {
        initUI();
    }

    private void initUI() {

        TextField nombre = new TextField("Nombre");
        TextField apellido = new TextField("Apellido");
        TextField correo = new TextField("Correo");
        DateField fecha = new DateField("Fecha");
        CheckBox activo = new CheckBox("Activo");
        TextArea descripcion = new TextArea("Desc.");

        Button save = new Button("Guardar");
        save.addClickListener(this::save);
        save.setStyleName(ValoTheme.BUTTON_PRIMARY);
        save.setClickShortcut(ShortcutAction.KeyCode.ENTER);

        Button cancel = new Button("Cancelar");
        cancel.addClickListener(this::cancel);
        cancel.setStyleName(ValoTheme.BUTTON_DANGER);

        HorizontalLayout buttons = new HorizontalLayout(save, cancel);

        addComponents(nombre, apellido, correo, fecha, activo, descripcion, buttons);

        binder.bind(nombre, Cliente::getNombre, Cliente::setNombre);
        binder.bind(apellido, Cliente::getApellido, Cliente::setApellido);
        binder.bind(correo, Cliente::getEmail, Cliente::setEmail);
        binder.bind(fecha, Cliente::getFecha, Cliente::setFecha);
        binder.bind(activo, Cliente::getActivo, Cliente::setActivo);
        binder.bind(descripcion, Cliente::getDescripcion, Cliente::setDescripcion);
    }

    public void setCliente(Cliente cliente) {
        this.cliente = cliente;
        this.binder.readBean(cliente);
    }

    public void cancel(Button.ClickEvent event) {
        binder.readBean(cliente);
    }

    public void save(Button.ClickEvent event) {
        try {
            binder.writeBean(cliente);

            getUI().getService().save(cliente);
            getUI().reloadData();
            
        } catch (ValidationException ex) {
            Notification.show("Error al guardar cliente.", Notification.Type.ERROR_MESSAGE);
        }
    }

    @Override
    public ClienteUI getUI() {
        return (ClienteUI) super.getUI();
    }

}

Para enlazar los campos del formulario y las propiedades de la clase Cliente usamos un objeto Binder<Cliente>, con el método bind(...) realizamos el correspondiente enlace.

// ...
    private final Binder<Cliente> binder = new Binder<>();

    private void initUI() {

        TextField nombre = new TextField("Nombre");
        TextField apellido = new TextField("Apellido");
        TextField correo = new TextField("Correo");
        DateField fecha = new DateField("Fecha");
        CheckBox activo = new CheckBox("Activo");
        TextArea descripcion = new TextArea("Desc.");

        // ...

        binder.bind(nombre, Cliente::getNombre, Cliente::setNombre);
        binder.bind(apellido, Cliente::getApellido, Cliente::setApellido);
        binder.bind(correo, Cliente::getEmail, Cliente::setEmail);
        binder.bind(fecha, Cliente::getFecha, Cliente::setFecha);
        binder.bind(activo, Cliente::getActivo, Cliente::setActivo);
        binder.bind(descripcion, Cliente::getDescripcion, Cliente::setDescripcion);
    }
// ...

Agregamos además dos botones, uno para cancelar la edición, con el método readBean(...) devolvemos los campos del formulario a su estado original, para guardar usamos writeBean(...), este método escribe los datos del formulario en el objeto especificado, luego guardamos el objeto en la base datos usando el repositorio JPA.

// ...
    public void cancel(Button.ClickEvent event) {
        binder.readBean(cliente);
    }

    public void save(Button.ClickEvent event) {
        try {
            binder.writeBean(cliente);

            getUI().getService().save(cliente);
            getUI().reloadData();
            
        } catch (ValidationException ex) {
            Notification.show("Error al guardar cliente.", Notification.Type.ERROR_MESSAGE);
        }
    }
// ...

Ahora debemos modificar la clase ClienteUI y agregarle el formulario que acabamos de agregar.

@Theme("valo")
@Title("Mis Clientes")
@SpringUI(path = "/clientes")
public class ClienteUI extends UI {

    @Autowired
    private ClienteService clientes;

    private Grid<Cliente> grid;

    @Override
    protected void init(VaadinRequest request) {
        
        ClienteForm form = new ClienteForm();

        Button add = new Button("Agregar");
        add.addClickListener(e -> {
            grid.getSelectionModel().deselectAll();
            form.setCliente(new Cliente());
        });

        grid = new Grid<>(Cliente.class);
        grid.setColumns("nombre", "apellido", "email", "fecha");
        grid.addSelectionListener(c -> {
            c.getFirstSelectedItem().ifPresent(form::setCliente);
        });

        VerticalLayout layout = new VerticalLayout(grid, add);
        HorizontalLayout root = new HorizontalLayout(layout, form);

        setContent(root);
        reloadData();
    }

    public void reloadData() {
        grid.setItems(clientes.findAll());
    }

    public ClienteService getService() {
        return clientes;
    }
}

Cada vez que se selecciona un nuevo elemento este se envía al formulario para que pueda ser editado, lo hacemos de esta manera.

grid.addSelectionListener(c -> {
    c.getFirstSelectedItem().ifPresent(form::setCliente);
});

Otra cosa que debemos tener presente, es que al presionar el botón para agregar un nuevo elemento se limpia la selección de la rejilla y se envía un objeto Cliente vacío al formulario.

Button add = new Button("Agregar");
add.addClickListener(e -> {
    grid.getSelectionModel().deselectAll();
    form.setCliente(new Cliente());
});

Al ejecutar la aplicación y ver el navegador web, tenemos:

tutorial spring boot vaadin formulario CRUD

Terminamos el tutorial por ahora, aun quedan muchas cosas por mejorar en nuestra aplicación, más adelante veremos como podemos validar los datos que ingresan al formulario, el botón de eliminar queda de tarea.

Descargar proyecto: spring_boot-vaadin-hibernate_jpa.zip

Comentarios

Entradas populares de este blog

Conectar SQL Server con Java

Entrenar OpenCV en Detección de Objetos

Procesamiento de imágenes en OpenCV

Acceso a la webcam con OpenCV

Conociendo la clase cv::Mat de OpenCV