Это короткий непрерывный проект о том, как создать простой сервер REST API при загрузке Spring.

Для начала краткий обзор ООП, поскольку эти концепции будут иметь жизненно важное значение в этом небольшом проекте и упражнении.

Резюме ООП

Если вам нужно подвести итоги, ознакомьтесь с этим обзором наиболее распространенных концепций ООП https://www.educative.io/edpresso/what-is-objectorhibited-programming

Проект

Обобщив все эти полезные концепции, пора применить их на практике.

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

Создайте новый проект весенней загрузки на основе Gradle и добавьте следующие зависимости в файл build.gradle:

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.h2database:h2'
//    Depending on which database you prefer
runtimeOnly 'org.postgresql:postgresql'

Создание моделей

Создайте пакет для моделей, в который будут помещены все классы моделей. У меня есть модельный университет, у которого есть атрибуты: имя, год основания и местонахождение. Вам необходимо создать геттеры и сеттеры для атрибутов. Это будет выглядеть так:

Следующим шагом является создание модели для студентов с именем, фамилией и датой рождения в качестве атрибутов.

Отношения

Теперь у нас есть два класса, которые не знают друг о друге. Нам нужно объявить отношения между Студентом и Университетом. Студент может быть только в одном университете, но в университете много студентов. Следовательно, это отношения «один ко многим».

На модели университета есть список студентов, связанных с университетом, как показано ниже.

private List<Student> students;
public List<Student> getStudents() {
    return students;
}
public void setStudents(List<Student> students) {
    this.students = students;
}

В модели «Студент» есть объект «университет», чтобы указать, куда зачислен конкретный студент. Это должно выглядеть следующим образом.

private University university;
public Student(String firstName, String lastName, University university) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.university = university;
}
public University getUniversity() {
    return university;
}
public void setUniversity(University university) {
    this.university = university;
}

Сохранение данных

Чтобы сохранить данные, вы аннотируете объявление класса как объект, а также имеете аннотацию table, в которой вы указываете имя таблицы в базе данных.

@Entity
@Table(name = "students")
public class Student {
    // ..... the rest of the content
}

Каждая сущность должна иметь первичный ключ, для этого аннотируйте атрибут id следующим образом:

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;

Что касается других атрибутов, аннотируйте только аннотацию столбца и укажите имя столбца (используя snake_case). Например:

@Column(name = "first_name")
private String firstName;

В модели университета аннотируйте атрибут студента с помощью OneToMany следующим образом:

@OneToMany(mappedBy = "university")
private List<Student> students;

Вы должны отобразить отношения в обоих направлениях, чтобы вы могли получить доступ к отношениям из обеих моделей. В модели Student будет столбец university_id, указывающий на университет, в который зачислен студент. Это будет выглядеть следующим образом:

@ManyToOne
@JsonIgnore
@JoinColumn(name = "university_id")
private University university;

Обратите внимание, что здесь есть аннотация JsonIgnore, чтобы избежать бесконечного объекта JSON, поскольку студент вызовет объект University, с которым он связан, а объект университета вызовет все студенческие объекты, с которыми он ассоциируется… .. это продолжается и продолжается.

База данных

Скорее всего, у вас возникла ошибка не удается разрешить в именах таблиц и столбцов. Чтобы избавиться от этого, добавьте в проект источник данных. Если вы не знакомы с этим, ознакомьтесь с этим руководством.

После добавления базы данных добавьте свои учетные данные БД в файл application.properties, который вы можете найти в папке src / main / resources. Это будет выглядеть примерно так:

server.port=8080
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:postgresql://localhost:5432/rest
spring.datasource.username=usernamedropdrop
spring.datasource.password=password

Репозитории

Теперь у нас есть таблицы со столбцами, но как они подключаются к базе данных для выполнения функций CRUD? Вот тут-то и пригодятся репозитории.

Создайте репозиторий интерфейса для каждой из сущностей, расширяющих JpaRepository, как показано ниже:

public interface UniversityRepository extends JpaRepository<University, Long> {
}

Не забудьте поместить репозитории в их пакет.

Услуги

Далее следует создать служебные интерфейсы, которые определяют методы, которые мы будем использовать для функций CRUD. Служба университета будет выглядеть следующим образом:

public interface UniversityService {
List<University> findAll();
    
    University findById(Long id);
    
    void delete(Long id);
    
    University createUniversity(University university);
    
    University update(Long id, University university);
}

Создайте аналогичный интерфейс для модели Student.

Теперь, чтобы подключить наши функции CRUD к базе данных, мы реализуем интерфейс службы и внедряем репозиторий, чтобы использовать его унаследованные функции. Не забудьте аннотировать класс реализации сервиса аннотацией сервиса. UniversityServiceImpl будет выглядеть следующим образом:

Обратите внимание на вызов класса NotFoundException. Создайте класс либо в собственном пакете исключений, либо в корневом пакете. Это будет выглядеть так:

Реализуйте то же самое для интерфейса StudentService.

Контроллеры

Создайте пакет для контроллеров и создайте класс UniversityController. Здесь мы будем открывать конечные точки.

В контроллере аннотируйте объявление класса как RestController, а также добавьте аннотацию RequestMapping со значением как университеты. Это сообщает Spring, что этот класс будет ожидать запросов по сети и будет обрабатывать запросы, направленные на yourbasedomain / University, в данном случае « http: // localhost: 8080 / университеты », если ваше приложение настроено для работы через порт 8080.

Чтобы использовать функциональные возможности, реализованные в классе реализации службы, введите класс UniversityService и потребуйте его в конструкторе.

Теперь UniversityController выглядит следующим образом:

@RestController
@RequestMapping(value = "universities")
public class UniversityController {
    private final UniversityService universityService;
public UniversityController(UniversityService universityService)
    {
        this.universityService = universityService;
    }
}

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

ПОЛУЧИТЬ

@GetMapping
public List<University> findAll(){
    return universityService.findAll();
}

POST

@PostMapping
public University createUniversity(@RequestBody University university) {
    return universityService.createUniversity(university);
}

ПАТЧ

Обратите внимание, что этот URL-адрес запроса будет похож на http: // localhost: 8080 / University / {id} с методом PATCH

@PatchMapping(value = "{id}")
public University updateUniversity(@PathVariable Long id, @RequestBody University university) {
    return universityService.update(id, university);
}

УДАЛИТЬ

@DeleteMapping(value = "{id}")
public void deleteUniversity(@PathVariable Long id) {
    universityService.delete(id);
}

Сделайте то же самое для StudentController.

Проверка

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

Мы реализуем валидацию из модели и будем использовать ее при получении запроса в контроллере.

В модели создайте интерфейс с именем Create, как показано ниже:

@Entity
@Table(name = "students")
public class Student {
// ..... the rest of the content
public interface Create{}
}

Мы будем использовать интерфейс создания, чтобы проверить наличие необходимых атрибутов при создании. Для этого добавьте аннотацию NotNull перед атрибутами следующим образом:

public class University {
// ... the rest of the content
  
  @NotNull(groups = Create.class)
  @Column(name = "name")
  private String name;
@NotNull(groups = Create.class)
  @Column(name = "location")
  private String location;
// ... the rest of the content
}

В контроллере в методе createUniversity добавьте аннотацию Validated и вызовите интерфейс Create из класса University, как показано ниже:

@PostMapping
public University createUniversity(@Validated(University.Create.class)
        @RequestBody University university) {
    return universityService.createUniversity(university);
}

Сделайте то же самое для модели ученика. Добавляйте аннотацию NotNull только к атрибутам, которые должны быть там.

Имеет смысл создать студента в рамках конкретного университета, в который они поступают. Чтобы обеспечить это, добавьте метод createStudent в UniversityService и не забудьте реализовать его и добавить конечную точку POST «/ University / {id} / student »

UniversityServiceImpl выглядит следующим образом:

private final StudentService studentService;
public UniversityServiceImpl(UniversityRepository universityRepository, StudentService studentService  ) {
    this.universityRepository = universityRepository;
    this.studentService = studentService;
}
// ...the rest of the content
@Override
public Student createStudent(Long universityId, Student student) {
    University university = findById(universityId);
    student.setUniversity(university);
    return studentService.createStudent(student);
}

Обратите внимание, что мы передаем студенческий сервис вместо репозитория.

Конечная точка на UniversityController будет выглядеть следующим образом:

@PostMapping(value = "{id}/students")
public Student createStudent(@PathVariable Long id,
                             @Validated(Student.Create.class)
                             @RequestBody Student student){
    return universityService.createStudent(id, student);
}

Тестирование

Вы можете протестировать открытые конечные точки с помощью Почтальона. Убедитесь, что вы указали все параметры.

Обратите внимание, что если вы отправляете запрос без обязательных атрибутов, он возвращает ошибку 400 BAD REQUEST с сообщением о том, что конкретный атрибут не может быть нулевым.

Упражнение

Вы можете разветвить репо из здесь, а затем продолжить выполнение упражнения, описанного ниже.

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

Предоставьте конечные точки:

  • Разрешить студентам просматривать доступные курсы.
  • Зарегистрируйтесь на курс, используя идентификатор курса.

Убедитесь, что вы обслуживаете:

  • Проверка при создании записи
  • Отношения "многие ко многим" между студентом и курсом

Подсказка: для отношений "многие ко многим" требуется сводная таблица, также известная как таблица соединений, в которой вам нужно объявить столбец соединения и столбец обратного соединения, то есть course_id и student_id соответственно.

@ManyToMany 
@JoinTable(name= “student_courses”, joinColumns = @JoinColumn(name = “course_id”), inverseJoinColumns = @JoinColumn(name=”student_id”) ) @JsonIgnore
private Set<Student> students = new HashSet<>();