Connect a View to Backend
On the previous part of this tutorial, you created a view using Vaadin components and layouts. On this part, you’ll connect the view to the backend to display and update data.
You can find the backend code in the src/main/java
directory.
This part of this tutorial covers three main aspects of this:
-
Spring Boot introduction;
-
Spring Service Interface to the backend; and
-
Accessing a service from a view.
Introduction to Spring Boot
Vaadin uses Spring Boot on the server. Spring Boot is an opinionated, convention-over-configuration approach to creating Spring applications. It automates much of the required configuration and manages an embedded Tomcat server. So, you don’t need to deploy the application to a separate server.
This tutorial uses the following features that are configured by Spring Boot:
-
Spring Data for accessing the database through JPA and Hibernate;
-
An embedded H2 Database for development (easy to replace with e.g. PostgreSQL for production);
-
Spring Boot DevTools for automatic code reload;
-
Embedded Tomcat server for deployment; and
-
Spring Security for authenticating users.
Backend Overview
The starter you downloaded contains the entities and repositories you need. It also contains sample data loaded using src/main/resources/data.sql
file.
Domain Model: Entities
The Vaadin CRM application has three JPA entities that make up its domain model: Contact
, Company
, and Status
. A contact belongs to a company and has a status.
You can find the entities in the com.example.application.data
package.
Database Access: Repositories
The application uses Spring Data JPA repositories for database access. Spring Data provides implementations of basic create, read, update, and delete (i.e., CRUD) database operations when you extend from the JpaRepository
interface.
You can find the repositories in the com.example.application.data
package.
The src/main/resources/data.sql
file contains sample data that Spring Boot populates to the database on startup.
Create a Service for Database Access
Instead of accessing the database directly from the view, you would create a Spring Service. The service class handles the application’s business logic and, in larger applications, it often transforms database entities into Data-Transfer Objects (DTO) for views. This tutorial shows how to create a single service that provides all of the methods you need.
First, create a new class, CrmService.java
, in the com.example.application.services
package with the following content:
package com.example.application.services;
import com.example.application.data.Company;
import com.example.application.data.Contact;
import com.example.application.data.Status;
import com.example.application.data.CompanyRepository;
import com.example.application.data.ContactRepository;
import com.example.application.data.StatusRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service // (1)
public class CrmService {
private final ContactRepository contactRepository;
private final CompanyRepository companyRepository;
private final StatusRepository statusRepository;
public CrmService(ContactRepository contactRepository,
CompanyRepository companyRepository,
StatusRepository statusRepository) { // (2)
this.contactRepository = contactRepository;
this.companyRepository = companyRepository;
this.statusRepository = statusRepository;
}
public List<Contact> findAllContacts(String stringFilter) {
if (stringFilter == null || stringFilter.isEmpty()) { // (3)
return contactRepository.findAll();
} else {
return contactRepository.search(stringFilter);
}
}
public long countContacts() {
return contactRepository.count();
}
public void deleteContact(Contact contact) {
contactRepository.delete(contact);
}
public void saveContact(Contact contact) {
if (contact == null) { // (4)
System.err.println("Contact is null. Are you sure you have connected your form to the application?");
return;
}
contactRepository.save(contact);
}
public List<Company> findAllCompanies() {
return companyRepository.findAll();
}
public List<Status> findAllStatuses(){
return statusRepository.findAll();
}
}
-
The
@Service
annotation makes this a Spring-managed service that you can inject into your view. -
Use Spring constructor injection to autowire the database repositories.
-
Check if there’s an active filter: return either all contacts, or use the repository to filter based on the string.
-
Service classes often include validation and other business rules before persisting data. You check here that you aren’t trying to save a
null
object.
Implement Filtering in Repository
Add the search()
method to the contacts repository so as to provide the service class with the required method for filtering contacts.
public interface ContactRepository extends JpaRepository<Contact, Long> {
@Query("select c from Contact c " +
"where lower(c.firstName) like lower(concat('%', :searchTerm, '%')) " +
"or lower(c.lastName) like lower(concat('%', :searchTerm, '%'))") // (1)
List<Contact> search(@Param("searchTerm") String searchTerm); // (2)
}
This example uses the @Query
annotation to define a custom query (see annotation 1). In this case, it checks if the string matches the first or the last name, and ignores the case. The query uses Java Persistence Query Language (JPQL) which is an SQL-like language for querying JPA-managed databases.
You don’t need to implement the method. Spring Data provides the implementation based on the query.
Using Backend Service
You can now inject the CrmService
into the list view to access the backend.
package com.example.application.views.list;
import com.example.application.data.Contact;
import com.example.application.services.CrmService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.grid.Grid;
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.PageTitle;
import com.vaadin.flow.router.Route;
@Route(value = "")
@PageTitle("Contacts | Vaadin CRM")
public class ListView extends VerticalLayout {
Grid<Contact> grid = new Grid<>(Contact.class);
TextField filterText = new TextField();
ContactForm form;
CrmService service;
public ListView(CrmService service) { // (1)
this.service = service;
addClassName("list-view");
setSizeFull();
configureGrid();
configureForm();
add(getToolbar(), getContent());
updateList(); // (2)
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.addClassNames("content");
content.setSizeFull();
return content;
}
private void configureForm() {
form = new ContactForm(service.findAllCompanies(), service.findAllStatuses()); // (3)
form.setWidth("25em");
}
private void configureGrid() {
grid.addClassNames("contact-grid");
grid.setSizeFull();
grid.setColumns("firstName", "lastName", "email");
grid.addColumn(contact -> contact.getStatus().getName()).setHeader("Status");
grid.addColumn(contact -> contact.getCompany().getName()).setHeader("Company");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
}
private HorizontalLayout getToolbar() {
filterText.setPlaceholder("Filter by name...");
filterText.setClearButtonVisible(true);
filterText.setValueChangeMode(ValueChangeMode.LAZY);
filterText.addValueChangeListener(e -> updateList()); // (4)
Button addContactButton = new Button("Add contact");
var toolbar = new HorizontalLayout(filterText, addContactButton);
toolbar.addClassName("toolbar");
return toolbar;
}
private void updateList() { // (5)
grid.setItems(service.findAllContacts(filterText.getValue()));
}
}
-
Autowire
CrmService
through the constructor. Save it in a field, so you can access it from other methods. -
Call
updateList()
once you have constructed the view. -
Use the service to fetch companies and statuses.
-
Call
updateList()
any time the filter changes. -
updateList()
sets the grid items by calling the service with the value from the filter text field.
Now build the project, refresh the browser, and verify that you can now see contacts in the grid. It should look like the screenshot here. Try filtering the contents by typing in the filter text field.
1EA60808-40B7-4F0C-8B71-C0CB905299D2