Jednym z głównych modułów frameworka Spring jest moduł Spring Data, dzięki któremu możemy zminimalizować niemal do zera ilość tak zwanego „boiler-plate” kodu przy tworzeniu znanych z Domain Driven Design repozytoriów. DDD jest podejściem do tworzenia obiektowego modelu naszej aplikacji, natomiast repozytoria są to klasy, które odpowiadają za pobieranie obiektów domenowych z zewnętrznego źródła danych, dodatkowo również tam umieszcza się różnorakie metody odpowiadające za zwracanie obiektów spełniających dane kryteria. Mówiąc jeszcze prościej – repozytoria odpowiadają za obsługę operacji typu CRUD (zestawu podstawowych operacji bazodanowych – Create, Read, Update, Delete) dla danego obiektu domenowego.
Utworzenie projektu
Zacznijmy tradycyjnie od utworzenia nowego projektu – w tym wypadku skorzystamy z generatora projektów springowych – strony http://start.spring.io/
Interesuję nas taka konfiguracja projektu:
Niezbędne dla nas zależności to JPA i H2 (wbudowana, lekka baza danych).
Utworzenie obiektu domenowego i repozytorium
Po utworzeniu projektu w IntelliJ dodajmy prosty obiekt domenowy Employee, z którego robimy od razu encję:
package pl.devfoundry.querydemo.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Employee {
@Id
@GeneratedValue
private int id;
private String name;
private int age;
private Employee() {
}
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
Następnie w tej samej paczce co klasa Employee dodajmy paczkę repository i tam utwórzmy nowy interfejs EmployeeRepository, który będzie rozszerzał interfejs JpaRepository rodem ze Spring Data
package pl.devfoundry.querydemo.domain.repository; import org.springframework.data.jpa.repository.JpaRepository; import pl.devfoundry.querydemo.domain.Employee; public interface EmployeeRepository extends JpaRepository{ }
Interfejs JpaRepository potrzebuje dwóch wartości generycznych – pierwsza z nich odpowiada encji, której repozytorium chcemy utworzyć. Drugie natomiast typowi klucza głównego tej encji (w naszym wypadku int -> Integer)
Teraz w głównej klasie aplikacji, możemy śmiało korzystać z tego interfejsu, by dodawać, usuwać i wyszukiwać wszystkich pracowników. To wszystko bez napisania nawet linijki kodu związanego z JPA.
Użyjmy naszego repozytorium. Utwórzmy springowy bean, który w metodzie init() będzie tworzył nowego pracownika.
package pl.devfoundry.querydemo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import pl.devfoundry.querydemo.domain.Employee;
import pl.devfoundry.querydemo.domain.repository.EmployeeRepository;
import javax.annotation.PostConstruct;
@Component
public class DataLoader {
@Autowired
EmployeeRepository employeeRepository;
@PostConstruct
public void init() {
System.out.println("Database operations");
employeeRepository.save(new Employee("Pawel", 32));
}
}
Wstrzykujemy poprzez @Autowired komponent EmployeeRepository, warto zwrócić tu uwagę, że sami takiego komponentu nigdzie nie definiowaliśmy – został on wygenerowany przez Spring Data automatycznie na podstawie interefejsu. Dzięki użyciu @PostConstruct mamy pewność, że kawałek kodu zawarty w metodzie init() wywoła się zaraz po utworzeniu komponenetu DataLoader.
Dodatkowo do pliku application.properties dodajmy wpisy:
spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true
Dzięki nim na konsoli będziemy widzieć jakie kwerendy wykonuje Hibernate.
Uwaga! Jeśli używasz Javy 9+ do dependencji wygenerowanego projektu należy dodać wpis:
javax.xml.bind jaxb-api 2.3.0
A dlaczego to już temat na zupełnie osobny wpis, albo i więcej. Na chwilę obecną musisz mi zaufać 🙂
Po wywołaniu tego kodu pośród logów zaobserwować można wpis:
Hibernate:
insert
into
employee
(age, name, id)
values
(?, ?, ?)
Czyli Hibernate dodał pracownika do bazy.
Proste wyszukiwania bazodanowe
Dodatkowo możemy w bardzo łatwy sposób dodawać metody, które będą wyszukiwały pracownika po danym parametrze. Wystarczy dodać metodę findByName do interfejsu by Spring Data automagicznie automatycznie utworzył niezbędny kod JPA i wygenerował kod dla metody.
public interface EmployeeRepository extends JpaRepository{ List findByName(String name); }
@Component
public class DataLoader {
@Autowired
EmployeeRepository employeeRepository;
@PostConstruct
public void init() {
System.out.println("Database operations");
employeeRepository.save(new Employee("Pawel", 32));
find();
}
private void find() {
List pawels = employeeRepository.findByName("Pawel");
pawels.forEach(System.out::println);
}
}
W logach znajdziemy wpis:
Employee{id=1, name='Pawel', age=32}
Podobnie możemy utworzyć metody np. findByNameAndAge, findByAge, findByNameOrAge
Własne metody wyszukiwania
Co w przypadku gdy chcemy utworzyć metodę, która wyszuka nam pracowników z imieniem zaczynającym się na literę „A”?
Możemy dodać deklarację metody findByNameStartingWith do interfejsu repozytorium i automat nam wygeneruję co trzeba. Jak widać Spring Data ma rozwiązanie na wszystko, ale na potrzeby tego wpisu załóżmy, że nie ma powyższej funkcjonalności i musimy dodać do naszego repozytorium własne zapytanie JPA. Robimy takie rzeczy za pomoca adnotacji @Query przy deklaracji metody w interfejsie EmployeeRepository
public interface EmployeeRepository extends JpaRepository{ List findByName(String name); @Query("Select e from Employee e WHERE e.name like 'P%'") List myOwnQuery(); }
Wówczas wywołanie myOwnQuery() wywoła na bazie zapytanie JPQL zawarte jako parametr dla adnotacji @Query.
@Component
public class DataLoader {
@Autowired
EmployeeRepository employeeRepository;
@PostConstruct
public void init() {
System.out.println("Database operations");
employeeRepository.save(new Employee("Pawel", 32));
find();
}
private void find() {
List pawels = employeeRepository.myOwnQuery();
pawels.forEach(System.out::println);
}
}
Oczywiście do tak utworzonych zapytań możemy przesyłać parametry, robi się to w następujący sposób:
public interface EmployeeRepository extends JpaRepository{ List findByName(String name); @Query("Select e from Employee e WHERE e.name like CONCAT(:firstLetter,'%')") List findByFirstLetter(@Param("firstLetter") String letter); }
@Component
public class DataLoader {
@Autowired
EmployeeRepository employeeRepository;
@PostConstruct
public void init() {
System.out.println("Database operations");
employeeRepository.save(new Employee("Pawel", 32));
find();
}
private void find() {
List pawels = employeeRepository.findByFirstLetter("P");
pawels.forEach(System.out::println);
}
}
Co ponownie zwróci nam wszystkich pracowników z imieniem zaczynającym się na literę P, a będzie to jeden pracownik o imieniu ‚Pawel’.
To są tylko podstawowe przykłady użycia modułu Spring Data, będącego częścią składową Spring Framework, a już one pokazują jaka moc w nim drzemie i jak bardzo przycina ilość kodu potrzebnego do komunikacji z bazą danych.
Kod przykładu dostępny jest na platformie gitub.

Bardzo fajne przykłady (nie mówię tylko tym wpisie). A Spring Data robi wrażenie, szczególnie że rozwiązuje zapytania po nazwie metody. 🙂 Czy w prostych przypadkach lepiej używać CrudRepository zamiast JpaRepository?
Hej, dzięki 🙂 Różnicy wielkiej nie ma – tzn. JpaRepository ma więcej metod (CrudRepository + Paging + Sorting + operacje batchowe), więc narzut na zasoby jest żaden. Moim zdaniem to kwestia tylko i wyłącznie tego ile funkcjonalności chcesz udostępnić użytkownikom repozytorium. Dodatkowo, jeśli dopiero się uczysz to lepiej od razu wskoczyć w JpaRepozytory i poznać jego wszystkie możliwości, bo na przykład wspomniana obsługa stronicowania wyników to chleb powszedni w każdej biznesowej aplikacji.
Przystępnie opisane. Dzięki