Friends of OpenJDK Today

Create a CRUD UI in Pure Java

February 02, 2022

Author(s)

In this guide, we create a web UI that performs full CRUD (create, read, update, and delete) operations on a Spring Data JPA backend. This UI is developed entirely in pure Java using Vaadin Flow (no HTML or JavaScript involved). You can explore the full source code on GitHub.

What You Need

  • About 15 minutes
  • JDK 17 or later

Step 1: Import a Starter Project

Click here to download an empty project starter. Unpack the downloaded zip into a folder on your computer, and import the project in the IDE of your choice.

The pom.xml of the starter project comes with all the dependencies necessary to complete this tutorial.

Step 2: Create the Backend Classes

Let's start by creating a simple JPA entity and a Spring Data repository.

The following Person class is a simple JPA entity with three fields, representing a person's first name, last name, and email.

package com.example.application;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

@Entity
public class Person {

   @NotBlank
   private String firstName;

   @NotBlank
   private String lastName;

   @Id
   @NotBlank
   @Email
   private String email;

   public String getEmail() {
       return email;
   }

   public void setEmail(String email) {
       this.email = email;
   }

   public String getFirstName() {
       return firstName;
   }

   public void setFirstName(String firstName) {
       this.firstName = firstName;
   }

   public String getLastName() {
       return lastName;
   }

   public void setLastName(String lastName) {
       this.lastName = lastName;
   }
}

 

We also create a repository interface using Spring Data in order to work with Person entities as follows:

package com.example.application;

import org.springframework.data.jpa.repository.JpaRepository;

public interface PersonRepository extends JpaRepository<Person, Long> {
}

 

In order to work with some data, we also populate the embedded H2 database with some random entries that are generated using the ExampleDataGenerator helper class. The following DataGenerator class generates 100 random entities.

package com.example.application;

import com.vaadin.exampledata.DataType;
import com.vaadin.exampledata.ExampleDataGenerator;
import com.vaadin.flow.spring.annotation.SpringComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;

import java.time.LocalDateTime;

@SpringComponent
public class DataGenerator {
   @Bean
   public CommandLineRunner loadData(@Autowired PersonRepository personRepo) {
       return args -> {
           Logger logger = LoggerFactory.getLogger(getClass());

           logger.info("Generating demo data");

           logger.info("... generating 100 Sample Person entities...");
           ExampleDataGenerator<Person> samplePersonGenerator = new ExampleDataGenerator<>(
                   Person.class, LocalDateTime.now());
           samplePersonGenerator.setData(Person::setFirstName, DataType.FIRST_NAME);
           samplePersonGenerator.setData(Person::setLastName, DataType.LAST_NAME);
           samplePersonGenerator.setData(Person::setEmail, DataType.EMAIL);
           personRepo.saveAll(samplePersonGenerator.create(100, 123));

           logger.info("Generated demo data");
       };
   }
}

 

Step 3: Create the Editor Layout

Next, we create the Editor Layout, which is the part of the UI that will enable users to manipulate the currently selected Person entity. We can do this in pure Java using Vaadin, which comes with an extensive set of UI components that we can use as the building blocks for any application.

The following EditorLayout class extends Vaadin VerticalLayout, and it includes three TextField components to hold the first name, last name, and email of the selected Person entity. EditorLayout also includes three Button components that will later be used to perform the CRUD operations. (Note, the save button is used to both create and update entities).

package com.example.application;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;

public class EditorLayout extends VerticalLayout {

   private TextField firstName;
   private TextField lastName;
   private TextField email;

   private Button deleteButton = new Button("Delete...");
   private Button cancelButton = new Button("Cancel");
   private Button saveButton = new Button("Save");

   public EditorLayout() {
       FormLayout formLayout = new FormLayout();

       firstName = new TextField("First Name");
       lastName = new TextField("Last Name");
       email = new TextField("Email");

       formLayout.add(firstName, lastName, email);

       HorizontalLayout buttonLayout = new HorizontalLayout();
       deleteButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_ERROR);
       cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
       saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
       buttonLayout.setWidthFull();
       buttonLayout.setJustifyContentMode(JustifyContentMode.BETWEEN);
       buttonLayout.setSpacing(false);
       buttonLayout.add(deleteButton, new HorizontalLayout(cancelButton, saveButton));

       add(formLayout, buttonLayout);
       setWidth("600px");
       setMinWidth("300px");
       setFlexGrow(0);
   }

   public Button getDeleteButton() {
       return deleteButton;
   }

   public Button getCancelButton() {
       return cancelButton;
   }

   public Button getSaveButton() {
       return saveButton;
   }

}

 

Step 4: Create the CRUD View

Now we define the main view that holds both the tabular data as well as the EditorLayout that was just created. This view extends Vaadin's SplitLayout component. This SplitLayout shows the tabular data inside a Vaadin Grid component on the left side of the screen, and the EditorLayout on its right side. When an item is selected from the Grid, the form is automatically updated.

Note that this view makes use of BeanValidationBinder in order to bind and validate the values of the EditorLayout form. Note also that the view is made accessible to the end user via the @Route annotation (in this case, it would be accessible via the empty `` route).

package com.example.application;

import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.router.Route;
import org.springframework.beans.factory.annotation.Autowired;

@Route("")
public class CrudView extends SplitLayout {

   private Grid<Person> grid;
   private final EditorLayout editorLayout;

   /**
    * The currently edited person
    */
   private Person person;

   ListDataProvider<Person> dataProvider;

   private final PersonRepository repo;

   private BeanValidationBinder<Person> binder;

   public CrudView(@Autowired PersonRepository repo) {
       this.repo = repo;

       configureGrid();

       editorLayout = new EditorLayout();
       configureBinding();

       setSizeFull();

       addToPrimary(grid);
       addToSecondary(editorLayout);
   }

   private void configureGrid() {
       // Auto create Grid's columns based on Person.class members
       grid = new Grid<>(Person.class, true);
       grid.setSizeFull();

       dataProvider = DataProvider.ofCollection(repo.findAll());
       grid.setDataProvider(dataProvider);

       // when a row is selected or deselected, populate form
       grid.asSingleSelect().addValueChangeListener(event -> {
           Person person = event.getValue();
           if (person != null) {
               populateForm(person);
           } else {
               clearForm();
           }
       });
   }

   private void configureBinding() {
       binder = new BeanValidationBinder<>(Person.class);

       //Bind member fields found in the EditorLayout object.
       binder.bindInstanceFields(editorLayout);

       editorLayout.getDeleteButton().addClickListener(e -> {
           if (this.person != null) {
               repo.delete(this.person);
               dataProvider.getItems().remove(person);

               clearForm();
               refreshGrid();
               Notification.show("Person deleted.");
           }
       });

       editorLayout.getCancelButton().addClickListener(e -> {
           clearForm();
           refreshGrid();
       });

       editorLayout.getSaveButton().addClickListener(e -> {
           try {
               if (this.person == null) {
                   this.person = new Person();
               }
               binder.writeBean(this.person);

               repo.save(this.person);
               dataProvider.getItems().add(person);

               clearForm();
               refreshGrid();
               Notification.show("Person details stored.");
           } catch (ValidationException validationException) {
               Notification.show("Please enter a valid person details.");
           }
       });
   }

   void clearForm() {
       populateForm(null);
   }

   void populateForm(Person person) {
       this.person = person;
       binder.readBean(this.person);
   }

   public void refreshGrid() {
       grid.select(null);
       dataProvider.refreshAll();
   }
}

 

Step 5: Run the Application

To run the project from the command line, type mvnw spring-boot:run (on Windows), or ./mvnw spring-boot:run (on macOS or Linux).

Then, in your browser, open http://localhost:8080/.

Go further

Congratulations! You have created a web UI that performs full CRUD (create, read, update, and delete) operations on a Spring Data JPA backend. And you did it in pure Java – without the need for any HTML or JavaScript, and without having to expose REST services or think about HTTP requests, responses, and filter chains.

You can explore the full source code on GitHub.

Visit start.vaadin.com to customize your own Vaadin app starter, or learn more about Vaadin Flow in its documentation.

Topics:

Related Articles

View All

Author(s)

Comments (1)

Your email address will not be published. Required fields are marked *

Highlight your code snippets using [code lang="language name"] shortcode. Just insert your code between opening and closing tag: [code lang="java"] code [/code]. Or specify another language.

Save my name, email, and website in this browser for the next time I comment.

Axel

Great article! Thumbs up!

Subscribe to foojay updates:

https://foojay.io/feed/
Copied to the clipboard