Search code examples
javahibernatejpaspring-data-jpahibernate-mapping

Hibernate OneToMany mapping & Query generation : More than one row with the given identifier was found


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

Solution

  • 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;
    
    
    }