【问题标题】:java jpa one to one relation is always null for one side?java jpa 一对一关系在一侧总是为空?
【发布时间】:2022-11-10 23:01:08
【问题描述】:

我有两个实体预订和 travelAgentBooking,预订可以单独存在,而 travelAgentBooing 必须有一个预订。

TABookingEntity 如下

@Entity
@ApplicationScoped
@Table(name = "TABooking")
@NamedQuery(name = "TABooking.findAll", query = "SELECT t FROM TABookingEntity t ORDER BY t.id ASC")
public class TABookingEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TABookingId_seq")
    @SequenceGenerator(name = "TABookingId_seq", initialValue = 1, allocationSize = 1)
    private Long id;

    @OneToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "booking_id", nullable = false)
    private BookingEntity flightbooking;

    // belong to upstream booking so we just store id here
    private Long taxibookingid;

    private Long hotelbookingid;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public BookingEntity getFlightbooking() {
        return flightbooking;
    }

    public void setFlightbooking(BookingEntity flightbooking) {
        this.flightbooking = flightbooking;
        if (flightbooking != null) {
            flightbooking.setTravelAgentBooking(this);
        }
    }

    public Long getTaxibookingId() {
        return taxibookingid;
    }

    public void setTaxibookingId(Long taxibookingid) {
        this.taxibookingid = taxibookingid;
    }

    public Long getHotelbookingId() {
        return hotelbookingid;
    }

    public void setHotelbookingId(Long hotelbookingid) {
        this.hotelbookingid = hotelbookingid;
    }

BookingEntity 在下面

@Entity
@ApplicationScoped
@Table(name = "booking")
@NamedQueries({ @NamedQuery(name = "Booking.findAll", query = "SELECT b FROM BookingEntity b ORDER BY b.d ASC"),
        @NamedQuery(name = "Booking.findByFlight", query = "SELECT b FROM BookingEntity b WHERE b.flight = :flight"),
        @NamedQuery(name = "Booking.findByDate", query = "SELECT b FROM BookingEntity b WHERE b.d = :d") })
public class BookingEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bookingId_seq")
    @SequenceGenerator(name = "bookingId_seq", initialValue = 1, allocationSize = 1)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id", nullable = false)
    private CustomerEntity customer;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "flight_id", nullable = false)
    private FlightEntity flight;
    
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "travelAgentBooking_id", nullable = true)
    private TABookingEntity travelAgentBooking;

    @NotNull
    @Column(name = "date")
    private Date d;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public CustomerEntity getCustomer() {
        return customer;
    }

    public void setCustomer(CustomerEntity customer) {
        this.customer = customer;
        if(customer != null)
            customer.addBooking(this);
    }

    public FlightEntity getFlight() {
        return flight;
    }

    public void setFlight(FlightEntity flight) {
        this.flight = flight;
    }

    public Date getDate() {
        return new Date(d.getTime());
    }

    public void setDate(Date d) {
        this.d = d;
    }
    
    public TABookingEntity getTravelAgentBooking() {
        return travelAgentBooking;
    }

    public void setTravelAgentBooking(TABookingEntity travelAgentBooking) {
        this.travelAgentBooking = travelAgentBooking;
    }

这是我首先创建预订的代码,然后将其设置为预订。

然后我正在尝试更新预订,因为它在创建时没有 travelAgentBooking 可以关联。

Booking booking = flightService.createBooking(tabooking.getFlightbooking());
tabooking.setFlightbooking(booking);

,,,,,,,,,,,
,,,,,,,,,,,

tabookingService.create(tabooking);
flightService.updateBooking(tabooking.getFlightbooking().getId(), tabooking.getFlightbooking());

运行它之后,travelAgentBooking 的表就完美了。 但是对于任何预订对象,引用 travelAgentBooking 的预订表列始终为空。

更新:

@PUT
    @Path("/{id:[0-9]+}")
    @Operation(description = "Update a Booking in the database")
    @APIResponses(value = { @APIResponse(responseCode = "200", description = "Booking updated successfully"),
            @APIResponse(responseCode = "400", description = "Invalid Booking supplied in request body"),
            @APIResponse(responseCode = "404", description = "Booking with id not found"),
            @APIResponse(responseCode = "409", description = "Booking details supplied in request body conflict with another existing Booking"),
            @APIResponse(responseCode = "500", description = "An unexpected error occurred whilst processing the request") })
    @Transactional
    public Response updateBooking(
            @Parameter(description = "Id of Booking to be updated", required = true) @Schema(minimum = "0") @PathParam("id") Integer id,
            @Parameter(description = "JSON representation of Booking object to be updated in the database", required = true) Booking booking) {
    Customer customer = customerService.findById(booking.getCustomer().getId())
            .orElseThrow(() -> new RestServiceException("We can't found customer", Response.Status.BAD_REQUEST));

    if (!customer.equals(booking.getCustomer()))
        throw new RestServiceException("use custoemr's own API for it update", Response.Status.BAD_REQUEST);

    Flight flight = flightService.findById(booking.getFlight().getId())
            .orElseThrow(() -> new RestServiceException("We can't found flight", Response.Status.BAD_REQUEST));

    if (!flight.equals(booking.getFlight()))
        throw new RestServiceException("use custoemr's own API for it update", Response.Status.BAD_REQUEST);

    try {
        bookingService.validateBooking(booking);
    } catch (ConstraintViolationException ce) {
        // Handle bean validation issues
        Map<String, String> responseObj = new HashMap<>();

        for (ConstraintViolation<?> violation : ce.getConstraintViolations()) {
            responseObj.put(violation.getPropertyPath().toString(), violation.getMessage());
        }
        throw new RestServiceException("Bad Request", responseObj, Response.Status.BAD_REQUEST, ce);
    } catch (UniqueFlightWithDateException e) {
        // we are updating an existence flight, so ignore this as expected
    }

    try {
        bookingService.update(id);
    } catch (ServiceException e) {
        Map<String, String> responseObj = new HashMap<>();
        responseObj.put("id", "please ensure the id is associated with this number");
        throw new RestServiceException("Bad Request", responseObj, Response.Status.NOT_FOUND, e);
    }
    bookingService.update(id);
    return Response.ok(booking).build();
}

BookingEntity update(BookingEntity booking) {
    log.info("BookingRepository.update() - Updating " + booking.getId());

    em.merge(booking);
    return booking;
}

【问题讨论】:

  • 您是否考虑过在booking 实体上设置travelAgentBooking。尽管在两个方向上都对oneToOne 关系进行了建模,但它不会自动完成。
  • @PierreDemeestere 我想我已经在 travelAgentBooking 实体初始化中做到了这一点
  • 没错,我错过了。你的交易是如何组织的?周围有什么服务方法?如果是这样,tabooking.setFlightbooking(booking); 将不会被提交。顺便说一句,此代码行上方的机制是什么有助于entityManager 刷新和事务提交?
  • @PierreDemeestere tabookingService.create(tabooking);将执行使 travelAgentBooking 正确的提交。但是 flightService.updateBooking 没有效果,reference 列中的 booking 全部为空。
  • 你能显示updateBooking的代码吗?

标签: java jpa


【解决方案1】:

从原始发布的代码来看,问题在于您有两个非常独立的单向关系并且只设置其中一个。由于它们是独立的,因此另一个保持为 null 并且在设置之前不能为 null 以外的任何内容。

@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "booking_id", nullable = false)
private BookingEntity flightbooking;

连接列在“TABooking”表中设置一个外键以指向 bookingEntity。它要求设置此关系引用以填充该外键值。同样的事情:

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "travelAgentBooking_id", nullable = true)
private TABookingEntity travelAgentBooking

它在“booking”表中创建自己的 travelAgentBooking_id 外键列,在您更新预订实例并设置此引用之前,该列将保持为空。如果只设置一侧,另一侧将始终在数据库中保持为空。

但是模型和您的期望存在两个问题。首先,从 cmets 来看,您并不打算将其作为两个独立的关系,而是希望它是一个单一的双向关系。为此,您需要一个外键,并选择“拥有”它的一方。拥有它的一方控制它:

@OneToOne(mappedBy "flightbooking", fetch = FetchType.LAZY)
private TABookingEntity travelAgentBooking

使用 mappedBy 告诉 JPA 另一方拥有该关系。只有当您设置 TABookingEntity.flightbooking 引用并保存/合并 TABookingEntity 实例时,才会设置外键列。

其次是您使用 JSON 等 Json 序列化并假设它遵守您的对象模型和 JPA 映射。它不是。 JPA 注释是为了让您的持久性提供者告诉它如何将您的模型序列化/反序列化到数据库中,但对于 JSON 序列化(或 xml 或任何其他 REST 格式)没有任何意义。您需要告诉您的 JSON 工具如何处理您的关系,这完全取决于您将如何期待和发送 JSON。有许多教程和不同的策略可以解决这个问题(请参阅此link 以获得良好的入门知识),但最简单的方法通常是选择图表的一部分并使用@JsonIgnore 排除它们:

@OneToOne(mappedBy "flightbooking", fetch = FetchType.LAZY)
@JsonIgnore
private TABookingEntity travelAgentBooking

这意味着您收到的代表预订的任何 JSON 都将具有 null tr​​avelAgentBooking。因此,如果您需要查看或设置这种关系,您的 api 将不得不发送/接收 TABookingEntity,它仍然会序列化航班预订参考。我选择这种方式是因为 flightbooking 拥有这种关系,所以它与 JPA 匹配,但它不需要。您可以而且应该弄清楚什么适用于您的客户端应用程序,它可能与 JPA 映射不同。我希望预订总是需要知道 TABookingEntity 并且您希望将其发送给客户端,因此您可以将 @JsonIgnore 注释放在另一边。如果这样做,您只需要确保当您想要更改或添加 TABookingEntity 时,您适当地修复了 TABookingEntity.flightbooking 引用,这样您就不会取消外键。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-21
    • 1970-01-01
    • 1970-01-01
    • 2012-08-19
    • 1970-01-01
    相关资源
    最近更新 更多