Spring Data
Spring Data comes with a common programming model for persisting data in various types of database engines, ranging from traditional relational databases (SQL databases) to various types of NoSQL database engines, such as document databases (for example, MongoDB), key-value databases (for example, Redis), and graph databases (for example, Neo4j).
The Spring Data project is divided into several subprojects, and in this book, we will use Spring Data subprojects for MongoDB and JPA that have been mapped to a MySQL database.
JPA stands for Jakarta Persistence API and is a Java specification about how to handle relational data. Please go to https://jakarta.ee/specifications/persistence/ for the latest specifications. Jakarta EE 9 is based on Jakarta Persistence 3.0.
The two core concepts of the programming model in Spring Data are entities and repositories. Entities and repositories generalize how data is stored and accessed from the various types of databases. They provide a common abstraction but still support adding database-specific behavior to the entities and repositories. These two core concepts are briefly explained together with some illustrative code examples as we proceed through this chapter. Remember that more details will be provided in the upcoming chapters!
Even though Spring Data provides a common programming model for different types of databases, this doesn’t mean that you will be able to write portable source code. For example, switching the database technology from a SQL database to a NoSQL database will, in general, not be possible without some changes in the source code!
Entity
An entity describes the data that will be stored by Spring Data. Entity classes are, in general, annotated with a mix of generic Spring Data annotations and annotations that are specific to each database technology.
For example, an entity that will be stored in a relational database can be annotated with JPA annotations such as the following:
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Table;
@Entity
@IdClass(ReviewEntityPK.class)
@Table(name = "review")
public class ReviewEntity {
@Id private int productId;
@Id private int reviewId;
private String author;
private String subject;
private String content;
If an entity is to be stored in a MongoDB database, annotations from the Spring Data MongoDB subproject can be used together with generic Spring Data annotations. For example, consider the following code:
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.mongodb.core.mapping.Document;
@Document
public class RecommendationEntity {
@Id
private String id;
@Version
private int version;
private int productId;
private int recommendationId;
private String author;
private int rate;
private String content;
The @Id
and @Version
annotations are generic annotations, while the @Document
annotation is specific to the Spring Data MongoDB subproject.
This can be revealed by studying the import
statements; the import
statements that contain mongodb
come from the Spring Data MongoDB subproject.
Repositories
Repositories are used to store and access data from different types of databases. In its most basic form, a repository can be declared as a Java interface, and Spring Data will generate its implementation on the fly using opinionated conventions. These conventions can be overridden and/or complemented by additional configuration and, if required, some Java code.
Spring Data also comes with some base Java interfaces, for example, CrudRepository
, to make the definition of a repository even simpler. The base interface, CrudRepository
, provides us with standard methods for create
, read
, update
, and delete
operations.
To specify a repository for handling the JPA entity ReviewEntity
, we only need to declare the following:
import org.springframework.data.repository.CrudRepository;
public interface ReviewRepository extends
CrudRepository<ReviewEntity, ReviewEntityPK> {
Collection<ReviewEntity> findByProductId(int productId);
}
In this example we use a class, ReviewEntityPK
, to describe a composite primary key. It looks as follows:
public class ReviewEntityPK implements Serializable {
public int productId;
public int reviewId;
}
We have also added an extra method, findByProductId
, which allows us to look up Review
entities based on productid
– a field that is part of the primary key. The naming of the method follows a naming convention defined by Spring Data that allows Spring Data to generate the implementation of this method on the fly as well.
If we want to use the repository, we can simply inject it and then start to use it, for example:
private final ReviewRepository repository;
@Autowired
public ReviewService(ReviewRepository repository) {
this.repository = repository;
}
public void someMethod() {
repository.save(entity);
repository.delete(entity);
repository.findByProductId(productId);
Added to the CrudRepository
interface, Spring Data also provides a reactive base interface, ReactiveCrudRepository
, which enables reactive repositories. The methods in this interface do not return objects or collections of objects; instead, they return Mono and Flux objects. Mono
and Flux
objects are, as we will see in Chapter 7, Developing Reactive Microservices, reactive streams that are capable of returning either 0...1
or 0...m
entities as they become available on the stream.
The reactive-based interface can only be used by Spring Data subprojects that support reactive database drivers; that is, they are based on non-blocking I/O. The Spring Data MongoDB subproject supports reactive repositories, while Spring Data JPA does not.
Specifying a reactive repository for handling the MongoDB entity, RecommendationEntity
, as described previously, might look something like the following:
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;
public interface RecommendationRepository extends ReactiveCrudRepository<RecommendationEntity, String> {
Flux<RecommendationEntity> findByProductId(int productId);
}
This concludes the section on Spring Data. Now let’s see how we can use Spring Cloud Stream to develop message-based asynchronous services.