简体   繁体   中英

org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session

I've an entity setup that looks like that:

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

Using: Hibernate 4.3.4

Now I want to create a new User and assign a project to him with the following settings:

The User has assigned a Project which contains two WorkPackages. Theses WorkPackages refer to the same Category. Project, WorkPackages and Categorys exist in the database.

Project, WorkPackages and Categorys are loaded from the database and transfered to a DTO using org.modelmapper.ModelMapper.

When I try to save the user i get the following error: 4355 [ERROR] UserServiceImpl: org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [de.java.appserver.persistence.model.Category#1] null

After 2 days of trying to find a solution I'm almost giving up.

User Entity: (Getter and Setter are implemented!)

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

        }

Project Entity: (Getter and Setter are implemented!)

    @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 Entity: (Getter and Setter are implemented!)

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

    }

Category Entity (Getter and Setter are implemented!)

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

Stacktrace from JUnit Test:

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)

Can anyone see a mistake in the code or has a proper solution?

Thanks in advance!

The equals() implementation based on hashCode() is definitely a source of problems. You should have proper equals method first of all.

The NonUniqueObjectException means that are two different object in the persistentContext with the same identifier (there are two objects pointing at the same registry loaded in the context) and you are trying to modify one of them.

To resolve this, you need to find the duplicated object and detached it from the context. You can do it using the following:

session.evict(duplicatedObject);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM