Vaadin + Spring Boot + Spring Data JPA CRUD example
Hello everyone, today we will learn how to develop a simple CRUD web application using Vaadin, Spring Boot, Spring Data JPA, and H2 Database.
Vaadin is the only framework that allows you to write UI 100% in Java without getting bogged down in JS, HTML, and CSS. If you prefer, you can also create layouts in HTML or with a visual designer. Vaadin apps run on the server and handle all communication automatically and securely.
The GitHub repository link is provided at the end of this tutorial. You can download the source code.
Technologies Used:
- Spring Boot 2.7.0
- JDK 17
- Vaadin 14.7.0
- Maven 3+
- npm package manager
- H2 Database
After completing this tutorial what we will build?
We will build a full-stack web application that is a basic User Management Application with CRUD features:
• Create User
• List User
• Update User
• Delete User
• View User
Project Structure:
Dependency Management - Maven - pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>springboot-vaadin-jpa-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-vaadin-jpa-crud</name>
<description>Demo project for Spring Boot + vaadin</description>
<properties>
<java.version>17</java.version>
<vaadin.version>14.7.0</vaadin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Create User Entity
package com.knf.dev.demo.springvaadincrud.backend.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "UserTable")
public class User {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
protected User() {
}
public User(String firstName, String lastName, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
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;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Create User Repository
package com.knf.dev.demo.springvaadincrud.backend.repository;
import com.knf.dev.demo.springvaadincrud.backend.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserRepository
extends JpaRepository<User, Long> {
List<User> findByEmailStartsWithIgnoreCase(String email);
}
Vaadin UI Java Files
Index.java
package com.knf.dev.demo.springvaadincrud.frontend.view;
import org.springframework.util.StringUtils;
import com.knf.dev.demo.springvaadincrud.backend.model.User;
import com.knf.dev.demo.springvaadincrud.backend.
repository.UserRepository;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.Route;
@Route(value="/")
public class Index extends VerticalLayout {
final Grid<User> grid;
final TextField filter;
private final UserRepository repo;
private final Button addNewBtn;
private final UserEditor editor;
public Index(UserRepository repo, UserEditor editor) {
this.repo = repo;
this.editor = editor;
this.grid = new Grid<>(User.class);
this.filter = new TextField();
this.addNewBtn = new Button
("Add User",VaadinIcon.PLUS.create());
addNewBtn.addThemeVariants
(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_CONTRAST);
// build layout
HorizontalLayout actions = new
HorizontalLayout(filter, addNewBtn);
add(actions, grid, editor);
grid.setHeight("300px");
grid.setColumns("id", "firstName", "lastName", "email");
grid.getColumnByKey("id").setWidth("60px").
setFlexGrow(0);
filter.setPlaceholder("Filter by email");
// Hook logic to components
/* Replace listing with filtered content when user
changes filter*/
filter.setValueChangeMode(ValueChangeMode.EAGER);
filter.addValueChangeListener
(e -> listUsers(e.getValue()));
/* Connect selected User to editor or hide if none
is selected */
grid.asSingleSelect().addValueChangeListener(e -> {
editor.editUser(e.getValue());
});
/* Instantiate and edit new
User the new button is clicked
*/
addNewBtn.addClickListener(e -> editor.editUser
(new User("", "", "")));
// Listen changes made by the editor,
// refresh data from backend
editor.setChangeHandler(() -> {
editor.setVisible(false);
listUsers(filter.getValue());
});
// Initialize listing
listUsers(null);
}
void listUsers(String filterText) {
if (StringUtils.isEmpty(filterText)) {
grid.setItems(repo.findAll());
} else {
grid.setItems(repo.
findByEmailStartsWithIgnoreCase(filterText));
}
}
}
UserEditor.java
package com.knf.dev.demo.springvaadincrud.frontend.view;
import com.knf.dev.demo.springvaadincrud.backend.model.User;
import com.knf.dev.demo.springvaadincrud.backend
.repository.UserRepository;
import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.KeyNotifier;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component
.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component
.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.annotation.UIScope;
import org.springframework.beans.factory.annotation.Autowired;
@SpringComponent
@UIScope
public class UserEditor extends VerticalLayout
implements KeyNotifier {
private final UserRepository repository;
/* Fields to edit properties in User entity */
TextField firstName = new TextField("First name");
TextField lastName = new TextField("Last name");
TextField email = new TextField("Email");
/* Action buttons */
Button save = new Button
("Save", VaadinIcon.CHECK.create());
Button cancel = new Button("Cancel");
Button delete = new Button
("Delete", VaadinIcon.TRASH.create());
HorizontalLayout actions = new HorizontalLayout
(save, cancel, delete);
Binder<User> binder = new Binder<>(User.class);
private User user;
private ChangeHandler changeHandler;
@Autowired
public UserEditor(UserRepository repository) {
this.repository = repository;
add(firstName, lastName, email, actions);
// bind using naming convention
binder.bindInstanceFields(this);
// Configure and style components
setSpacing(true);
save.getElement().getThemeList().add("primary");
delete.getElement().getThemeList().add("error");
addKeyPressListener(Key.ENTER, e -> save());
// wire action buttons to save, delete and reset
save.addClickListener(e -> save());
delete.addClickListener(e -> delete());
cancel.addClickListener(e -> editUser(user));
setVisible(false);
}
void delete() {
repository.delete(user);
changeHandler.onChange();
}
void save() {
repository.save(user);
changeHandler.onChange();
}
public final void editUser(User usr) {
if (usr == null) {
setVisible(false);
return;
}
final boolean persisted = usr.getId() != null;
if (persisted) {
// Find fresh entity for editing
user = repository.findById(usr.getId()).get();
} else {
user = usr;
}
cancel.setVisible(persisted);
/* Bind user properties to similarly named fields
Could also use annotation or "manual binding"
or programmatically
moving values from fields to entities before saving*/
binder.setBean(user);
setVisible(true);
// Focus first name initially
firstName.focus();
}
public void setChangeHandler(ChangeHandler h) {
/* ChangeHandler is notified when either save or delete
is clicked*/
changeHandler = h;
}
public interface ChangeHandler {
void onChange();
}
}
Spring Boot Main Driver
package com.knf.dev.demo.springvaadincrud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot
.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.
run(Application.class, args);
}
}
Download the complete source code - click here
Local Setup and Run the application
Step 1: Download or clone the source code from GitHub to a local machine - Click here
Step 2: mvn clean install
Step 3: Run the Spring Boot application - mvn spring-boot:run
Step 1: Download or clone the source code from GitHub to a local machine - Click here
Step 2: mvn clean install
Step 3: Run the Spring Boot application - mvn spring-boot:run