具有父复合 pk 的 JPA OneToMany 是子主键派生实体问题的一部分

Posted

技术标签:

【中文标题】具有父复合 pk 的 JPA OneToMany 是子主键派生实体问题的一部分【英文标题】:JPA OneToMany with Parent composite pk is part of child primary key Derived Entity issue 【发布时间】:2021-11-09 00:03:09 【问题描述】:

一对多与父复合主键是子主键问题的一部分。使用以下代码 sn-ps 引发异常

示例 JSON 嵌入数据如下:

"pt_reg_no": "1000", //序列号生成 "game_year": "G12021", “名字”:“我的名字”, “事件详情”:[ "major_event_code": "A", "sub_event_code": "A7", “category_code”:“MO” , "major_event_code": "B", "sub_event_code": "B7", “category_code”:“WO” ]

参与者可以注册多个活动: 参与者(复合键) - pt_reg_no, game_year EventDetails(复合键) - pt_reg_no、game_year、sub_event_code、category_code

//Parent Class IDclass
public class ParticipantKey implements Serializable 

    private Long pt_reg_no;
    private String game_year;


@IdClass(ParticipantKey.class)
public class ParticipantModel  

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "participantseq"
    )
    @SequenceGenerator(
        name = "participantseq",
        allocationSize = 1
    )
    private Long pt_reg_no;
    
    @Id
    private String game_year = 'G12021';
    
    
    @OneToMany(mappedBy = "participantModel", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<EventDetailsModel> eventDetails = new HashSet<>(); 

    public Set<EventDetailsModel> getEventDetails() 
        return eventDetails;
       
    public void setEventDetails(Set<EventDetailsModel> eventDetails) 
        this.eventDetails = eventDetails;

        for (EventDetailsModel b : eventDetails) 
            b.setParticipantModel(this);
        
    
    

    
//Child class IDclass    
public class ParticipantEventDetailsId implements Serializable 
        private ParticipantKey participantModel;
        private String sub_event_code;
        private String category_code;
    
    
public class EventDetailsModel  
      @Id    
      @Column(name = "sub_event_code")
      private String sub_event_code;
        
      @Id
      @Column(name = "category_code")
      private String category_code;
        
      @Id
      @ManyToOne(cascade = CascadeType.ALL)
      @JoinColumns(
         @JoinColumn(name = "pt_reg_no", referencedColumnName = "pt_reg_no"),
          @JoinColumn(name = "game_year", referencedColumnName = "game_year")
      )
      private ParticipantModel participantModel;


    public ParticipantModel getParticipantModel() 
        return participantModel;
    
    public void setParticipantModel(ParticipantModel participantModel) 
        this.participantModel = participantModel;
    
    
    
//---------------------------------------------------
//JSON input data received into ParticipantModel from @requestBody
public class FormDataObjects 
    private ParticipantModel formData;

    @JsonCreator
    public FormDataObjects(ParticipantModel formData) 
        this.formData = formData;
    


//Controller Entry point
@RequestMapping(path = "/participantData", method = RequestMethod.POST)
    @Transactional
    public ResponseEntity<?> participantRegistration(@Valid @RequestBody FormDataObjects values) 
        ParticipantModel participant = values.getFormData();
        participant.setGame_year(new SimpleDateFormat("yyyy").format(new Date()) + GenricData.game);
     
        ParticipantModel person = participantRepository.save(participant);
        
                if (person == null) 
            return ResponseEntity.ok("Participant is not inserted");
         else 
            Set<EventDetailsModel> eventDetails = participant.getEventDetails();
            if (eventDetails.size() > 0) 
                eventDetails.forEach(event -> 
                    
                    //event.setPt_reg_no(person.getPt_reg_no());
                    //event.setGame_year(person.getGame_year());
                    //event.setParticipantModel(participant);
                    entityManager.persist(event);
                    entityManager.flush();
                    entityManager.clear();
                );

            

        

        return ResponseEntity.ok("inserted");
    

【问题讨论】:

【参考方案1】:

您可以通过像这样映射您的详细信息类来尝试“派生身份”:

public class ParticipantEventDetailsId implements Serializable 
    private String sub_event_code;
    private String category_code;
    private ParticipantKey participantModel; // matches name of attribute and type of ParticipantModel PK

    
public class EventDetailsModel  

    @Id
    @Column(name = "sub_event_code")
    private String sub_event_code;

    @Id
    @Column(name = "category_code")
    private String category_code;

    @Id
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumns(
        @JoinColumn(name = "pt_reg_no", referencedColumnName = "pt_reg_no"),
        @JoinColumn(name = "game_year", referencedColumnName = "game_year")
    )
    private ParticipantModel participantModel;


在第 2.4.1 节的JPA 2.2 spec 中讨论了派生的身份(带有示例)。

【讨论】:

ParticipantModel [pt_reg_no=1000, game_year=G12021,name=bapuji, eventDetails=[EventDetailsModel [major_event_code=A, state_abbr=null, sub_event_code=A7, category_code=MO]]] 谢谢@Brian下面的异常嵌套异常是 org.hibernate.HibernateException: No part of a complex identifier may be null] 有根本原因 @BapujiNakka,可能是您调用participant.getEventDetails() 返回的参与者详细信息不包含指向ParticipantModel 的反向指针(即EventDetilasModel.participantModelnull)。 是的@Brian,它只为空,如下所示:- ParticipantModel [pt_reg_no=null, game_year=202126, name=Bapuji, eventDetails=[EventDetailsModel [major_event_code=A, state_abbr=null, sub_event_code=A7 , category_code=MO ,participantModel=null]]] 1)如何将反向指针设置为父级。在我的问题 ABOVE 中更新了 getter 和 setter。你能看看有什么问题吗? 2) 父级中的 pt_reg_no 序列号也由于某些冲突而没有生成【参考方案2】:

OneToMany 映射的替代解决方案父级具有复合键并且父级也需要生成序列号

在设置或列出子实体时使用 @Transient 注释 1) 使用上述注释保存父实体有助于 jpa 从保存中跳过 EventsDetailsModel 列表/集 2) 迭代 Child 并获取已保存的 Parent 复合键值,并将子数据与子键属性一起持久化。

//Parent Class IDclass
public class ParticipantKey implements Serializable 

    private Long pt_reg_no;
    private String game_year;


@IdClass(ParticipantKey.class)
public class ParticipantModel  

    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "participantseq"
    )
    @SequenceGenerator(
        name = "participantseq",
        allocationSize = 1
    )
    private Long pt_reg_no;
    
    @Id
    private String game_year = 'G12021';
    
    @Transient  //Find exact path for Transient
    private Set<EventDetailsModel> eventDetails = new HashSet<>(); 

    public Set<EventDetailsModel> getEventDetails() 
        return eventDetails;
       

    

    
//Child class IDclass    
public class ParticipantEventDetailsId implements Serializable 
        private Long pt_reg_no;
        private String game_year;
        private String sub_event_code;
        private String category_code;
    
    
public class EventDetailsModel  
      @Id    
      @Column(name = "sub_event_code")
      private String sub_event_code;
        
      @Id
      @Column(name = "category_code")
      private String category_code;
        
      @Id
      private Long pt_reg_no;

      @Id
      private String game_year;
  
    
    
//---------------------------------------------------
//JSON input data received into ParticipantModel from @requestBody
public class FormDataObjects 
    private ParticipantModel formData;

    @JsonCreator
    public FormDataObjects(ParticipantModel formData) 
        this.formData = formData;
    


//Controller Entry point
@RequestMapping(path = "/participantData", method = RequestMethod.POST)
    @Transactional
    public ResponseEntity<?> participantRegistration(@Valid @RequestBody FormDataObjects values) 
        ParticipantModel participant = values.getFormData();
        participant.setGame_year(new SimpleDateFormat("yyyy").format(new Date()) + GenricData.game);
     
        ParticipantModel person = participantRepository.save(participant);
        
                if (person == null) 
            return ResponseEntity.ok("Participant is not inserted");
         else 
            Set<EventDetailsModel> eventDetails = participant.getEventDetails();
            if (eventDetails.size() > 0) 
                eventDetails.forEach(event -> 
                    
                    event.setPt_reg_no(person.getPt_reg_no());
                    event.setGame_year(person.getGame_year());
                    entityManager.persist(event);
                    entityManager.flush();
                    entityManager.clear();
                );

            

        

        return ResponseEntity.ok("inserted");
    

【讨论】:

以上是关于具有父复合 pk 的 JPA OneToMany 是子主键派生实体问题的一部分的主要内容,如果未能解决你的问题,请参考以下文章

JPA 2.0 (Hibernate) 使用 @JoinTable 为 @OneToMany 生成不正确的连接表 PK

单向 OneToMany JPA 仅向我显示第一个相关对象“n”次

Spring Data JPA:保存嵌套的 OneToMany 子级(具有级联)返回 NULL 父级外部对象

休眠 - 使用包含父 ID 的复合键 - OneToMany

JPA @OneToMany 不保存父 ID

在 Hibernate/JPA 中保存一个带有子对象的对象 - @OneToMany