Eu tenho uma entidade Foo que faz referência a uma barra de entidade:
@Entity public class Foo { @OneToOne(cascade = {PERSIST, MERGE, REFRESH}, fetch = EAGER) public Bar getBar() { return bar; } }
Quando eu persisto um novo Foo, ele pode obter uma referência a uma nova barra ou a uma barra existente. Quando ele recebe uma barra existente, que por acaso é desanexada, meu provedor de JPA (Hibernate) lança a seguinte exceção:
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.Bar at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:102) at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:636) at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:628) at org.hibernate.engine.EJB3CascadingAction$1.cascade(EJB3CascadingAction.java:28) at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:291) at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:239) at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192) at org.hibernate.engine.Cascade.cascade(Cascade.java:153) at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:454) at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:288) at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204) at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130) at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:49) at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154) at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110) at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61) at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:645) at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:619) at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:623) at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:220) ... 112 more
Quando eu me certifico de que a referência a Bar é gerenciada (anexada) ou quando omito o PERSIST em cascata na relação, tudo funciona bem.
Nenhuma solução, no entanto, é 100% satisfatória. Se eu remover a cascata persistir, obviamente não posso mais persistir em Foo com uma referência a uma nova barra. Tornar a referência à Barra gerenciada requer um código assim antes de persistir:
if (foo.getBar().getID() != null && !entityManager.contains(foo.getBar())) { foo.setBar(entityManager.merge(foo.getUBar())); } entityManager.persist(foo);
Para um único Bar, isso pode não parecer grande coisa, mas se eu tiver que levar em conta todas as propriedades, acabarei com um código horrível que parece derrotar a razão de usar o ORM em primeiro lugar. Eu poderia muito bem persistir meu gráfico de object manualmente usando o JDBC novamente.
Quando é fornecida uma referência de Bar existente, a única coisa que o JPA precisa fazer é pegar seu ID e inseri-lo em uma coluna da tabela que contém o Foo. Ele faz exatamente isso quando Bar está conectado, mas lança a exceção quando Bar é desanexado.
Minha pergunta é; Por que precisa de Bar para ser anexado? Certamente, sua ID não será alterada quando a instância da barra passar de desanexada para o estado anexado, e essa ID parece ser a única coisa necessária aqui.
Isso é talvez um bug no Hibernate ou estou faltando alguma coisa?
Você pode usar merge()
vez de persist()
neste caso:
foo = entityManager.merge(foo);
Quando aplicada à nova instância, merge()
torna persistente (na verdade – retorna a instância persistente com o mesmo estado) e mescla as referências em cascata, conforme você tenta fazer manualmente.
Se bem entendi, você só precisa da referência Bar
para permitir que o novo Foo
tenha o valor da chave estrangeira (para a Bar
existente) quando persistir. Existe um método JPA no EntityManager
chamado getReference()
que pode ser útil para você neste caso. O método getReference()
é semelhante a find()
exceto pelo fato de não se incomodar em retornar uma instância gerenciada (de Bar
), a menos que já esteja em cache no contexto de persistência. Ele retornará um object proxy que satisfará suas necessidades de chave estrangeira para manter o object Foo
. Eu não tenho certeza se este é o tipo de solução que você estava esperando, mas tente e veja se isso funciona para você.
Também notei pelo seu código que você está usando o access ao estilo “propriedade” em vez do access ao estilo “campo” anotando seu método getter (para o relacionamento Bar
). Alguma razão para isso? É recomendável anotar os membros em vez dos getters por motivos de desempenho. É suposto ser mais eficiente para o provedor JPA acessar o campo diretamente, em vez de usar getters e setters.
EDITAR:
Como alguém mencionou, o uso de uma mesclagem em cascata () persistirá em novas entidades, bem como mesclará entidades modificadas e reconectará os desvios separados que possuem um relacionamento com a opção em cascata MERGE. Usar a opção de cascata PERSIST não irá reconectar nada ou mesclar nada e deve ser usado quando esse for o comportamento desejado.