Spring Data – @Query

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).


Jeśli chcesz prześledzić zmiany krok po kroku w formie wideo, to zapraszamy na nasz kanał na YouTube.


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<Employee, Integer> {
}

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:

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>

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<Employee, Integer> {

    List<Employee> 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<Employee> 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<Employee, Integer> {

    List<Employee> findByName(String name);

    @Query("Select e from Employee e WHERE e.name like 'P%'")
    List<Employee> 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<Employee> 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<Employee, Integer> {

    List<Employee> findByName(String name);

    @Query("Select e from Employee e WHERE e.name like CONCAT(:firstLetter,'%')")
    List<Employee> 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<Employee> 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.


Podziel się tym wpisem:

3 komentarze do wpisu „Spring Data – @Query

  1. 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.

Dodaj komentarz