repository/README.md
The Repository design pattern acts as a central hub for managing all Java data access logic, abstracting the details of data storage and retrieval from the rest of the application.
Real-world example
Imagine a library system where a librarian acts as the repository. Instead of each library patron searching through the entire library for a book (the data), they go to the librarian (the repository) who knows exactly where each book is located, regardless of whether it's on a shelf, in the storeroom, or borrowed by someone else. The librarian abstracts the complexities of book storage, allowing patrons to request books without needing to understand the storage system. This setup simplifies the process for patrons (clients) and centralizes the management of books (data access logic).
In plain words
The Repository pattern provides a central place for handling all data access logic, abstracting the complexities of data storage and retrieval from the rest of the application.
Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.
Sequence diagram
Let's first look at the person entity that we need to persist.
@ToString
@EqualsAndHashCode
@Setter
@Getter
@Entity
@NoArgsConstructor
public class Person {
@Id
@GeneratedValue
private Long id;
private String name;
private String surname;
private int age;
public Person(String name, String surname, int age) {
this.name = name;
this.surname = surname;
this.age = age;
}
}
We are using Spring Data to create the PersonRepository, so it becomes really simple.
@Repository
public interface PersonRepository extends CrudRepository<Person, Long>, JpaSpecificationExecutor<Person> {
Person findByName(String name);
}
Additionally, we define a helper class PersonSpecifications for specification queries.
public class PersonSpecifications {
public static class AgeBetweenSpec implements Specification<Person> {
private final int from;
private final int to;
public AgeBetweenSpec(int from, int to) {
this.from = from;
this.to = to;
}
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.between(root.get("age"), from, to);
}
}
public static class NameEqualSpec implements Specification<Person> {
public String name;
public NameEqualSpec(String name) {
this.name = name;
}
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.equal(root.get("name"), this.name);
}
}
}
And here's the repository example in action.
public static void main(String[] args) {
var context = new ClassPathXmlApplicationContext("applicationContext.xml");
var repository = context.getBean(PersonRepository.class);
var peter = new Person("Peter", "Sagan", 17);
var nasta = new Person("Nasta", "Kuzminova", 25);
var john = new Person("John", "lawrence", 35);
var terry = new Person("Terry", "Law", 36);
// Add new Person records
repository.save(peter);
repository.save(nasta);
repository.save(john);
repository.save(terry);
// Count Person records
LOGGER.info("Count Person records: {}", repository.count());
// Print all records
var persons = (List<Person>) repository.findAll();
persons.stream().map(Person::toString).forEach(LOGGER::info);
// Update Person
nasta.setName("Barbora");
nasta.setSurname("Spotakova");
repository.save(nasta);
repository.findById(2L).ifPresent(p -> LOGGER.info("Find by id 2: {}", p));
// Remove record from Person
repository.deleteById(2L);
// count records
LOGGER.info("Count Person records: {}", repository.count());
// find by name
repository
.findOne(new PersonSpecifications.NameEqualSpec("John"))
.ifPresent(p -> LOGGER.info("Find by John is {}", p));
// find by age
persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40));
LOGGER.info("Find Person with age between 20,40: ");
persons.stream().map(Person::toString).forEach(LOGGER::info);
repository.deleteAll();
context.close();
}
Program output:
INFO [2024-05-27 07:00:32,847] com.iluwatar.repository.App: Count Person records: 4
INFO [2024-05-27 07:00:32,859] com.iluwatar.repository.App: Person(id=1, name=Peter, surname=Sagan, age=17)
INFO [2024-05-27 07:00:32,859] com.iluwatar.repository.App: Person(id=2, name=Nasta, surname=Kuzminova, age=25)
INFO [2024-05-27 07:00:32,859] com.iluwatar.repository.App: Person(id=3, name=John, surname=lawrence, age=35)
INFO [2024-05-27 07:00:32,859] com.iluwatar.repository.App: Person(id=4, name=Terry, surname=Law, age=36)
INFO [2024-05-27 07:00:32,869] com.iluwatar.repository.App: Find by id 2: Person(id=2, name=Barbora, surname=Spotakova, age=25)
INFO [2024-05-27 07:00:32,873] com.iluwatar.repository.App: Count Person records: 3
INFO [2024-05-27 07:00:32,878] com.iluwatar.repository.App: Find by John is Person(id=3, name=John, surname=lawrence, age=35)
INFO [2024-05-27 07:00:32,881] com.iluwatar.repository.App: Find Person with age between 20,40:
INFO [2024-05-27 07:00:32,881] com.iluwatar.repository.App: Person(id=3, name=John, surname=lawrence, age=35)
INFO [2024-05-27 07:00:32,881] com.iluwatar.repository.App: Person(id=4, name=Terry, surname=Law, age=36)
Benefits:
Trade-offs: