【问题标题】:Dealing with circular relationships in JPA在 JPA 中处理循环关系
【发布时间】:2021-07-19 09:18:43
【问题描述】:

我正在 JavaEE 中为处理管理房间预订的应用程序开发一个 REST API。 我遇到了以下问题:

我的应用有 3 个实体类:

@Entity
public class Reservation {

    @GeneratedValue
    @Id
    private Integer id;
    @ManyToOne
    @JoinColumn(name="room_id")
    private Room room;
    private LocalDateTime reservationStart;
    private LocalDateTime reservationEnd;
    @ManyToOne
    @JoinColumn(name="user_id")
    private User user;
@Entity
public class Room {
    @GeneratedValue
    @Id
    private Integer id;
    private String description;
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "room")
    private List<Reservation> reservations;
}
@Entity
public class User {
    @Id
    @GeneratedValue
    private Integer id;
    private String firstName;
    private String surname;
    private UserRole role;
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    private List<Reservation> reservations;
}

我想在使用 JPA 的 DAO 中的一个查询中检索属于用户的所有数据: 个人数据、用户的预订以及每次预订的房间详情, 无需创建无限数量的嵌套对象:

{
  "user": {
    "id": "id",
    "firstname": "name",
    "lastname": "lastname",
    "email": "email",
    "Reservations": [
      {
      "id" : "1234",
      "startdate": "S",
      "stopdate": "date",
      "room": {
        "id": "id",
        "description": "somedescription"
      }
    },
    {
      "id" : "2134",
      "startdate": "S",
      "stopdate": "date",
      "room": {
        "id": "id",
        "description": "description"
      }
    }
    ]
  }
}

天真的方法:

    public User read(Integer id) {
        return entityManager.find(User.class, id);
    }

显然返回 LazyInitializationError,Reservations 作为 PersistentBag 返回,而不是 List,因为它们没有被初始化。

    public User read(Integer id) {
        User user = entityManager.find(User.class, id);
        user.getReservations();
        return user;
    }

返回相同。

也是这样:

    public User read(Integer id) {
        Query query = entityManager.createQuery("select u from User u " +
                "join fetch u.reservations r " +
                "where u.id = :id");
        query.setParameter("id", id);
        Object singleResult = query.getSingleResult();
        return (User) singleResult;
    }

切换到 FetchType.Eager 以 RESTEASY008205 结束:JSON Binding 序列化错误 java.lang.StackOverflowError,我认为是因为 yasson 尝试执行无限深度嵌套的 JSON。

我不想在@ManyToOne 关系上使用@JsonbTransient 注释,因为我还希望能够检索,例如,属于一个房间的所有预订,比如按日期过滤,以及在这些预订中谁的信息保留它们,所以我需要这些字段。

我可以通过简单的 SQL 查询(在控制台)检索数据:

select * from User
         LEFT JOIN Reservation as reservation on user_id = reservation.user_id
         LEFT JOIN Room as room on reservation.room_id = room.id
WHERE User_id = 1

但 entityManager.createNativeQuery 返回

NonUniqueDiscoveredSqlAliasException: Encountered a duplicated sql alias [id] during auto-discovery of a native-sql query

JAX-RS 端点被标记为事务性。它有一个直接注入的 DAO 对象,作为回报,它有一个注入的 EntityManager。 我正在使用 JBOSS 20.0.1FINAL、Hibernate 5.4.9、JAVAEE 8.0.1、Java 1.8。

可能这只是 JPQL 中一个很好的查询的问题,但到目前为止我已经花了 2 天多的时间,仍然无法让它工作(但学到了很多关于调试 Hibernate 的知识),所以任何帮助将不胜感激。

【问题讨论】:

    标签: hibernate jpa


    【解决方案1】:

    这通常通过引入 DTO 来解决,我认为这是 Blaze-Persistence Entity Views 的完美用例。

    我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您按照自己喜欢的方式定义目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

    使用 Blaze-Persistence Entity-Views 的用例的 DTO 模型可能如下所示:

    @EntityView(User.class)
    public interface UserDto {
        @IdMapping
        Integer getId();
        String getFirstname();
        String getLastname();
        String getEmail();
        Set<ReservationDto> getReservations();
    
        @EntityView(Reservation.class)
        interface ReservationDto {
            @IdMapping
            Integer getId();
            @Mapping("reservationStart")
            LocalDateTime getStartdate();
            @Mapping("reservationEnd")
            LocalDateTime getEnddate();
            RoomDto getRoom();
        }
        @EntityView(Room.class)
        interface RoomDto {
            @IdMapping
            Integer getId();
            String getDescription();
        }
    }
    

    查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

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

    Spring Data 集成让您可以像使用 Spring Data Projections 一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

    Page<UserDto> findAll(Pageable pageable);
    

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

    【讨论】:

    • 这绝对是有道理的,尽管我选择了手动 DTO,但最终能够让它工作。话虽如此,我将研究一下 Spring Data 或您的库。这绝对应该是自动化的,而不是必须手动处理所有的 getter、setter 和循环槽嵌套集合。
    猜你喜欢
    • 1970-01-01
    • 2015-08-19
    • 1970-01-01
    • 1970-01-01
    • 2018-07-09
    • 1970-01-01
    • 2022-01-18
    • 1970-01-01
    • 2018-11-29
    相关资源
    最近更新 更多