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.

Jan
źródło
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.

jbx
źródło
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
29

Po prostu ustaw kaskadę na MERGE, to powinno załatwić sprawę.

andy.codes
źródło
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.

dsk
źródło
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)
Ice Blue
źródło
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;
rex roy
źródło
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.

user2845946
źródło
3

Dodaj adnotację @GeneratedValue do wstawianego komponentu bean.

Hemant kumar
źródło
Dzięki! To był dokładnie mój problem
DriLLFreAK100
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.

Waqar
źródło
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.

Ales Potocnik Hahonina
źródło
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ą.

Nguyen Vu Quang
źródło
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.

Buddhi
źródło
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
juldeh
źródło
0

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

Fawad Khaliq
źródło
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>
张云 风
źródło
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);
        }
}
shubhranshu
źródło
0

jeśli używasz EntityRepository, użyj saveAndFlush zamiast save

Jayen Chondigara
źródło
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.

Aaron Byrnes
źródło
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;
}

}

Sandeep Patel
źródło
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.

George Michael
źródło
0

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

currentSession.getTransaction().commit();

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

Mahdi
źródło
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.

user158037
źródło