【问题标题】:Spring-Data-JPA - Infinite recursion after inserting one record to databaseSpring-Data-JPA - 将一条记录插入数据库后无限递归
【发布时间】:2019-08-13 01:24:19
【问题描述】:

对控制器的获取、发布和更新 HTTP 请求第一次运行良好(当相应的数据库表为空时)但在向数据库插入一条记录后,所有请求都进入无限递归

这是我的架构:

问题在于 TripRequestController 处理架构左上角的 Request

我认为问题来自Request_modification 表,就在Request 表的下方

以下是Request实体

package com.eventumsolutions.estrips.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

import javax.persistence.*;
import java.sql.Date;
import java.sql.Time;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name="request")
public class TripRequest {


    public TripRequest() {

    }

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="Req_id")
    private Long id;

    @Column(name="Start_date")
    private Date startDate;

    @Column(name="End_date")
    private Date endDate;

    @Column(name="Destination")
    private String destination;

    @Column(name="Two_way")
    private Boolean twoWay;

    @Column(name="Expected_duration")
    private Integer expectedDuration;

    @Column(name="Arrival_time")
    private Time arrivalTime;

    @Column(name="Suggested_pickup_time")
    private Time suggestedPickupTime;

    @Column(name="Seats")
    private Integer seats;

    // don't apply cascading deletes
    @ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE,
            CascadeType.DETACH, CascadeType.REFRESH},
            fetch = FetchType.EAGER)
    @JoinColumn(name="Modified_by")
    private Employee modifiedBy;

    @ManyToMany(fetch = FetchType.EAGER)
    @Fetch(value = FetchMode.SUBSELECT)
    @JoinTable(
            name ="request_has_employee",
            joinColumns=@JoinColumn(name="Req_id"),
            inverseJoinColumns = @JoinColumn(name="Emp_id")
    )
    @JsonIgnore
    private List<Employee> employees;

    @OneToOne(mappedBy = "id.request",
            cascade = CascadeType.ALL)
    @JsonIgnore
    private RequestProcess requestProcess;

    @OneToOne(mappedBy = "id.parentRequest",
            cascade = CascadeType.ALL)
    @JsonIgnore
    private RequestModification requestModificationAsParent;

    @OneToOne(mappedBy = "id.childRequest",
            cascade = CascadeType.ALL)
    @JsonIgnore
    private RequestModification requestModificationAsChild;

    public void addEmployee(Employee employee) {
        if (employees == null) {
            employees = new ArrayList<>();
        }
        employees.add(employee);
    }

    public Long getId() {
        return id;
    }

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

    public Date getStartDate() {
        return startDate;
    }

    public void setStartDate(Date startDate) {
        this.startDate = startDate;
    }

    public Date getEndDate() {
        return endDate;
    }

    public void setEndDate(Date endDate) {
        this.endDate = endDate;
    }

    public String getDestination() {
        return destination;
    }

    public void setDestination(String destination) {
        this.destination = destination;
    }

    public Boolean getTwoWay() {
        return twoWay;
    }

    public void setTwoWay(Boolean twoWay) {
        this.twoWay = twoWay;
    }

    public Integer getExpectedDuration() {
        return expectedDuration;
    }

    public void setExpectedDuration(Integer expectedDuration) {
        this.expectedDuration = expectedDuration;
    }

    public Time getArrivalTime() {
        return arrivalTime;
    }

    public void setArrivalTime(Time arrivalTime) {
        this.arrivalTime = arrivalTime;
    }

    public Time getSuggestedPickupTime() {
        return suggestedPickupTime;
    }

    public void setSuggestedPickupTime(Time suggestedPickupTime) {
        this.suggestedPickupTime = suggestedPickupTime;
    }

    public Integer getSeats() {
        return seats;
    }

    public void setSeats(Integer seats) {
        this.seats = seats;
    }

    public Employee getModifiedBy() {
        return modifiedBy;
    }

    public void setModifiedBy(Employee modifiedBy) {
        this.modifiedBy = modifiedBy;
    }

    @JsonIgnore
    public List<Employee> getEmployees() {
        return employees;
    }

    @JsonIgnore
    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }

    @JsonIgnore
    public RequestProcess getRequestProcess() {
        return requestProcess;
    }

    @JsonIgnore
    public void setRequestProcess(RequestProcess requestProcess) {
        this.requestProcess = requestProcess;
    }

    @JsonIgnore
    public RequestModification getRequestModificationAsParent() {
        return requestModificationAsParent;
    }

    @JsonIgnore
    public void setRequestModificationAsParent(RequestModification requestModificationAsParent) {
        this.requestModificationAsParent = requestModificationAsParent;
    }

    @JsonIgnore
    public RequestModification getRequestModificationAsChild() {
        return requestModificationAsChild;
    }

    @JsonIgnore
    public void setRequestModificationAsChild(RequestModification requestModificationAsChild) {
        this.requestModificationAsChild = requestModificationAsChild;
    }

    @Override
    public String toString() {
        System.out.println("request hnaaaaaaaaaaaaaaaaa");
        return "TripRequest{" +
                "id=" + id +
                ", startDate=" + startDate +
                ", endDate=" + endDate +
                ", destination='" + destination + '\'' +
                ", twoWay=" + twoWay +
                ", expectedDuration=" + expectedDuration +
                ", arrivalTime=" + arrivalTime +
                ", suggestedPickupTime=" + suggestedPickupTime +
                ", seats=" + seats +
                ", modifiedBy=" + modifiedBy +
//              ", employees=" + employees +
                '}';
    }
}






以下是RequestModification实体

package com.eventumsolutions.estrips.entity;

import org.apache.coyote.Request;

import javax.persistence.*;

@Entity
@Table(name="request_modification")
public class RequestModification {

   public RequestModification(){

   }

   @EmbeddedId
   private RequestModificationKey id;

   public RequestModificationKey getId() {
       return id;
   }

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

}

这是RequestModificationKey

package com.eventumsolutions.estrips.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;

@Embeddable
public class RequestModificationKey  implements Serializable {

   public RequestModificationKey(){

   }

   @OneToOne
   @JoinColumn(name="Parent_req_id", referencedColumnName = "Req_id")
   private TripRequest parentRequest;

   @OneToOne
   @JoinColumn(name="Child_req_id", referencedColumnName = "Req_id")
   private TripRequest childRequest;

   public TripRequest getParentRequest() {
       return parentRequest;
   }

   public void setParentRequest(TripRequest parentRequest) {
       this.parentRequest = parentRequest;
   }

   public TripRequest getChildRequest() {
       return childRequest;
   }

   public void setChildRequest(TripRequest childRequest) {
       this.childRequest = childRequest;
   }

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       RequestModificationKey that = (RequestModificationKey) o;
       return Objects.equals(parentRequest, that.parentRequest) &&
               Objects.equals(childRequest, that.childRequest);
   }

   @Override
   public int hashCode() {
       return Objects.hash(parentRequest, childRequest);
   }

   @Override
   public String toString() {
       System.out.println("mod hnaaaaaaaaaaaaaaaaa");
       return "RequestModificationKey{" +
               "parentRequest=" + parentRequest +
               ", childRequest=" + childRequest +
               '}';
   }
}

最后,这是我的控制器:

package com.eventumsolutions.estrips.controller;

import com.eventumsolutions.estrips.entity.TripRequest;
import com.eventumsolutions.estrips.entity.Employee;
import com.eventumsolutions.estrips.exception.ResourceNotFoundException;
import com.eventumsolutions.estrips.repository.TripRequestRepository;
import com.eventumsolutions.estrips.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.Optional;

@RestController
@RequestMapping(path="/api")
@CrossOrigin
public class RequestController {

    @Autowired
    private TripRequestRepository tripRequestRepository;

    @Autowired
    private EmployeeRepository employeeRepository;

    @GetMapping("/tripRequests")
    public Page<TripRequest> getTripRequests(Pageable pageable) {
        return tripRequestRepository.findAll(pageable);
    }

    @PostMapping("/tripRequests")
    public TripRequest createTripRequest(@Valid @RequestBody TripRequest tripRequest) {
        Long empId = 1L;
        return employeeRepository.findById(empId)
                .map(employee -> {
                    tripRequest.setModifiedBy(employee);
                    return tripRequestRepository.save(tripRequest);
                }).orElseThrow(() -> new ResourceNotFoundException("Employee not found with id " + empId));
    }

    @PutMapping("/tripRequests/{tripRequestId}")
    public TripRequest updateTripRequest(@PathVariable Long tripRequestId,
                              @Valid @RequestBody TripRequest tripRequestRequest) {
        Long empId = 1L;
        if(!employeeRepository.existsById(empId)) {
            throw new ResourceNotFoundException("Employee not found with id " + empId);
        }
        Optional<Employee> modifier = employeeRepository.findById(empId);
        tripRequestRequest.setModifiedBy(modifier.get());
        return tripRequestRepository.findById(tripRequestId)
                .map(tripRequest -> {
                    //[TODO]: employee cascading
                    tripRequest.setStartDate(tripRequestRequest.getStartDate());
                    tripRequest.setEndDate(tripRequestRequest.getEndDate());
                    tripRequest.setDestination(tripRequestRequest.getDestination());
                    tripRequest.setTwoWay(tripRequestRequest.getTwoWay());
                    tripRequest.setExpectedDuration(tripRequestRequest.getExpectedDuration());
                    tripRequest.setArrivalTime(tripRequestRequest.getArrivalTime());
                    tripRequest.setSuggestedPickupTime(tripRequestRequest.getSuggestedPickupTime());
                    tripRequest.setSeats(tripRequestRequest.getSeats());
                    tripRequest.setModifiedBy(tripRequestRequest.getModifiedBy());
                    return tripRequestRepository.save(tripRequest);
                }).orElseThrow(() -> new ResourceNotFoundException("TripRequest not found with id " + tripRequestId));
    }

    @DeleteMapping("/tripRequests/{tripRequestId}")
    public ResponseEntity<?> deleteTripRequest(@PathVariable Long tripRequestId) {
        return tripRequestRepository.findById(tripRequestId)
                .map(tripRequest -> {
                    tripRequestRepository.delete(tripRequest);
                    return ResponseEntity.ok().build();
                }).orElseThrow(() -> new ResourceNotFoundException("TripRequest not found with id " + tripRequestId));
    }
}

处理Request 表的所有请求在它为空时都可以正常工作, 第一个 post 请求也可以正常工作,但是当 Request 表包含一条记录时,任何 HTTP 请求都会导致无限递归,并显示以下错误消息

There was an unexpected error (type=Internal Server Error, status=500).
No message available
java.lang.StackOverflowError
    at org.hibernate.loader.plan.build.internal.returns.AbstractExpandingFetchSource.getBidirectionalEntityReferences(AbstractExpandingFetchSource.java:90)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:151)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:141)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:155)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:141)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:155)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:141)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:155)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:141)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:155)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:141)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:155)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:141)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:155)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:141)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:155)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:141)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:155)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:141)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:155)
    at org.hibernate.loader.plan.exec.process.internal.AbstractRowReader.resolveEntityKey(AbstractRowReader.java:141)
...

【问题讨论】:

    标签: java spring spring-mvc spring-data-jpa spring-data


    【解决方案1】:

    我建议您首先为所有实体实施 equals 和 hashCode。我见过由默认 equals 和 hashCode 方法引起的类似错误。

    Refer to this article for useful information.

    【讨论】:

    • 感谢回复,我什至通过将一些急切的关联更改为懒惰来解决它
    猜你喜欢
    • 2019-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多