Ошибка гибернации: другой объект с тем же значением идентификатора уже был связан с сеансом


94

По сути, у меня есть несколько объектов в этой конфигурации (реальная модель данных немного сложнее):

  • A имеет отношение "многие ко многим" с B. (B имеет inverse="true")
  • B имеет отношения "многие к одному" с C. (я cascadeустановил "save-update")
  • C - это своего рода таблица типов / категорий.

Также, вероятно, следует упомянуть, что первичные ключи генерируются базой данных при сохранении.

С моими данными я иногда сталкиваюсь с проблемами, когда у A есть набор разных объектов B, и эти объекты B относятся к одному и тому же объекту C.

Когда я звоню session.saveOrUpdate(myAObject), я получаю ошибку спящего режима , говоря: "a different object with the same identifier value was already associated with the session: C". Я знаю, что спящий режим не может вставить / обновить / удалить один и тот же объект дважды в одном сеансе, но есть ли способ обойти это? Это не похоже на такую ​​необычную ситуацию.

Во время моего исследования этой проблемы я видел, как люди предлагали использовать session.merge(), но когда я это делаю, любые «конфликтующие» объекты вставляются в базу данных как пустые объекты со всеми значениями, установленными на null. Ясно, что это не то, что мы хотим.

[Edit] Еще я забыл упомянуть, что (по не зависящим от меня причинам архитектуры) каждое чтение или запись должно выполняться в отдельном сеансе.


Посмотрим, поможет ли вам этот ответ ..
joaonlima

Ответы:


98

Скорее всего, это потому, что объекты B не относятся к одному и тому же экземпляру объекта Java C. Они относятся к одной и той же строке в базе данных (то есть к одному и тому же первичному ключу), но представляют собой разные ее копии.

Итак, что происходит, так это то, что сеанс Hibernate, который управляет объектами, будет отслеживать, какой объект Java соответствует строке с тем же первичным ключом.

Один из вариантов - убедиться, что сущности объектов B, которые ссылаются на одну и ту же строку, фактически ссылаются на один и тот же экземпляр объекта C. В качестве альтернативы отключите каскадирование для этой переменной-члена. Таким образом, когда B сохраняется, C нет. Однако вам придется сохранить C вручную отдельно. Если C - это таблица типов / категорий, тогда, вероятно, имеет смысл быть таким.


3
Спасибо, jbx. Как вы сказали, оказывается, что объекты B относятся к нескольким экземплярам C в памяти. По сути, происходит то, что одна часть моей программы читает в C и присоединяет ее к B. Другая часть загружает другой B с тем же C из базы данных. Оба присоединяются к A, что вызывает ошибку при сохранении. Я установил <pre> каскад </pre> для отношения B-> C на «<pre> none </pre>», но все равно получаю ту же ошибку. В отношениях «многие к одному» или «один ко многим» существует ли способ указать Hibernate только на изменение внешнего ключа и не беспокоиться об остальном?
Джон

1
Есть ли у первичного ключа C стратегия генерации идентификаторов? Как генератор последовательности или что-то подобное?
jbx

Да, у каждого своя последовательность в базе. Как вы упомянули, проблема оказалась в каскадировании. Мы отключили каскадирование для таблиц типов, а для остальных мы использовали каскад «слияния», который позволил нам вызвать merge () без создания всех этих пустых строк. Я отметил ваш ответ соответствующим образом, спасибо!
Джон

14
Я использовал merge () вместо saveOrUpdate () и BOOM! это работает :)
Лахиру Рухунаге


13

Вам нужно сделать только одно. Запустите, session_object.clear()а затем сохраните новый объект. Это очистит сеанс (как правильно названо) и удалит оскорбительный повторяющийся объект из вашего сеанса.


10

Я согласен с @Hemant Kumar, большое спасибо. По его решению я решил свою проблему.

Например:

@Test
public void testSavePerson() {
    try (Session session = sessionFactory.openSession()) {
        Transaction tx = session.beginTransaction();
        Person person1 = new Person();
        Person person2 = new Person();
        person1.setName("222");
        person2.setName("111");
        session.save(person1);
        session.save(person2);
        tx.commit();
    }
}

Person.java

public class Person {
    private int id;
    private String name;

    @Id
    @Column(name = "id")
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Basic
    @Column(name = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Этот код всегда делает ошибку в моем приложении:, A different object with the same identifier value was already associated with the sessionпозже я узнал, что забыл автоматически увеличить свой первичный ключ!

Мое решение - добавить этот код на ваш первичный ключ:

@GeneratedValue(strategy = GenerationType.AUTO)

6

Это означает, что вы пытаетесь сохранить несколько строк в своей таблице со ссылкой на один и тот же объект.

проверьте свойство id вашего Entity Class.

@Id
private Integer id;

к

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(unique = true, nullable = false)
private Integer id;

1
не тот случай с вопросом согласно приведенному описанию
Судип Бхандари

5

Перенести задачу присвоения идентификатора объекта из Hibernate в базу данных, используя:

<generator class="native"/>

Это решило проблему для меня.



3

Один из способов решить указанную выше проблему - переопределить расширение hashcode().
Также очистите сеанс гибернации до и после сохранения.

getHibernateTemplate().flush();

Явная установка отсоединенного объекта nullтакже помогает.


2

Только что наткнулся на это сообщение, но в коде С #. Не уверен, что это актуально (хотя сообщение об ошибке точно такое же).

Я отлаживал код с помощью точек останова и расширял некоторые коллекции с помощью частных членов, пока отладчик находился в точке останова. Повторный запуск кода без рытья структур заставил сообщение об ошибке исчезнуть. Похоже, что просмотр частных коллекций с отложенной загрузкой заставил NHibernate загружать вещи, которые не должны были загружаться в то время (потому что они были в закрытых членах).

Сам код заключен в довольно сложную транзакцию, которая может обновлять большое количество записей и множество зависимостей как часть этой транзакции (процесса импорта).

Надеюсь, это ключ к разгадке всех, кто сталкивается с проблемой.


2

Найдите в Hibernate атрибут «Cascade» и удалите его. Когда вы устанавливаете "Cascade" доступным, он будет вызывать другие операции (сохранение, обновление и удаление) для других сущностей, которые связаны со связанными классами. Так будет случиться такая же индивидуальная ценность. Со мной это сработало.


1

У меня была эта ошибка несколько дней подряд, и я потратил слишком много времени на ее исправление.

 public boolean save(OrderHeader header) {
    Session session = sessionFactory.openSession();


    Transaction transaction = session.beginTransaction();

    try {
        session.save(header);

        for (OrderDetail detail : header.getDetails()) {
            session.save(detail);
        }

        transaction.commit();
        session.close();

        return true;
    } catch (HibernateException exception) {

        exception.printStackTrace();
        transaction.rollback();
        return false;
    }
}

Прежде чем я получу эту ошибку, я не упомянул тип генерации идентификатора в объекте OrderDetil. когда без создания идентификатора OrderDetail он сохраняет значение 0 для каждого объекта OrderDetail. это то, что объяснил #jbx. Да, это лучший ответ. вот один пример, как это происходит.


1

Попробуйте разместить код вашего запроса раньше. Это решит мою проблему. например, измените это:

query1 
query2 - get the error 
update

к этому:

query2
query1
update

0

возможно, вы не устанавливаете идентификатор объекта перед вызовом запроса на обновление.


3
Если бы он не был, у него не было бы этой проблемы. Проблема в том, что у него есть два объекта с одинаковым идентификатором.
aalku

0

Я столкнулся с проблемой из-за неправильной генерации первичного ключа, когда я вставляю такую ​​строку:

public void addTerminal(String typeOfDevice,Map<Byte,Integer> map) {
        // TODO Auto-generated method stub
        try {
            Set<Byte> keySet = map.keySet();
            for (Byte byte1 : keySet) {
                Device device=new Device();
                device.setNumDevice(DeviceCount.map.get(byte1));
                device.setTimestamp(System.currentTimeMillis());
                device.setTypeDevice(byte1);
                this.getHibernateTemplate().save(device);
            }
            System.out.println("hah");
        }catch (Exception e) {
            // TODO: handle exception
            logger.warn("wrong");
            logger.warn(e.getStackTrace()+e.getMessage());
        }
}

Я меняю класс генератора идентификаторов на идентификатор

<id name="id" type="int">
    <column name="id" />
    <generator class="identity"  />
 </id>

0

В моем случае только flush () не работал. Мне пришлось использовать clear () после flush ().

public Object merge(final Object detachedInstance)
    {
        this.getHibernateTemplate().flush();
        this.getHibernateTemplate().clear();
        try
        {
            this.getHibernateTemplate().evict(detachedInstance);
        }
}


0

Если оставить вкладку выражений в моей IDE открытой, что вызывает вызов hibernate для объекта, вызывающего это исключение. Я пытался удалить этот же объект. Также у меня была точка останова на вызове удаления, которая, похоже, необходима для возникновения этой ошибки. Простое создание другой вкладки выражений в качестве передней вкладки или изменение настройки, чтобы ide не останавливался на точках останова, решило эту проблему.


0

Убедитесь, что ваша сущность имеет тот же тип генерации, что и все сопоставленные сущности.

Пример: UserRole

public class UserRole extends AbstractDomain {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String longName;

private String shortName;

@Enumerated(EnumType.STRING)
private CommonStatus status;

private String roleCode;

private Long level;

@Column(columnDefinition = "integer default 0")
private Integer subRoleCount;

private String modification;

@ManyToOne(fetch = FetchType.LAZY)
private TypeOfUsers licenseType;

}

Модуль:

public class Modules implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String longName;

private String shortName;

}

Главный объект с отображением

public class RoleModules implements Serializable{

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private UserRole role;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private Modules modules;

@Type(type = "yes_no")
private boolean isPrimaryModule;

public boolean getIsPrimaryModule() {
    return isPrimaryModule;
}

}


0

В дополнение ко всем предыдущим ответам, возможное решение этой проблемы в крупномасштабном проекте, если вы используете объект значения для своих классов, не устанавливая атрибут id в классе VO Transformer.


0

просто зафиксируйте текущую транзакцию.

currentSession.getTransaction().commit();

теперь вы можете начать другую транзакцию и делать что угодно с сущностью


0

Другой случай, когда такое же сообщение об ошибке может быть сгенерировано, настраиваемое allocationSize:

@Id
@Column(name = "idpar")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "paramsSequence")
@SequenceGenerator(name = "paramsSequence", sequenceName = "par_idpar_seq", allocationSize = 20)
private Long id;

без соответствия

alter sequence par_idpar_seq increment 20;

может вызвать проверку ограничения во время вставки (что легко понять) или «другой объект с таким же значением идентификатора уже был связан с сеансом» - этот случай был менее очевиден.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.