Mam model obiektów utrwalony przez JPA, który zawiera relację wiele do jednego: Account
ma wiele Transactions
. A Transaction
ma jeden Account
.
Oto fragment kodu:
@Entity
public class Transaction {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
private Account fromAccount;
....
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToMany(cascade = {CascadeType.ALL},fetch= FetchType.EAGER, mappedBy = "fromAccount")
private Set<Transaction> transactions;
Jestem w stanie utworzyć Account
obiekt, dodać do niego transakcje i Account
poprawnie utrwalić obiekt. Ale kiedy tworzę transakcję, używając istniejącego już utrwalonego Konta i utrwalając Transakcję , otrzymuję wyjątek:
Przyczyna: org.hibernate.PersistentObjectException: odłączony byt przekazany do trwałego: com.paulsanwald.Account at org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java:141)
Tak więc jestem w stanie utrzymać Account
transakcję zawierającą transakcje, ale nie transakcję zawierającą transakcję Account
. Myślałem, że dzieje się tak, ponieważ Account
może nie być dołączony, ale ten kod wciąż daje mi ten sam wyjątek:
if (account.getId()!=null) {
account = entityManager.merge(account);
}
Transaction transaction = new Transaction(account,"other stuff");
// the below fails with a "detached entity" message. why?
entityManager.persist(transaction);
Jak mogę poprawnie zapisać obiekt Transaction
powiązany z już utrwalonym Account
obiektem?
Odpowiedzi:
Jest to typowy problem z dwukierunkową spójnością. Jest dobrze omówione w tym linku, a także w tym linku.
Zgodnie z artykułami w poprzednich 2 linkach musisz naprawić seterów po obu stronach relacji dwukierunkowej. Przykładowy setter dla jednej strony znajduje się w tym łączu.
Przykładowy setter dla strony Many znajduje się w tym łączu.
Po poprawieniu ustawień należy zadeklarować, że typ dostępu encji to „Właściwość”. Najlepszą praktyką do deklarowania typu dostępu „Właściwość” jest przeniesienie WSZYSTKICH adnotacji z właściwości elementu do odpowiednich modułów pobierających. Dużym ostrzeżeniem jest, aby nie mieszać typów dostępu „Pole” i „Właściwość” w klasie encji, w przeciwnym razie zachowanie nie jest określone w specyfikacji JSR-317.
źródło
detached entity passed to persist
dlaczego poprawa spójności sprawia, że działa? Ok, spójność została naprawiona, ale obiekt jest nadal odłączony.Rozwiązanie jest proste, wystarczy użyć
CascadeType.MERGE
zamiastCascadeType.PERSIST
lubCascadeType.ALL
.Miałem ten sam problem i
CascadeType.MERGE
pracował dla mnie.Mam nadzieję, że jesteś posortowany.
źródło
Usuń kaskadę z elementu potomnego
Transaction
, powinno to być po prostu:(
FetchType.EAGER
można usunąć, jak to domyślnie dla@ManyToOne
)To wszystko!
Czemu? Mówiąc „kaskadowo WSZYSTKO” na encji podrzędnej, wymagasz
Transaction
, aby każda operacja DB była propagowana do encji nadrzędnejAccount
. Jeśli to zrobiszpersist(transaction)
,persist(account)
zostanie również wywołane.Ale tylko przejściowe (nowe) byty mogą być przekazywane
persist
(Transaction
w tym przypadku). Odłączone (lub inne nieprzemijające stany) mogą nie (Account
w tym przypadku, ponieważ są już w DB).Dlatego otrzymujesz wyjątek „odłączony byt przekazany do trwałego” .
Account
Jednostka rozumie! Nie tenTransaction
, do którego dzwoniszpersist
.Na ogół nie chcesz rozmnażać się z dziecka na rodzica. Niestety istnieje wiele przykładów kodu w książkach (nawet w dobrych) i przez sieć, które właśnie to robią. Nie wiem, dlaczego ... Może czasami po prostu kopiowałem w kółko bez większego zastanowienia ...
Zgadnij, co się stanie, jeśli
remove(transaction)
nadal będziesz mówić „kaskadowo WSZYSTKO” w tym @ManyToOne?account
(Btw, wszystkie inne transakcje!) Również zostaną usunięte z bazy danych. Ale to nie był twój cel, prawda?źródło
Korzystanie ze scalania jest ryzykowne i trudne, więc w twoim przypadku jest to nieprzyjemne obejście. Musisz przynajmniej pamiętać, że po przekazaniu obiektu encji do scalenia przestaje on być dołączany do transakcji, a zamiast tego zwracany jest nowy, teraz dołączony byt. Oznacza to, że jeśli ktoś nadal ma w posiadaniu stary obiekt bytu, zmiany w nim są po cichu ignorowane i wyrzucane po zatwierdzeniu.
Nie wyświetlasz tutaj pełnego kodu, więc nie mogę dokładnie sprawdzić wzorca transakcji. Jednym ze sposobów na osiągnięcie takiej sytuacji jest to, że podczas wykonywania scalania i utrzymywania transakcji nie jest aktywna. W takim przypadku oczekuje się, że dostawca trwałości otworzy nową transakcję dla każdej wykonywanej operacji JPA oraz natychmiast zatwierdzi i zamknie ją przed powrotem połączenia. W takim przypadku scalanie zostanie uruchomione w pierwszej transakcji, a następnie po zwróceniu metody scalania transakcja zostanie zakończona i zamknięta, a zwrócona jednostka zostanie odłączona. Utrwalenie poniżej otworzy następnie drugą transakcję i spróbuje odwołać się do jednostki, która jest odłączona, dając wyjątek. Zawsze zawijaj kod w transakcji, chyba że dobrze wiesz, co robisz.
Korzystając z transakcji zarządzanej przez kontener, wyglądałoby to tak. Uwaga: zakłada się, że metoda znajduje się w komponencie bean sesji i jest wywoływana przez interfejs lokalny lub zdalny.
źródło
@Transaction(readonly=false)
warstwy usług. Nadal otrzymuję ten sam problem,Prawdopodobnie w tym przypadku
account
obiekt został uzyskany przy użyciu logiki scalania ipersist
służy do utrwalania nowych obiektów i będzie narzekać, jeśli hierarchia ma już utrwalony obiekt. WsaveOrUpdate
takich przypadkach powinieneś użyć zamiastpersist
.źródło
merge
natransaction
obiekcie otrzymujesz ten sam błąd?transaction
to nie przetrwało? Jak sprawdziłeś? Zauważ, żemerge
zwraca odwołanie do utrwalonego obiektu.Nie przekazuj id (pk), aby utrwalić metodę, ani spróbuj metody save () zamiast persist ().
źródło
TestEntityManager.persistAndFlush()
, aby instancja była zarządzana i trwała, a następnie zsynchronizować kontekst trwałości z bazową bazą danych. Zwraca oryginalną jednostkę źródłowąAby rozwiązać problem, wykonaj następujące kroki:
1. Usuwanie kaskadowego powiązania dzieci
Tak, trzeba usunąć
@CascadeType.ALL
ze@ManyToOne
stowarzyszenia. Podmioty potomne nie powinny kaskadować do stowarzyszeń nadrzędnych. Tylko jednostki nadrzędne powinny być kaskadowane do jednostek podrzędnych.Zauważ, że ustawiłem ten
fetch
atrybut na,FetchType.LAZY
ponieważ chętne pobieranie jest bardzo niekorzystne dla wydajności .2. Ustaw obie strony stowarzyszenia
Ilekroć masz powiązanie dwukierunkowe, musisz zsynchronizować obie strony za pomocą
addChild
iremoveChild
metod w encji nadrzędnej:Aby uzyskać więcej informacji o tym, dlaczego ważna jest synchronizacja obu końców powiązania dwukierunkowego, zapoznaj się z tym artykułem .
źródło
W definicji encji nie określasz @JoinColumn dla
Account
przyłączonego do aTransaction
. Będziesz chciał coś takiego:EDYCJA: Cóż, myślę, że byłoby to przydatne, gdybyś używał
@Table
adnotacji na swojej klasie. Heh :)źródło
Nawet jeśli adnotacje są zadeklarowane poprawnie, aby właściwie zarządzać relacją jeden do wielu, nadal możesz napotkać ten precyzyjny wyjątek. Podczas dodawania nowego obiektu podrzędnego
Transaction
do dołączonego modelu danych należy zarządzać wartością klucza podstawowego - chyba że nie należy tego robić . Jeśli podasz wartość klucza podstawowego dla encji podrzędnej zadeklarowanej w następujący sposób przed wywołaniempersist(T)
, napotkasz ten wyjątek.W tym przypadku adnotacje deklarują, że baza danych będzie zarządzać generowaniem wartości klucza podstawowego jednostki po wstawieniu. Podanie jednego (na przykład za pomocą narzędzia do ustawiania identyfikatora) powoduje ten wyjątek.
Alternatywnie, ale faktycznie tak samo, ta deklaracja adnotacji powoduje ten sam wyjątek:
Dlatego nie ustawiaj
id
wartości w kodzie aplikacji, gdy jest ona już zarządzana.źródło
Jeśli nic nie pomaga i nadal występuje ten wyjątek, przejrzyj swoje
equals()
metody - i nie uwzględniaj w nim kolekcji podrzędnej. Zwłaszcza jeśli masz głęboką strukturę osadzonych kolekcji (np. A zawiera Bs, B zawiera Cs itp.).Na przykład
Account -> Transactions
:W powyższym przykładzie usuń transakcje z
equals()
czeków. Wynika to z faktu, że hibernacja oznacza, że nie próbujesz aktualizować starego obiektu, ale przekazujesz nowy obiekt, aby zachować go przy każdej zmianie elementu w kolekcji potomnej.Oczywiście te rozwiązania nie będą pasować do wszystkich aplikacji i należy dokładnie zaprojektować to, co chcesz uwzględnić w metodach
equals
ihashCode
.źródło
Być może jest to błąd OpenJPA. Po przywróceniu resetuje pole @Version, ale pcVersionInit pozostaje prawdą. Mam AbstraceEntity, który zadeklarował pole @Version. Mogę to obejść, resetując pole pcVersionInit. Ale to nie jest dobry pomysł. Myślę, że to nie działa, gdy ma się element utrzymujący kaskadę.
źródło
Musisz ustawić transakcję dla każdego konta.
Lub może wystarczyć (jeśli to konieczne), aby ustawić identyfikatory na null z wielu stron.
źródło
źródło
Moja odpowiedź oparta na JPA z Spring Data: po prostu dodałem
@Transactional
adnotację do mojej zewnętrznej metody.Dlaczego to działa?
Podmiot potomny natychmiast się odłączał, ponieważ nie istniał aktywny kontekst sesji hibernacji. Podanie transakcji wiosennej (dane JPA) zapewnia obecność sesji hibernacji.
Odniesienie:
https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/
źródło
W moim przypadku popełniałem transakcję, gdy użyto metody persist . Po zmianie metody keep to save problem został rozwiązany.
źródło
Jeśli powyższe rozwiązania nie działają, wystarczy raz skomentować metody pobierające i ustawiające klasy encji i nie ustawiać wartości id. (Klucz podstawowy). To zadziała.
źródło
@OneToMany (mappedBy = "xxxx", cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE}) działało dla mnie.
źródło
Rozwiązane przez zapisanie obiektu zależnego przed następnym.
Stało się tak, ponieważ nie ustawiałem identyfikatora (który nie został wygenerowany automatycznie). i próba zapisania w relacji @ManytoOne
źródło