带有Spring Boot 2023的基本REST API
#初学者 #api #java #springboot

在本教程中,我将向您展示如何使用Java和Maven的Spring Boot开发非常基本的REST API。

设置项目

首先,您需要创建一个Spring Boot项目。为此,您可以使用Spring Initializr

添加以下依赖性:

  • Lombok
  • Spring Web
  • 春季数据JPA
  • PostgreSQL驱动程序

然后,单击“生成”下载带有项目的邮政编码。

Spring Initializr set up

解压缩项目并以您的喜好iDE打开它,我将使用Intellij Idea Community Edition 2022.1.2。

运行apiapplication.java文件:

IDE after run ApiApplication.java

要求评论

现在,假设您得到以下数据库设计:
Database diagram

您必须为每个实体创建一个带有以下端点的REST API:

来宾:

方法 端点
get /api/guests
get /api/guests/{id}
post /api/guests
put /api/guests/{id}
delete /api/guests/{id}

房间:

方法 端点
get /api/rooms
get /api/rooms/{id}
post /api/rooms
put /api/rooms/{id}
delete /api/rooms/{id}

保留:

方法 端点
get /api/revervations
get /api/revervations/{id}
post /api/revervations
put /api/revervations/{id}
delete /api/revervations/{id}

响应的示例:
获取/api/guests

{
  "content": [
    {
      "id": 4,
      "name": "Javier",
      "lastName": "Vega",
      "email": "test@gmail.com"
    }
  ],
  "pageNo": 0,
  "pageSize": 10,
  "totalElements": 1,
  "totalPages": 1,
  "last": true
}

获取/api/guests/{id}

{
  "id": 4,
  "name": "Javier",
  "lastName": "Vega",
  "email": "test@gmail.com"
}

发布/api/guests

// Body
{
    "name": "Javier",
    "lastName": "Vega",
    "email": "test@gmail.com",
    "password": "1234"
}
// Response
{
    "id": 5,
    "name": "Javier",
    "lastName": "Vega",
    "email": "test@gmail.com"
}

放置/api/guests/{id}

// Body
{
    "name": "Javier A.",
    "lastName": "Vega",
    "email": "test@gmail.com",
    "password": "1234"
}
// Response
{
    "id": 5,
    "name": "Javier A.",
    "lastName": "Vega",
    "email": "test@gmail.com"
}

删除/api/guests/{id}

Guest deleted

必须使用PostgreSQL进行数据库。

开始编码

项目目录结构

com.hotel.api中创建以下软件包5:

  • 控制器
  • dto
  • 例外
  • mapper
  • 模型
  • 存储库
  • 服务

项目配置

我们需要使用一个PostgreSQL数据库,因此我在本地计算机中创建了一个数据库。要将我们的Spring Boot应用程序连接到数据库,我们需要将以下属性添加到application.properties文件:

spring.datasource.url=jdbc:postgresql://localhost:5432/hotel
spring.datasource.username=postgres
spring.datasource.password=1234
spring.datasource.driver-class-name=org.postgresql.Driver

# Testing
# This will drop any table in the database
# and create new ones base on the models
spring.jpa.hibernate.ddl-auto=create-drop

# Development
# This will update table schemas base on the models,
# but not will not remove columns that no longer exist
# in the models, it will just add new columns if needed.
#spring.jpa.hibernate.ddl-auto=update

# Production
#spring.jpa.hibernate.ddl-auto=none

# Show generated queries in logs - Spring Boot uses logback
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

楷模

要创建一个代表数据库中表的模型,我们需要使用注释@Entity。默认情况下,表的名称将是小写的类名。如果您想要桌子的自定义名称,请使用@Table(name = "your_table_name")

类的每个属性将对应于表中的列。默认情况下,列名将与属性相同。如果您希望该列具有与属性名称不同的名称,请使用@Column(name = "your_column_name")

要使一个属性对应于表的PK,我们需要使用@Id。如果您希望该值是自动生成的,则使用@GeneratedValue(strategy = GenerationType.IDENTITY)

由于客人可以保留许多预订,因此我们需要使用注释@OneToMany(mappedBy = "guest", cascade = CascadeType.ALL, orphanRemoval = true)。这将使将来提供客人以及他们所有的保留。

package com.hotel.api.model;

@Data // Add getters and setters for all attributes
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Guest {
    @Id // Make the attribute the PK of the table.
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    @Column(name = "last_name")
    private String lastName;
    private String email;
    private String password;

    @OneToMany(mappedBy = "guest",
               cascade = CascadeType.ALL,
               orphanRemoval = true)
    private List<Reservation> reservations = new ArrayList<>();
}

package com.hotel.api.model;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Room {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    @Column(unique = true)
    private String number;
    private String type;
}

预订实体与房间和客人相关。如果您想查询这些实体的数据以及预订数据,则需要使用@ManyToOne(fetch = FetchType.LAZY)@JoinColumn(name = "FK_column_name")

package com.hotel.api.model;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Reservation {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String title;
    @Column(name = "date_start")
    private LocalDate dateStart;
    @Column(name = "date_end")
    private LocalDate dateEnd;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "room_id")
    private Room room;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "guest_id")
    private Guest guest;
}

存储库

要创建一个存储库,我们只需要创建一个扩展JpaRepository<Entity, IdDataType>的接口即可。该界面将为我们提供一些查询数据库的方法:

  • findall()
  • findbyid()
  • 保存()
  • delete()
package com.hotel.api.repository;

public interface GuestRepository extends JpaRepository<Guest, Integer>
{
}

服务接口

现在,我们需要使用我们希望在服务中实现的所有方法的定义创建一个接口。

package com.hotel.api.service;

public interface GuestService {
    GuestDto createGuest(Guest guest);
    GuestsResponse getAllGuests(int pageNo, int pageSize);
    GuestDto getGuestById(int id);
    GuestDto updateGuest(GuestDto guestDto, int id);
    void deleteGuestById(int id);

    GuestReservationsResponse getGuestReservations(int id);
}

服务实施

服务是用于查询数据的实施的类。要使一堂课一个服务,您需要使用注释@Service。然后,实现服务的接口并覆盖所有方法。

package com.hotel.api.service.impl;

@Service
public class GuestServiceImpl implements GuestService {

    private final GuestRepository guestRepository;

    @Autowired
    public GuestServiceImpl(GuestRepository guestRepository) {
        this.guestRepository = guestRepository;
    }

    @Override
    public GuestDto createGuest(Guest guest) {
        Guest newGuest = guestRepository.save(guest);
        return GuestMapper.mapToDto(newGuest);
    }

    @Override
    public GuestsResponse getAllGuests(int pageNo, int pageSize) {
        Pageable pageable = PageRequest.of(pageNo, pageSize);
        Page<Guest> guests = guestRepository.findAll(pageable);
        List<Guest> listOfGuests = guests.getContent();
        List<GuestDto> content = listOfGuests.stream()
        .map(GuestMapper::mapToDto)
        .collect(Collectors.toList());

        GuestsResponse guestsResponse = new GuestsResponse();
        guestsResponse.setContent(content);
        guestsResponse.setPageNo(guests.getNumber());
        guestsResponse.setPageSize(guests.getSize());
        guestsResponse.setTotalElements(guests.getTotalElements());
        guestsResponse.setTotalPages(guests.getTotalPages());
        guestsResponse.setLast(guests.isLast());

        return guestsResponse;
    }

    @Override
    public GuestDto getGuestById(int id) {
        Guest guest = guestRepository.findById(id)
        .orElseThrow(
            () -> new GuestNotFoundException("Guest could not be found")
        );
        return GuestMapper.mapToDto(guest);
    }

    @Override
    public GuestDto updateGuest(GuestDto guestDto, int id) {
        Guest guest = guestRepository.findById(id)
        .orElseThrow(
            () -> new GuestNotFoundException("Guest could not be found")
        );

        guest.setName(guestDto.getName());
        guest.setLastName(guestDto.getLastName());
        guest.setEmail(guestDto.getEmail());

        Guest updatedGuest = guestRepository.save(guest);

        return GuestMapper.mapToDto(updatedGuest);
    }

    @Override
    public void deleteGuestById(int id) {
        Guest guest = guestRepository.findById(id)
        .orElseThrow(
            () -> new GuestNotFoundException("Guest could not be found")
        );
        guestRepository.delete(guest);
    }

    @Override
    public GuestReservationsResponse getGuestReservations(int id) {
        Guest guest = guestRepository.findById(id)
        .orElseThrow(
            () -> new GuestNotFoundException("Guest could not be found")
        );
        return GuestMapper.mapToGuestReservationsResponse(guest);
    }
}

控制器

控制器是Spring Boot应用程序中的最后一层,在这里您定义R​​EST API中可用的端点。要创建一个控制器,您需要带有注释@RestController@RequestMapping("/api/")的类。您将其放入@RequestMapping中的内容将添加到控制器内部的所有映射中。

例如,我有一个带有注释@GetMapping("guests")的方法getGuests,因此将调用此方法的端点将为/api/guests

所有映射到端点的方法应返回ResponseEntity对象。它将包含我们的数据以及其他属性(例如状态代码)。

package com.hotel.api.controller;

@RestController
@RequestMapping("/api/")
public class GuestController {
    private final GuestService guestService;

    @Autowired
    public GuestController(GuestService guestService) {
        this.guestService = guestService;
    }

    @GetMapping("guests")
    public ResponseEntity<GuestsResponse> getGuests(
            @RequestParam(
                value = "pageNo",
                defaultValue = "0",
                required = false) int pageNo,
            @RequestParam(
                value = "pageSize",
                defaultValue = "10",
                required = false) int pageSize
    ){
        return new ResponseEntity<>(
            guestService.getAllGuests(pageNo, pageSize), HttpStatus.OK
        );
    }

    @GetMapping("guests/{id}")
    public ResponseEntity<GuestDto> guestDetail(
        @PathVariable int id){
        return new ResponseEntity<>(
            guestService.getGuestById(id), HttpStatus.OK
        );
    }

    @PostMapping("guests")
    @ResponseStatus(HttpStatus.CREATED)
    public ResponseEntity<GuestDto> createGuest(
        @RequestBody Guest guest){
        return new ResponseEntity<>(
            guestService.createGuest(guest), HttpStatus.CREATED);
    }

    @PutMapping("guests/{id}")
    public ResponseEntity<GuestDto> updateGuest(
        @RequestBody GuestDto guestDto,
        @PathVariable("id") int guestId
    ){
        GuestDto response = guestService.updateGuest(guestDto, guestId);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    @DeleteMapping("guests/{id}")
    public ResponseEntity<String> deleteGuest(
        @PathVariable("id") int guestId
    ){
        guestService.deleteGuestById(guestId);
        return new ResponseEntity<>("Guest deleted", HttpStatus.OK);
    }

    @GetMapping("guests/{id}/reservations")
    public ResponseEntity<GuestReservationsResponse> guestReservations(
        @PathVariable int id
    ){
        return new ResponseEntity<>(
            guestService.getGuestReservations(id), HttpStatus.OK
        );
    }
}

DTO(数据传输对象)

在整个代码中,您应该看到我使用了kude24类。 DTO类旨在作为实体和发送给客户的数据之间的中介。

例如,来宾模型具有属性密码。当我们从数据库中查询数据时,默认情况下会带来所有属性,但我不想将它们全部发送给客户端。因此,我需要一个仅包含必要属性的类。这是DTO课程发挥作用的时候。

package com.hotel.api.dto;

@Data
public class GuestDto {
    private int id;
    private String name;
    private String lastName;
    private String email;
}

我还使用了另一个名为GuestReservationsResponse的DTO发送来宾数据及其所有预订。

package com.hotel.api.dto;

@Data
public class GuestReservationsResponse{
    private int id;
    private String name;
    private String lastName;
    private String email;
    private List<ReservationInfo> reservations;
}

最后,我使用另一个名为GuestsResponse的DTO使用分页发送客人。

package com.hotel.api.dto;

@Data
public class GuestsResponse {
    private List<GuestDto> content;
    private int pageNo;
    private int pageSize;
    private long totalElements;
    private int totalPages;
    private boolean last;
}

映射者

要在实体和DTO之间进行转换,我使用的是具有将实体模型转换为DTO的方法,反之亦然。

在这种情况下,我创建了一个使用方法mapToDtomapToGuestReservationsResponse的类GuestMapper,因为我需要经常进行这些转换。

package com.hotel.api.mapper;

@NoArgsConstructor
public class GuestMapper {
    public static GuestDto mapToDto(Guest guest){
        GuestDto guestDto = new GuestDto();
        guestDto.setId(guest.getId());
        guestDto.setName(guest.getName());
        guestDto.setLastName(guest.getLastName());
        guestDto.setEmail(guest.getEmail());
        return guestDto;
    }

    public static GuestReservationsResponse mapToGuestReservationsResponse(Guest guest){
        GuestReservationsResponse guestReservationsResponse = new GuestReservationsResponse();

        List<ReservationInfo> reservationsInfo = ReservationMapper.mapElementsToReservationInfo(
            guest.getReservations()
        );

        guestReservationsResponse.setId(guest.getId());
        guestReservationsResponse.setName(guest.getName());
        guestReservationsResponse.setLastName(
            guest.getLastName()
        );
        guestReservationsResponse.setEmail(guest.getEmail());
        guestReservationsResponse.setReservations(reservationsInfo);

        return guestReservationsResponse;
    }
}

错误处理

在整个代码中,您应该看到我使用了kude30类。此类将被视为例外,并将通过Spring Boot处理,该启动将向客户端发送错误响应。

package com.hotel.api.exception;

public class GuestNotFoundException extends RuntimeException{
    @Serial
    private static final long serialVersionUID = 1;

    public GuestNotFoundException(String message){
        super(message);
    }
}

要将错误处理添加到我们的Spring Boot应用程序中,我们需要使用注释@ControllerAdvice和此类中创建一个类,我们需要创建一个将处理异常并向客户端发送错误响应的方法。

我们定义的方法需要用@ExceptionHandler(ExceptionClass.class)注释,ExceptionClass应该是扩展异常的类。另外,我们的班级需要返回ResponseEntity<ErrorObject>,其中ErrorObject可以是我们想要的任何类。

在这种特定情况下,ExceptionClassGuestNotFoundExceptionErrorObjectErrorObject

package com.hotel.api.exception;

@ControllerAdvice
public class GlobalException {
    @ExceptionHandler(GuestNotFoundException.class)
    public ResponseEntity<ErrorObject> handleGuestNotFoundException(
        GuestNotFoundException ex, WebRequest request
    ){
        ErrorObject errorObject = new ErrorObject();

        errorObject.setStatusCode(HttpStatus.NOT_FOUND.value());
        errorObject.setMessage(ex.getMessage());
        errorObject.setTimestamp(new Date());

        return new ResponseEntity<ErrorObject>(
            errorObject, HttpStatus.NOT_FOUND
        );
    }
}
package com.hotel.api.exception;

@Data
public class ErrorObject {
    private Integer statusCode;
    private String message;
    private Date timestamp;
}

结论

就是这样。现在,我们有一个基本的REST API在Spring Boot中工作。我已经解释了使用Spring Boot构建REST API的基础知识。房间和预订存储库,服务和控制器的代码几乎与客人相同。您可以在我的GitHub上查看所有源代码。

谢谢您的阅读。