Spring Boot and Vaadin : Creating a Spring Boot backend: database, JPA repositories, and services
Most real-life applications need to persist and retrieve data from a database. In this tutorial, we use an in-memory H2 database. You can easily adapt the configuration to use another database, like MySQL or Postgres.
There are a fair number of classes to copy and paste to set up your backend. You can make your life easier by downloading a project with all the changes, if you prefer. The download link is at the end of this chapter. The code from the previous tutorial chapter can be found here, if you want to jump directly into this chapter.
Installing the database dependencies
We use Spring Data for data access. Under the hood, it uses Hibernate to map Java objects to database entities through the Java Persistence API. Spring Boot takes care of configuring all these tools for you.
To add database dependencies:
1. In the <dependencies> tag in your pom.xml file, add the following dependencies for H2 and Spring Data:
pom.xml
<dependencies>
<!--all existing dependencies -->
<!--database 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>
</dependencies>
2. Save your file and when IntelliJ asks if you want to enable automatic importing of Maven dependencies, select Enable Auto-Import.
If IntelliJ doesn’t ask you to import dependencies, or if you use another IDE, type mvn install in the command line (while in the root of your project folder) to download the dependencies.
NOTE
H2 is a great database for tutorials because you don’t need to install external software. If
you prefer, you can easily change to another database.
See:
•Setting up MySQL
•Setting up Postgres
The instructions in the remainder of this tutorial are the same, regardless of which database you use. To keep things simple, we recommend sticking with H2.
Defining the data model
Our application is a customer relationship management (CRM) system that manages contacts and companies. To map content to our database, we need to create the
following entity classes:
• Contact: An employee at a company.
• Company: An entity that can have several employees.
• AbstractEntity: A common superclass for both.
To create your entity classes:
1. Create a new package: com.vaadin.tutorial.crm.backend.entity.
2. Create three classes, AbstractEntity, Contact, and Company, in the new package,
using the code detailed below.
The easiest way to do this is to copy the full class and paste it into the package in the project view. IntelliJ (and most other IDEs) will automatically create the Java file for you.
a. Start by adding AbstractEntity, the common superclass. It defines how objects ids are generated and how object equality is determined.
AbstractEntity.java
package com.vaadin.tutorial.crm.backend.entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
@MappedSuperclass
public abstract class AbstractEntity {
@Id
@GeneratedValue(strategy= GenerationType.SEQUENCE)
private Long id;
public Long getId() {
return id;
}
public boolean isPersisted() {
return id != null;
}
@Override
public int hashCode() {
if (getId() != null) {
return getId().hashCode();
}
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AbstractEntity other = (AbstractEntity) obj;
if (getId() == null || other.getId() == null) {
return false;
}
return getId().equals(other.getId());
}
}
b. Next, create the Contact class:
Contact.java
38
package com.vaadin.tutorial.crm.backend.entity;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Entity
public class Contact extends AbstractEntity implements Cloneable {
public enum Status {
ImportedLead, NotContacted, Contacted, Customer, ClosedLost
}
@NotNull
@NotEmpty
private String firstName = "";
@NotNull
@NotEmpty
private String lastName = "";
@ManyToOne
@JoinColumn(name = "company_id")
private Company company;
@Enumerated(EnumType.STRING)
@NotNull
private Contact.Status status;
@Email
@NotNull
@NotEmpty
private String email = "";
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setCompany(Company company) {
this.company = company;
}
public Company getCompany() {
return company;
}
@Override
public String toString() {
return firstName + " " + lastName;
}
}
c. Finally, copy over the Company class:
Company.java
package com.vaadin.tutorial.crm.backend.entity;
import javax.persistence.*;
import java.util.LinkedList;
import java.util.List;
@Entity
public class Company extends AbstractEntity {
private String name;
@OneToMany(mappedBy = "company", fetch = FetchType.EAGER)
private List<Contact> employees = new LinkedList<>();
public Company() {
}
public Company(String name) {
setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Contact> getEmployees() {
return employees;
}
}
3. Verify that you’re able to build the project successfully.
If you see a lot of errors about missing classes, double check the Maven dependencies and run mvn install to make sure they are downloaded. Creating repositories to access the database Now that you have defined the data model, the next step is to create repository classes to access the database. Spring Boot makes this a painless process. All you need to do is define an interface that describes the entity type and primary key type, and Spring Data will configure it for you.
To create your repository classes:
41
1. Create a new package com.vaadin.tutorial.crm.backend.repository.
2. Copy the following two repository classes into the package:
ContactRepository.java
package com.vaadin.tutorial.crm.backend.repository;
import com.vaadin.tutorial.crm.backend.entity.Contact;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface ContactRepository extends JpaRepository<Contact, Long> {
}
CompanyRepository.java
package com.vaadin.tutorial.crm.backend.repository;
import com.vaadin.tutorial.crm.backend.entity.Company;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CompanyRepository extends JpaRepository<Company, Long> {
}
Creating service classes for business logic
It’s good practice to not let UI code access the database directly. Instead, we create service classes that handle business logic and database access. This makes it easier for you to control access and to keep your data consistent. To create your service classes:
1. Create a new package com.vaadin.tutorial.crm.backend.service.
2. Copy the following two service classes into the package:
ContactService.java
package com.vaadin.tutorial.crm.backend.service;
import com.vaadin.tutorial.crm.backend.entity.Contact;
import com.vaadin.tutorial.crm.backend.repository.CompanyRepository;
import com.vaadin.tutorial.crm.backend.repository.ContactRepository;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@Service ①
public class ContactService {
private static final Logger LOGGER = Logger.getLogger(ContactService.class
.getName());
private ContactRepository contactRepository;
private CompanyRepository companyRepository;
public ContactService(ContactRepository contactRepository,
CompanyRepository
companyRepository) { ②
this.contactRepository = contactRepository;
this.companyRepository = companyRepository;
}
public List<Contact> findAll() {
return contactRepository.findAll();
}
public long count() {
return contactRepository.count();
}
public void delete(Contact contact) {
contactRepository.delete(contact);
}
public void save(Contact contact) {
if (contact == null) { ③
LOGGER.log(Level.SEVERE,
"Contact is null. Are you sure you have connected your form
to the application?");
return;
}
contactRepository.save(contact);
}
}
① The @Service annotation lets Spring know that this is a service class and makes it available for injection. This allows you to easily use it from your UI code later on.
② The constructor takes 2 parameters: ContactRepository and CompanyRepository. Spring provides instances based on the interfaces we defined earlier.
③ For now, most operations are just passed through to the repository. The only exception is the save method, which checks for null values before attempting to save.
CompanyService.java
package com.vaadin.tutorial.crm.backend.service;
import com.vaadin.tutorial.crm.backend.entity.Company;
import com.vaadin.tutorial.crm.backend.repository.CompanyRepository;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class CompanyService {
private CompanyRepository companyRepository;
public CompanyService(CompanyRepository companyRepository) {
this.companyRepository = companyRepository;
}
public List<Company> findAll() {
return companyRepository.findAll();
}
}
Populating with test data
Next, we add a method that generates test data to populate our database. This makes it easier to work with the application. To do this, add the following method at the end of ContactService:
ContactService.java
@PostConstruct ①
public void populateTestData() {
if (companyRepository.count() == 0) {
companyRepository.saveAll( ②
Stream.of("Path-Way Electronics", "E-Tech Management", "Path-E-Tech
Management")
.map(Company::new)
.collect(Collectors.toList()));
}
if (contactRepository.count() == 0) {
Random r = new Random(0);
List<Company> companies = companyRepository.findAll();
contactRepository.saveAll( ③
Stream.of("Gabrielle Patel", "Brian Robinson", "Eduardo Haugen",
"Koen Johansen", "Alejandro Macdonald", "Angel Karlsson", "Yahir
Gustavsson", "Haiden Svensson",
"Emily Stewart", "Corinne Davis", "Ryann Davis", "Yurem Jackson",
"Kelly Gustavsson",
"Eileen Walker", "Katelyn Martin", "Israel Carlsson", "Quinn
Hansson", "Makena Smith",
"Danielle Watson", "Leland Harris", "Gunner Karlsen", "Jamar Olsson
", "Lara Martin",
"Ann Andersson", "Remington Andersson", "Rene Carlsson", "Elvis
Olsen", "Solomon Olsen",
"Jaydan Jackson", "Bernard Nilsen")
.map(name -> {
String[] split = name.split(" ");
Contact contact = new Contact();
contact.setFirstName(split[0]);
contact.setLastName(split[1]);
contact.setCompany(companies.get(r.nextInt(companies.size())));
contact.setStatus(Contact.Status.values()[r.nextInt(Contact
.Status.values().length)]);
String email = (contact.getFirstName() + "." + contact
.getLastName() + "@" + contact.getCompany().getName().replaceAll("[\\s-]", "") +
".com").toLowerCase();
contact.setEmail(email);
return contact;
}).collect(Collectors.toList()));
}
}
① The @PostConstruct annotation tells Spring to run this method after constructing
ContactService.
② Creates 3 test companies.
③ Creates test contacts.
Restart the server to pick up all the new dependencies You need to stop and restart the application to make sure all the new POM dependencies are picked up correctly. You can download the project with a fully set-up back end below. Unzip the project and follow the instructions in the importing chapter.
Download from GitHub
In the next chapter, we’ll use the back end to populate data into a data grid in the browser.
There are a fair number of classes to copy and paste to set up your backend. You can make your life easier by downloading a project with all the changes, if you prefer. The download link is at the end of this chapter. The code from the previous tutorial chapter can be found here, if you want to jump directly into this chapter.
Installing the database dependencies
We use Spring Data for data access. Under the hood, it uses Hibernate to map Java objects to database entities through the Java Persistence API. Spring Boot takes care of configuring all these tools for you.
To add database dependencies:
1. In the <dependencies> tag in your pom.xml file, add the following dependencies for H2 and Spring Data:
pom.xml
<dependencies>
<!--all existing dependencies -->
<!--database 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>
</dependencies>
2. Save your file and when IntelliJ asks if you want to enable automatic importing of Maven dependencies, select Enable Auto-Import.
If IntelliJ doesn’t ask you to import dependencies, or if you use another IDE, type mvn install in the command line (while in the root of your project folder) to download the dependencies.
NOTE
H2 is a great database for tutorials because you don’t need to install external software. If
you prefer, you can easily change to another database.
See:
•Setting up MySQL
•Setting up Postgres
The instructions in the remainder of this tutorial are the same, regardless of which database you use. To keep things simple, we recommend sticking with H2.
Defining the data model
Our application is a customer relationship management (CRM) system that manages contacts and companies. To map content to our database, we need to create the
following entity classes:
• Contact: An employee at a company.
• Company: An entity that can have several employees.
• AbstractEntity: A common superclass for both.
To create your entity classes:
1. Create a new package: com.vaadin.tutorial.crm.backend.entity.
2. Create three classes, AbstractEntity, Contact, and Company, in the new package,
using the code detailed below.
The easiest way to do this is to copy the full class and paste it into the package in the project view. IntelliJ (and most other IDEs) will automatically create the Java file for you.
a. Start by adding AbstractEntity, the common superclass. It defines how objects ids are generated and how object equality is determined.
AbstractEntity.java
package com.vaadin.tutorial.crm.backend.entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
@MappedSuperclass
public abstract class AbstractEntity {
@Id
@GeneratedValue(strategy= GenerationType.SEQUENCE)
private Long id;
public Long getId() {
return id;
}
public boolean isPersisted() {
return id != null;
}
@Override
public int hashCode() {
if (getId() != null) {
return getId().hashCode();
}
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AbstractEntity other = (AbstractEntity) obj;
if (getId() == null || other.getId() == null) {
return false;
}
return getId().equals(other.getId());
}
}
b. Next, create the Contact class:
Contact.java
38
package com.vaadin.tutorial.crm.backend.entity;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Entity
public class Contact extends AbstractEntity implements Cloneable {
public enum Status {
ImportedLead, NotContacted, Contacted, Customer, ClosedLost
}
@NotNull
@NotEmpty
private String firstName = "";
@NotNull
@NotEmpty
private String lastName = "";
@ManyToOne
@JoinColumn(name = "company_id")
private Company company;
@Enumerated(EnumType.STRING)
@NotNull
private Contact.Status status;
@NotNull
@NotEmpty
private String email = "";
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setCompany(Company company) {
this.company = company;
}
public Company getCompany() {
return company;
}
@Override
public String toString() {
return firstName + " " + lastName;
}
}
c. Finally, copy over the Company class:
Company.java
package com.vaadin.tutorial.crm.backend.entity;
import javax.persistence.*;
import java.util.LinkedList;
import java.util.List;
@Entity
public class Company extends AbstractEntity {
private String name;
@OneToMany(mappedBy = "company", fetch = FetchType.EAGER)
private List<Contact> employees = new LinkedList<>();
public Company() {
}
public Company(String name) {
setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Contact> getEmployees() {
return employees;
}
}
3. Verify that you’re able to build the project successfully.
If you see a lot of errors about missing classes, double check the Maven dependencies and run mvn install to make sure they are downloaded. Creating repositories to access the database Now that you have defined the data model, the next step is to create repository classes to access the database. Spring Boot makes this a painless process. All you need to do is define an interface that describes the entity type and primary key type, and Spring Data will configure it for you.
To create your repository classes:
41
1. Create a new package com.vaadin.tutorial.crm.backend.repository.
2. Copy the following two repository classes into the package:
ContactRepository.java
package com.vaadin.tutorial.crm.backend.repository;
import com.vaadin.tutorial.crm.backend.entity.Contact;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface ContactRepository extends JpaRepository<Contact, Long> {
}
CompanyRepository.java
package com.vaadin.tutorial.crm.backend.repository;
import com.vaadin.tutorial.crm.backend.entity.Company;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CompanyRepository extends JpaRepository<Company, Long> {
}
Creating service classes for business logic
It’s good practice to not let UI code access the database directly. Instead, we create service classes that handle business logic and database access. This makes it easier for you to control access and to keep your data consistent. To create your service classes:
1. Create a new package com.vaadin.tutorial.crm.backend.service.
2. Copy the following two service classes into the package:
ContactService.java
package com.vaadin.tutorial.crm.backend.service;
import com.vaadin.tutorial.crm.backend.entity.Contact;
import com.vaadin.tutorial.crm.backend.repository.CompanyRepository;
import com.vaadin.tutorial.crm.backend.repository.ContactRepository;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@Service ①
public class ContactService {
private static final Logger LOGGER = Logger.getLogger(ContactService.class
.getName());
private ContactRepository contactRepository;
private CompanyRepository companyRepository;
public ContactService(ContactRepository contactRepository,
CompanyRepository
companyRepository) { ②
this.contactRepository = contactRepository;
this.companyRepository = companyRepository;
}
public List<Contact> findAll() {
return contactRepository.findAll();
}
public long count() {
return contactRepository.count();
}
public void delete(Contact contact) {
contactRepository.delete(contact);
}
public void save(Contact contact) {
if (contact == null) { ③
LOGGER.log(Level.SEVERE,
"Contact is null. Are you sure you have connected your form
to the application?");
return;
}
contactRepository.save(contact);
}
}
① The @Service annotation lets Spring know that this is a service class and makes it available for injection. This allows you to easily use it from your UI code later on.
② The constructor takes 2 parameters: ContactRepository and CompanyRepository. Spring provides instances based on the interfaces we defined earlier.
③ For now, most operations are just passed through to the repository. The only exception is the save method, which checks for null values before attempting to save.
CompanyService.java
package com.vaadin.tutorial.crm.backend.service;
import com.vaadin.tutorial.crm.backend.entity.Company;
import com.vaadin.tutorial.crm.backend.repository.CompanyRepository;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class CompanyService {
private CompanyRepository companyRepository;
public CompanyService(CompanyRepository companyRepository) {
this.companyRepository = companyRepository;
}
public List<Company> findAll() {
return companyRepository.findAll();
}
}
Populating with test data
Next, we add a method that generates test data to populate our database. This makes it easier to work with the application. To do this, add the following method at the end of ContactService:
ContactService.java
@PostConstruct ①
public void populateTestData() {
if (companyRepository.count() == 0) {
companyRepository.saveAll( ②
Stream.of("Path-Way Electronics", "E-Tech Management", "Path-E-Tech
Management")
.map(Company::new)
.collect(Collectors.toList()));
}
if (contactRepository.count() == 0) {
Random r = new Random(0);
List<Company> companies = companyRepository.findAll();
contactRepository.saveAll( ③
Stream.of("Gabrielle Patel", "Brian Robinson", "Eduardo Haugen",
"Koen Johansen", "Alejandro Macdonald", "Angel Karlsson", "Yahir
Gustavsson", "Haiden Svensson",
"Emily Stewart", "Corinne Davis", "Ryann Davis", "Yurem Jackson",
"Kelly Gustavsson",
"Eileen Walker", "Katelyn Martin", "Israel Carlsson", "Quinn
Hansson", "Makena Smith",
"Danielle Watson", "Leland Harris", "Gunner Karlsen", "Jamar Olsson
", "Lara Martin",
"Ann Andersson", "Remington Andersson", "Rene Carlsson", "Elvis
Olsen", "Solomon Olsen",
"Jaydan Jackson", "Bernard Nilsen")
.map(name -> {
String[] split = name.split(" ");
Contact contact = new Contact();
contact.setFirstName(split[0]);
contact.setLastName(split[1]);
contact.setCompany(companies.get(r.nextInt(companies.size())));
contact.setStatus(Contact.Status.values()[r.nextInt(Contact
.Status.values().length)]);
String email = (contact.getFirstName() + "." + contact
.getLastName() + "@" + contact.getCompany().getName().replaceAll("[\\s-]", "") +
".com").toLowerCase();
contact.setEmail(email);
return contact;
}).collect(Collectors.toList()));
}
}
① The @PostConstruct annotation tells Spring to run this method after constructing
ContactService.
② Creates 3 test companies.
③ Creates test contacts.
Restart the server to pick up all the new dependencies You need to stop and restart the application to make sure all the new POM dependencies are picked up correctly. You can download the project with a fully set-up back end below. Unzip the project and follow the instructions in the importing chapter.
Download from GitHub
In the next chapter, we’ll use the back end to populate data into a data grid in the browser.
Comments
Post a Comment