PersistentObjectException: odłączony obiekt przekazany do persist wyrzucony przez JPA i Hibernate

Mam model obiektowy JPA, który zawiera relację many-To-one: An 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ć obiekt Account, dodać do niego transakcje i poprawnie utrzymywać obiekt Account. Ale kiedy tworzę transakcję, używając istniejącego już konta i utrzymując transakcję , dostaję wyjątek:

Spowodowane przez: org.hibernacja.PersistentObjectException: detergent entity passed to persist: com.paulsanwald.Konto w org.hibernacja.wydarzenie.wewnętrzne.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.java:141)

Tak więc, jestem w stanie utrzymać Account, która zawiera transakcje, ale nie transakcję, która ma Account. Myślałem, że to dlatego, że Account może nie być dołączony, ale ten kod nadal 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ć Transaction, powiązany z już istniejącym obiektem Account?

Author: Vlad Mihalcea, 2012-11-13

18 answers

Jest to typowy problem dwukierunkowej spójności. Jest dobrze omówione w ten link jak również ten link.

Zgodnie z artykułami w poprzednich 2 linkach musisz naprawić swoje setery po obu stronach relacji dwukierunkowej. Przykładowy setter dla jednej strony znajduje się w tym linku.

Przykładowy setter dla wielu stron znajduje się w ten link.

Po poprawieniu ustawień chcesz zadeklarować typ dostępu encji jako "Własność". Najlepszą praktyką deklarowania typu dostępu "Property" jest przeniesienie wszystkich adnotacji z właściwości elementu do odpowiednich getterów. Dużym słowem ostrożności jest nie mieszanie typów dostępu "Field" I "Property" w ramach klasy encji, w przeciwnym razie zachowanie jest niezdefiniowane przez specyfikacje JSR-317.

 136
Author: Sym-Sym,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-15 22:43:33

Rozwiązanie jest proste, wystarczy użyć CascadeType.MERGE zamiast CascadeType.PERSIST lub CascadeType.ALL.

Miałem ten sam problem i CascadeType.MERGE zadziałał dla mnie.

Mam nadzieję, że wszystko załatwione.
 293
Author: ochiWlad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-17 15:40:23

Usuń kaskadowanie z jednostki potomnejTransaction, powinno być tylko:

@Entity class Transaction {
    @ManyToOne // no cascading here!
    private Account account;
}

(FetchType.EAGER można usunąć, jak również jest to domyślne dla @ManyToOne)

To wszystko!

Dlaczego? Mówiąc "kaskadowo wszystko" na jednostce potomnej Transaction wymagasz, aby każda operacja DB została propagowana do jednostki macierzystej Account. Jeśli to zrobić persist(transaction), persist(account) będzie również wywoływany.

Ale tylko przejściowe (nowe) byty mogą być przekazywane do persist (Transaction w tym przypadku). Wolnostojący (lub inny stan nie-transientowy) może nie (Account w tym przypadku, ponieważ jest już w DB).

Dlatego otrzymujesz wyjątek "odłączony obiekt przekazany do persist". Account byt jest przeznaczony! Nie Transaction, na które dzwonisz persist.


Zazwyczaj nie chcesz rozmnażać się z dziecka Na rodzica. Niestety jest wiele przykładów kodu w książkach (nawet w dobrych) i w sieci, które właśnie to robią. Nie wiem, dlaczego... Może czasami po prostu kopiowane bez zastanowienia...

Zgadnij, co się stanie, jeśli zadzwonisz remove(transaction) nadal masz "kaskadowe wszystko" w tym @ ManyToOne? account (btw, ze wszystkimi innymi transakcjami!) zostaną również usunięte z DB. Ale to nie był twój zamiar, prawda?

 27
Author: Eugen Labun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-08 09:52:45

Nie przekazuj id (pk) do metody persist lub spróbuj metody save () zamiast persist ().

 17
Author: Angad Bansode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-15 13:33:18

Używanie merge jest ryzykowne i trudne, więc jest to brudne obejście w Twoim przypadku. Musisz pamiętać, że gdy przekazujesz obiekt encji do scalenia, to zatrzymuje dołączenie do transakcji, a zamiast tego zwracany jest nowy, teraz dołączony encja. Oznacza to, że jeśli ktoś posiada stary obiekt encji, zmiany w nim są po cichu ignorowane i odrzucane przy zatwierdzaniu.

Nie pokazujesz tutaj pełnego kodu, więc nie mogę dwukrotnie sprawdzić twojego wzór transakcji. Jednym ze sposobów, aby dostać się do takiej sytuacji jest, jeśli nie masz aktywnej transakcji podczas wykonywania merge and persist. W takim przypadku oczekuje się, że dostawca persistence otworzy nową transakcję dla każdej operacji JPA i natychmiast zatwierdzi ją i zamknie przed powrotem połączenia. W takim przypadku scalanie zostanie uruchomione w pierwszej transakcji, a następnie po powrocie metody merge transakcja zostanie zakończona i zamknięta, a zwrócony obiekt zostanie odłączony. Persist pod nim otworzy drugą transakcję i spróbuje odnieść się do jednostki, która jest odłączona, dając wyjątek. Zawsze zawijaj kod wewnątrz transakcji, chyba że dobrze wiesz, co robisz.

Używając transakcji zarządzanej kontenerem wyglądałoby to mniej więcej tak. Uwaga: zakłada się, że metoda znajduje się w fasoli sesji i jest wywoływana przez lokalny lub Zdalny interfejs.

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void storeAccount(Account account) {
    ...

    if (account.getId()!=null) {
        account = entityManager.merge(account);
    }

    Transaction transaction = new Transaction(account,"other stuff");

    entityManager.persist(account);
}
 15
Author: Zds,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-01 15:26:31

Prawdopodobnie w tym przypadku uzyskałeś swój obiekt account używając logiki merge, a {[1] } jest używany do utrzymywania nowych obiektów i będzie narzekać, jeśli hierarchia ma już utrzymany obiekt. W takich przypadkach należy użyć saveOrUpdate zamiast persist.

 13
Author: dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-13 23:02:34

Usuwanie asocjacji dziecka

Więc musisz usunąć {[2] } ze Stowarzyszenia @ManyToOne. Podmioty potomne nie powinny kaskadowo łączyć się ze stowarzyszeniami rodziców. Tylko jednostki nadrzędne powinny kaskadowo przekształcać się w jednostki podrzędne.

@ManyToOne(fetch= FetchType.LAZY)

Zauważ, że ustawiłem atrybut fetch na FetchType.LAZY, ponieważ eager fetching jest bardzo zły dla wydajności.

Ustawienie obu stron Stowarzyszenia

Gdy masz dwukierunkowy związek, musisz zsynchronizować obie strony za pomocą addChild i removeChild metody w jednostce dominującej:

public void addTransaction(Transaction transaction) {
    transcations.add(transaction);
    transaction.setAccount(this);
}

public void removeTransaction(Transaction transaction) {
    transcations.remove(transaction);
    transaction.setAccount(null);
}
 11
Author: Vlad Mihalcea,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-08 22:20:18

W definicji encji nie określasz @ JoinColumn dla Account połączonego z Transaction. Będziesz chciał coś takiego:

@Entity
public class Transaction {
    @ManyToOne(cascade = {CascadeType.ALL},fetch= FetchType.EAGER)
    @JoinColumn(name = "accountId", referencedColumnName = "id")
    private Account fromAccount;
}

EDIT: Cóż, myślę, że byłoby to przydatne, gdybyś używał @Table adnotacji na swojej klasie. Heh. :)

 5
Author: NemesisX00,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-13 23:12:12

Nawet jeśli Twoje adnotacje są poprawnie zadeklarowane, aby prawidłowo zarządzać relacją jeden do wielu, nadal możesz napotkać ten precyzyjny wyjątek. Podczas dodawania nowego obiektu potomnego, Transaction, do dołączonego modelu danych musisz zarządzać wartością klucza głównego - , chyba że nie powinieneś . Jeśli przed wywołaniem persist(T) podasz podstawową wartość klucza dla elementu potomnego zadeklarowanego w następujący sposób, napotkasz ten wyjątek.

@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
....

W tym przypadku adnotacje deklarują że baza danych będzie zarządzać generowaniem podstawowych wartości klucza jednostki po wstawieniu. Podanie jednego samodzielnie (np. poprzez setter Id) powoduje ten wyjątek.

Alternatywnie, ale faktycznie tak samo, deklaracja adnotacji daje ten sam wyjątek:

@Entity
public class Transaction {
    @Id
    @org.hibernate.annotations.GenericGenerator(name="system-uuid", strategy="uuid")
    @GeneratedValue(generator="system-uuid")
    private Long id;
....

Dlatego nie ustawiaj wartości id w kodzie aplikacji, gdy jest już zarządzana.

 4
Author: dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-17 18:40:24

Jeśli nic nie pomaga i nadal otrzymujesz ten wyjątek, Przejrzyj swoje metody equals() - i nie dołączaj do nich kolekcji potomnych. Szczególnie jeśli masz głęboką strukturę osadzonych kolekcji(np. A zawiera Bs, B zawiera Cs, itp.).

W przykładzie Account -> Transactions:

  public class Account {

    private Long id;
    private String accountName;
    private Set<Transaction> transactions;

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (!(obj instanceof Account))
        return false;
      Account other = (Account) obj;
      return Objects.equals(this.id, other.id)
          && Objects.equals(this.accountName, other.accountName)
          && Objects.equals(this.transactions, other.transactions); // <--- REMOVE THIS!
    }
  }

W powyższym przykładzie Usuń transakcje z czeków equals(). Dzieje się tak, ponieważ hibernate oznacza, że nie próbujesz zaktualizować starego obiektu, ale przekazujesz nowy obiekt do persist, za każdym razem, gdy zmienisz element na kolekcja dla dzieci.
Oczywiście te rozwiązania nie będą pasować do wszystkich aplikacji i powinieneś starannie zaprojektować to, co chcesz uwzględnić w metodach equals i hashCode.

 4
Author: FazoM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-04 23:03:43

Moja Wiosenna odpowiedź oparta na JPA: po prostu dodałem adnotację @Transactional do mojej zewnętrznej metody.

Dlaczego to działa

Obiekt potomny natychmiast został odłączony, ponieważ nie było aktywnego kontekstu sesji hibernacji. Dostarczenie transakcji Spring (Data JPA) zapewnia obecność sesji hibernacji.

Odniesienie:

Https://vladmihalcea.com/a-beginners-guide-to-jpa-hibernate-entity-state-transitions/

 3
Author: JJ Zabkar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-10-22 21:09:38

Może to błąd OpenJPA, po wycofaniu resetuje pole @ Version, ale pcVersionInit zachowuje prawdę. Mam abstrakcję, która zadeklarowała pole @ Version. Mogę obejść to, resetując pole pcVersionInit. Ale to nie jest dobry pomysł. Myślę, że to nie działa, gdy kaskada utrzymują podmiot.

    private static Field PC_VERSION_INIT = null;
    static {
        try {
            PC_VERSION_INIT = AbstractEntity.class.getDeclaredField("pcVersionInit");
            PC_VERSION_INIT.setAccessible(true);
        } catch (NoSuchFieldException | SecurityException e) {
        }
    }

    public T call(final EntityManager em) {
                if (PC_VERSION_INIT != null && isDetached(entity)) {
                    try {
                        PC_VERSION_INIT.set(entity, false);
                    } catch (IllegalArgumentException | IllegalAccessException e) {
                    }
                }
                em.persist(entity);
                return entity;
            }

            /**
             * @param entity
             * @param detached
             * @return
             */
            private boolean isDetached(final Object entity) {
                if (entity instanceof PersistenceCapable) {
                    PersistenceCapable pc = (PersistenceCapable) entity;
                    if (pc.pcIsDetached() == Boolean.TRUE) {
                        return true;
                    }
                }
                return false;
            }
 1
Author: Yaocl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-05 15:54:06

Musisz ustawić transakcję dla każdego konta.

foreach(Account account : accounts){
    account.setTransaction(transactionObj);
}

Lub wystarczy (jeśli to właściwe) ustawić ids NA null po wielu stronach.

// list of existing accounts
List<Account> accounts = new ArrayList<>(transactionObj.getAccounts());

foreach(Account account : accounts){
    account.setId(null);
}

transactionObj.setAccounts(accounts);

// just persist transactionObj using EntityManager merge() method.
 1
Author: stakahop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-31 11:19:51
cascadeType.MERGE,fetch= FetchType.LAZY
 1
Author: James Siva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-23 13:46:00

Rozwiązane przez zapisanie obiektu zależnego przed następnym.

Przydarzyło mi się to, ponieważ nie ustawiałem identyfikatora (który nie był generowany automatycznie). and trying to save with relation @ManytoOne

 1
Author: Shahid Hussain Abbasi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-03-30 11:17:28

W moim przypadku dokonywałem transakcji przy użyciu metody persist . Po zmianie metody persist na Save została ona rozwiązana.

 0
Author: H.Ostwal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-05 13:36:47

Jeśli powyższe rozwiązania nie działają tylko jeden raz skomentuj metody getter i setter klasy entity i nie ustawiaj wartości id.(Klucz podstawowy) Więc to zadziała.

 0
Author: Shubham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-22 10:08:53

@OneToMany (mappedBy = "xxxx", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.Usunąć}) zadziałało na mnie.

 0
Author: SateeshKasaboina,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-09-01 10:24:06