简体   繁体   English

Spring 启动 Rest API, Z9CE3D1BD8890F16A0C448093595087CZ 是什么方法?

[英]Spring Boot Rest API, JPA Entities, DTOs, what is the best approach?

I was given this assignment, just for practice, it became very long and challenging, but it has taught me a lot, on lambdas and JPA mainly.我接到了这个任务,只是为了练习,它变得非常漫长和具有挑战性,但它教会了我很多东西,主要是关于 lambdas 和 JPA。

It is a basic Rest API, which is used to create Hotels, Rooms, Guests, Reservations, types of guests, types of rooms, etc.它是一个基本的 Rest API,用于创建酒店、房间、客人、预订、客人类型、房间类型等。

My initial problem was learning about JPA relations, OneToOne, OneToMany, etc., unidirectional, bidirectional, and what not.我最初的问题是学习 JPA 关系、OneToOne、OneToMany 等,单向、双向等等。

I'm also using PostgreSQL, using "sping.jpa.hibernate.ddl-auto=create-drop(or update)" , change as needed, when I want to recreate the DB for whatever reason.我也在使用 PostgreSQL,使用"sping.jpa.hibernate.ddl-auto=create-drop(or update)" ,根据需要更改。

So I'm very happy and excited using my new @Annotations to relate my Entities, and fetch back lists of whatever information I needed, came across multiple problems, read many many questions here, solved my problems, but now I have come across a new problem, but then, started questioning my approach, maybe I should not leave everything to JPA.所以我很高兴和兴奋使用我的新@Annotations 来关联我的实体,并获取我需要的任何信息的列表,遇到了多个问题,在这里阅读了很多问题,解决了我的问题,但现在我遇到了新问题,但随后,开始质疑我的方法,也许我不应该把一切都留给 JPA。

Let me show you what I mean.让我告诉你我的意思。 I'm going to keep my classes short to show only relevant information.我将保持我的课程简短,只显示相关信息。

I have my reservation entity.我有我的预订实体。

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

This is my Room entity.这是我的房间实体。

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

And this is my Guest entity.这是我的来宾实体。


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

At the beginning a reservation could only have 1 room, but the requirement changed and it can have multiple rooms now.一开始只能订1间房,后来需求变了,现在可以订多间房了。 So now, the guest list needs to be linked to the room linked to the reservation, and not directly to the reservation.所以现在,客人名单需要链接到与预订相关的房间,而不是直接链接到预订。 (I know I have a Guest and a Room, and also the List of both, this is because I'm using the single Guest as the name for the reservation, and the single Room, as the "Main" room, but don't mind that please). (我知道我有一个客人和一个房间,还有两者的列表,这是因为我使用单身客人作为预订的名称,而单人房作为“主要”房间,但不要请不要介意)。

Letting JPA aside, because every challenge I have faced I would ask my self "how to do it JPAish?", and then research how to do it with JPA (that's how I learned about the @ManyToMany, etc. annotations).把 JPA 放在一边,因为我面临的每一个挑战我都会问自己“JPAish 怎么做?”,然后研究如何用 JPA 来做(这就是我了解@ManyToMany 等注释的方法)。

What I would do is just create a new table, to relate the reservations to the room (which is already done in my entities with JPA), and then add also de guest id.我要做的只是创建一个新表,将预订与房间相关联(这已经在我的实体中使用 JPA 完成),然后还添加 de guest id。

So, this new table, would have a PK with reservation_id, room_id and guest_id.因此,这个新表将有一个带有 reservation_id、room_id 和 guest_id 的 PK。 Very easy, then create my Reservation model, which have a List of Room, and this Room model, would have a List of Guest.很简单,然后创建我的 Reservation model,它有一个房间列表,而这个房间 model 将有一个客人列表。 Easy.简单的。

But I don't want to add a List of Guest in my current Room entity, because I have an endpoint and maybe a couple of other functions, which retrieves my Room entity, and I don't want to add a List of Guest, because as the time passes, this list would grow bigger and bigger, and it is information you don't need to be passing around.但我不想在我当前的 Room 实体中添加访客列表,因为我有一个端点,可能还有几个其他函数可以检索我的 Room 实体,而且我不想添加访客列表,因为随着时间的推移,这个列表会越来越大,而且它是你不需要传递的信息。

So I did some research and found that I can extend my entity with @Inheritance or @MappedSuperclass, and I could create maybe a Reservation_Room model, which includes a List of Guest and add a List of Reservation_Room instead of a List of Room in my Reservation Entity, which I really wouldn't know if it is even possible.所以我做了一些研究,发现我可以用@Inheritance 或@MappedSuperclass 扩展我的实体,我可以创建一个Reservation_Room model,其中包括一个客人列表并在我的预订中添加一个Reservation_Room 列表而不是房间列表实体,我真的不知道它是否可能。

Having said that, and before I keep researching and start making modifications to my code, it got me wondering, if this would be the right approach?话虽如此,在我继续研究并开始修改我的代码之前,我想知道这是否是正确的方法? Or if I'm forcing JPA too much on this?或者,如果我在这方面强迫 JPA 太多? What would be the best approach for this?最好的方法是什么? Can a 3 id relation table be easily implemented/mapped on JPA?可以在 JPA 上轻松实现/映射 3 id 关系表吗?

The main goal would be to have my Room entity exposed as it is, but when a Room is added to a Reservation, this Room would also have a List of Guest.主要目标是让我的房间实体按原样公开,但是当房间被添加到预订时,这个房间也会有一个客人列表。 Can I do this JPAish?我可以做这个 JPAish 吗? Or should I create a new model and fill with the information as needed?或者我应该创建一个新的 model 并根据需要填写信息? This wouldn't exempt me from creating my 3 ids table.这不会免除我创建我的 3 ids 表。

Based on what you wrote here, I think you might be at a point where you are realizing that the persistence model doesn't always match the presentation model, which you use in your HTTP endpoints.根据您在此处所写的内容,我认为您可能已经意识到持久性 model 并不总是与您在 Z293C9EA246FF9985DC6F62A650F78986 端点中使用的演示 model 匹配。 This is usually the point where people discover DTOs, which you also seem to have heard of.这通常是人们发现 DTO 的地方,您似乎也听说过。

DTOs should be adapted/created to the needs of the representation of an endpoint. DTO 应根据端点表示的需要进行调整/创建。 If you don't want to expose certain state, then simply don't declare a getter/field for that data in a DTO.如果您不想公开某些 state,那么就不要在 DTO 中为该数据声明 getter/field。 The persistence model should simply be designed in a way, so that you can persist and query data the way you need it.持久化 model 应该简单地设计成某种方式,以便您可以按需要的方式持久化和查询数据。 Translation between DTOs and entities is a separate thing, for which I can only recommend you to giveBlaze-Persistence Entity Views a try. DTO 和实体之间的转换是另一回事,对此我只能建议您尝试Blaze-Persistence Entity Views

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids.我创建了该库以允许在 JPA 模型和自定义接口或抽象 class 定义的模型之间轻松映射,例如 Spring 类固醇上的数据投影。 The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.这个想法是您以您喜欢的方式定义您的目标结构(域模型),并通过 JPQL 表达式将 map 属性(吸气剂)定义为实体 model。

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:使用 Blaze-Persistence Entity-Views 的 DTO model 可能如下所示:

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

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

ReservationDto a = entityViewManager.find(entityManager, ReservationDto.class, id);

The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features Spring 数据集成允许您几乎像 Spring 数据投影一样使用它: https://persistence.blazebit.com/documentation/entity-view/manual-html/

Page<ReservationDto> findAll(Pageable pageable);

The best part is, it will only fetch the state that is actually necessary!最好的部分是,它只会获取实际需要的 state!

I would say that you need to add a layer between persistence and the endpoints.我会说您需要在持久性和端点之间添加一个层。 So, you will have Controllers/Services/Repositories (in the Spring world).因此,您将拥有控制器/服务/存储库(在 Spring 世界中)。 You should use entities as return type from Repositories (so used them in Services as well), but return DTOs to Controllers.您应该使用实体作为存储库的返回类型(因此也在服务中使用它们),但将 DTO 返回给控制器。 In this way, you will decouple any modification that you do between them (eg you may lose interest to return a field stored in an entity, or you may want to add more information to the dto from other sources).通过这种方式,您将解耦您在它们之间所做的任何修改(例如,您可能对返回存储在实体中的字段失去兴趣,或者您可能希望从其他来源向 dto 添加更多信息)。

In this particular case, I would create 4 tables: Reservations, Guests, Rooms, GuestsForReservation.在这种特殊情况下,我将创建 4 个表:Reservations、Guests、Rooms、GuestsForReservation。

  • Guests will contain id + guests data (name, phone number, etc)客人将包含 id +客人数据(姓名、电话号码等)
  • Rooms will contain id + room data房间将包含 id + 房间数据
  • GuestsForReservation will contain id + reservationId + guestId (so you can get the list of guests for each reservation). GuestForReservation 将包含 id + reservationId + guestId(因此您可以获得每个预订的客人列表)。 FK for reservationId and guestId, PK for synthetic id mentioned. FK 用于 reservationId 和 guestId,PK 用于提到的合成 id。
  • Reservations will contain id (synthetic), room id, date from, date to, potentially main guest id (it could be the person paying the bill, if it makes sense for you).预订将包含 id(合成)、房间 id、日期从、日期到,可能是主要客人 id(如果对您有意义,它可能是支付账单的人)。 No link to the GuestForReservation table, or you can have a list of GuestForReservation if you need to.没有指向 GuestForReservation 表的链接,或者如果需要,您可以获取 GuestForReservation 列表。

When you want to reserve a room, you have a ReservationRequest object, which will go to the ReservationService, here you are going to query the ReservationRepository by roomId and dates.当您要预订房间时,您有一个 ReservationRequest object,它将 go 到 ReservationService,这里您将通过 roomId 和日期查询 ReservationRepository。 If nothing is returned, you create the various entities and persist them in ReservationRepository and GuestForReservation repository.如果没有返回任何内容,则创建各种实体并将它们保存在 ReservationRepository 和 GuestForReservation 存储库中。

By using the service and the combination of various repositories, you should be able to get all the information that you need (list of guests per room, list of guests per date, etc).通过使用该服务和各种存储库的组合,您应该能够获得所需的所有信息(每个房间的客人列表、每个日期的客人列表等)。 At the service level, you then map the data you need to a DTO and pass it to the controller (in the format that you need), or even to other services (depending on your needs).在服务级别,您然后 map 将您需要的数据传递给 DTO 并将其传递给 controller(以您需要的格式),甚至传递给其他服务(取决于您的需要)。

For what concern the mapping between entities and DTOs, there are different options, you could simply create a Component called ReservationMapper (for example) and do it yourself (take an entity and build a DTO with what you need);对于实体和 DTO 之间的映射,有不同的选择,您可以简单地创建一个名为 ReservationMapper 的组件(例如)并自己做(获取一个实体并根据您的需要构建一个 DTO); implements Converter from the Springframework;从 Springframework 实现 Converter; use MapStruct (cumbersome in my opinion);使用 MapStruct(在我看来很麻烦); etc.等等

If you want to represent in JPA an id made of multiple columns, usually @Embeddable classes are used (you should mark them as EmbeddedId when you use them), you can google them for more info.如果你想在 JPA 中表示一个由多列组成的 id,通常使用@Embeddable类(使用它们时应该将它们标记为EmbeddedId ),你可以谷歌它们以获取更多信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM