Spring Boot — пример с Postgres и JPA

REST-JPA-Main

В данной статье речь пойдет о фреймворке Spring, а точнее о Spring Boot. Я покажу, как быстро в считанные минуты запустить простое приложение и подключить к нему базу данных postgresql. Мы сделаем пару примеров на простые операции с базой данных.

Для того, чтобы не терять времени и говорить на одном языке я предполагаю, что читатель уже знаком с таким понятием как Spring, знает, что такое JPA, Hibernate и что такое Maven.  Если все это для Вас ново — предлагаю для ознакомления статьи в порядке важности: Spring MVC первое веб приложение — о спринг фреймворке и архитектурах веб приложения; Spring Boot простое приложение — о спринг бут кратко, но понятно; Что такое hibernate — о работе с базами данных.

Для создания приложения и подключения всех необходимых зависимостей воспользуемся инструментом SPRING INITIALIZR — это просто сайт, где Вы можете выбрать все необходимые зависимости для приложения и система выдаст готовое приложение доступное для скачивания.

Для начала нужно перейти на сайт https://start.spring.io/ ввести group и artifact своего приложения. Идентично тому, как мы вводим groupId и artifactId когда создаем Maven приложение. Далее в строке  нужно ввести зависимости, которые нужно добавить в приложение. После этого нажимаем большую кнопку Generate project и должна начаться загрузка архива с приложением:

spring initializr

Для тех, кому привычнее создавать приложения в обычной манере — можно не пользоваться этим инструментом. Главное создать простое Spring Boot приложение и подключить к нему зависимости для Postgresql, JPA, WEB. Скачанный архив нужно распаковать в удобную папку и открыть приложение с помощью любимой идее. На этот раз я буду пользоваться intellij idea. Как я неоднократно упоминал — выбор инструмента программирования не имеет значения. Главное, чтобы Вам было удобно.

И так, приложение создано и открыто.

структура приложения

На рисунке выше — структура моего приложения. В нем создан только один класс, который является точкой входа в приложение. Его код ни чем не примечательный и таковым и останется.

Код   
package com.javamaster.springjpapostgres;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringjpapostgresApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringjpapostgresApplication.class, args);
    }
}

Файл pom.xml:

Код   
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.javamaster</groupId>
   <artifactId>springjpapostgres</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>springjpapostgres</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.0.3.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-jpa</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>


      <dependency>
         <groupId>org.postgresql</groupId>
         <artifactId>postgresql</artifactId>
         <version>9.4-1206-jdbc42</version>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>


</project>

Теперь пришло время настроить нашу Postgresql базу данных. Я создам простую базу данных, которую назову usersmanagement. В ней будет две таблицы: users и address. У нас будет очень таки стандартная предметная область: управление пользователями. Пользователи будут иметь некоторые атрибуты и адрес.

Код   
create database usersmanagement;

CREATE TABLE public.address
(
    id SERIAL PRIMARY KEY NOT NULL,
    city TEXT,
    street TEXT,
    home_number VARCHAR(5)
);
CREATE UNIQUE INDEX address_id_uindex ON public.address (id);

CREATE TABLE public.users
(
    id SERIAL NOT NULL,
    name TEXT,
    email VARCHAR(20),
    address_id INT PRIMARY KEY,
    CONSTRAINT users_address_id_fk FOREIGN KEY (address_id) REFERENCES address (id)
);
CREATE UNIQUE INDEX users_id_uindex ON public.users (id);

Теперь нужно создать в нашем приложении файл настроек доступа к базе данных. В Spring Boot это делается очень просто: нужно в папке проекта найти директорию resources. В ней должен быть файл application.properties. Если его нет — нужно создать:

путь к application.properties

Теперь, в данном файле нужно указать путь к базе, имя пользователя и пароль:

Код   
spring.datasource.url=jdbc:postgresql://localhost:5432/usersmanagement
spring.datasource.username=postgres
spring.datasource.password=postgres

Далее пропишем классы сущностей базы данных. Для этого создадим пакет entity и поместим в него два класса: Address иUsers.

Код   
package com.javamaster.springjpapostgres.entity;

import javax.persistence.*;

@Entity
@Table(name = "address")
public class Address {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String city;

    @Column
    private String street;

    @Column(name = "home_number")
    private String homeNumber;

//getters and setters
}
Код   
package com.javamaster.springjpapostgres.entity;

import javax.persistence.*;

@Entity
@Table(name = "users")
public class Users {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String name;

    @Column
    private String email;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id")
    private Address address;

}
//getters and setters

Теперь пришло время воспользоваться Spring JPA и написать методы доступа к базе данных. Для того, чтобы воспользоваться нужно создать свой интерфейс и унаследовать его от готового интерфейса JpaRepository <T, ID> где T — это сущность для доступа к которой будут использоваться методы, а ID — тип первичного ключа. На примере станет яснее.

Создадим сперва пакет и дадим ему имя repository. В нем будем создавать наши интерфейсы для доступа к сущностям в базе данных. В него поместим интерфейс UsersRepository и AddressRepository. Далее проделаем над интерфейсами процедуру описанную выше и получаем код который ниже.

Код   
package com.javamaster.springjpapostgres.repository;

import com.javamaster.springjpapostgres.entity.Address;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AddressRepository extends JpaRepository<Address, Long> {
}
Код   
package com.javamaster.springjpapostgres.repository;

import com.javamaster.springjpapostgres.entity.Users;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UsersRepository extends JpaRepository<Users, Long> {
}

Когда мы унаследовались от JpaRepository нам стали доступны стандартные методы над сущностью: сохранение, удаление, получение всех, получение по айди. В этих интерфейсах мы можем написать свои методы доступа к сущности: например получить по определенному полю или полям. Здесь также есть возможность написать собственный запрос на SQL.

Сейчас на примере попытаюсь показать большинство возможностей.

Код   
package com.javamaster.springjpapostgres.repository;

import com.javamaster.springjpapostgres.entity.Users;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface UsersRepository extends JpaRepository<Users, Long> {

    List<Users> findAllByName(String name);//просто правильное название метода даст возможность
    //избежать запросов на SQL

    @Query("select u from Users u where u.email like '%@gmail.com%'")//если этого мало можно написать
    //собственный запрос на языке похожем на SQL
    List<Users> findWhereEmailIsGmail();
   
    @Query(value = "select * from users where name like '%smith%'", nativeQuery = true)
    //если и этого мало - можно написать запрос на чистом SQL и все это будет работать
    List<Users> findWhereNameStartsFromSmith();
}

Код и комментарии говорят сами за себя. Пример выше приведен только в качестве демонстрации широких возможностей Sping JPA. Давайте теперь попробуем воспользоваться нашими методами и напишем слой сервисов в нашем приложении.

Для этого создадим пакет serivce и поместим в него классы UsersService, AddressService.

Код   
package com.javamaster.springjpapostgres.service;

import com.javamaster.springjpapostgres.entity.Address;
import com.javamaster.springjpapostgres.repository.AddressRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AddressService {

    @Autowired
    private final AddressRepository addressRepository;

    public AddressService(AddressRepository addressRepository){
        this.addressRepository = addressRepository;
    }

    public void createAddress(Address address){
        addressRepository.save(address);
    }
//далее уже допишите сами)
}
Код   
package com.javamaster.springjpapostgres.service;

import com.javamaster.springjpapostgres.entity.Users;
import com.javamaster.springjpapostgres.repository.UsersRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {

    @Autowired
    private final UsersRepository usersRepository;

    public UserService(UsersRepository usersRepository){
        this.usersRepository = usersRepository;
    }

    public void createUsers(Users users) {
        usersRepository.save(users);
    }

    public List<Users> findAll(){
        return usersRepository.findAll();
    }

    public Users findById(Long userId){
        return usersRepository.findById(userId).orElse(null);
    }

    public List<Users> findAllByName(String name){
        return usersRepository.findAllByName(name);
    }

    public List<Users> findWhereEmailIsGmail(){
        return usersRepository.findWhereEmailIsGmail();
    }

    public List<Users> findWhereNameStartsFromSmith(){
        return usersRepository.findWhereNameStartsFromSmith();
    }
}

Как уже было сказано: стандартные методы работы над сущностью доступны из под коробки. Разработчику нужно только достать их и использовать в своем сервисе.

Для тестирования наших методов работы с базой данных я написал простой метод, который вызывается при запуске Spring Boot приложения. Добавил я его в свой главный класс запуска приложения. Вот как теперь выглядит класс SpringjpapostgresApplication:

Код   
package com.javamaster.springjpapostgres;

import com.javamaster.springjpapostgres.entity.Address;
import com.javamaster.springjpapostgres.entity.Users;
import com.javamaster.springjpapostgres.service.AddressService;
import com.javamaster.springjpapostgres.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;

@SpringBootApplication
public class SpringjpapostgresApplication {

   @Autowired
   private UserService userService;

   public static void main(String[] args) {
      SpringApplication.run(SpringjpapostgresApplication.class, args);
   }

   @EventListener(ApplicationReadyEvent.class)
   private void testJpaMethods(){
      Address address = new Address();
      address.setCity("Kiev");
      address.setHomeNumber("4");
      address.setStreet("Main Street");
      Address address1 = new Address();
      address1.setCity("Lviv");
      Users users = new Users();
      users.setAddress(address);
      users.setEmail("someEmail@gmail.com");
      users.setName("Smith");
      userService.createUsers(users);
      Users users1 = new Users();
      users1.setName("Jon Dorian");
      users1.setEmail("gmailEmail@gmail.com");
      users1.setAddress(address1);
      userService.createUsers(users1);

      userService.findAll().forEach(it-> System.out.println(it));

      userService.findAllByName("Smith").forEach(it-> System.out.println(it));

      userService.findWhereEmailIsGmail().forEach(it-> System.out.println(it));

      userService.findWhereNameStartsFromSmith().forEach(it-> System.out.println(it));
   }
}

Результат вывода в консоль:

Users{id=5, name=’Smith’, email=’someEmail@gmail.com’, address=Address{id=8, city=’Kiev’, street=’Main Street’, homeNumber=’4′}}
Users{id=6, name=’Jon Dorian’, email=’gmailEmail@gmail.com’, address=Address{id=9, city=’Lviv’, street=’null’, homeNumber=’null’}}
Users{id=7, name=’Smith’, email=’someEmail@gmail.com’, address=Address{id=10, city=’Kiev’, street=’Main Street’, homeNumber=’4′}}
Users{id=8, name=’Jon Dorian’, email=’gmailEmail@gmail.com’, address=Address{id=11, city=’Lviv’, street=’null’, homeNumber=’null’}}
Users{id=5, name=’Smith’, email=’someEmail@gmail.com’, address=Address{id=8, city=’Kiev’, street=’Main Street’, homeNumber=’4′}}
Users{id=7, name=’Smith’, email=’someEmail@gmail.com’, address=Address{id=10, city=’Kiev’, street=’Main Street’, homeNumber=’4′}}
Users{id=5, name=’Smith’, email=’someEmail@gmail.com’, address=Address{id=8, city=’Kiev’, street=’Main Street’, homeNumber=’4′}}
Users{id=6, name=’Jon Dorian’, email=’gmailEmail@gmail.com’, address=Address{id=9, city=’Lviv’, street=’null’, homeNumber=’null’}}
Users{id=7, name=’Smith’, email=’someEmail@gmail.com’, address=Address{id=10, city=’Kiev’, street=’Main Street’, homeNumber=’4′}}
Users{id=8, name=’Jon Dorian’, email=’gmailEmail@gmail.com’, address=Address{id=11, city=’Lviv’, street=’null’, homeNumber=’null’}}

Если Вы следовали вышеописанным пунктам, то должны при запуске получить ошибку:

org.hibernate.LazyInitializationException: could not initialize proxy [com.javamaster.springjpapostgres.entity.Address#8] — no Session. Это очень распространенная ошибка особенно среди новичков. Так как мы в наших сущностях указали FetchType.LAZY это будет означать, что Адрес не будет автоматически доставаться из базы при выборе Пользователя. С точки зрения производительности — это очень хорошая практика. Старайтесь избегать FetchType.EAGER в реальный проектах, если хотите сократить количество запросов в базу данных. Для того, чтобы избежать вышеописанной ошибки нужно над методами сервисов добавить аннотацию @Transactional. Это должно решить проблему. Мне было лень добавлять эту аннотацию, поэтому я изменил FetchType.LAZY на FetchType.EAGER в сущность Users.

Код   
package com.javamaster.springjpapostgres.entity;

import javax.persistence.*;

@Entity
@Table(name = "users")
public class Users {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String name;

    @Column
    private String email;

    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "address_id")
    private Address address;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Users{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", address=" + address +
                '}';
    }
}

Теперь все должно работать.

Я не зря выбрал для данного примера модули для веб приложения. В следующий раз постараюсь показать REST API, а сервисы послужат нам хорошим примером.

Теперь Вы знаете основы Spring JPA и сможете начать пользоваться этим замечательным инструментом, который значительно экономит время программиста для написания слоя доступа к базе данных.

Пример кода находится по ссылке: https://github.com/caligula95/springjpapostgres

Ссылка на менторство по java. Подразумевает создание спринг веб приложения с нуля.

Понравилась статья? Поделиться с друзьями:
Комментарии: 7
  1. Elena

    спасибо! познавательно

  2. Vladimir

    Подскажите пжл как передать в аннотацию параметр, не совсем понимаю как мне использовать оператор IN

    1. аываыв

      авыаыв

      1. аываыв

        ппрар

  3. Denis

    @Query(«select u from Users u where u.email like :=email»)//если этого мало можно написать
    //собственный запрос на языке похожем на SQL
    List findWhereEmailIsGmail(@Param String email);

  4. Федор

    Спасибо огромное! Очень полезно
    Редко оставляю отзывы, но эта статья меня выручила!

    1. Denys (автор)

      Здравствуйте! Спасибо за отзыв)

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

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: