Sping Boot Rest API、JPA实体、DTO,最好的方法是什么?

ndh0cuux  于 2022-11-14  发布在  其他
关注(0)|答案(2)|浏览(157)

我被分配了这个任务,只是为了练习,它变得很长,很有挑战性,但它教会了我很多,主要是关于Lambdas和JPA。
它是一个基本的Rest API,用于创建酒店、房间、客人、预订、客人类型、房间类型等。
我最初的问题是学习JPA关系、OneToOne、OneToMany等,单向、双向等等。
我还使用PostgreSQL,使用"sping.jpa.hibernate.ddl-auto=create-drop(or update)",当我出于任何原因想要重新创建数据库时,根据需要进行更改。
因此,我非常高兴和兴奋地使用我的新@Annotations来关联我的实体,并取回我需要的任何信息的列表,遇到了多个问题,在这里读到了许多问题,解决了我的问题,但现在我遇到了一个新问题,但随后,开始质疑我的方法,也许我不应该把所有事情都留给JPA。
让我来告诉你我的意思。我将保持我的类短,只显示相关的信息。
我有我的预订实体。

@Data
    @Entity
    @Table(name = "reservation")
    public class Reservation {
      @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      private Long id;
      @OneToOne(cascade = CascadeType.ALL)
      @JoinColumn(name = "guest", referencedColumnName = "id")
      @JsonManagedReference
      @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
      private Guest guest;
      @OneToOne(cascade = CascadeType.ALL)
      @JoinColumn(name = "room", referencedColumnName = "id")
      private Room room;
      @ManyToMany(fetch = FetchType.LAZY,
          cascade = CascadeType.ALL)
      @JoinTable(name = "reservation_rooms",
          joinColumns = { @JoinColumn(name = "reservation_id" )},
          inverseJoinColumns = { @JoinColumn(name = "room_id") }
      )
      @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
      private List<ReservationRoom> roomList = new ArrayList<>();
    
      private LocalDate start_date;
      private LocalDate end_date;
      private Boolean check_in;
      private Boolean check_out;
    
      public void addRoom(Room room) {
        this.roomList.add(room);
      }
    
      public void removeRoom(Long id) {
        Room room = this.roomList.stream().filter(g -> g.getId() == id).findFirst().orElse(null);
        if (room != null) {
          this.roomList.remove(room);
        }
      }
    
    }

这是我的Room实体。

@Data
    @Entity
    @Table(name = "room")
    public class Room {
      @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      private Long id;
      private String name;
      private String description;
      private Integer floor;
      @JsonProperty("max_guests")
      private Integer maxGuests;
      @ManyToOne(fetch = FetchType.LAZY)
      @JsonBackReference
      private Hotel hotel;
      @ManyToOne(fetch = FetchType.LAZY)
      @JsonProperty("type")
      @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
      private RoomType roomType;
    
      @Override
      public boolean equals(Object o) {
        if (this == o) {
          return true;
        }
        if (!(o instanceof Room)) {
          return false;
        }
        return id != null && id.equals(((Room) o).getId());
      }
    
      @Override
      public int hashCode() {
        return getClass().hashCode();
      }
    }

这是我的Guest实体。

@Data
    @Entity
    @Table(name = "guest")
    public class Guest {
      @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      private Long id;
      private String first_name;
      private String last_name;
      private String email;
      @ManyToOne(fetch = FetchType.LAZY)
      @JsonProperty("type")
      @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
      private GuestType guest_type;
      @ManyToMany(fetch = FetchType.LAZY,
          cascade =  {
              CascadeType.PERSIST,
              CascadeType.MERGE
          },
          mappedBy = "guestList"
      )
      @JsonBackReference
      @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
      private List<Reservation> reservationList = new ArrayList<>();
    
      public Guest(){}
    
      public Guest(Long id) {
        this.id = id;
      }
    
      public List<Reservation> getReservationList() {
        return reservationList;
      }
    
      public void setReservationList(List<Reservation> reservationList) {
        this.reservationList = reservationList;
      }
    }

一开始一个预订只能有一个房间,但现在要求变了,可以有多个房间。所以现在,客人列表需要链接到与预订相关联的房间,而不是直接链接到预订。(我知道我有一个客人和一个房间,也有两者的列表,这是因为我用单个客人作为预订的名字,用单个房间作为预订的名字,作为“主”房间,但请不要介意)。
暂且不谈JPA,因为我所面临的每一个挑战我都会问自己“如何使用JPAish?",然后研究如何使用JPA(这就是我如何了解@ManyToMany等注解的方法)。
我所要做的只是创建一个新表,将预订与房间关联起来(这在我的实体中已经用JPA完成了),然后还添加de guest id。
因此,这个新表将有一个包含reservation_id、room_id和guest_id的PK。非常简单,然后创建我的Reservation模型,其中包含一个Room列表,而这个Room模型将包含一个Guest列表。简单。
但是我不想在我当前的Room实体中添加一个List of Guest,因为我有一个端点,也许还有其他几个函数,它们会检索我的Room实体,我也不想添加一个List of Guest,因为随着时间的推移,这个列表会越来越大,而这是你不需要传递的信息。
因此,我做了一些研究,发现我可以使用@Inheritance或@MappedSuperclass扩展我的实体,并且我可以创建一个Reservation_Room模型,其中包括一个Guest列表,并在我的预订实体中添加一个Reservation_Room列表,而不是一个Room列表,我真的不知道这是否可行。
话虽如此,但在我继续研究并开始修改我的代码之前,我不禁要问,这是否是正确的方法?或者我是否在这方面对JPA要求太高了?什么是最好的方法?3 id关系表可以在JPA上轻松实现/Map吗?

主要目标是公开我的Room实体,但是当一个Room被添加到Reservation中时,这个Room也会有一个List of Guest。我可以这样做吗?或者我应该创建一个新的模型并根据需要填充信息吗?这不会免除我创建3 id表的责任。

cyvaqqii

cyvaqqii1#

根据您在这里所写的内容,我认为您可能已经意识到持久性模型并不总是与表示模型相匹配,您在HTTP端点中使用的表示模型通常是在这个时候发现DTO的,您似乎也听说过DTO。
DTO应该根据端点表示的需要进行修改/创建。如果您不想公开某个状态,那么就不要在DTO中为该数据声明getter/field。持久性模型应该简单地设计成一种方式,以便您可以按照需要的方式持久化和查询数据。DTO和实体之间的转换是一件单独的事情,我只能建议您给予Blaze-Persistence实体视图。
我创建这个库是为了允许在JPA模型和自定义接口或抽象类定义的模型之间进行简单的Map,就像Spring Data Projections一样。其思想是您可以按照自己喜欢的方式定义目标结构(域模型),并通过JPQL表达式将属性(getter)Map到实体模型。
使用Blaze-Persistence Entity-Views时,您的用例的DTO模型可能如下所示:

@EntityView(Reservation.class)
public interface ReservationDto {
    @IdMapping
    Long getId();
    GuestDto getGuest();
    List<RoomDto> getRooms();
}
@EntityView(Guest.class)
public interface GuestDto {
    @IdMapping
    Long getId();
    String getName();
}
@EntityView(Room.class)
public interface RoomDto {
    @IdMapping
    Long getId();
    String getName();
}

查询是将实体视图应用于查询的问题,最简单的是按id查询。
ReservationDto a = entityViewManager.find(entityManager, ReservationDto.class, id);
Spring Data 集成允许您像Spring Data 投影一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<ReservationDto> findAll(Pageable pageable);

最好的部分是,它只会获取实际需要的状态!

ig9co6j1

ig9co6j12#

我认为您需要在持久性和端点之间添加一个层。(在Spring世界中)。您应该使用实体作为Repositories的返回类型(因此在服务中也使用它们),但将DTO返回给Controller。这样,您将解耦在它们之间所做的任何修改(例如,您可能对返回存储在实体中的字段失去兴趣,或者您可能希望从其他源向dto添加更多信息)。
在此特定情况下,我将创建4个表:预订,客人,客房,客人预订。

  • 客人将包含身份证+客人数据(姓名、电话号码等)
  • 房间将包含id +房间数据
  • GuestsForReservation将包含id + reservationId + guestId(这样你就可以得到每个预订的客人列表)。FK为reservationId和guestId,PK为提到的合成id。
  • 预订将包含id(合成)、房间id、日期自、日期至、潜在的主要客人id(如果对您有意义,它可以是支付账单的人)。没有到GuestForReservation表的链接,或者如果需要,您可以有GuestForReservation的列表。

当你想预订房间时,你有一个ReservationRequest对象,它将转到ReservationService,在这里你将通过roomId和日期查询ReservationRepository。如果没有返回任何东西,你将创建各种实体,并将它们持久保存在ReservationRepository和GuestForReservation存储库中。
通过使用该服务和各种存储库的组合,您应该能够获得所需的所有信息(每个房间的客人列表、每个日期的客人列表等)。在服务级别,然后将所需的数据Map到DTO并将其传递给控制器(以所需的格式),甚至传递给其他服务(取决于您的需要)。
对于实体和DTO之间的Map,有不同的选择,您可以简单地创建一个名为ReservationMapper的组件(例如),然后自己动手(使用一个实体,并根据需要构建一个DTO);从Spring框架实现Converter;使用MapStruct(在我看来很麻烦);等等。
如果您想在JPA中表示一个由多个列组成的id,通常会使用@Embeddable类(在使用它们时应该将它们标记为EmbeddedId),您可以在Google上搜索它们以获得更多信息。

相关问题