0

I am using spring-boot-starter-data-jpa 1.5.1.RELEASE which internally uses hibernate-core 5.0.11.Final

My entity looks like this:

AreaDto

@Entity
@Table(name = "AREA")
@EntityListeners(AuditingEntityListener.class)
public class AreaDto {

    @Id
    @Column(name = "AREA_ROWID")
    private String areaRowId;

    @OneToMany(cascade = CascadeType.DETACH)
    @JoinColumn(name = "AREA_ROWID")
    private Collection<FestivalDto> festival;


    @OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
    private Collection<ActionDto> actions;


    @OneToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "FESTIVAL", joinColumns = {
        @JoinColumn(name = "AREA_ROWID", referencedColumnName = "AREA_ROWID")}, inverseJoinColumns = {
            @JoinColumn(name = "FESTIVAL_ROWID", referencedColumnName = "FESTIVAL_ROWID")})
    private Collection<ActionDto> festivalActions;


}

FestivalDto

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "FESTIVAL")
public class FestivalDto {

    @Id
    @Column(name = "FESTIVAL_ROWID")
    @GeneratedValue(generator = "FESTIVAL_ROWID_SEQ")
    private Long festivalRowId;

    
    @ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
    @JoinColumn(name = "AREA_ROWID")
    private AreaDto area;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "festival")
    private Collection<ActionDto> actions = Lists.newArrayList();

}

ActionDto

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "ACTION")
public class ActionDto implements Serializable {

...

    @Id
    @Column(name = "ACTION_ID")
    @GeneratedValue(generator = "ACTION_ID_SEQ")
    private Long actionId;

    @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
    @ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
    @JoinColumn(name = "FESTIVAL_ROWID")
    private FestivalDto festival;

    @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
    @ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
    @JoinColumn(name = "AREA_ROWID")
    private AreaDto area;


}

I'm trying to make sense of the below ideas:

  1. What is the strategy used by hibernate to decide on the festival_rowid (or festival_row ids) used to get all the associated action? How will hibernate generated SQL query vary if i change festivalActions fetch strategies between LAZY and EAGER? I know about proxying, collection proxying and all, my question is specific to how those sql is generated and how it may have an impact on deciding the value of bind parameter.

  2. Is my mapping accurate or should I be using a multimap for this relationship since an area could have multiple festival and each festival could have multiple actions

Background: I am getting below error which goes away if I change the fetch type from LAZY to EAGER. Hoping to understand the behaviour for gaining some confidence in the fix. I have read SO and error

org.hibernate.HibernateException: More than one row with the given identifier was found: data.dto.ActionDto@280856b5
RBz
  • 896
  • 3
  • 17
  • 34
  • try `private Collection festivalActions;` to `private Set festivalActions;` – silentsudo Nov 07 '20 at 02:55
  • Thanks for the response. Just tried it to see the following error, posting as i am trying to figure out what it is. Invocation of init method failed; nested exception is org.hibernate.MappingException: Foreign key (FKa86q0syleg2erdobm8fce4sn3:action [festival_rowid])) must have same number of columns as the referenced primary key (festival [area_rowid,festival_rowid]) Does this mean my mapping is broken due to use of same name for column join? – RBz Nov 07 '20 at 03:35
  • Could you share the ActionDto and FestivalDto classes as well. That would make it more clear. – midhun mathew Nov 07 '20 at 03:39
  • Updated as suggested. – RBz Nov 07 '20 at 03:57
  • Also I saw here: https://stackoverflow.com/a/26729334/3988992 If I have an effective composite id for an entity I should be implementing serialisable and my class should have equals() and hash-code implemented. Does that mean my problem is not having `FestivalDto` implementing these things? I can see my `ActionDto` implementing `Serializable` however don't have any custom equals or hash-code. – RBz Nov 07 '20 at 07:34

1 Answers1

1

This mapping does not make much sense. You can't map festivalActions this way because there is no way to persist the state properly through such a mapping. Also festival in AreaDto should be mapped by the area in FestivalDto. Try the following instead:

@Entity
@Table(name = "AREA")
@EntityListeners(AuditingEntityListener.class)
public class AreaDto {

    @Id
    @Column(name = "AREA_ROWID")
    private String areaRowId;

    @OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
    private Collection<FestivalDto> festival;


    @OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
    private Collection<ActionDto> actions;

    public Collection<ActionDto> getFestivalActions() {
        return festival.stream().flatMap(f -> f.actions.stream()).collect(Collectors.toList());
    }


}

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "FESTIVAL")
public class FestivalDto {

    @Id
    @Column(name = "FESTIVAL_ROWID")
    @GeneratedValue(generator = "FESTIVAL_ROWID_SEQ")
    private Long festivalRowId;

    
    @ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
    @JoinColumn(name = "AREA_ROWID")
    private AreaDto area;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "festival")
    private Collection<ActionDto> actions = Lists.newArrayList();

}

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "ACTION")
public class ActionDto implements Serializable {

...

    @Id
    @Column(name = "ACTION_ID")
    @GeneratedValue(generator = "ACTION_ID_SEQ")
    private Long actionId;

    @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
    @ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
    @JoinColumn(name = "FESTIVAL_ROWID")
    private FestivalDto festival;

    @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
    @ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
    @JoinColumn(name = "AREA_ROWID")
    private AreaDto area;


}
Christian Beikov
  • 15,141
  • 2
  • 32
  • 58
  • my getter was simply returning festivalActions. Changing it to your snippet is fixing the issue while keeping the fetch type as LAZY. Is it hibernate not able to understand that each festival may or may not have multiple actions? Or was my implementation plain wrong? – RBz Nov 07 '20 at 11:20
  • Also could you please elaborate a little on the parts where you mentioned *"there is no way to persist the state properly"* and why *"usage of `mappedBy` instead of `JoinColumn`"* was suggested. This piece of code is working fine for persistence without any changes.Is there any corner cases that I'm missing? It will help me understand the issue better.TIA – RBz Nov 07 '20 at 11:29
  • 1
    The problem is that weird things might happen when using a table once as join table and another time for an entity. Hibernate just needs a single representation for a table. Your previous `festivalActions` mapping would work if the `festival` table was just a join table, but it is an entity as well. There is no way for Hibernate to properly persist entries of the set to the join table as various attributes which are defined in the `Festival` entity are not available in that assocation. – Christian Beikov Nov 08 '20 at 16:05
  • Wierd things did happen. The bind parameter on the fetival_rowid while fetching festivalactions was wrong in my case. It mistook action_rowid as festival_rowid as per the logs. Since it's my configuration which broke it, I will close this with your answer. Appreciate the help. – RBz Nov 09 '20 at 01:25