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
cascade
ustawione"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.
Odpowiedzi:
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.
źródło
Po prostu ustaw kaskadę na MERGE, to powinno załatwić sprawę.
źródło
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.źródło
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 session
póź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)
źródło
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;
źródło
Przenieś zadanie przypisania ID obiektu z Hibernate do bazy danych przy użyciu:
<generator class="native"/>
To rozwiązało problem.
źródło
Dodaj adnotację @GeneratedValue do wstawianego komponentu bean.
źródło
Jednym ze sposobów rozwiązania powyższego problemu będzie zastąpienie
hashcode()
.Opróżnij także sesję hibernacji przed i po zapisaniu.
Pomocne jest również jawne ustawienie odłączonego obiektu na
null
.źródło
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.
źródło
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ą.
źródło
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.
źródło
Spróbuj wcześniej umieścić kod zapytania. To rozwiązuje mój problem. np. zmień to:
do tego:
źródło
możesz nie ustawiać identyfikatora obiektu przed wywołaniem zapytania aktualizującego.
źródło
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
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); } }
źródło
jeśli używasz EntityRepository, użyj saveAndFlush zamiast save
źródło
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.
źródło
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; }
}
źródło
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.
źródło
po prostu zatwierdź bieżącą transakcję.
teraz możesz rozpocząć kolejną transakcję i zrobić wszystko dla podmiotu
źródło
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.
źródło