Spring Data JPA. Пишем DAO и Services. Часть 2

В прошлом уроке мы создали ERD и на её основе построили Entity. В этом уроке мы научимся создавать слой DAO на основе созданных Entity и Services, разберемся, зачем эти два слоя нужны.

 

Если вы еще не читали первую часть урока то вот Spring Data JPA. Работа с БД. Часть 1.

 

Шаг 0. Подготовка БД

Не забывайте что мы используем MySQL базу данных, поэтому вам нужно установить MySQL сервер и желательно редактор БД MySQL Workbench. Скачать можно тут.

После установки откройте MySQL Workbench, подключитесь к серверу:

После, создайте базу данных, нажав по соответствующем значку:

либо выполнив SQL запрос:

CREATE SCHEMA `testdb` DEFAULT CHARACTER SET utf8 ;

После этого можно переходить к созданию репозиториев. Нам не придётся создавать таблицы в БД их проект создаст сам.

 

Шаг 1. DAO либо Repository

Для начало разберемся что же такое DAO?

DAO (Data Access Object) — это слой объектов которые обеспечивают доступ к данным.

Обычно для реализации DAO используется EntityManager и с его помощью мы работаем с нашей БД, но в нашем случае это система не подойдет, так как мы изучаем Spring Data  нам нужно использовать её средства иначе незачем он нам.

Spring Data предоставляет набор готовых реализаций для создания DAO но Spring предпочли этот слой называть не DAO, а Repository.

О самом фреймворке Spring Data вы можите почитать тут.

Теперь давайте для каждого Entity создадим Repository, который позволит оперировать объектом в БД.

Создать Repository довольно просто, даже больше чем просто, давайте создадим для Bank entity его Repository и увидим на сколько все просто:

package com.devcolibri.dataexam.repository;

import com.devcolibri.dataexam.entity.Bank;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BankRepository extends JpaRepository<Bank, Long> {
}

В 6-й строке видно что мы создали интерфейс с именем BankRepository и унаследовались от JpaRepository.

JpaRepository — это интерфейс фреймворка Spring Data предоставляющий набор стандартных методов JPA для работы с БД.

Ну создали интерфей и что дальше? Спросите вы. На основе этого интерфейса Spring Data предоставит реализации с методами, которые мы использовали в Entity Manager, немного позже вы увидите пример использования Repository.

Для создания Repository нужно придерживаться несколько правил:

1 — Имя репозитория должно начинаться с имени сущности NameReposytory (необязательно).

2 — Второй Generic должен быть оберточным типом того типа которым есть ID нашей сущности (обязательно).

3 — Первый Generic должен быть объектом нашей сущности для которой мы создали Repository, это указывает на то, что Spring Data олжен предоставить реализацию методов для работы с этой сущностью (обязательно).

4 — Мы должны унаследовать свой интерфейс от JpaRepository, иначе Spring Data непредоставит реализацию для нашего репозитория (обязательно).

Давайте продолжим создание Repositories для другий сущностей.

Создаем BankAccountRepositroy для BankAccount:

package com.devcolibri.dataexam.repository;

import com.devcolibri.dataexam.entity.BankAccount;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BankAccountRepositroy extends JpaRepository<BankAccount, Long>{
}

Создаем ClientRepository для Client:

package com.devcolibri.dataexam.repository;

import com.devcolibri.dataexam.entity.Client;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ClientRepository extends JpaRepository<Client, Long> {
}

Создаем WorkerRepository для Worker:

package com.devcolibri.dataexam.repository;

import com.devcolibri.dataexam.entity.Worker;
import org.springframework.data.jpa.repository.JpaRepository;

public interface WorkerRepository extends JpaRepository<Worker, Long> {
}

В результате мы получим набор следующийх Repositories:

По сути, теперь у нас есть слой обеспечивающий доступ к данным в БД.

 

Шаг 2. Создание Services

И так мы разобрались зачем нужны DAO/Repositories, для обеспечение доступа к данным, в нашем случае это БД MySQL.

Но напрямую использовать Repositories для получение данных на Пользовательский Интерфейс не принято и считается плохим тоном, для этого были придуманы Services.

Service — это Java класс, который предоставляет с себя основную (Бизнес-Логику). В основном сервис использует готовые DAO/Repositories или же другиесервисы, для того чтобы предоставить конечные данные для пользовательского интерфейса.

 Давайте создади пакет service и в нем создадим наш первый сервис который будет предоставлять конечные данные для пользовательского интерфейса.

Я создам сервис для сущности Bank по аналогии делаются другие.

Создаем новый интерфейс BankService:

package com.devcolibri.dataexam.service;

import com.devcolibri.dataexam.entity.Bank;

import java.util.List;

public interface BankService {

    Bank addBank(Bank bank);
    void delete(long id);
    Bank getByName(String name);
    Bank editBank(Bank bank);
    List<Bank> getAll();

}

В этом интерфейс мы указали, какие методы нам будут нужны для написания бизнес-логики проекта.

Теперь создаем в этом же пакете, новый пакет impl в котором будут лежать реализации всех интерфейсов. Структура сервиса и его реализации:

Давайте создадим BankServiceImpl который будет реализовывать BankService интерфейс:

 

package com.devcolibri.dataexam.service.impl;

import com.devcolibri.dataexam.entity.Bank;
import com.devcolibri.dataexam.repository.BankRepository;
import com.devcolibri.dataexam.service.BankService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BankServiceImpl implements BankService {

    @Autowired
    private BankRepository bankRepository;

    @Override
    public Bank addBank(Bank bank) {
        Bank savedBank = bankRepository.saveAndFlush(bank);

        return savedBank;
    }

    @Override
    public void delete(long id) {
        bankRepository.delete(id);
    }

    @Override
    public Bank getByName(String name) {
        return bankRepository.findByName(name);
    }

    @Override
    public Bank editBank(Bank bank) {
        return bankRepository.saveAndFlush(bank);
    }

    @Override
    public List<Bank> getAll() {
        return bankRepository.findAll();
    }
}

Теперь разберем, что же мы тут написали:

14 строка — это аннотация которая позволит Spring инициализировать наш сервис;

15 строка — объевление нашего сервиса (обратите внимание, что это интерфейс, а не реализация), который позволит нам использовать его бизнес-логику;

19 строка — тут мы сохраняем Bank в БД используя метод saveAndFlush, используя просто save() мы сохраняем запись но после вызова flush данные попадают в БД;

26 строка — удаление Bank по его id;

31 строка — этот метод не предоставляется Spring Data в следующем шаге мы рассмотрим как его сделать;

36 строка — update можно сделать тем же методом что и сохранение, так как hibernate умный, и он проверит, есть ли запись в БД, которую мы хотим сохранить, если есть, то он её обновит;

41 строка — получаем все данные с БД, а именно все банки.

 

Шаг 3. Кастомный метод в Spring Data

И так, в реализации BankService есть метод getByName(), который должен получать Bank по имени, в стандартных средствах Spring Data нет такой возможности, поэтому мы напишем свой кастомный метод.

Для этого зайдите в интерфейс BankRepository и там напишите следующий метод:

public interface BankRepository extends JpaRepository<Bank, Long> {

    @Query("select b from Bank b where b.name = :name")
    Bank findByName(@Param("name") String name);

}

В строке 3 мы используем аннотацию @Query которая позволяет создать SQL запрос, но этот запрос содержит параметр :name, его иы проставляем в структуре метода findByName() ипользуя аннотаци @Param в параметре которой мы указываем имя параметра запроса name.

Spring Data на основе предоставленых данных в аннотациях сам предоставит реализацию этого метода, и это замечательно, так как теперь мы его можем использовать:

@Override
public Bank getByName(String name) {
    return bankRepository.findByName(name);
}

И вот финальная структура проекта:

Смотрите в следующем уроке, то как тестировать Сервисы:

Spring Data JPA. JUnit тесты для Services. Часть 3 (пишу …)

На этом все, качайте исходники в начале урока, разбирайтесь, если что-то непонятно, то все вопросы в комментарии.

 

 

Урок создан: 09 мая 2014 | Просмотров: 79808 | Автор: Александр Барчук | Правила перепечатки


Добавить комментарий

Добавить комментарий

Ваш e-mail не будет опубликован.

Комментарии:

  • 26 мая 2014 в 18:05

    Mikhail

    Не могу понять необходимости такого количества разных интерефейсов. Кстати для чего создавать интерфейс BankService, если JpaRepository и так реализовывает эти методы …

    • 27 мая 2014 в 10:25

      Александр Барчук

      Интерфейс вам позволяют не зависить от определёной реализации, а Services позволит вам сделать более подробную логику, например вам нужно получить пользователя с БД (с эти справится Repository) а потом получить у этого пользователя email и после этого отправить письмо, все это мы делаем в Service.

  • 05 июня 2014 в 17:13

    Ivan

    Скажите, а как выставить роутер в контроллере? что б я мог проверить данные с базы

  • 09 июня 2014 в 18:43

    javarob

    >19 строка — тут мы сохраняем Bank в БД используя метод saveAndFlush, используя просто save() мы сохраняем запись но после вызова flush данные попадают в БД;

    В чём разница между ПРОСТО «сохраняем» (save) и flush?

    • 10 июня 2014 в 15:15

      Александр Барчук

      flush — комитит транзацкцию в БД.

      • 11 июня 2014 в 15:44

        javarob

        Отлично. А «save» куда сохраняет?

        • 13 июня 2014 в 08:51

          Александр Барчук

          В базу данных :)

          • 13 июня 2014 в 16:30

            javarob

            Отлично:
            1) НачатьТранзакцию. … save … ЗавершитьТранзакцию.
            2) НачатьТранзакцию. … saveAndFlush

            Во втором случае фактически вызов saveAndFlush завершает Транзакцию? — Я правильно понял этот Flush ?

            • 13 июня 2014 в 22:03

              Александр Барчук

              да, все верно.

              • 24 июня 2014 в 18:49

                javarob

                Верно то верно, но разницу между КОГДА использовать save(…) , а когда saveAndFlush(…) я так и не уловил.
                Но она же есть, эта разница?

                • 11 июля 2014 в 16:23

                  Александр Барчук

                  saveAndFlush вернет вам сохраненый объект в БД с уже проставленым ID который в большинстве случаев генерируется в БД, ну и сохранить вы можите только один объект, а save позволяет сохранить сразу несколько сущностей.

                  • 03 января 2015 в 17:11

                    Cooler

                    Разница в том, что save не сразу закидывает в базу данные, а по завершению транзакции, которая может завершиться например даже не в ДАО слое, и если мы к этим данным хотим обратиться или как-то использовать до завершения транзакции, тогда нам нужно вызвать flush(), который запишет данные.

          • 19 декабря 2014 в 20:54

            olzhas

            Когда будет 3 часть??

  • 09 июня 2014 в 18:46

    javarob

    Насколько я понял, Spring на основании только interface САМ создаст имплементацию всех методов этого interface.

    Я правильно понял?

  • 29 июля 2014 в 23:33

    Densk

    Ваш материал интересный! Спасибо!

  • 02 августа 2014 в 12:58

    Sergey

    Здравствуйте Александр. возникла проблема при компиляции в классе AppInitializer:
    «java: cannot access org.springframework.web.context.AbstractContextLoaderInitializer
    class file for org.springframework.web.context.AbstractContextLoaderInitializer not found» как вы думаете с чем это связанно, может с тем что явно не указывается инициализация в applicationContext.xml

  • 12 сентября 2014 в 13:06

    andrey

    Спасибо, круто. А то я все смотрел DAO (ршиуктфеу) и не как не мог понять почему мне надо отдельно реализовывать эти методы. Кстати получается что spring Data это аналаг hibernate?

    • 15 октября 2014 в 03:22

      Aleksey

      Нет, hibernate это ORM в данном примере используется Spring data он занимается только обслуживанием, и предоставляет репозиторий с уже готовыми CRUD методами, и практически избавляет от шаблонного, дублирующего кода, в данном примере так же используется спецификация JPA. Работая через данную спецификацию можно без сильных телодвижениях заменить hibernate на другой ORM Framework более того если потребуется переписать все на работу через JDBC. Spring по большому счету избавляет вас от тесных связей от сторонних библиотек и значительно упрощает разработку избавляя вас от написания шаблонного кода

  • 15 октября 2014 в 03:29

    Aleksey

    Пример действительно очень хороший, но хорошо бы добавить конфигурацию с применений xml. И так же рассказать об обработки исключений spring, которые предоставляют свою иархию исключений для работы с данными

    • 03 января 2015 в 17:12

      Cooler

      хмл, это типа вчерашний день, все переезжают на чистую джаву или конфиги пишут на груви

  • 01 мая 2015 в 00:39

    naXa!

    Скажите, пожалуйста, пробовали ли вы загрузить данные с FetchType.LAZY используя Spring Data?

    • 05 мая 2015 в 13:35

      Александр Барчук

      Да, все нормально работает, отличие только в дополнительном запросе на беграунде.

      • 29 июля 2015 в 20:08

        Figli

        Александр, а когда ждать продолжения? очень уж интересная тема но оборванная по средине :(

  • 21 сентября 2015 в 12:39

    Sasha

    Помогите, не могу разобраться. Установил на компьютере MySQL server(стандартная установка, порт не менял(3306)) и Workbench, через Workbench создал базу данных(назвал mydb), в ней создал все таблицы и связал их через foreign key. Тем не менее java не видит базу данных, код на скрине(http://prntscr.com/8ir0oy) выкидывает NPE. Вот такой у меня конфигурационный файл (http://prntscr.com/8ir11g). Подскажите, в чем может быть проблема?

    • 28 сентября 2015 в 19:03

      Александр

      Напишите что в 41 строке класса BankServiceImpl

    • 29 сентября 2015 в 12:54

      Sasha

      Судя по всему, spring не заполняет поле помеченное @Autowired. Есть предположение, что этот пример должен работать через сервлеты. Можете привести недостающий код чтобы можно было запустить пример на своем компьютере?

      • 26 июля 2017 в 20:04

        Александр

        И как сделать это через сервлеты? Вам удалось решить проблему?

  • 23 ноября 2015 в 07:34

    freedom4live

    большое спасибо за статьи… А продолжение будет? (((( Очень полезная информация….

  • 27 ноября 2015 в 14:55

    Максим

    Как его запустить?

  • 08 января 2016 в 10:27

    MAlex

    Огромное спасибо за статью, очень хотелось бы увидеть статью про dbcp2.

  • 15 января 2016 в 18:10

    RJA

    тут не понятно какую проблему решает слой Service если все его методы делегируют в слой dao

  • 20 января 2016 в 20:32

    Dmitri

    привет, а как написать запрос, чтобы выбрать все BankAccount по имени Client, т.е. надо найти все BankAccount в которых Client.name = @Param(«name») ?

  • 25 марта 2016 в 23:19

    Михайло

    А ще буде 3тя частина?? А то вже ніби 2 роки вона пишеться =)

  • 22 мая 2016 в 22:04

    Dmytro

    Одни из наиболее толковых уроков по Java. Автору — большое спасибо!

  • 31 августа 2016 в 20:36

    Александр

    Как реализовать методы в service с динамическим запросом (формирующимся при выполнении)?

  • 30 сентября 2016 в 12:28

    Sergey

    Как теперь это запустить?

  • 15 ноября 2016 в 16:56

    Vitaliy

    Александр, пожалуйста пересмотрите исходники своего сайта. Он регулярно начинает выедать все свободные ресурсы процессора, и весить Chrome. И не надо говорить что браузер кривой, может и есть у него проблемы, но он занимает более 50% доли среди браузеров.
    У вас хоть и хорошые материалы на сайте, но регулярный висняк — очень напрягает.
    Разберитесь пожалуйста.

    • 05 декабря 2016 в 22:32

      Pavel

      Подтверждаю, Firefox (macOS), если оставить на 1-2 часа вкладку открытой, начинается жор ресурсов + дикие тормоза браузера.

  • 23 декабря 2016 в 01:39

    Вадим

    Обязательно ли использовать именно такой шаблон для пакетов и имен Services.
    То есть, можно ли так:
    package service.interface: BankServiceInterface;
    package service: BankService??

  • 24 декабря 2016 в 14:56

    Andrew

    Не хватает урока, как это все запихнуть в Spring Security

  • 29 декабря 2016 в 08:37

    Сергей

    Огромнейшее СПАСИБО! Отличный и доходчивый урок.

  • 12 января 2017 в 15:28

    Евгений

    А почему бы не использовать generic и не писать много интерфейсов и их реализаций? Или в таком сетапе через generic не подхватится ничего?

  • 03 марта 2017 в 12:47

    Андрей

    не пойму как нужно использовать public interface BankService и зачем он нужен, если BankServiceImpl аннотирован как сервис и все равно будет использоваться он. В чем смысл такого интерфейса?

  • 12 марта 2017 в 23:55

    Ramiz

    Идея ругается на проделанный третий шаг: Can’t resolve symbol ‘Bank’.
    Скачал ваш проект: там то же самое выдает.