Błąd hibernacji: inny obiekt o tej samej wartości identyfikatora był już powiązany z sesją


94

Zasadniczo mam kilka obiektów w tej konfiguracji (rzeczywisty model danych jest nieco bardziej złożony):

  • A ma relację wiele do wielu z B. (B ma inverse="true")
  • B ma relację „wiele do jednego” z C. (mam cascadeustawione "save-update")
  • C jest rodzajem tabeli typów / kategorii.

Powinienem również wspomnieć, że klucze podstawowe są generowane przez bazę danych przy zapisywaniu.

W przypadku moich danych czasami napotykam problemy, w których A ma zestaw różnych obiektów B, a te obiekty B odnoszą się do tego samego obiektu C.

Kiedy zadzwonić session.saveOrUpdate(myAObject), dostaję hibernacji błąd mówiąc: "a different object with the same identifier value was already associated with the session: C". Wiem, że hibernacja nie może wstawić / zaktualizować / usunąć tego samego obiektu dwa razy w tej samej sesji, ale czy jest jakiś sposób na obejście tego? Nie wydaje się, żeby była to taka niezwykła sytuacja.

Podczas moich badań nad tym problemem widziałem, jak ludzie sugerowali użycie programu session.merge(), ale kiedy to robię, wszelkie "sprzeczne" obiekty są wstawiane do bazy danych jako puste obiekty ze wszystkimi wartościami ustawionymi na zero. Najwyraźniej nie tego chcemy.

[Edytuj] Kolejną rzeczą, o której zapomniałem wspomnieć, jest to, że (z powodów architektonicznych, na które nie mam wpływu), każdy odczyt lub zapis trzeba wykonać w osobnej sesji.


Sprawdź, czy ta odpowiedź ci pomoże ...
joaonlima

Odpowiedzi:


98

Najprawdopodobniej jest to spowodowane tym, że obiekty B nie odnoszą się do tej samej instancji obiektu Java C. Odnoszą się do tego samego wiersza w bazie danych (tj. Tego samego klucza podstawowego), ale są jego różnymi kopiami.

Tak więc sesja Hibernacji, która zarządza jednostkami, śledziłaby, który obiekt Java odpowiada wierszowi z tym samym kluczem podstawowym.

Jedną z opcji byłoby upewnienie się, że jednostki obiektów B, które odnoszą się do tego samego wiersza, faktycznie odwołują się do tej samej instancji obiektu C. Alternatywnie wyłącz kaskadowanie dla tej zmiennej składowej. W ten sposób, gdy B jest trwałe, C nie jest. Będziesz musiał jednak zapisać C ręcznie oddzielnie. Jeśli C jest tabelą typów / kategorii, prawdopodobnie ma to sens.


3
Dzięki jbx. Jak powiedziałeś, okazuje się, że obiekty B odnoszą się do wielu instancji C w pamięci. Zasadniczo dzieje się tak, że jedna część mojego programu czyta w C i dołącza ją do B. Inna część ładuje inne B tym samym C z bazy danych. Oba są dołączane do A, co powoduje błąd podczas zapisywania. Ustawiłem <pre> kaskadę </pre> dla relacji B-> C na „<pre> brak </pre>”, ale nadal otrzymuję ten sam błąd. Czy w relacjach typu „wiele do jednego” lub „jeden do wielu” można powiedzieć Hibernate tylko, że ma zmienić klucz obcy i nie martwić się o resztę?
Jan

1
Czy klucz podstawowy C ma strategię generowania identyfikatorów? Jak generator sekwencji czy coś podobnego?
jbx

Tak, każdy ma swoją własną sekwencję w bazie danych. Jak wspomniałeś, problemem okazało się kaskadowanie. Wyłączyliśmy kaskadowanie dla tabel typu, a dla pozostałych zastosowaliśmy kaskadę "merge", która pozwoliła nam wywołać funkcję merge () bez tworzenia tych wszystkich pustych wierszy. Odpowiednio zaznaczyłem twoją odpowiedź, dzięki!
John

14
Użyłem merge () zamiast saveOrUpdate () i BOOM! to działa :)
Lahiru Ruhunage


13

Musisz zrobić tylko jedną rzecz. Uruchom, session_object.clear()a następnie zapisz nowy obiekt. Spowoduje to wyczyszczenie sesji (jak trafnie nazwano) i usunięcie obraźliwego zduplikowanego obiektu z sesji.


10

Zgadzam się z @Hemant Kumar, bardzo dziękuję. Zgodnie z jego rozwiązaniem rozwiązałem swój problem.

Na przykład:

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

}

Ten kod zawsze popełnia błąd w mojej aplikacji: A different object with the same identifier value was already associated with the sessionpóźniej dowiedziałem się, że zapomniałem automatycznie zwiększyć klucz główny!

Moim rozwiązaniem jest dodanie tego kodu do klucza podstawowego:

@GeneratedValue(strategy = GenerationType.AUTO)

6

Oznacza to, że próbujesz zapisać wiele wierszy w tabeli z odniesieniem do tego samego obiektu.

sprawdź właściwość id klasy jednostki.

@Id
private Integer id;

do

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

1
nie w przypadku pytania zgodnie z podanym opisem
Sudip Bhandari

5

Przenieś zadanie przypisania ID obiektu z Hibernate do bazy danych przy użyciu:

<generator class="native"/>

To rozwiązało problem.



3

Jednym ze sposobów rozwiązania powyższego problemu będzie zastąpienie hashcode().
Opróżnij także sesję hibernacji przed i po zapisaniu.

getHibernateTemplate().flush();

Pomocne jest również jawne ustawienie odłączonego obiektu na null.


2

Właśnie natknąłem się na tę wiadomość, ale w kodzie C #. Nie jestem pewien, czy jest to istotne (chociaż dokładnie ten sam komunikat o błędzie).

Debugowałem kod za pomocą punktów przerwania i rozszerzyłem niektóre kolekcje za pomocą prywatnych członków, gdy debugger był w punkcie przerwania. Ponowne uruchomienie kodu bez przekopywania się przez struktury sprawiło, że komunikat o błędzie zniknął. Wygląda na to, że przeglądanie prywatnych, leniwie ładowanych kolekcji spowodowało, że NHibernate załadował rzeczy, które nie miały być ładowane w tym czasie (ponieważ znajdowały się w prywatnych członkach).

Sam kod jest opakowany w dość skomplikowaną transakcję, która może zaktualizować dużą liczbę rekordów i wiele zależności w ramach tej transakcji (proces importu).

Mam nadzieję, że wskazówka dla każdego, kto napotka ten problem.


2

Znajdź atrybut „Cascade” w Hibernate i usuń go. Gdy ustawisz opcję „Kaskada”, wywoła ona inne operacje (zapisywanie, aktualizowanie i usuwanie) na innych obiektach, które są powiązane z pokrewnymi klasami. Tak więc ta sama wartość tożsamości będzie miała miejsce. Zadziałało ze mną.


1

Miałem ten błąd kilka dni później i zbyt szybko przyspieszyłem naprawianie tego błędu.

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

Zanim otrzymałem ten błąd, nie wspomniałem o typie generowania identyfikatora w obiekcie OrderDetil. gdy nie generuje identyfikatora Orderdetails, przechowuje Id jako 0 dla każdego obiektu OrderDetail. to właśnie wyjaśnił #jbx. Tak, to najlepsza odpowiedź. ten jeden przykład, jak to się dzieje.


1

Spróbuj wcześniej umieścić kod zapytania. To rozwiązuje mój problem. np. zmień to:

query1 
query2 - get the error 
update

do tego:

query2
query1
update

0

możesz nie ustawiać identyfikatora obiektu przed wywołaniem zapytania aktualizującego.


3
Gdyby nie był, nie miałby tego problemu. Problem w tym, że ma dwa obiekty o tym samym identyfikatorze.
aalku

0

Spotkałem się z problemem, ponieważ generowanie klucza podstawowego jest nieprawidłowe, gdy wstawiam wiersz taki:

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

Zmieniam klasę generatora id na tożsamość

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

0

W moim przypadku nie działał tylko flush (). Musiałem użyć clear () po flush ().

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


0

Jeśli pozostawiono otwartą kartę wyrażeń w moim IDE, która powodowała hibernację, wywołanie get na obiekcie powodującym ten wyjątek. Próbowałem usunąć ten sam obiekt. Miałem również punkt przerwania w wywołaniu usuwania, który wydaje się być niezbędny, aby wystąpił ten błąd. Samo ustawienie kolejnej karty wyrażeń jako przedniej karty lub zmiana ustawienia tak, aby ide nie zatrzymywał się na punktach przerwania, rozwiązało ten problem.


0

Upewnij się, że Twoja encja ma ten sam typ generacji ze wszystkimi mapowanymi jednostkami

Np .: rola użytkownika

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;

}

Moduł:

public class Modules implements Serializable {

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

private String longName;

private String shortName;

}

Jednostka główna z mapowaniem

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

Oprócz wszystkich poprzednich odpowiedzi, możliwe rozwiązanie tego problemu w projekcie na dużą skalę, jeśli używasz obiektu wartości dla swoich klas, nie ustawisz atrybutu id w klasie VO Transformer.


0

po prostu zatwierdź bieżącą transakcję.

currentSession.getTransaction().commit();

teraz możesz rozpocząć kolejną transakcję i zrobić wszystko dla podmiotu


0

Inny przypadek, w którym ten sam komunikat o błędzie może zostać wygenerowany, niestandardowy allocationSize:

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

bez dopasowania

alter sequence par_idpar_seq increment 20;

może powodować walidację ograniczeń podczas wstawiania (to jest łatwe do zrozumienia) lub "inny obiekt o tej samej wartości identyfikatora był już powiązany z sesją" - ten przypadek był mniej oczywisty.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.