Search code examples
spring-boothibernatejpa

JPA bi-directional @OneToMany not populating Foreign Key on reference table


I have two tables "Action" and "Action_Function" where later has Composite Primary Key (Action_id & Sequence_nr). But, when I execute repository.save(action) the value Action_id column on Action_Function table insert as null.

I am using Spring Boot 3 with Spring Data JPA(Hibernate).

Action

Action_id (pk)

Action_Function

Action_id (pk) Sequence_nr (pk) <-- Sequence_nr comes from UI(DTO)

I am using @IdClass for the convenience of MapStruct DTO to entity mapping, hence not using @Embeddable.

@Entity
@Table(name = "ACTION")
public class Action {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "Action_id")
    private Integer actionId;

    @OneToMany(mappedBy = "action", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<ActionFunction> actionFunctions = new HashSet<>();

    // Other fields
}
@Entity
@Table(name = "ACTION_FUNCTION")
@IdClass(ActionFunctionId.class)
public class ActionFunction {

    @Id
    @Column(name = "Action_id")
    private Integer actionId;

    @Id
    @Column(name = "Sequence_nr")
    private Integer sequenceNr;

    // here, added insertable and updatable false otherwise JPA tries to add two Action_id 
    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, optional = false)
    @JoinColumn(name = "Action_id")
    @MapsId("actionId")
    private Action action;

    // other fields
}
public class ActionFunctionId implements Serializable {

    @Serial
    private static final long serialVersionUID = -4601022716234244483L;

    private Integer actionId;

    private Integer sequenceNr;

    // Equals & hashCode
}

Here is how I am populating entities, a simple representation of my current code where they get populated using MapStruct.

@Service
public class ActionServiceImpl {

        public void saveAction(){
            ActionFunction actionFunction = new ActionFunction();
            actionFunction.setSequenceNr(1);

            ActionFunction actionFunction2 = new ActionFunction();
            actionFunction2.setSequenceNr(2);

            Action action = new Action();
            action.setActionFunctions(Set.of(actionFunction, actionFunction2));
            Action actionSaved = actionRepository.save(action);
        }
    }

ActionRepository is simple Spring Data Jpa Repository. We are following DDD and these two entities are part of same Aggregate, hence we can have one Repository with root aggregate Action.

@Repository
 public interface ActionRepository extends JpaRepository<Action, Integer> {
        
        }

I really appreciate some advice how to fix this.


Solution

  • JPA added options to allow deriving the ID in a child entity from the parent. To have your child use the parent's ID as part of its compound ID, you just need to mark the relationship with the MapsId annotation:

    @Entity
    @Table(name = "ACTION_FUNCTION")
    @IdClass(ActionFunctionId.class)
    public class ActionFunction {
    
        @Id
        @Column(name = "Action_id")
        private Integer actionId;
    
        @Id
        @Column(name = "Sequence_nr")
        private Integer sequenceNr;
    
        @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, optional = false)
        //This will have JPA set the actionId property using the action.id value
        @MapsId("actionId")
        private Action action;
    
        // other fields
    }