org.hibernate.NonUniqueObjectException: различен обект със същата стойност на идентификатор вече е свързан със сесията

Имам настройка на обект, която изглежда така:

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

Използване на: Hibernate 4.3.4

Сега искам да създам нов потребител и да му задам проект със следните настройки:

Потребителят е възложил проект, който съдържа два работни пакета. Тези работни пакети се отнасят за същата категория. Проект, работни пакети и категории съществуват в базата данни.

Проект, работни пакети и категории се зареждат от базата данни и се прехвърлят към DTO с помощта на org.modelmapper.ModelMapper.

Когато се опитвам да запазя потребителя, получавам следната грешка: 4355 [ГРЕШКА] UserServiceImpl: org.hibernate.NonUniqueObjectException: различен обект със същата стойност на идентификатора вече е свързан със сесията: [de.java.appserver.persistence.model .Категория#1] нула

След 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 Entity: (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;
}

Stacktrace от 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)

Може ли някой да види грешка в кода или има правилно решение?

Благодаря предварително!


person user3411197    schedule 12.03.2014    source източник
comment
какво правиш в creatUser(user)?   -  person sakura    schedule 12.03.2014
comment
Добавих createUser(user) от DtoFactory към публикацията.   -  person user3411197    schedule 12.03.2014
comment
Можете ли да предоставите проследяване на стека?   -  person Markus    schedule 13.03.2014
comment
Вижте публикацията за проследяването на стека   -  person user3411197    schedule 13.03.2014


Отговори (2)


Изпълнението на equals(), базирано на hashCode(), определено е източник на проблеми. Първо трябва да имате подходящ метод за равенство.

person Hakan Serce    schedule 13.03.2014
comment
Какъв би бил най-добрият начин за правилни методи за равенство? - person user3411197; 13.03.2014

Изключението NonUniqueObjectException означава, че има два различни обекта в persistentContext с един и същ идентификатор (има два обекта, сочещи към един и същ регистър, зареден в контекста) и вие се опитвате да промените един от тях.

За да разрешите това, трябва да намерите дублирания обект и да го отделите от контекста. Можете да го направите, като използвате следното:

session.evict(duplicatedObject);
person Kadu Borges    schedule 01.10.2020