Como corrigir org.hibernate.LazyInitializationException - não foi possível inicializar o proxy - nenhuma sessão

188

Eu recebo a seguinte exceção:

Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
    at sei.persistence.wf.entities.Element_$$_jvstc68_47.getNote(Element_$$_jvstc68_47.java)
    at JSON_to_XML.createBpmnRepresantation(JSON_to_XML.java:139)
    at JSON_to_XML.main(JSON_to_XML.java:84)

quando tento ligar das principais linhas a seguir:

Model subProcessModel = getModelByModelGroup(1112);
System.out.println(subProcessModel.getElement().getNote());

Eu implementei o getModelByModelGroup(int modelgroupid)método primeiro assim:

public static Model getModelByModelGroup(int modelGroupId, boolean openTransaction) {

    Session session = SessionFactoryHelper.getSessionFactory().getCurrentSession();     
    Transaction tx = null;

    if (openTransaction) {
        tx = session.getTransaction();
    }

    String responseMessage = "";

    try {
        if (openTransaction) {
            tx.begin();
        }
        Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
        query.setParameter("modelGroupId", modelGroupId);

        List<Model> modelList = (List<Model>)query.list(); 
        Model model = null;

        for (Model m : modelList) {
            if (m.getModelType().getId() == 3) {
                model = m;
                break;
            }
        }

        if (model == null) {
            Object[] arrModels = modelList.toArray();
            if (arrModels.length == 0) {
                throw new Exception("Non esiste ");
            }

            model = (Model)arrModels[0];
        }

        if (openTransaction) {
            tx.commit();
        }

        return model;

   } catch(Exception ex) {
       if (openTransaction) {
           tx.rollback();
       }
       ex.printStackTrace();
       if (responseMessage.compareTo("") == 0) {
           responseMessage = "Error" + ex.getMessage();
       }
       return null;
    }
}

e conseguiu a exceção. Em seguida, um amigo me sugeriu que sempre testasse a sessão e obtenha a sessão atual para evitar esse erro. Então eu fiz isso:

public static Model getModelByModelGroup(int modelGroupId) {
    Session session = null;
    boolean openSession = session == null;
    Transaction tx = null;
    if (openSession) {
        session = SessionFactoryHelper.getSessionFactory().getCurrentSession(); 
        tx = session.getTransaction();
    }
    String responseMessage = "";

    try {
        if (openSession) {
            tx.begin();
        }
        Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
        query.setParameter("modelGroupId", modelGroupId);

        List<Model> modelList = (List<Model>)query.list(); 
        Model model = null;

        for (Model m : modelList) {
            if (m.getModelType().getId() == 3) {
                model = m;
                break;
            }
        }

        if (model == null) {
            Object[] arrModels = modelList.toArray();
            if (arrModels.length == 0) {
                throw new RuntimeException("Non esiste");
            }

            model = (Model)arrModels[0];

            if (openSession) {
                tx.commit();
            }
            return model;
        } catch(RuntimeException ex) {
            if (openSession) {
                tx.rollback();
            }
            ex.printStackTrace();
            if (responseMessage.compareTo("") == 0) {
                responseMessage = "Error" + ex.getMessage();
            }
            return null;        
        }
    }
}

mas ainda assim, obtenha o mesmo erro. Tenho lido muito sobre esse erro e encontrei algumas soluções possíveis. Uma delas era definir lazyLoad como false, mas não tenho permissão para fazer isso, por isso me sugeriram controlar a sessão

Blerta Dhimitri
fonte

Respostas:

93

O que há de errado aqui é que sua configuração de gerenciamento de sessões está definida para fechar a sessão quando você confirma a transação. Verifique se você tem algo como:

<property name="current_session_context_class">thread</property>

na sua configuração.

Para superar esse problema, você pode alterar a configuração do factory de sessão ou abrir outra sessão e apenas pedir esses objetos carregados com preguiça. Mas o que eu sugeriria aqui é inicializar essa coleção lenta no próprio getModelByModelGroup e chamar:

Hibernate.initialize(subProcessModel.getElement());

quando você ainda está na sessão ativa.

E uma última coisa. Um conselho amigável. Você tem algo parecido com isto em seu método:

for (Model m : modelList) {
    if (m.getModelType().getId() == 3) {
        model = m;
        break;
    }
}

Por favor, insted este código apenas filtre os modelos com id de tipo igual a 3 na instrução de consulta apenas algumas linhas acima.

Um pouco mais de leitura:

configuração de fábrica de sessões

problema com a sessão fechada

goronância
fonte
1
Obrigado! Eu resolvi meu problema usando openSession () em vez de getCurrentSession () como um dos links que você deu me sugeriu isso, mas agora eu tenho medo, se é errado fazê-lo
Blerta Dhimitri
2
Não, provavelmente está bom. Mas leia um pouco mais para poder controlar totalmente suas sessões e transações. É muito importante conhecer o básico, porque todas as tecnologias de nível superior, como Spring, Hibernate e mais, operam com o mesmo conceito.
Goroncy
179

Se você usar o Spring, marcar a classe como @Transactional , o Spring tratará do gerenciamento de sessões.

@Transactional
public class MyClass {
    ...
}

Usando @Transactional , muitos aspectos importantes, como a propagação de transações, são tratados automaticamente. Nesse caso, se outro método transacional for chamado, o método terá a opção de ingressar na transação em andamento, evitando a exceção "no session".

AVISO Se você usar @Transactional, esteja ciente do comportamento resultante. Consulte este artigo para obter armadilhas comuns. Por exemplo, as atualizações para entidades são mantidas, mesmo se você não chamar explicitamentesave

user2601995
fonte
21
Não posso exagerar a importância dessa resposta. Eu seriamente recomendo tentar esta opção primeiro.
Sparkyspider
6
Observe também que você precisa adicionar @EnableTransactionManagementà sua configuração para permitir transações. " se outro método transacional for chamado, o método terá a opção de ingressar na transação em andamento " esse comportamento é diferente para as diferentes formas de implementação das transações, ou seja, proxy de interface versus proxy de classe ou tecelagem AspectJ. Consulte a documentação .
Erik Hofer
1
Deveríamos entender que a Transactionalanotação do Spring é recomendada, não apenas para modificar transações, mas também para acessar apenas as transações?
Stephane
8
Eu seriamente recomendo usar esta anotação na parte superior da classe apenas para teste. O código real deve marcar cada método como transação na classe separadamente. A menos que todos os métodos da classe exijam conexão aberta com a transação com o banco de dados.
M1ld 6/12
1
Não é seguro colocar @Transactional (readOnly = true) em vez de apenas @Transactional?
Hamedz 02/06/19
105

Você pode tentar definir

<property name="hibernate.enable_lazy_load_no_trans">true</property>

em hibernate.cfg.xml ou persistence.xml

O problema a ter em mente com esta propriedade está bem explicado aqui

Wilianto Indrawan
fonte
8
Você também pode explicar seu significado?
Mohit Kanwar
2
Também estou curioso para saber o que isso faz. Corrigiu o problema que eu estava tendo, mas gostaria de entender o porquê.
Hassan
3
para persistence.xml:<property name="hibernate.enable_lazy_load_no_trans" value="true"/>
ACV
33
Você percebe que hibernate.enable_lazy_load_no_transé um anti-padrões , certo?
Vlad Mihalcea 13/09/16
6
NÃO USE ESTA PROPRIEDADE, SE A SPRING GERIR AS SUAS OPERAÇÕES, ESSA PROPRIEDADE LEVARÁ À EXPLOSÃO DAS OPERAÇÕES, SIMPLESMENTE A PRIMAVERA DESLIGARÁ O APLICATIVO
Youans
54

A melhor maneira de lidar com issoLazyInitializationException é usar a JOIN FETCHdiretiva:

Query query = session.createQuery(
    "from Model m " +
    "join fetch m.modelType " +
    "where modelGroup.id = :modelGroupId"
);

De qualquer forma, NÃO use os seguintes antipadrões, conforme sugerido por algumas das respostas:

Às vezes, uma projeção de DTO é uma escolha melhor do que buscar entidades e, dessa forma, você não receberá nenhuma LazyInitializationException.

Vlad Mihalcea
fonte
Como posso identificar qual chamada está tendo problemas? Estou achando difícil identificar a ligação. Existe alguma maneira ? Para fins de teste que usei FetchType=EAGER, mas essa não é a solução correta, certo?
Shantaram Tupe
Basta usar o log. E EAGER é ruim, sim.
Vlad Mihalcea
Em seguida, você deve usar DTOs ou inicializar todas as associações antes de sair do @Transactionalserviço.
Vlad Mihalcea
2
Devemos defender as melhores práticas empresariais, mas não a solução rápida.
etlds 22/02
21

Eu estava recebendo o mesmo erro de um para muitos relacionamentos na anotação abaixo.

@OneToMany(mappedBy="department", cascade = CascadeType.ALL)

Alterado como abaixo, após adicionar fetch = FetchType.EAGER, funcionou para mim.

@OneToMany(mappedBy="department", cascade = CascadeType.ALL, fetch=FetchType.EAGER)
Smruti R Tripathy
fonte
26
Sim, pode corrigi-lo, mas agora você está carregando toda a árvore de dados. Isto terá impactos desempenho negativo na maioria dos casos
astro8891
13

se você usar o jpa de dados spring, boot inicial, poderá adicionar esta linha em application.properties

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
Shaaban Ebrahim
fonte
7
Isso é considerado como um antipadrão. vladmihalcea.com/...
Sudip Bhandari
9

Esta exceção, devido a quando você ligar session.getEntityById(), a sessão será fechada. Portanto, você precisa anexar novamente a entidade à sessão. A solução Or Easy é apenas configurar default-lazy="false" para o seu entity.hbm.xmlou, se você estiver usando anotações, adicionar @Proxy(lazy=false)à sua classe de entidade.

Reddeiah Pidugu
fonte
5

Eu encontrei o mesmo problema. Acho que outra maneira de corrigir isso é que você pode alterar a consulta para ingressar buscar seu elemento do modelo da seguinte maneira:

Query query = session.createQuery("from Model m join fetch m.element where modelGroup.id = :modelGroupId")
Tony Vu
fonte
4

Isso significa que o objeto que você está tentando acessar não está carregado, portanto, escreva uma consulta que faça uma busca por junção do objeto que você está tentando acessar.

Por exemplo:

Se você estiver tentando obter o ObjectB do ObjectA, em que o ObjectB é uma chave estrangeira no ObjectA.

Inquerir :

SELECT objA FROM ObjectA obj JOIN FETCH obj.objectB objB
rex roy
fonte
3

Existem várias boas respostas aqui que tratam desse erro em um escopo amplo. Encontrei uma situação específica com o Spring Security que tinha uma solução rápida, embora provavelmente não ótima.

Durante a autorização do usuário (imediatamente após o login e a autenticação), eu estava testando uma entidade do usuário para uma autoridade específica em uma classe personalizada que estende o SimpleUrlAuthenticationSuccessHandler.

Minha entidade de usuário implementa UserDetails e possui um conjunto de funções carregadas preguiçosamente que lançou a exceção "org.hibernate.LazyInitializationException - não pôde inicializar o proxy - sem sessão". Alterar esse conjunto de "fetch = FetchType.LAZY" para "fetch = FetchType.EAGER" corrigiu isso para mim.

Coruja noturna
fonte
2

Enfrentou a mesma exceção em diferentes casos de uso.

insira a descrição da imagem aqui

Caso de uso: tente ler dados do banco de dados com projeção DTO.

Solução: use o método get em vez de carregar .

Operação genérica

public class HibernateTemplate {
public static Object loadObject(Class<?> cls, Serializable s) {
    Object o = null;
    Transaction tx = null;
    try {
        Session session = HibernateUtil.getSessionFactory().openSession();
        tx = session.beginTransaction();
        o = session.load(cls, s); /*change load to get*/
        tx.commit();
        session.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return o;
}

}

Classe de persistência

public class Customer {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "Id")
private int customerId;

@Column(name = "Name")
private String customerName;

@Column(name = "City")
private String city;

//constructors , setters and getters

}

Interface CustomerDAO

public interface CustomerDAO 
     {
   public CustomerTO getCustomerById(int cid);
     }

Classe de Objeto de Transferência de Entidade

public class CustomerTO {

private int customerId;

private String customerName;

private String city;

//constructors , setters and getters

}

Classe de fábrica

public class DAOFactory {

static CustomerDAO customerDAO;
static {
    customerDAO = new HibernateCustomerDAO();
}

public static CustomerDAO getCustomerDAO() {
    return customerDAO;
}

}

DAO específico da entidade

public class HibernateCustomerDAO implements CustomerDAO {

@Override
public CustomerTO getCustomerById(int cid) {
    Customer cust = (Customer) HibernateTemplate.loadObject(Customer.class, cid);
    CustomerTO cto = new CustomerTO(cust.getCustomerId(), cust.getCustomerName(), cust.getCity());
    return cto;
}

}

Recuperando Dados: Classe de Teste

CustomerDAO cdao = DAOFactory.getCustomerDAO();
CustomerTO c1 = cdao.getCustomerById(2);
System.out.println("CustomerName -> " + c1.getCustomerName() + " ,CustomerCity -> " + c1.getCity());

Dados atuais

insira a descrição da imagem aqui

Consulta e Saída Gerada pelo Sistema Hibernate

Hibernação: selecione customer0_.Id como Id1_0_0_, customer0_.City como cidade2_0_0_, customer0_.Name como Name3_0_0_ do CustomerLab31 customer0_ onde customer0_.Id =?

CustomerName -> Cody, CustomerCity -> LA

Kms
fonte
1

Se você estiver usando o Grail'sFramework, é simples resolver uma exceção de inicialização lenta usando a Lazypalavra-chave em um campo específico na Classe de Domínio.

Por exemplo:

class Book {
    static belongsTo = [author: Author]
    static mapping = {
        author lazy: false
    }
}

Encontre mais informações aqui

Zeb
fonte
1

No meu caso, um extraviado session.clear()estava causando esse problema.

absin
fonte
1

Isso significa que você está usando JPA ou hibernar em seu código e executando operações de modificação no DB sem fazer a transação da lógica de negócios. A solução mais simples para isso é marcar seu código @Transactional

Aman Goel
fonte
-1

usa session.get (*. class, id); mas não carrega função

Artavazd Manukyan
fonte
3
você pode explicar isso por favor?
Newday
-2

você também pode resolvê-lo adicionando lazy = false ao seu arquivo * .hbm.xml ou pode iniciar seu objeto no Hibernate.init (Object) ao obter o objeto do db

Sandeep Roniyaar
fonte
10
geralmente adicionar lazy = false não é uma boa ideia. por isso preguiçoso é verdadeiro por padrão
MoienGK
O OP disse claramente de antemão que ele não tem permissão para fazer isso.
GingerHead #
-2

Faça as seguintes alterações no servlet-context.xml

    <beans:property name="hibernateProperties">
        <beans:props>

            <beans:prop key="hibernate.enable_lazy_load_no_trans">true</beans:prop>

        </beans:props>
    </beans:property>
ArunDhwaj IIITH
fonte