It all starts with the Spring Data Commons module, which provides the backbone for various Spring Data projects. Then, there's the Spring Data JPA module, building on JPA for even smoother data handling.
Today, you're going to learn about JpaRepository. It's your go-to for simplified data access, armed with a range of methods and extended interfaces. Simply put, everything we did before with CrudRepository, PagingAndSortingRepository, and Custom queries with @Query applies to JpaRepository. Let's get some hands-on experience with it!
Project setup
Use Spring initializer or, alternatively, IDE to create a Spring Boot project with the following dependencies:
Maven
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Gradle(Kotlin)
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
JpaRepository interface overview
Let's first take a look at the interfaces that JpaRepository interface extends so we can understand it in more detail:
The above diagram shows that JpaRepository extends ListCrudRepository, ListPagingAndSortingRepository, and QueryByExampleExecutor. This means that everything we did before using these interfaces we can also do it with JpaRepository. You can think about it as a 31 interface.
This is the source code for JpaRepository :
public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
void flush();
<S extends T> S saveAndFlush(S entity);
<S extends T> List<S> saveAllAndFlush(Iterable<S> entities);
void deleteAllInBatch(Iterable<T> entities);
void deleteAllByIdInBatch(Iterable<ID> ids);
void deleteAllInBatch();
T getReferenceById(ID id);
<S extends T> List<S> findAll(Example<S> example);
<S extends T> List<S> findAll(Example<S> example, Sort sort);
}
The JpaRepository itself has some useful methods. Here's a brief explanation of the most important methods in this repository:
-
flush()synchronizes the persistence context with the underlying database. It forces any pending changes to be written to the database. -
saveAndFlush(S entity)saves an entity to the database and immediately flushes the changes, ensuring that the data is persisted right away. -
saveAllAndFlush(Iterable<S> entities)saves a collection of entities and immediately flushes the changes, ensuring that all the data is immediately persisted. -
deleteAllInBatch(Iterable<T> entities)deletes a collection of entities in a single batch operation, which can be more efficient than deleting them one by one. -
findAll(Example<S> example)finds all entities that match the example provided.Exampleis used to specify the criteria for the search.
There is no need to go over all the other methods — they are the same, and their names are self-explanatory.
CRUD operations
The JpaRepository extends ListCrudRepository, which in turn extends the CrudRepository interface. This makes it able to handle CRUD operations. Let's take a look at an example.
Start by making an entity:
@Entity
public class Course {
@Id
@GeneratedValue
private Long id;
private String name;
private String description;
@Enumerated(EnumType.STRING)
private Status status;
public Course(String name, String description, Status status) {
this.name = name;
this.description = description;
this.status = status;
}
// toString and all args constructor, no arg constructor, getter and setters omitted
}
public enum Status {
ALPHA, BETA, READY
}
In the next step, let's create the repository:
@Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
}
That's it! We don't have to implement any methods as they will be implemented for us. And now, let's use the class directly:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@Component
public class Runner implements CommandLineRunner {
private final CourseRepository repository;
public Runner(CourseRepository repository) {
this.repository = repository;
}
@PostConstruct
public void init() {
repository.save(new Course("Pharma1", "Basics of pharma", Status.ALPHA));
repository.save(new Course("Pharma1", "Basics of pharma for non pharmacists", Status.BETA));
repository.save(new Course("Pharma2", "Be expert at pharma", Status.ALPHA));
repository.save(new Course("Anatomy", "Get the basic knowledge of human anatomy", Status.BETA));
}
@Override
public void run(String... args) {
System.out.println(repository.findAll());
}
}
}
The init() method annotated with @PostConstruct is executed after the bean has been constructed and all dependency injections are complete. We did this to pre-populate the database with some initial data when the application will start.
Here's the output:
[Course(id=1, name=Pharma1, description=Basics of pharma, status=ALPHA), Course(id=2, name=Pharma2, description=Be expert at pharma, status=ALPHA)]Paging and sorting
The JpaRepository extends the PagingAndSortingRepository interface, so it's also able to handle paging and sorting. Let's see an example of using it.
We will keep everything like the example above, except the part for paging:
@Override
public void run(String... args) {
Page<Course> firstPage = repository.findAll(PageRequest.of(0, 1, Sort.by(Sort.Direction.ASC, "name")));
Page<Course> secondPage = repository.findAll(PageRequest.of(1, 1, Sort.by(Sort.Direction.ASC, "name")));
System.out.println("Page 1:");
firstPage.getContent().forEach(System.out::println);
System.out.println("Page 2:");
secondPage.getContent().forEach(System.out::println);
}
This code gets two pages (0-indexed) with one element per page, sorted by the "name" attribute in ascending order, and prints each one.
Here's the output:
Page 1:
Course(id=1, name=Pharma1, description=Basics of pharma, status=ALPHA)
Page 2:
Course(id=2, name=Pharma2, description=Be expert at pharma, status=ALPHA)Custom queries
We can also define custom queries using the @Query annotation, which can be a helpful tool for working with databases in Spring Data JPA. Here's an example:
@Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
@Query("SELECT c FROM Course c WHERE c.name = :name AND c.status = 'ALPHA'")
Course findCourseByNameAndStatusIsAlpha(String name);
}
@Override
public void run(String... args) {
System.out.println(repository.findCourseByNameAndStatusIsAlpha("Pharma1"));
}
As you can see, it's pretty straightforward! We just wrote the query and used it.
Here's the output:
Course{id=1, name='Pharma1', description='Basics of pharma', status=ALPHA}
We can also use a property expression method:
@Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
List<Course> findByStatus(Status status);
}
@Override
public void run(String... args) {
System.out.println(repository.findByStatus(Status.ALPHA));
}
Here's the output:
[Course{id=1, name='Pharma1', description='Basics of pharma', status=ALPHA}, Course{id=3, name='Pharma2', description='Be expert at pharma', status=ALPHA}]Query by example
QueryByExampleExecutor is a marker interface in Spring Data JPA that indicates that a repository supports queries by example. You can build dynamic queries using an example entity as a template. It provides a way to make queries based on an example entity's attributes without explicitly writing JPQL or SQL queries.
public interface QueryByExampleExecutor<T> {
<S extends T> Optional<S> findOne(Example<S> var1);
<S extends T> Iterable<S> findAll(Example<S> var1);
<S extends T> Iterable<S> findAll(Example<S> var1, Sort var2);
<S extends T> Page<S> findAll(Example<S> var1, Pageable var2);
<S extends T> long count(Example<S> var1);
<S extends T> boolean exists(Example<S> var1);
}
The QueryByExampleExecutor interface has methods like findOne(), findAll(), count(), and exists(), but the difference here is that these methods take parameters as instances of the Example interface.
As we know, the JpaRepository extends this repository, giving it the same capabilities for query by example. Let's modify the previous code:
@Override
public void run(String... args) {
Course exampleCourse = new Course();
exampleCourse.setStatus(Status.ALPHA);
Example<Course> example = Example.of(exampleCourse);
List<Course> courses = repository.findAll(example);
courses.forEach(System.out::println);
}
The Example.of() creates an instance of Example based on a given object. We can now perform a query using this example object in a repository method.
Here's the output:
Course{id=1, name='Pharma1', description='Basics of pharma', status=ALPHA}
Course{id=3, name='Pharma2', description='Be expert at pharma', status=ALPHA}
As you see, it returns a list of courses with status "ALPHA".
You can also configure how the attributes are matched using an ExampleMatcher. For example, you can make the matching case-insensitive or specify how null values will be handled.
@Override
public void run(String... args) {
Course exampleCourse = new Course();
exampleCourse.setName("pharma1");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnoreCase()
.withIgnoreNullValues();
Example<Course> example = Example.of(exampleCourse, matcher);
List<Course> courses = repository.findAll(example);
courses.forEach(System.out::println);
}
Here's the output:
Course{id=1, name='Pharma1', description='Basics of pharma', status=ALPHA}
Course{id=2, name='Pharma1', description='Basics of pharma for non pharmacists', status=BETA}
You might also want to find any records where the name contains the specified substring:
@Override
public void run(String... args) {
Course exampleCourse = new Course();
exampleCourse.setName("phar");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnoreCase()
.withIgnoreNullValues()
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
Example<Course> example = Example.of(exampleCourse, matcher);
List<Course> courses = repository.findAll(example);
courses.forEach(System.out::println);
}
Here's the output:
Course{id=1, name='Pharma1', description='Basics of pharma', status=ALPHA}
Course{id=2, name='Pharma1', description='Basics of pharma for non pharmacists', status=BETA}
Course{id=3, name='Pharma2', description='Be expert at pharma', status=ALPHA}Conclusion
In this topic, you learned about JpaRepository. Overall, everything we accomplished earlier with CrudRepository, PagingAndSortingRepository, and custom queries using @Query can be seamlessly executed using JpaRepository. Through practical examples, you saw how JpaRepository can perform CRUD operations, handle custom queries, and provide paging and sorting. This deep dive into JpaRepository showcases its versatility and effectiveness in streamlining data access.