Search code examples
javahibernatejpaormhibernate-mapping

Hibernate @OneToMany association tries to set a null FK value


I have the following simple example. When it runs, I see the QuoteRequest object being generated with auto-generated id. Next, I see the Accidents object being generated, but quote_request_id that's being inserted is null, so I get an error:

Column 'quote_request_id' cannot be null

@Entity
@Table(name = "Quotes")
public class QuoteRequest
{
    public QuoteRequest(){}

    @Id
    @Column(name = "quote_request_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long QuoteRequestId;


    @OneToMany(mappedBy = "quoteRequest", cascade = CascadeType.ALL)
    @OrderColumn(name = "accidents_id")
    private Accidents[] accidents;

    // Getters and setters
}

@Entity
@Table(name = "Accidents")
public class Accidents
{
    public Accidents()
    {
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "accidents_id")
    private long AccidentId;


    @Column(name = "amount", nullable = false)
    private Float amount;

    @ManyToOne(optional = false, cascade = CascadeType.ALL)
    @JoinColumn(name = "quote_request_id", nullable = false)
    private QuoteRequest quoteRequest;

    // Getters and setters
}

Why isn't it inserting the newly generated ID into the accidents column?


Solution

  • When creating Accidents, I don't set the quoteRequest property. Should I be doing anything with that? Do I need to have a bi-directional relationship?

    This is the reason.

    First, "Do I need to have a bi-directional relationship?". Well, you are the only one that can tell. However, you are already having bi-directional relationship. By the way you set that up, Accident side is owning the relationship. That means, for the DB Column Accident.quote_req_id is relying on the field Accident.quoteRequest. As you have not populate that field, Hibernate is assuming that is a null. Hence it is inserting null. All expected behavior.

    In short, you should make sure QuoteRequest.accident and Accident.quoteRequest is consistent, for example:

    class QuoteRequest {
        private List<Accident> accidents = new ArrayList<>();  // I prefer List or Set
    
        public void setAccidents(List<Accident> accidents) {
            this.accidents.clear();
            if (accidents != null) {
                // intentionally keeping another list, to be defensive and not
                // affected by subsequent change in the caller's list
                this.accidents = this.accidents.addAll(accidents);
                for (Accident accident:accidents) {
                    accident.setQuoteRequest(this);
                }
            }
        }
    }
    
    class Accident {
        private QuoteRequest quoteRequest;
        public setQuoteRequest(QuoteRequest quoteRequest) {
            // there will be something more...
            this.quoteRequest = quoteRequest;
        }
    }
    

    This is just a very basic example on what it will look like. In short, you need Accident.quoteRequest set with appropriate value. There are still lots of issue in above implementation and I will leave it for you (e.g. when you previously has accidents set and now replaced with another list)