Помилка сплячого режиму: інший об’єкт з однаковим значенням ідентифікатора вже був пов’язаний із сеансом


94

У мене по суті є деякі об'єкти в цій конфігурації (реальна модель даних трохи складніша):

  • А має стосунки багато-до-багатьох з Б. (У має inverse="true")
  • B має багато-до-одного стосунки з C. (я cascadeвстановив "save-update")
  • С - це своєрідна таблиця типів / категорій.

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

Зі своїми даними я іноді стикаюся з проблемами, коли 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. Очевидно, що це не те, що ми хочемо.

[Редагувати] Ще одне, про що я забув згадати, - це те, що (з архітектурних причин, не залежних від мене), кожне читання чи запис потрібно робити в окремій сесії.


Подивіться, чи допоможе вам ця відповідь ..
joaonlima

Відповіді:


98

Швидше за все, тому, що об'єкти B не посилаються на той самий екземпляр об'єкта Java C. Вони посилаються на один і той же рядок у базі даних (тобто той самий первинний ключ), але це різні його копії.

Отже, відбувається те, що сесія Hibernate, яка управляє сутностями, буде відстежувати, який об’єкт Java відповідає рядку з тим самим первинним ключем.

Одним із варіантів було б переконатися, що сутності об’єктів B, які посилаються на один рядок, насправді посилаються на один і той же екземпляр об’єкта C. Альтернативно вимкніть каскадування для цієї змінної-члена. Таким чином, коли B зберігається, 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
private Integer id;

до

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

1
справа не в питанні згідно з даним описом
Судіп Бхандарі,

5

Перенесіть завдання присвоєння ідентифікатора об'єкта з режиму глибокого сну в базу даних, використовуючи:

<generator class="native"/>

Це вирішило проблему для мене.



3

Одним із способів вирішення вищезазначеної проблеми буде перевизначення hashcode().
Також очистіть сеанс глибокого сну до та після збереження.

getHibernateTemplate().flush();

Явне встановлення для відокремленого об’єкта nullтакож допомагає.


2

Щойно натрапив на це повідомлення, але в коді c #. Не впевнений, що це релевантно (хоча те саме повідомлення про помилку).

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

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

Сподіваємось, підказка для когось іншого, хто стикається з проблемою.


2

Знайдіть атрибут "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. коли без створення ідентифікатора Orderdetails він зберігає Id як 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

У моєму випадку не працював лише флеш (). Мені довелося використовувати clear () після змиву ().

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


0

Якщо залишити вкладку виразів у моїй 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;

може спричинити перевірку обмежень під час вставки (це легко зрозуміти) або ths "інший об’єкт з однаковим значенням ідентифікатора вже був пов’язаний із сеансом" - цей випадок був менш очевидним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.