Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.2k views
in Technique[技术] by (71.8m points)

Hibernate: More than one row with the given identifier was found error

I'm using spring 4.0.5 and hibernate 4.3.5; I'm facing an error with hibernate and I can't figure where I'm wrong (because I'm sure I'm wrong). I have a table related with itself, it represents a web tree where each root node can have several children, so I created this class:

@DynamicUpdate
@Cache(region = "it.eng.angelo.spring.dao.hibernate.models.WebTree", usage = CacheConcurrencyStrategy.READ_WRITE)
@Entity
@Table(name = "MEDIA_GALL_TREE", indexes = {@Index(name = "NOME_FOLDER_IDX", columnList = "NOME_FOLDER")})
public class WebTree extends AbstractModel
{

    private static final long serialVersionUID = -4572195412018767502L;
    private long id;
    private String text;
    private boolean opened;
    private boolean disabled;
    private boolean selected;
    private Set<WebTree> children = new HashSet<WebTree>(0);
    private Set<Media> media = new HashSet<Media>(0);
    private WebTree father;
    private WcmDomain dominio;
    public WebTree()
    {
        super();
    }
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID_FOLDER", unique = true, nullable = false)
    public long getId()
    {
        return id;
    }
    public void setId(long id)
    {
        this.id = id;
    }
    @Column(name = "NOME_FOLDER", nullable = false, unique=false)
    public String getText()
    {
        return text;
    }
    public void setText(String text)
    {
        this.text = text;
    }
    @Column(name = "OPENED_FOLDER")
    public boolean isOpened()
    {
        return opened;
    }
    public void setOpened(boolean opened)
    {
        this.opened = opened;
    }
    @Column(name = "DISABLED_FOLDER")
    public boolean isDisabled()
    {
        return disabled;
    }
    public void setDisabled(boolean disabled)
    {
        this.disabled = disabled;
    }
    @Column(name = "SELECTED_FOLDER")
    public boolean isSelected()
    {
        return selected;
    }
    public void setSelected(boolean selected)
    {
        this.selected = selected;
    }
    @OneToMany(mappedBy = "father", orphanRemoval = true, targetEntity = WebTree.class)
    public Set<WebTree> getChildren()
    {
        return children;
    }
    public void setChildren(Set<WebTree> children)
    {
        this.children = children;
    }
    @ManyToOne(targetEntity = WebTree.class)
    @JoinColumn(name = "ID_PADRE", nullable = true)
    public WebTree getFather()
    {
        return father;
    }
    public void setFather(WebTree father)
    {
        this.father = father;
    }
    @OneToOne
    @JoinColumn(name="ID_DOMINIO", nullable=false)
    public WcmDomain getDominio()
    {
        return dominio;
    }
    public void setDominio(WcmDomain dominio)
    {
        this.dominio = dominio;
    }
    @OneToMany( mappedBy = "folder", orphanRemoval = true, targetEntity = Media.class, cascade = { CascadeType.ALL })
    public Set<Media> getMedia()
    {
        return media;
    }
    public void setMedia(Set<Media> media)
    {
        this.media = media;
    }

}

As you can see...it's a pretty simple POJO class; now I created this unit test:

@Test
public void testLoadModifyTree()
{
    try
    {
        DetachedCriteria dc = DetachedCriteria.forClass(MediaGalleryTree.class);
        dc.setFetchMode("father", FetchMode.JOIN);
        dc.add(Property.forName("id").eq(4l));
        List<MediaGalleryTree> result = hibSvc.search(dc, IConstants.NO_PAGINATION, IConstants.NO_PAGINATION);
        for (MediaGalleryTree mediaGalleryTree : result)
        {
            logger.info(mediaGalleryTree.getId());
        }
    }
    catch (Exception e)
    {
        logger.error(e.getMessage(), e);
    }
}

Well, I checked and in the DB I have only 1 record with ID 4; well when I execute this query I got the following error:

18:48:43,123 ERROR [WcmHibernateDao] Errore nella ricerca con detached criteria DetachableCriteria(CriteriaImpl(it.eng.comi.spring.dao.hibernate.models.MediaGalleryTree:this[][id=4])); More than one row with the given identifier was found: 2, for class: it.eng.comi.spring.dao.hibernate.models.MediaGalleryTree
org.hibernate.HibernateException: More than one row with the given identifier was found: 2, for class: it.eng.comi.spring.dao.hibernate.models.MediaGalleryTree
    at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:100)
    at org.hibernate.loader.entity.EntityLoader.loadByUniqueKey(EntityLoader.java:161)
    at org.hibernate.persister.entity.AbstractEntityPersister.loadByUniqueKey(AbstractEntityPersister.java:2385)
    at org.hibernate.type.EntityType.loadByUniqueKey(EntityType.java:767)
    at org.hibernate.type.EntityType.resolve(EntityType.java:505)
    at org.hibernate.engine.internal.TwoPhaseLoad.doInitializeEntity(TwoPhaseLoad.java:170)
    at org.hibernate.engine.internal.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:144)
    at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:1114)
    at org.hibernate.loader.Loader.processResultSet(Loader.java:972)
    at org.hibernate.loader.Loader.doQuery(Loader.java:920)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354)
    at org.hibernate.loader.Loader.doList(Loader.java:2553)
    at org.hibernate.loader.Loader.doList(Loader.java:2539)
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2369)
    at org.hibernate.loader.Loader.list(Loader.java:2364)
    at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:126)
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1682)
    at org.hibernate.internal.CriteriaImpl.list(CriteriaImpl.java:380)
    at it.eng.comi.spring.dao.WcmHibernateDao.searchEntity(WcmHibernateDao.java:140)
    at it.eng.comi.spring.service.impl.WcmRdbmsExtSvcImpl.search(WcmRdbmsExtSvcImpl.java:237)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy45.search(Unknown Source)
    at it.eng.comi.test.ComiTests.testLoadModifyTree(ComiTests.java:578)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:233)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:87)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:176)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
18:48:43,135 ERROR [WcmRdbmsExtSvcImpl] Errore nella ricerca con deatchedCriteria DetachableCriteria(CriteriaImpl(it.eng.comi.spring.dao.hibernate.models.MediaGalleryTree:this[][id=4])); Errore nella ricerca con detached criteria DetachableCriteria(CriteriaImpl(it.eng.comi.spring.dao.hibernate.models.MediaGalleryTree:this[][id=4])); More than one row with the given identifier was found: 2, for class: it.eng.comi.spring.dao.hibernate.models.MediaGalleryTree
it.eng.comi.exception.CoMiDbException: Errore nella ricerca con detached criteria DetachableCriteria(CriteriaImpl(it.eng.comi.spring.dao.hibernate.models.MediaGalleryTree:this[][id=4])); More than one row with the given identifier was found: 2, for class: it.eng.comi.spring.dao.hibernate.models.MediaGalleryTree
    at it.eng.comi.spring.dao.WcmHibernateDao.searchEntity(WcmHibernateDao.java:146)
    at it.eng.comi.spring.service.impl.WcmRdbmsExtSvcImpl.search(WcmRdbmsExtSvcImpl.java:237)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(Transactio

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I got bit by the org.hibernate.HibernateException: More than one row with the given identifier was found: problem and found no help on StackOverflow. It took awhile to sort out the issue so I'm documenting the solution here. I was using JPA/Hibernate and Spring Data.

First off, this was not caused by duplicate rows in the database, as it's obviously not possible to have duplicate primary keys. Instead this was caused by Hibernate looking up an object, and eagerly filling in a One-to-one relationship with a LEFT OUTER JOIN. Hibernate assumed a single row would come back, but two came back because there were two objects associated with the one-to-one relationship.

Here's a simplified version of my objects:

@Entity
@Table(name = "plate")
public class Plate {
    private static final long serialVersionUID = 1L;

    @Id
    @SequenceGenerator(name="test_seq", sequenceName="test_seq")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "test_seq")
    @Column(name = "id")
    private Long id;

    @Version
    @Column(name = "object_version")
    private long objectVersion;

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

    @OneToOne(mappedBy = "plate")
    private Sheet sheet;

    public Sheet getSheet() {
        return sheet;
    }

    public void setSheet(Sheet sheet) {
        if (this.sheet != null) {
            this.sheet.setPlate(null);
        }

        this.sheet = sheet;
        sheet.setPlate(this);
    }
}


@Entity
@Table(name = "sheet")
public class Sheet {
    private static final long serialVersionUID = 1L;

    @Id
    @SequenceGenerator(name="test_seq", sequenceName="test_seq")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "test_seq")
    @Column(name = "id")
    private Long id;

    @Version
    @Column(name = "object_version")
    private long objectVersion;

    @Column(name = "sheet_name")
    private String sheetName;

    @OneToOne
    @JoinColumn(name = "plate_id")
    private Plate plate;

    public Plate getPlate() {
        return plate;
    }

    // Do not use. Use Plate.setSheet() instead
    void setPlate(Plate plate) {
        this.plate = plate;
    }
}

The problem is with the @OneToOne relationship between Plate and Sheet. I initially did not want to delete orphan Sheets. In my code I looked up a Plate by id and added a new Sheet. This removed the Plate-to-Sheet relationship from the original Sheet. When I commit the transaction I assumed JPA would save all modified objects (Plate, the original Sheet, and the new Sheet).

This was not true! JPA apparently finds modified objects by walking down the hierarchy from the object that was originally loaded (Plate), and it misses the fact that the original Sheet, which is now orphaned, was modified. This meant the original Sheet's sheet.plate_id column was not cleared in the database. In other words, I broke their relationship in the data model, but it failed to save to the database. So the next time I try to load the plate Hibernate runs a query like this:

select
    plate1_.id as id1_19_12_, 
    plate1_.object_version as object_v2_19_12_, 
    plate1_.name as name3_19_12_, 
    sheet2_.id as id1_39_12_, 
    sheet2_.object_version as object_v2_39_12_, 
    sheet2_.sheet_name as sheet_nam2_39_12_, 
    sheet2_.plate_id as plate_id4_39_12_, 
from 
    plate plate1_
    left outer join sheet sheet2_ on plate1_.id = sheet2_.plate_id
where 
    plate1_.id=?

Which brings back 2 rows in the result set and produces this error:

org.hibernate.HibernateException: More than one row with the given identifier was found: 10045, for class: com.example.Plate

This is deceptive: there is only one Plate row with that id in the database, but there are two Sheets linked to it.

The solution: Seems I have two choices: either cascade all and delete orphan on the one-to-one relationship, or explicitly call my repository class to look up and save the original Sheet every time I remove its relationship with Plate. I opted for the first choice and added this to the plate class:

@OneToOne(mappedBy = "plate", cascade = CascadeType.ALL, orphanRemoval = true)
private Sheet sheet;

This fixed the issue.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...