Como corrigir o erro "objeto referencia uma instância transitória não salva - salve a instância transitória antes de liberar"

610

Recebo o seguinte erro ao salvar o objeto usando o Hibernate

object references an unsaved transient instance - save the transient instance before flushing
Tushar Ahirrao
fonte
1
No contexto desejado, esse erro é? é com ou sem variável transitória?
Vijay

Respostas:

803

Você deve incluir cascade="all"(se estiver usando xml) ou cascade=CascadeType.ALL(se estiver usando anotações) em seu mapeamento de coleção.

Isso acontece porque você tem uma coleção em sua entidade e essa coleção possui um ou mais itens que não estão presentes no banco de dados. Ao especificar as opções acima, você diz ao hibernate para salvá-las no banco de dados ao salvar seus pais.

Bozho
fonte
7
Isso não está implícito? Você não gostaria que o Hibernate os salvasse sempre?
Marcus Leon
29
@ Marcus - não, não é. Você pode lidar com eles manualmente.
Bozho
5
Bozho está certo. Encontrei circunstâncias em que tive coleções que queria gerenciar manualmente devido ao seu tamanho ou devido a regras de negócios que não permitem que todos os objetos em uma coleção sejam salvos ao mesmo tempo.
Alex Marshall
26
Isso não acontece apenas para as coleções, mas também simples de um para um mapeamentos
Sebastien Lorber
12
Não seria melhor começar com CascadeType.PERSIST e usar persist para salvar?
Sergii Shevchyk
248

Eu acredito que isso pode ser apenas uma resposta repetida, mas apenas para esclarecer, eu fiz isso em um @OneToOnemapeamento e também em um @OneToMany. Nos dois casos, foi o fato de que o Childobjeto que eu estava adicionando ao Parentainda não estava salvo no banco de dados. Então, quando eu adicionava o Childe Parent, em seguida, salvava o Parent, o Hibernate lançava a "object references an unsaved transient instance - save the transient instance before flushing"mensagem ao salvar o pai.

Adicionando no cascade = {CascadeType.ALL}na Parent'sreferência à Childresolveu o problema em ambos os casos. Isso salvou o Childe o Parent.

Desculpe por repetir as respostas, só queria esclarecer mais as pessoas.

@OneToOne(cascade = {CascadeType.ALL})
@JoinColumn(name = "performancelog_id")
public PerformanceLog getPerformanceLog() {
    return performanceLog;
}
McDonald Noland
fonte
7
E se eu não quiser economizar em cascata um relacionamento @OneToOne? Ao criar os dois objetos pela primeira vez, como posso salvar no banco de dados sem disparar a exceção?
Xtian
4
E se eu quiser salvar a criança em alguns casos e não em outros?
Omaruchan
@xtian: Bem, então você deve cuidar da ordem correta de salvar no banco de dados persistindo os objetos com um EntityManager. No básico, você apenas diz em.persist (object1); em.persist (objeto2); etc
kaba713
Eu entendi esse problema especificamente quando usei @ Herança, neste caso TABLE_PER_CLASS, estava fazendo referência a uma subclasse. CascadeType.ALL corrigiu.
Jim ReesPotter
Ou você criou seu objeto de entidade com new MyEntity(sem sincronizar com o banco de dados - liberação), em vez de obter sua instância sincronizada do banco de dados. Fazer consultas do Hibernate usando essa instância informa que o que você espera estar no banco de dados difere do que você tem na memória do aplicativo. Nesse caso - simplesmente sincronize / instale sua entidade a partir do DB e use-a. Nenhum CascadeType.ALL é necessário então.
Zon
67

Isso acontece ao salvar um objeto quando o Hibernate pensa que precisa salvar um objeto associado àquele que você está salvando.

Eu tinha esse problema e não queria salvar as alterações no objeto referenciado, por isso queria que o tipo de cascata fosse NENHUM.

O truque é garantir que o ID e VERSION no objeto referenciado estejam definidos para que o Hibernate não pense que o objeto referenciado é um novo objeto que precisa ser salvo. Isso funcionou para mim.

Examine todos os relacionamentos da classe que você está salvando para descobrir os objetos associados (e os objetos associados aos objetos associados) e verifique se o ID e VERSION estão definidos em todos os objetos da árvore de objetos.

Cookalino
fonte
4
Esse comentário me colocou no caminho certo. Eu estava atribuindo uma nova instância do pai à propriedade de seu filho. Então, a NH pensou que eram instâncias diferentes.
Elvin
2
Sim. Isso acontece se, por exemplo, o ID do objeto associado não for incluído (por exemplo, foi ignorado pelo @JsonIgnore). O Hibernate não tem como identificar a entidade associada, portanto, deseja salvá-la.
Rori Stumpf
36

Introdução

Como expliquei neste artigo ao usar o JPA e o Hibernate, uma entidade pode estar em um dos 4 estados a seguir:

  • Novo - Um objeto recém-criado que nunca foi associado a uma Sessão do Hibernate (também conhecido como Contexto de Persistência) e não está mapeado para nenhuma linha da tabela de banco de dados é considerado no estado Novo ou Transitório.

    Para se tornar persistente, precisamos chamar explicitamente o persistmétodo ou fazer uso do mecanismo de persistência transitiva.

  • Persistente - Uma entidade persistente foi associada a uma linha da tabela do banco de dados e está sendo gerenciada pelo Contexto de Persistência atualmente em execução.

    Qualquer alteração feita em uma entidade será detectada e propagada no banco de dados (durante o tempo de liberação da sessão).

  • Desanexado - Depois que o Contexto de Persistência em execução no momento é fechado, todas as entidades gerenciadas anteriormente ficam desanexadas. As alterações sucessivas não serão mais rastreadas e nenhuma sincronização automática de banco de dados ocorrerá.

  • Removido - Embora a JPA exija que apenas as entidades gerenciadas possam ser removidas, o Hibernate também pode excluir entidades desanexadas (mas apenas por meio de uma removechamada de método).

Transições de estado da entidade

Para mover uma entidade de um estado para outro, você pode usar os métodos persist, removeou merge.

Estados da entidade JPA

Corrigindo o problema

O problema que você está descrevendo na sua pergunta:

object references an unsaved transient instance - save the transient instance before flushing

é causado pela associação de uma entidade no estado de Novo a uma entidade que está no estado de Gerenciado .

Isso pode acontecer quando você está associando uma entidade filha a uma coleção um para muitos na entidade pai, e a coleção não faz cascadeo estado de transição.

Portanto, como expliquei neste artigo , você pode corrigir isso adicionando cascata à associação de entidades que acionou essa falha, da seguinte maneira:

A @OneToOneassociação

@OneToOne(
    mappedBy = "post",
    orphanRemoval = true,
    cascade = CascadeType.ALL)
private PostDetails details;

Observe o CascadeType.ALLvalor que adicionamos para o cascadeatributo.

A @OneToManyassociação

@OneToMany(
    mappedBy = "post", 
    orphanRemoval = true,
    cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<>();

Novamente, o CascadeType.ALLé adequado para as associações bidirecionais@OneToMany .

Agora, para que a cascata funcione corretamente em um bidirecional, você também precisa garantir que as associações pai e filho estejam sincronizadas.

Confira este artigo para obter mais detalhes sobre qual é a melhor maneira de atingir esse objetivo.

A @ManyToManyassociação

@ManyToMany(
    mappedBy = "authors",
    cascade = {
        CascadeType.PERSIST, 
        CascadeType.MERGE
    }
)
private List<Book> books = new ArrayList<>();

Em uma @ManyToManyassociação, você não pode usar CascadeType.ALLou, orphanRemovalcomo isso, irá propagar a transição do estado da entidade de exclusão de um pai para outra entidade pai.

Portanto, para @ManyToManyassociações, você geralmente coloca em cascata as operações CascadeType.PERSISTou CascadeType.MERGE. Como alternativa, você pode expandir isso para DETACHou REFRESH.

Para mais detalhes sobre a melhor maneira de mapear uma @ManyToManyassociação, consulte este artigo também.

Vlad Mihalcea
fonte
Explicação completa e consistente! Bom trabalho!
Frio
Você pode encontrar centenas de explicações detalhadas no meu tutorial do Hibernate .
Vlad Mihalcea
30

Ou, se você deseja usar "poderes" mínimos (por exemplo, se você não deseja excluir em cascata) para obter o que deseja, use

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

...

@Cascade({CascadeType.SAVE_UPDATE})
private Set<Child> children;
Haris Osmanagić
fonte
11
Essa deve ser a resposta aceita. CascadeType.ALL ist muito amplo
lilalinux
5
A partir do Hibernate 5.2.8, parece não haver maneira de obter o mesmo efeito com as anotações da JPA. Por exemplo, @ManyToMany(cascade={PERSIST, MERGE, REFRESH, DETACH})(todos, exceto REMOVE), não atualiza em cascata as atualizações, como o Hibernate CascadeType.SAVE_UPDATE.
jcsahnwaldt diz GoFundMonica 16/03
25

No meu caso, foi causado por não estar CascadeTypedo @ManyToOnelado do relacionamento bidirecional. Para ser mais preciso, eu estava CascadeType.ALLdo @OneToManylado e não estava @ManyToOne. Adicionando CascadeType.ALLao @ManyToOneproblema resolvido. Lado um para muitos :

@OneToMany(cascade = CascadeType.ALL, mappedBy="globalConfig", orphanRemoval = true)
private Set<GlobalConfigScope>gcScopeSet;

Lado muitos-para-um (causou o problema)

@ManyToOne
@JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;

Muitos-para-um (corrigido adicionando CascadeType.PERSIST)

@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name="global_config_id")
private GlobalConfig globalConfig;
allap
fonte
Se eu fizer dessa maneira e salvar a entidade pai, o que está acontecendo é que há duas inserções na minha tabela pai que são duas linhas. Eu acho que é porque temos uma cascata nas entidades pai e filho?
theprogrammer
18

Isso ocorreu para mim ao persistir em uma entidade na qual o registro existente no banco de dados tinha um valor NULL para o campo anotado com @Version (para bloqueio otimista). A atualização do valor NULL para 0 no banco de dados corrigiu isso.

dukethrash
fonte
Essa deve ser uma nova pergunta e deve ser adicionada como um bug, pelo menos a exceção enganosa. Isso acabou sendo a causa do meu problema.
BML
Isso corrige meu problema
Jad Chahine
11

Esta não é a única razão para o erro. Encontrei-o agora devido a um erro de digitação na minha codificação, que acredito, definiu o valor de uma entidade que já estava salva.

X x2 = new X();
x.setXid(memberid); // Error happened here - x was a previous global entity I created earlier
Y.setX(x2);

Eu localizei o erro encontrando exatamente qual variável causou o erro (neste caso String xid). Eu usei um catchtodo o bloco de código que salvou a entidade e imprimiu os traços.

{
   code block that performed the operation
} catch (Exception e) {
   e.printStackTrace(); // put a break-point here and inspect the 'e'
   return ERROR;
}
nanospeck
fonte
1
Problema semelhante ao meu. Afinal, quando recarreguei a entidade localmente, defina a propriedade e salvei, funcionou bem.
CsBalazsHungary
8

Não use Cascade.Allaté que você realmente precise. Rolee Permissionter manyToManyrelação bidirecional . Em seguida, o código a seguir funcionaria bem

    Permission p = new Permission();
    p.setName("help");
    Permission p2 = new Permission();
    p2.setName("self_info");
    p = (Permission)crudRepository.save(p); // returned p has id filled in.
    p2 = (Permission)crudRepository.save(p2); // so does p2.
    Role role = new Role();
    role.setAvailable(true);
    role.setDescription("a test role");
    role.setRole("admin");
    List<Permission> pList = new ArrayList<Permission>();
    pList.add(p);
    pList.add(p2);
    role.setPermissions(pList);
    crudRepository.save(role);

enquanto se o objeto é apenas um "novo", ele lançaria o mesmo erro.

Tiina
fonte
1
é 100% correto, o Cascade. Toda a solução é lenta e só deve ser aplicada quando necessário. primeiro, se a entidade já existir, verifique se está carregada no gerenciador de entidades atual, se não estiver carregada.
Renato Mendes
7

Se sua coleção for anulável, tente: object.SetYouColection(null);

Joabe Lucena
fonte
Este foi totalmente o meu problema. Eu nunca teria imaginado que precisava defini-lo manualmente como nulo.
Deadron
Este também foi o meu problema. Eu não estava usando uma coleção, então não tentei isso no começo, mas configurei meu objeto como nulo e agora funciona.
Fodder
5

Para adicionar meus 2 centavos, recebi o mesmo problema ao enviar acidentalmente nullcomo o ID. O código abaixo mostra meu cenário (e o OP não mencionou nenhum cenário específico) .

Employee emp = new Employee();
emp.setDept(new Dept(deptId)); // --> when deptId PKID is null, same error will be thrown
// calls to other setters...
em.persist(emp);

Aqui, estou configurando o ID do departamento existente para uma nova instância de funcionário sem realmente obter a entidade do departamento primeiro, pois não quero que outra consulta de seleção seja acionada.

Em alguns cenários, o deptIdPKID é proveniente nulldo método de chamada e estou recebendo o mesmo erro.

Portanto, observe os nullvalores do ID da PK

manikanta
fonte
e se for anulável?
Vipin cp
Eu tenho um problema semelhante. Eu recebo a exceção quando meu deptID é 0. Qualquer outro valor maior que 0 funciona. O engraçado é que eu tenho um departamento com id = 0. #
Gustavo Gustavo
5

Esse problema aconteceu comigo quando eu criei uma nova entidade e uma entidade associada em um método marcado como e @Transactional, em seguida, realizei uma consulta antes de salvar. Ex

@Transactional
public someService() {
    Entity someEntity = new Entity();
    AssocaiatedEntity associatedEntity = new AssocaitedEntity();
    someEntity.setAssociatedEntity(associatedEntity);
    associatedEntity.setEntity(someEntity);

    // Performing any query was causing hibernate to attempt to persist the new entity. It would then throw an exception
    someDao.getSomething();

    entityDao.create(someEntity);
}

Para corrigir, executei a consulta antes de criar a nova entidade.

mad_fox
fonte
4

além de todas as outras boas respostas, isso pode acontecer se você mergepersistir em um objeto e acidentalmente esquecer de usar a referência mesclada do objeto na classe pai. considere o seguinte exemplo

merge(A);
B.setA(A);
persist(B);

Nesse caso, você mescla, Amas esquece de usar o objeto mesclado de A. Para resolver o problema, você deve reescrever o código assim.

A=merge(A);//difference is here
B.setA(A);
persist(B);
Hossein Nasr
fonte
3

eu recebo esse erro quando uso

getSession().save(object)

mas funciona sem problemas quando uso

getSession().saveOrUpdate(object) 
acpuma
fonte
3

Eu também enfrentei a mesma situação. Ao definir a anotação a seguir, a propriedade resolveu a exceção solicitada.

A exceção que eu enfrentei.

Exception in thread "main" java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.model.Car_OneToMany

Para superar, a anotação que usei.

    @OneToMany(cascade = {CascadeType.ALL})
    @Column(name = "ListOfCarsDrivenByDriver")
    private List<Car_OneToMany> listOfCarsBeingDriven = new ArrayList<Car_OneToMany>();

O que fez o Hibernate lançar a exceção:

Essa exceção é lançada no seu console porque o objeto filho anexado ao objeto pai não está presente no banco de dados naquele momento.

Ao fornecer @OneToMany(cascade = {CascadeType.ALL}), ele diz ao Hibernate para salvá-los no banco de dados enquanto salva o objeto pai.

Dulith De Costa
fonte
2

Por uma questão de completude: A

org.hibernate.TransientPropertyValueException 

com mensagem

object references an unsaved transient instance - save the transient instance before flushing

também ocorrerá quando você tentar persistir / mesclar uma entidade com uma referência a outra entidade que por acaso é desanexada .

Ralf
fonte
1

Um outro motivo possível: no meu caso, eu estava tentando salvar o filho antes de salvar o pai, em uma entidade nova.

O código era algo parecido com isso em um modelo User.java:

this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.setNewPassword(password);
this.timeJoin = new Date();
create();

O método setNewPassword () cria um registro PasswordHistory e o adiciona à coleção de histórico em User. Como a instrução create () ainda não havia sido executada pelo pai, ela estava tentando salvar em uma coleção de uma entidade que ainda não havia sido criada. Tudo o que tive que fazer para corrigi-lo foi mover a chamada setNewPassword () após a chamada para create ().

this.lastName = lastName;
this.isAdmin = isAdmin;
this.accountStatus = "Active";
this.timeJoin = new Date();
create();
this.setNewPassword(password);
Jeremy Goodell
fonte
1

Há outra possibilidade que pode causar esse erro no modo de hibernação. Você pode definir uma referência não salva do seu objeto Apara uma entidade anexada Be desejar persistir o objeto C. Mesmo neste caso, você receberá o erro mencionado acima.

Hossein Nasr
fonte
1

Se você estiver usando o Spring Data JPA, a @Transactionalanotação adicional à sua implementação de serviço resolveria o problema.

Usama
fonte
1

Eu acho que é porque você tentou persistir um objeto que tem uma referência a outro objeto que ainda não persiste e, portanto, tenta no "lado do banco de dados" colocar uma referência a uma linha que não existe


fonte
0

A maneira simples de resolver esse problema é salvar as duas entidades. primeiro salve a entidade filha e, em seguida, salve a entidade pai. Porque a entidade pai depende da entidade filha para o valor da chave estrangeira.

Abaixo exame simples de um para um relacionamento

insert into Department (name, numOfemp, Depno) values (?, ?, ?)
Hibernate: insert into Employee (SSN, dep_Depno, firstName, lastName, middleName, empno) values (?, ?, ?, ?, ?, ?)

Session session=sf.openSession();
        session.beginTransaction();
        session.save(dep);
        session.save(emp);
shashigura
fonte
1
É o contrário - a entidade filha mantém o valor FK e depende do pai, portanto, você deve salvar o pai primeiro! No bloco de código, você está certo.
Sõber
0

Uma causa possível do erro é a inexistência da configuração do valor da entidade controladora; por exemplo, para um relacionamento departamento-funcionários, você deve escrever isso para corrigir o erro:

Department dept = (Department)session.load(Department.class, dept_code); // dept_code is from the jsp form which you get in the controller with @RequestParam String department
employee.setDepartment(dept);
feromix
fonte
0

Há muitas possibilidades desse erro, outras também estão em adicionar página ou editar página. No meu caso, eu estava tentando salvar um objeto AdvanceSalary. O problema é que, na edição, o AdvanceSalary employee.employee_id é nulo, pois na edição não foi definido o employee.employee_id. Eu criei um campo oculto e o configurei. meu código funcionando absolutamente bem.

    @Entity(name = "ic_advance_salary")
    @Table(name = "ic_advance_salary")
    public class AdvanceSalary extends BaseDO{

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private Integer id;

        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "employee_id", nullable = false)
        private Employee employee;

        @Column(name = "employee_id", insertable=false, updatable=false)
        @NotNull(message="Please enter employee Id")
        private Long employee_id;

        @Column(name = "advance_date")
        @DateTimeFormat(pattern = "dd-MMM-yyyy")
        @NotNull(message="Please enter advance date")
        private Date advance_date;

        @Column(name = "amount")
        @NotNull(message="Please enter Paid Amount")
        private Double amount;

        @Column(name = "cheque_date")
        @DateTimeFormat(pattern = "dd-MMM-yyyy")
        private Date cheque_date;

        @Column(name = "cheque_no")
        private String cheque_no;

        @Column(name = "remarks")
        private String remarks;

        public AdvanceSalary() {
        }

        public AdvanceSalary(Integer advance_salary_id) {
            this.id = advance_salary_id;
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public Employee getEmployee() {
            return employee;
        }

        public void setEmployee(Employee employee) {
            this.employee = employee;
        }


        public Long getEmployee_id() {
            return employee_id;
        }

        public void setEmployee_id(Long employee_id) {
            this.employee_id = employee_id;
        }

    }
Ravindra
fonte
0

Eu enfrentei essa exceção quando não persisti no objeto pai, mas estava salvando o filho. Para resolver o problema, na mesma sessão persisti os objetos filho e pai e usei CascadeType.ALL no pai.

Tadele Ayelegn
fonte
0

Caso 1: Eu estava recebendo essa exceção ao tentar criar um pai e salvar a referência pai no filho e, em seguida, em alguma outra consulta DELETE / UPDATE (JPQL). Então, eu apenas libero () a entidade recém-criada depois de criar pai e depois de criar filho usando a mesma referência pai. Funcionou para mim.

Caso 2:

Classe pai

public class Reference implements Serializable {

    @Id
    @Column(precision=20, scale=0)
    private BigInteger id;

    @Temporal(TemporalType.TIMESTAMP)
    private Date modifiedOn;

    @OneToOne(mappedBy="reference")
    private ReferenceAdditionalDetails refAddDetails;
    . 
    .
    .
}

Classe Criança:

public class ReferenceAdditionalDetails implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    @OneToOne
    @JoinColumn(name="reference",referencedColumnName="id")
    private Reference reference;

    private String preferedSector1;
    private String preferedSector2;
    .
    .

}

No caso acima, onde pai (Referência) e filho (ReferenceAdditionalDetails) tendo um relacionamento OneToOne e quando você tenta criar a entidade Reference e, em seguida, seu filho (ReferenceAdditionalDetails), ela fornecerá a mesma exceção. Portanto, para evitar a exceção, você deve definir nulo para a classe filho e criar o pai. (Código de exemplo)

.
.
reference.setRefAddDetails(null);
reference = referenceDao.create(reference);
entityManager.flush();
.
.
Harsh Vardhan Singh Solanki
fonte
0

Meu problema estava relacionado ao @BeforeEachJUnit. E mesmo que eu salvei as entidades relacionadas (no meu caso @ManyToOne), recebi o mesmo erro.

O problema está de alguma forma relacionado à sequência que tenho em meus pais. Se eu atribuir o valor a esse atributo, o problema será resolvido.

Ex. Se eu tiver a entidade Pergunta que pode ter algumas categorias (uma ou mais) e a entidade Pergunta tiver uma sequência:

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "feedbackSeq")
@Id
private Long id;

Eu tenho que atribuir o valor question.setId(1L);

NikNik
fonte
0

Basta criar o Constructor do seu mapeamento em sua classe base. Como se você deseja uma relação individual na Entidade A, Entidade B. Se você está usando A como classe base, A deve ter um Construtor e B como argumento.

Bilal Ahmad
fonte