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

时间:2014-03-12 15:06:53

标签: java hibernate dao dto entities

我的实体设置如下:

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

使用:Hibernate 4.3.4

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

用户已分配了一个包含两个WorkPackage的项目。这些WorkPackages指的是同一类别。 Project,WorkPackages和Categorys存在于数据库中。

Project,WorkPackages和Categorys从数据库加载并使用org.modelmapper.ModelMapper转移到DTO。

当我尝试保存用户时,我收到以下错误:4355 [ERROR] UserServiceImpl:org.hibernate.NonUniqueObjectException:具有相同标识符值的另一个对象已与会话关联:[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);
    }
    }

AbstractModel:

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

        return isEqual;
    }

UserService:

    @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);
            }
        }

GenericDao:

    @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 Test的Stacktrace:

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)

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

提前致谢!

2 个答案:

答案 0 :(得分:3)

基于hashCode()的equals()实现肯定是问题的根源。首先你应该有适当的equals方法。

答案 1 :(得分:0)

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

要解决此问题,您需要找到重复的对象并将其与上下文分离。您可以使用以下方法进行操作:

session.evict(duplicatedObject);