Spring Boot Rest API,JPA实体,DTO,最佳方案是什么?

3

我被分配了这个任务,只是为了练习,但它变得非常长和具有挑战性,但它教会了我很多关于lambda和JPA的知识。

这是一个基本的Rest API,用于创建酒店、房间、客人、预订、客人类型、房间类型等。

我的初始问题是学习JPA关系,OneToOne、OneToMany等,单向、双向等等。

我还使用PostgreSQL,使用"sping.jpa.hibernate.ddl-auto=create-drop(or update)",根据需要更改,当我想为任何原因重新创建DB时。

所以我非常高兴和兴奋地使用我的新@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);
        }
      }
    
    }

这是我的房间实体。

    @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;
      }
    }

起初,预订只能有1个房间,但要求已更改,现在可以有多个房间。因此,客人列表需要与预订的房间相关联,而不是直接与预订相关联。让我们先抛开JPA不考虑,每当我面临挑战时,我都会问自己“怎样用JPA处理?”然后研究如何使用JPA处理(这就是我了解@ManyToMany等注释的方法)。
我的做法是创建一个新表来将预订与房间关联(这已经在我的实体中使用JPA完成),然后添加客人ID。所以,这个新表将有一个PK,其中包括预订ID、房间ID和客人ID。非常简单,然后创建我的Reservation模型,其中包括Room的列表,而这个Room模型将包括Guest的列表。很容易。
但我不想在当前的Room实体中添加Guest的列表,因为我有一个端点和可能有几个其他函数,它们检索我的Room实体,我不想添加一个Guest的列表,因为随着时间的推移,这个列表会越来越大,并且这是您不需要传递的信息。
所以我进行了一些研究,发现我可以通过@Inheritance或@MappedSuperclass扩展我的实体,我可以创建一个Reservation_Room模型,其中包括Guest的列表,并在我的Reservation实体中添加一个Reservation_Room的列表,而不是Room的列表,我真的不知道这是否可行。
说了这么多,在我继续研究和修改代码之前,我想知道,这是否是正确的方法?或者我是否过度强制使用JPA?对此最佳的处理方式是什么?可以轻松地实现/映射具有三个ID关系的表吗?
主要目标是让我的Room实体以其当前状态暴露,但是当将Room添加到预订中时,该Room也将具有Guest的列表。我能用JPA做到这一点吗?还是应该创建一个新模型并根据需要填充信息?这并不意味着我不需要创建我的三个ID表。
2个回答

3
根据你在此处写的内容,我认为你可能已经意识到持久化模型并不总是匹配你在HTTP端点中使用的表示模型。这通常是人们发现DTO的时候,而你似乎也听说过它们。
DTO应该根据端点表示的需求进行适应/创建。如果你不想暴露某些状态,那么在DTO中就不要声明该数据的getter/field。持久化模型应该简单地设计成你需要持久化和查询数据的方式。DTO和实体之间的转换是一个独立的问题,我只能建议你尝试一下 Blaze-Persistence Entity Views
我创建了这个库,以便轻松映射JPA模型和自定义接口或抽象类定义的模型,类似于强化版的Spring Data Projections。这个想法是你可以按照自己喜欢的方式定义目标结构(领域模型),并通过JPQL表达式将属性(getter)映射到实体模型。
使用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 Projections一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<ReservationDto> findAll(Pageable pageable);

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

虽然这很好,但它没有解决主要问题,即如何在将房间附加到预订时将客人列表附加到房间。 - Lauro182
我不确定我完全理解这里的问题。当您拥有DTO模型时,可以根据需要随意更改实体模型。因此,不要深入研究您的域模型,为什么您现在拥有的“ReservationRoom”无法满足您的需求?看起来您已经拥有了所有必要的数据结构,只需要查询所需的数据即可。 - Christian Beikov

0
我认为你需要在持久层和端点之间添加一层。因此,在Spring世界中,你将拥有Controllers/Services/Repositories。你应该使用实体作为Repository的返回类型(因此在Service中也要使用它们),但是向Controllers返回DTO。这样,您就可以解耦它们之间进行的任何修改(例如,您可能不再希望返回存储在实体中的字段,或者您可能希望从其他来源添加更多信息到DTO中)。
在这种特殊情况下,我会创建4个表:Reservations、Guests、Rooms、GuestsForReservation。
  • Guests将包含id + guests数据(姓名、电话号码等)
  • Rooms将包含id + room数据
  • GuestsForReservation将包含id + reservationId + guestId(因此您可以获取每个预订的客人列表)。reservationId和guestId的FK,synthetic id提到的PK。
  • Reservations将包含id(合成的)、房间id、日期从、日期到、可能的主要客人id(如果对您有意义,它可以是支付账单的人)。没有与GuestForReservation表的链接,或者如果需要,您可以有一个GuestForReservation列表。
当您想要预订房间时,您会有一个ReservationRequest对象,它将进入ReservationService,这里您将通过roomId和日期查询ReservationRepository。如果没有返回任何内容,则创建各种实体并将它们保存在ReservationRepository和GuestForReservation repository中。
通过使用服务和各种存储库的组合,您应该能够获得所需的所有信息(每个房间的客人列表,每个日期的客人列表等)。在服务级别上,然后将您需要的数据映射到DTO,并将其传递给控制器(以您需要的格式),甚至传递给其他服务(根据您的需求)。
对于实体和DTO之间的映射,有不同的选项,您可以简单地创建一个名为ReservationMapper的组件(例如),并自己完成(使用实体构建DTO所需的内容);从Springframework实现转换器;使用MapStruct(我认为很繁琐)等。
如果您想在JPA中表示由多个列组成的ID,通常会使用@Embeddable类(在使用它们时应将它们标记为EmbeddedId),您可以在Google上搜索更多信息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接