This chapter covers how to use Java Persistent API (JPA) with Spring Boot and how to define a database by using entity classes. In the first phase, we will be using the H2 in-memory database for development and demonstration purposes. H2 is an in-memory SQL database that is good for fast development or demonstration purposes. In the second phase, we will move from H2 to use MariaDB. This chapter also describes the creation of CRUD repositories and a one-to-many connection between database tables.
In this chapter, we will cover the following topics:
Java SDK version 8 or higher is necessary to use Spring Boot (http://www.oracle.com/technetwork/java/javase/downloads/index.html). The Spring Boot application we created in previous chapters is required.
A MariaDB installation is necessary to create the database application (https://downloads.mariadb.org/).
The code for this chapter can be found at the following GitHub link: https://github.com/PacktPublishing/Full-Stack-Development-with-Spring-Boot-and-React/tree/main/Chapter03.
Check out the following video to see the Code in Action: https://bit.ly/3lVCuVe
Object Relational Mapping (ORM) is a technique that allows you to fetch from and manipulate a database by using an object-oriented programming paradigm. ORM is really good for programmers because it relies on object-oriented concepts rather than database structures. It also makes development much faster and reduces the amount of source code. ORM is mostly independent of databases, and developers don't have to worry about vendor-specific SQL statements.
Java Persistent API (JPA) provides object-relational mapping for Java developers. The JPA entity is a Java class that presents the structure of a database table. The fields of an entity class present the columns of the database tables.
Hibernate is the most popular Java-based JPA implementation and is used in Spring Boot by default. Hibernate is a mature product and is widely used in large-scale applications.
Next, we will start to implement our first entity class using the H2 database.
An entity class is a simple Java class that is annotated with JPA's @Entity annotation. Entity classes use the standard JavaBean naming convention and have proper getter and setter methods. The class fields have private visibility.
JPA creates a database table called by the name of the class when the application is initialized. If you want to use some other name for the database table, you can use the @Table annotation in your entity class.
At the beginning of this chapter, we will use the H2 database (https://www.h2database.com/), which is embedded in our in-memory database. To be able to use JPA and the H2 database, we have to add the following dependencies to the pom.xml file:
<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>
Let's look at the following steps to create entity classes:
package com.packt.cardatabase.domain;
import javax.persistence.Entity;
@Entity
public class Car {
}
Tip
You can use the Ctrl + Shift + O shortcut in Eclipse IDE to import missing packages automatically.
Important Note
This book is based on Spring Boot 2 and the upcoming Spring Boot 3 is using Jakarta EE instead of Java EE. Therefore, you need to replace all javax imports with jakarta if you are using Spring Boot 3.
For example, javax.persistence.Entity is replaced with jakarta.persistence.Entity
package com.packt.cardatabase.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Car {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String brand, model, color,
registerNumber;
private int year, price;
}
Tip
You can use the Ctrl + Shift + O shortcut in the Eclipse IDE to import missing packages automatically.
The primary key is defined by using the @Id annotation. The @GeneratedValue annotation defines that the ID is automatically generated by the database. We can also define our key generation strategy; the AUTO type means that the JPA provider selects the best strategy for a particular database and that it is also the default generation type. As well as this, you can create a composite primary key by annotating multiple attributes with the @Id annotation.
The database columns are named according to class field naming conventions by default. If you want to use some other naming convention, you can use the @Column annotation. With the @Column annotation, you can define the column's length and whether the column is nullable. The following code shows an example of using the @Column annotation. With this definition, the column's name in the database is explanation, the length of the column is 512, and it is not nullable:
@Column(name="explanation", nullable=false, length=512)
private String description
Tip
Eclipse provides the automatic addition of getters, setters, and constructors. Activate your cursor inside the class and right-click. From the menu, select Source | Generate Getters and Setters... or Source | Generate Constructor using fields....
package com.packt.cardatabase.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Car {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String brand, model, color,
registerNumber;
private int year, price;
public Car() {}
public Car(String brand, String model, String
color,
String registerNumber, int year, int
price) {
super();
this.brand = brand;
this.model = model;
this.color = color;
this.registerNumber = registerNumber;
this.year = year;
this.price = price;
}
The following is the source code for the Car entity class's getters and setters:
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
// Rest of the setters and getters. See the whole source code from GitHub
spring.datasource.url=jdbc:h2:mem:testdb
spring.jpa.show-sql=true
Important Note
When you are editing the application.properties file, you have to make sure that there are no extra spaces at the end of the lines. Otherwise, the settings won't work. This might happen often when you copy/paste settings.
Important Note
If spring.datasource.url is not defined in the application.properties file, Spring Boot creates a random data source URL that can be seen in the console when you run the application; for example, H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:b92ad05e-8af4-4c33-b22d-ccbf9ffe491e'.
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
Tip
You can also change the H2 database username and password by using the following settings in the application.properties file: spring.datasource.username and spring.datasource.password.
Now, you can see our CAR table in the database. You may notice that the register number has an underscore between the words. The reason for the underscore is the camel case naming of the attribute (registerNumber):
With that, we have created our first entity class and learned how JPA generates a database table from the entity class. Next, we will create a repository class that provides CRUD operations.
The Spring Boot Data JPA provides a CrudRepository interface for Create, Read, Update, and Delete (CRUD) operations. It provides CRUD functionalities to our entity class.
Let's create our repository in the domain package, as follows:
package com.packt.cardatabase.domain;
import org.springframework.data.repository
.CrudRepository;
public interface CarRepository extends CrudRepository<Car, Long> {
}
CarRepository now extends the Spring Boot JPA CrudRepository interface. The <Car, Long> type arguments define that this is the repository for the Car entity class and that the type of the ID field is Long.
The CrudRepository interface provides multiple CRUD methods that we can now start to use. The following table lists the most commonly used methods:
If the method returns only one item, Optional<T> is returned instead of T. The Optional class was introduced in Java 8 SE and is a type of single-value container that either contains a value or doesn't. If there is value, the isPresent() method returns true; otherwise, it returns false. If there is a value, you can get it by using the get() method. By using Optional, we can prevent null pointer exceptions.
package com.packt.cardatabase;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.
SpringBootApplication;
@SpringBootApplication
public class CardatabaseApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run
(CardatabaseApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
// Place your code here
}
}
package com.packt.cardatabase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation
.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.
SpringBootApplication;
import com.packt.cardatabase.domain.Car;
import com.packt.cardatabase.domain.CarRepository;
@SpringBootApplication
public class CardatabaseApplication implements CommandLineRunner {
private static final Logger logger =
LoggerFactory.getLogger(CardatabaseAp
plication.class);
@Autowired
private CarRepository repository;
public static void main(String[] args) {
SpringApplication.run
(CardatabaseApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
// Place your code here
}
}
// CardataseApplication.java run method
@Override
public void run(String... args) throws Exception {
repository.save(new Car(“Ford", “Mustang", “Red",
“ADF-1121", 2021, 59000));
repository.save(new Car(“Nissan", “Leaf", “White",
“SSJ-3002", 2019, 29000));
repository.save(new Car(“Toyota", “Prius",
“Silver",
“KKO-0212", 2020, 39000));
// Fetch all cars and log to console
for (Car car : repository.findAll()) {
logger.info(car.getBrand() + “ “ + car
.getModel());
}
}
The insert statements and cars we logged can be seen in the Eclipse console once the application has been executed:
You can now use the H2 console to fetch cars from the database, as shown in the following screenshot:
package com.packt.cardatabase.domain;
import java.util.List;
import org.springframework.data.repository.
CrudRepository;
public interface CarRepository extends CrudRepository
<Car, Long> {
// Fetch cars by brand
List<Car> findByBrand(String brand);
// Fetch cars by color
List<Car> findByColor(String color);
// Fetch cars by year
List<Car> findByYear(int year);
}
There can be multiple fields after the By keyword, concatenated with the And and or keywords:
package com.packt.cardatabase.domain;
import java.util.List;
import org.springframework.data.repository.
CrudRepository;
public interface CarRepository extends CrudRepository
<Car, Long> {
// Fetch cars by brand and model
List<Car> findByBrandAndModel(String brand, String
model);
// Fetch cars by brand or color
List<Car> findByBrandOrColor(String brand, String
color);
}
Queries can be sorted by using the OrderBy keyword in the query method:
package com.packt.cardatabase.domain;
import java.util.List;
import org.springframework.data.repository.
CrudRepository;
public interface CarRepository extends CrudRepository
<Car, Long> {
// Fetch cars by brand and sort by year
List<Car> findByBrandOrderByYearAsc(String brand);
}
package com.packt.cardatabase.domain;
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.
CrudRepository;
public interface CarRepository extends CrudRepository
<Car, Long> {
// Fetch cars by brand using SQL
@Query(“select c from Car c where c.brand = ?1")
List<Car> findByBrand(String brand);
}
With the @Query annotation, you can use more advanced expressions, such as like. The following example shows the usage of the like query in CrudRepository:
package com.packt.cardatabase.domain;
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
public interface CarRepository extends CrudRepository
<Car, Long> {
// Fetch cars by brand using SQL
@Query(“select c from Car c where c.brand like
%?1")
List<Car> findByBrandEndsWith(String brand);
}
Spring Data JPA also provides PagingAndSortingRepository, which extends CrudRepository. This offers methods to fetch entities using pagination and sorting. This is a good option if you are dealing with larger amounts of data because you don't have to return everything from a large result set. You can also sort your data into some meaningful order. PagingAndSortingRepository can be created in a similar way to how we created CrudRepository:
package com.packt.cardatabase.domain;
import org.springframework.data.repository.
PagingAndSortingRepository;
public interface CarRepository extends
PagingAndSortingRepository <Car, Long> {
}
In this case, you now have the two new additional methods that the repository provides:
At this point, we have completed our first database table and we are ready to add relationships between the database tables.
Next, we create a new table called owner that has a one-to-many relationship with the car table. In this case, a one-to-many relationship means that the owner can own multiple cars, but a car can only have one owner. The following Unified Modeling Language (UML) diagram shows the relationship between the tables:
The following are the steps to create a new table:
The following is the source code of the Owner entity class:
// Owner.java
package com.packt.cardatabase.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Owner {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private long ownerid;
private String firstname, lastname;
public Owner() {}
public Owner(String firstname, String lastname) {
super();
this.firstname = firstname;
this.lastname = lastname;
}
public long getOwnerid() {
return ownerid;
}
public void setOwnerid(long ownerid) {
this.ownerid = ownerid;
}
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;
}
}
The following is the source code for OwnerRepository:
// OwnerRepository.java
package com.packt.cardatabase.domain;
import org.springframework.data.repository.
CrudRepository;
public interface OwnerRepository extends
CrudRepository<Owner, Long> {
}
Now, our domain package contains two entity classes and repositories:
// Car.java
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="owner")
private Owner owner;
//Getter and setter
public Owner getOwner() {
return owner;
}
public void setOwner(Owner owner) {
this.owner = owner;
}
// Owner.java
@OneToMany(cascade=CascadeType.ALL, mappedBy="owner")
private List<Car> cars;
public List<Car> getCars() {
return cars;
}
public void setCars(List<Car> cars) {
this.cars = cars;
}
The @OneToMany annotation has two attributes that we are using. The cascade attribute defines how cascading affects the entities in the case of deletions or updates. The ALL attribute setting means that all operations are cascaded. For example, if the owner is deleted, the cars that are linked to that owner are deleted as well. The mappedBy="owner" attribute setting tells us that the Car class has the owner field, which is the foreign key for this relationship.
When you run the project, by looking in the console, you will see that the relationship has been created:
// Car.java constructor
public Car(String brand, String model, String color,
String registerNumber, int year, int price, Owner
owner) {
super();
this.brand = brand;
this.model = model;
this.color = color;
this.registerNumber = registerNumber;
this.year = year;
this.price = price;
this.owner = owner;
}
// CardatabaseApplication.java
import com.packt.cardatabase.domain.Owner;
import com.packt.cardatabase.domain.OwnerRepository;
Now, let's inject OwnerRepository into the CardatabaseApplication class:
@Autowired
private OwnerRepository orepository;
At this point, we must modify the run method to save owners and link owners and cars:
@Override
public void run(String... args) throws Exception {
// Add owner objects and save these to db
Owner owner1 = new Owner(“John" , “Johnson");
Owner owner2 = new Owner(“Mary" , “Robinson");
orepository.saveAll(Arrays.asList(owner1,
owner2));
// Add car object and link to owners and save
these to db
Car car1 = new Car(“Ford", “Mustang", “Red",
“ADF-1121", 2021, 59000, owner1);
Car car2 = new Car(“Nissan", “Leaf", “White",
“SSJ-3002", 2019, 29000, owner2);
Car car3 = new Car(“Toyota", “Prius", “Silver",
“KKO-0212", 2020, 39000, owner2);
repository.saveAll(Arrays.asList(car1, car2,
car3));
for (Car car : repository.findAll()) {
logger.info(car.getBrand() + “ “ +
car.getModel());
}
}
Now, if you run the application and fetch cars from the database, you will see that the owners are now linked to the cars:
If you want to create a many-to-many relationship instead, which means, in practice, that an owner can have multiple cars and a car can have multiple owners, you should use the @ManyToMany annotation. In our example application, we will use a one-to-many relationship. The code that you have completed here will be needed in the next chapter.
Next, you will learn how to change the relationship to many-to-many. In a many-to-many relationship, it is recommended that you use Set instead of List with Hibernate:
// Car.java
@ManyToMany(mappedBy="cars")
private Set<Owner> owners = new HashSet<Owner>();
public Set<Owner> getOwners() {
return owners;
}
public void setOwners(Set<Owner> owners) {
this.owners = owners;
}
// Owner.java
@ManyToMany(cascade=CascadeType.PERSIST)
@JoinTable(name="car_owner",
joinColumns = { @JoinColumn(name="ownerid") },
inverseJoinColumns = { @JoinColumn(name="id") })
private Set<Car> cars = new HashSet<Car>();
public Set<Car> getCars() {
return cars;
}
public void setCars(Set<Car> cars) {
this.cars = cars;
}
Now, the database UML diagram looks as follows:
Now that we have used an in-memory H2 database, we are going to use a MariaDB database instead.
Now, we will switch our database from H2 to MariaDB. The database tables are still created automatically by JPA. However, before we run our application, we have to create a database for it. In this section, we will be using the one-to-many relationship from the previous section.
The database can be created by using HeidiSQL. Open HeidiSQL and follow these steps:
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
</dependency>
spring.datasource.url=jdbc:mariadb://localhost:3306/cardb
spring.datasource.username=root
spring.datasource.password=YOUR_PASSWORD
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb:
//localhost:3306/cardb
spring.datasource.username=root
spring.datasource.password=YOUR_PASSWORD
spring.datasource.driver-class-name=
org.mariadb.jdbc.Driver
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create-drop
Now, your application is ready to use with MariaDB.
In this chapter, we used JPA to create our Spring Boot application database. First, we created entity classes, which are mapped to database tables. Then, we created CrudRepository for our entity class, which provides CRUD operations for the entity. After that, we managed to add some demo data to our database by using CommandLineRunner. We also created one-to-many relationships between two entities. At the beginning of this chapter, we used the H2 in-memory database, while at the end, we switched the database to MariaDB.
In the next chapter, we will create a RESTful web service for our backend. We will also look at testing the RESTful web service with the curl command-line tool, and also by using Postman GUI.
Answer the following questions to test your knowledge of this chapter:
Packt has other great resources for Spring Boot:
3.135.196.103