org.hibernate.NonUniqueObjectException:具有相同标识符值的不同对象已与会话关联

Posted

技术标签:

【中文标题】org.hibernate.NonUniqueObjectException:具有相同标识符值的不同对象已与会话关联【英文标题】:org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session 【发布时间】:2014-04-16 19:56:32 【问题描述】:

我有一个看起来像这样的实体设置:

User <- (M:N) -> Project
Project <- (1:n) -> WorkPackage
WorkPackage <- (N:1) -> Category

使用:休眠 4.3.4

现在我想创建一个新用户并使用以下设置为他分配一个项目:

用户分配了一个包含两个工作包的项目。这些工作包引用相同的类别。项目、工作包和类别存在于数据库中。

项目、工作包和类别从数据库加载并使用 org.modelmapper.ModelMapper 传输到 DTO。

当我尝试保存用户时,我收到以下错误:4355 [错误] UserServiceImpl::[de.java.appserver。 persistence.model.Category#1] null

在尝试找到解决方案 2 天后,我几乎要放弃了。

用户实体:(实现了Getter和Setter!)

        @Entity
        @Table
        public class User extends AbstractModel 

        private static final long serialVersionUID = 5668294997295174851L;

        @Id
        @GenericGenerator(name = "generator", strategy = "increment")
        @GeneratedValue(generator = "generator")
        @Column(unique = true, nullable = false)
        private int userId;

        @Column(nullable = false)
        private String firstName;

        @Column(nullable = false)
        private String lastName;

        @Column(nullable = false)
        private String password;

        @Column(unique = true, nullable = false)
        private String email;

        @ManyToMany
        @Cascade( CascadeType.ALL )
        private Set<Role> roles;

        @ManyToMany
        @Cascade( CascadeType.ALL, CascadeType.MERGE )
        private Set<Project> projects;

        @Column
        private String theme;

        @OneToOne
        @Cascade( CascadeType.ALL )
        private Contract contract;

        @OneToMany(mappedBy = "calendarEntryUser")
        @Cascade( CascadeType.ALL )
        private Set<CalendarEntry> calendarEntries;

        /**
         * Default Constructor. Creates an empty object.
         */
        public User() 
            // nothing to do here!
        

        /**
         * Convenience Constructor. Use this constructor to create a new
         * @link User object,
         * 
         * @param firstName
         *            The first name of the user. May not be null.
         * @param lastName
         *            The last name of the user. May not be null.
         * @param password
         *            The password of the user. May not be null.
         * @param email
         *            The email of the user. May not be null.
         * @param roles
         *            The roles assigned to the user
         */
        public User(String firstName, String lastName, String password,
                String email, Set<Role> roles) 
            this.firstName = firstName;
            this.lastName = lastName;
            this.password = password;
            this.email = email;
            this.roles = roles;
            this.theme = "default";
        

        /**
         * Uses Guava to assist in providing hash code of this user instance.
         * 
         * @return the hash code.
         */
        @Override
        public int hashCode() 
            return com.google.common.base.Objects.hashCode(this.lastName,
                    this.firstName, this.email, this.password, 
                    this.theme);
        

        

项目实体:(实现了Getter和Setter!)

    @Entity
    @Table
    public class Project extends AbstractModel 

    private static final long serialVersionUID = -8619177706660662830L;

    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(unique = true, nullable = false)
    private int projectId;

    @Column(unique = true, nullable = false)
    private String name;

    @ManyToMany(mappedBy = "projects")
    @Cascade(CascadeType.ALL)
    private Set<User> projectUsers;

    @OneToMany
    @Cascade( CascadeType.ALL )
    @JoinColumn(name = "project")
    private Set<WorkPackage> workPackages;

    /**
     * Default Constructor. Creates an empty object.
     */
    public Project() 
        // nothing to do here!
    

    /**
     * Convenience Constructor. Use this constructor to create a new
     * @link Project object.
     * 
     * @param name
     *            The name of the project. May not be null.
     * @param workPackageSet
     *            Set of @link WorkPackage
     * @param userSet
     *            Set of @link User
     */
    public Project(String name, Set<WorkPackage> workPackageSet, Set<User> userSet) 
        this.name = name;
        this.workPackages = workPackageSet;
        this.projectUsers = userSet;
    

    /**
     * Uses Guava to assist in providing hash code of this user instance.
     * 
     * @return the hash code.
     */
    @Override
    public int hashCode() 
        return com.google.common.base.Objects.hashCode(this.name);
    
    

WorkPackage 实体:(已实现 Getter 和 Setter!)

    @Entity
    @Table
    public class WorkPackage extends AbstractModel 

    private static final long serialVersionUID = 6953170627587422231L;

    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(unique = true, nullable = false)
    private int workPackageId;

    @Column(nullable = false)
    private String name;

    @ManyToOne
    @Cascade( CascadeType.ALL )
    private Project project;

    @ManyToOne
    @Cascade( CascadeType.SAVE_UPDATE )
    @JoinColumn(name = "cat_id")
    private Category category;

    @OneToMany
    @Cascade( CascadeType.ALL )
    private Set<CalendarEntry> calendarEntries;

    /**
     * Default Constructor. Creates an empty object.1
     */
    public WorkPackage() 
        // nothing to do here!
    

    /**
     * Convenience Constructor. Use this constructor to create a new
     * @link WorkPackage object.
     * 
     * @param name
     *            The name of the work package. May not be null.
     * @param project
     *            The project assigned to this @link WorkPackage
     * @param category
     *            The category assigned to this @link WorkPackage
     */
    public WorkPackage(String name, Project project, Category category) 
        this.name = name;
        this.project = project;
        this.category = category;
    

    /**
     * Uses Guava to assist in providing hash code of this user instance.
     * 
     * @return the hash code.
     */
    @Override
    public int hashCode() 
        return com.google.common.base.Objects.hashCode(this.name, this.workPackageId);
    

    

类别实体(Getter 和 Setter 已实现!)

    @Entity
    @Table
    public class Category extends AbstractModel 

    private static final long serialVersionUID = 7469802197491523844L;

    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(unique = true, nullable = false)
    private int categoryId;

    @Column(unique = true, nullable = false)
    private String name;

    @OneToMany(mappedBy = "category")
    @Cascade(CascadeType.SAVE_UPDATE)
    private Set<WorkPackage> workPackages;

    /**
     * Default Constructor. Creates an empty object.
     */
    public Category() 
        // nothing to do here!
    

    /**
     * Convenience Constructor. Use this constructor to create a new
     * @link Category object.
     * 
     * @param name
     *            The name of the category. May not be null.
     */
    public Category(String name) 
        this.name = name;
    

    /**
     * Uses Guava to assist in providing hash code of this user instance.
     * 
     * @return the hash code.
     */
    @Override
    public int hashCode() 
        return com.google.common.base.Objects.hashCode(this.name);
    
    

抽象模型:

    @Override
    public boolean equals(Object object) 
        boolean isEqual = false;
        if (null != object && object.getClass() == this.getClass()) 
            isEqual = object.hashCode() == this.hashCode();
        

        return isEqual;
    

用户服务:

    @Override
    public boolean saveUser(UserDto user) 
        Transaction tx = null;
        boolean success = false;
        try 
            tx = HibernateUtil.getSession().beginTransaction();
            userDao.saveUser(DtoFactory.INSTANCE.createUser(user));
            tx.commit();
            success = true;
         catch (HibernateException ex) 
            LOGGER.error(ex + " " + ex.getCause());
            tx.rollback();
        
        return success;
    

UserDao 扩展了 GenericDao:

     @Override
        public void saveUser(User user) 
            if (user.getUserId() == 0) 
                saveOrUpdate(user);
             else 
                merge(user);
            
        

通用道:

    @Override
    public void saveOrUpdate(E element) 
        HibernateUtil.getSession().saveOrUpdate(element);
    

DtoFactory createUser():

/**
 * Creates a @link User from a @link UserDto.
 * 
 * @param userDto
 *            The @link UserDto that should be converted
 * @return Created @link User
 */
public User createUser(UserDto userDto) 
    User user = new User();
    if (userDto == null) 
        user = null;
     else 
        // mapper.map(userDto, user);
        user = mapper.map(userDto, User.class);
    
    return user;

来自 JUnit 测试的堆栈跟踪:

org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [de.java.appserver.persistence.model.Role#1]
    at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:617)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:301)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:244)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:109)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:90)
    at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:684)
    at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:676)
    at org.hibernate.engine.spi.CascadingActions$5.cascade(CascadingActions.java:235)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:350)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:293)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161)
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:379)
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:319)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:296)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:118)
    at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:460)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:294)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:194)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:137)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:209)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:194)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:114)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:90)
    at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:684)
    at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:676)
    at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:671)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.hibernate.context.internal.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:356)
    at com.sun.proxy.$Proxy28.saveOrUpdate(Unknown Source)
    at de.java.appserver.persistence.dao.impl.GenericDaoImpl.saveOrUpdate(GenericDaoImpl.java:46)
    at de.java.appserver.persistence.dao.impl.UserDaoImpl.saveOrUpdate(UserDaoImpl.java:1)
    at de.java.appserver.persistence.dao.impl.UserDaoImpl.saveUser(UserDaoImpl.java:29)
    at de.java.appserver.service.hibernate.impl.UserServiceImpl.saveUser(UserServiceImpl.java:47)
    at de.java.appserver.service.hibernate.UserServiceTest.createNewUser(UserServiceTest.java:202)
    at de.java.appserver.service.hibernate.UserServiceTest.testCreateNewUserWithRoleAndProject(UserServiceTest.java:178)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    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.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    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.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    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)

任何人都可以看到代码中的错误或有适当的解决方案吗?

提前致谢!

【问题讨论】:

你在creatUser(user)做什么? 我将 DtoFactory 中的 createUser(user) 添加到帖子中。 你能提供一个堆栈跟踪吗? 查看堆栈跟踪的帖子 Hibernate Error: org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session的可能重复 【参考方案1】:

基于 hashCode() 的 equals() 实现绝对是问题的根源。首先,您应该有适当的 equals 方法。

【讨论】:

正确的 equals 方法的最佳方法是什么?【参考方案2】:

NonUniqueObjectException 表示persistentContext 中具有相同标识符的两个不同对象(有两个对象指向上下文中加载的同一个注册表)并且您正在尝试修改其中一个。

要解决这个问题,您需要找到重复的对象并将其从上下文中分离出来。您可以使用以下方法:

session.evict(duplicatedObject);

【讨论】:

以上是关于org.hibernate.NonUniqueObjectException:具有相同标识符值的不同对象已与会话关联的主要内容,如果未能解决你的问题,请参考以下文章