Cómo corregir la excepción LazyInitializationException de Hibernate: no se pudo inicializar de manera perezosa una colección de roles, no se pudo inicializar el proxy - sin sesión

103

En el AuthenticationProvider personalizado de mi proyecto de primavera, estoy intentando leer la lista de autoridades del usuario registrado, pero me enfrento al siguiente error:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.horariolivre.entity.Usuario.autorizacoes, could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:566)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:186)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:545)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:124)
    at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:266)
    at com.horariolivre.security.CustomAuthenticationProvider.authenticate(CustomAuthenticationProvider.java:45)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:177)
    at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:94)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:211)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:57)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)

Al leer otros temas de aquí en StackOverflow, entiendo que esto sucede debido a la forma en que el marco maneja este tipo de atributo, pero no puedo encontrar ninguna solución para mi caso. ¿Alguien puede señalar qué estoy haciendo mal y qué puedo hacer para solucionarlo?

El código de mi proveedor de autenticación personalizado es:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UsuarioHome usuario;

    public CustomAuthenticationProvider() {
        super();
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        System.out.println("CustomAuthenticationProvider.authenticate");

        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        Usuario user = usuario.findByUsername(username);

        if (user != null) {
            if(user.getSenha().equals(password)) {
                List<AutorizacoesUsuario> list = user.getAutorizacoes();

                List <String> rolesAsList = new ArrayList<String>();
                for(AutorizacoesUsuario role : list){
                    rolesAsList.add(role.getAutorizacoes().getNome());
                }

                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
                for (String role_name : rolesAsList) {
                    authorities.add(new SimpleGrantedAuthority(role_name));
                }

                Authentication auth = new UsernamePasswordAuthenticationToken(username, password, authorities);
                return auth;
            }
            else {
                return null;
            }
        } else {
            return null;
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

}

Mis clases de entidad son:

UsuarioHome.java

@Entity
@Table(name = "usuario")
public class Usuario implements java.io.Serializable {

    private int id;
    private String login;
    private String senha;
    private String primeiroNome;
    private String ultimoNome;
    private List<TipoUsuario> tipoUsuarios = new ArrayList<TipoUsuario>();
    private List<AutorizacoesUsuario> autorizacoes = new ArrayList<AutorizacoesUsuario>();
    private List<DadosUsuario> dadosUsuarios = new ArrayList<DadosUsuario>();
    private ConfigHorarioLivre config;

    public Usuario() {
    }

    public Usuario(String login, String senha) {
        this.login = login;
        this.senha = senha;
    }

    public Usuario(String login, String senha, String primeiroNome, String ultimoNome, List<TipoUsuario> tipoUsuarios, List<AutorizacoesUsuario> autorizacoesUsuarios, List<DadosUsuario> dadosUsuarios, ConfigHorarioLivre config) {
        this.login = login;
        this.senha = senha;
        this.primeiroNome = primeiroNome;
        this.ultimoNome = ultimoNome;
        this.tipoUsuarios = tipoUsuarios;
        this.autorizacoes = autorizacoesUsuarios;
        this.dadosUsuarios = dadosUsuarios;
        this.config = config;
    }

    public Usuario(String login, String senha, String primeiroNome, String ultimoNome, String tipoUsuario, String[] campos) {
        this.login = login;
        this.senha = senha;
        this.primeiroNome = primeiroNome;
        this.ultimoNome = ultimoNome;
        this.tipoUsuarios.add(new TipoUsuario(this, new Tipo(tipoUsuario)));
        for(int i=0; i<campos.length; i++)
            this.dadosUsuarios.add(new DadosUsuario(this, null, campos[i]));
    }

    @Id
    @Column(name = "id", unique = true, nullable = false)
    @GeneratedValue(strategy=GenerationType.AUTO)
    public int getId() {
        return this.id;
    }

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

    @Column(name = "login", nullable = false, length = 16)
    public String getLogin() {
        return this.login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    @Column(name = "senha", nullable = false)
    public String getSenha() {
        return this.senha;
    }

    public void setSenha(String senha) {
        this.senha = senha;
    }

    @Column(name = "primeiro_nome", length = 32)
    public String getPrimeiroNome() {
        return this.primeiroNome;
    }

    public void setPrimeiroNome(String primeiroNome) {
        this.primeiroNome = primeiroNome;
    }

    @Column(name = "ultimo_nome", length = 32)
    public String getUltimoNome() {
        return this.ultimoNome;
    }

    public void setUltimoNome(String ultimoNome) {
        this.ultimoNome = ultimoNome;
    }

    @ManyToMany(cascade=CascadeType.ALL)
    @JoinTable(name = "tipo_usuario", joinColumns = { @JoinColumn(name = "fk_usuario") }, inverseJoinColumns = { @JoinColumn(name = "fk_tipo") })
    @LazyCollection(LazyCollectionOption.TRUE)
    public List<TipoUsuario> getTipoUsuarios() {
        return this.tipoUsuarios;
    }

    public void setTipoUsuarios(List<TipoUsuario> tipoUsuarios) {
        this.tipoUsuarios = tipoUsuarios;
    }

    @ManyToMany(cascade=CascadeType.ALL)
    @JoinTable(name = "autorizacoes_usuario", joinColumns = { @JoinColumn(name = "fk_usuario") }, inverseJoinColumns = { @JoinColumn(name = "fk_autorizacoes") })
    @LazyCollection(LazyCollectionOption.TRUE)
    public List<AutorizacoesUsuario> getAutorizacoes() {
        return this.autorizacoes;
    }

    public void setAutorizacoes(List<AutorizacoesUsuario> autorizacoes) {
        this.autorizacoes = autorizacoes;
    }

    @ManyToMany(cascade=CascadeType.ALL)
    @JoinTable(name = "dados_usuario", joinColumns = { @JoinColumn(name = "fk_usuario") }, inverseJoinColumns = { @JoinColumn(name = "fk_dados") })
    @LazyCollection(LazyCollectionOption.TRUE)
    public List<DadosUsuario> getDadosUsuarios() {
        return this.dadosUsuarios;
    }

    public void setDadosUsuarios(List<DadosUsuario> dadosUsuarios) {
        this.dadosUsuarios = dadosUsuarios;
    }

    @OneToOne
    @JoinColumn(name="fk_config")
    public ConfigHorarioLivre getConfig() {
        return config;
    }

    public void setConfig(ConfigHorarioLivre config) {
        this.config = config;
    }
}

AutorizacoesUsuario.java

@Entity
@Table(name = "autorizacoes_usuario", uniqueConstraints = @UniqueConstraint(columnNames = "id"))
public class AutorizacoesUsuario implements java.io.Serializable {

    private int id;
    private Usuario usuario;
    private Autorizacoes autorizacoes;

    public AutorizacoesUsuario() {
    }

    public AutorizacoesUsuario(Usuario usuario, Autorizacoes autorizacoes) {
        this.usuario = usuario;
        this.autorizacoes = autorizacoes;
    }

    @Id
    @Column(name = "id", unique = true, nullable = false)
    @GeneratedValue(strategy=GenerationType.AUTO)
    public int getId() {
        return this.id;
    }

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

    @OneToOne
    @JoinColumn(name = "fk_usuario", nullable = false, insertable = false, updatable = false)
    public Usuario getUsuario() {
        return this.usuario;
    }

    public void setUsuario(Usuario usuario) {
        this.usuario = usuario;
    }

    @OneToOne
    @JoinColumn(name = "fk_autorizacoes", nullable = false, insertable = false, updatable = false)
    public Autorizacoes getAutorizacoes() {
        return this.autorizacoes;
    }

    public void setAutorizacoes(Autorizacoes autorizacoes) {
        this.autorizacoes = autorizacoes;
    }

}

Autorizacoes.java

@Entity
@Table(name = "autorizacoes")
public class Autorizacoes implements java.io.Serializable {

    private int id;
    private String nome;
    private String descricao;

    public Autorizacoes() {
    }

    public Autorizacoes(String nome) {
        this.nome = nome;
    }

    public Autorizacoes(String nome, String descricao) {
        this.nome = nome;
        this.descricao = descricao;
    }

    @Id
    @Column(name = "id", unique = true, nullable = false)
    @GeneratedValue(strategy=GenerationType.AUTO)
    public int getId() {
        return this.id;
    }

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

    @Column(name = "nome", nullable = false, length = 16)
    public String getNome() {
        return this.nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    @Column(name = "descricao", length = 140)
    public String getDescricao() {
        return this.descricao;
    }

    public void setDescricao(String descricao) {
        this.descricao = descricao;
    }
}

Proyecto completo disponible en github

-> https://github.com/klebermo/webapp_horario_livre

Kleber Mota
fuente
Busque a sus autoridades con entusiasmo o utilice un OpenSessionInViewFilter.
Bart
es exactamente lo que estoy tratando de ver cómo hacerlo. Lo que probé fue esto: List <Autorizacoes> autoridad = user.getAutorizacoes () , dentro de la misma función de la asignación de UsernamePasswordAuthenticationToken, pero aún no funciona.
Kleber Mota
2
@ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
Bart
Ok, lo intento, pero sigo sin funcionar. Mi clase de entidad actualizada: github.com/klebermo/webapp_horario_livre/blob/master/src/com/… , My Current
Kleber Mota

Respuestas:

134

fetch=FetchType.EAGERDebe agregar dentro de sus anotaciones ManyToMany para retirar automáticamente las entidades secundarias:

@ManyToMany(fetch = FetchType.EAGER)

Una mejor opción sería implementar un transactionManager de primavera agregando lo siguiente a su archivo de configuración de primavera:

<bean id="transactionManager"
    class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<tx:annotation-driven />

Luego puede agregar una anotación @Transactional a su método de autenticación de esta manera:

@Transactional
public Authentication authenticate(Authentication authentication)

Esto luego iniciará una transacción de la base de datos durante la duración del método de autenticación, lo que permitirá recuperar cualquier colección diferida de la base de datos cuando intente usarla.

jcmwright80
fuente
1
En realidad, tengo el transactionManager configurado en mi aplicación y lo uso en mis clases de DAO. Si intento usar el método de autenticación de AuthenticationProvider como sugieres, obtengo un error Causado por: java.lang.IllegalArgumentException: No se puede establecer com.horariolivre.security.CustomAuthenticationProvider campo com.horariolivre.security.SecurityConfig.authenticationProvider en $ Proxy36 . Recibo el mismo error si uso add fetchType = FetchType.EAGER dentro de mi anotación ManyToMany (y puedo usar esto en un solo atributo; tengo tres del mismo tipo en mi clase de entidad Usuario).
Kleber Mota
3
Bueno, debe recorrer las entidades secundarias que desea usar dentro de una transacción para evitar la LazyInitializationException. Dado que su anotación transaccional está en el nivel de dao en un método genérico, probablemente no querrá hacer eso allí, por lo que deberá implementar una clase de servicio frente al dao que tenga los límites @Transactional desde los cuales puede caminar entidades secundarias deseadas
jcmwright80
Protip para alguien que se encuentre con esto en el futuro; @Transaction debe estar en un método público. Si no es así, esto no funcionará. Puede que haya o no advertencias.
Nicolas
usó el tipo de búsqueda y funcionó perfectamente, pregunte cuál es la diferencia en el uso de la búsqueda ansiosa para la contraparte de @transactional
Austine Gwa
33

La mejor manera de manejarloLazyInitializationException es usar la JOIN FETCHdirectiva para todas las entidades que necesita buscar.

De todos modos, NO use los siguientes Anti-Patrones como lo sugieren algunas de las respuestas:

A veces, una proyección DTO es una mejor opción que buscar entidades y, de esta manera, no obtendrá ninguna LazyInitializationException.

Vlad Mihalcea
fuente
1
fetch join es equivalente a obtener ansiosamente. Lo que puede no ser siempre factible ni eficiente. Además, la forma habitual de obtener objetos no es a través de consultas jpql. El hecho de que la sesión abierta sea vista es un antipatrón es una posibilidad remota y, sinceramente, no estoy de acuerdo. Debe usarse con precaución, obviamente, pero hay muchos casos de uso perfectamente buenos que se benefician de él.
fer.marino
4
No, NO lo es . Open Session in View es un truco y una señal de que las entidades se obtienen incluso para proyecciones de solo lectura. No existen muchos casos de uso perfectamente buenos que se beneficien de él , sin importar cuánto intente justificarlo. No hay excusa para obtener más datos de los que realmente necesita, así como tampoco hay excusa para filtrar datos extraídos fuera de los límites de la capa de servicios transaccionales.
Vlad Mihalcea
Hola Vlad, ¿Puedes explicar por qué FETCH JOIN no es equivalente a una carga ansiosa? Estoy leyendo este artículo: blog.arnoldgalovics.com/2017/02/27/… . Y dice "Una mejor idea es cargar la relación en el momento en que está cargando la entidad matriz - Compañía -. Esto se puede hacer con un Fetch Join". Entonces es una carga impaciente. ¿No es así?
Geek
1
Liderar con entusiasmo significa contribuir FetchType.EAGERa sus asociaciones. JOIN FETCH es para FetchType.LAZYasociaciones que deben buscarse con entusiasmo en el momento de la consulta.
Vlad Mihalcea
25

Agregar la siguiente propiedad a su persistence.xml puede resolver su problema temporalmente

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

Como @ vlad-mihalcea dijo que es un antipatrón y no resuelve completamente el problema de la inicialización perezosa, inicialice sus asociaciones antes de cerrar la transacción y use DTO en su lugar.

Mohammad-Hossein Jamali
fuente
16

Yo también tuve este problema cuando estaba haciendo pruebas unitarias. Una solución muy simple a este problema es usar la anotación @Transactional que mantiene la sesión abierta hasta el final de la ejecución.

KarthikaSrinivasan
fuente
¿Está utilizando Hibernate Transational o JPA Transactional?
jDub9
1
Usé
11

La razón es que cuando usa la carga diferida, la sesión se cierra.

Hay dos soluciones.

  1. No use carga diferida.

    Establecer lazy=falseen XML o Establecer @OneToMany(fetch = FetchType.EAGER)en anotación.

  2. Utilice carga diferida.

    Establecer lazy=trueen XML o Establecer @OneToMany(fetch = FetchType.LAZY)en anotación.

    y agrega OpenSessionInViewFilter filtertuweb.xml

Detalle Ver mi publicación.

https://stackoverflow.com/a/27286187/1808417

saneryee
fuente
1
OpenSessionInViewFilter también es un anti-patrón. También sugiero que nunca establezca un mapeo en EAGER, ya que habrá muchos casos en los que no necesitará esos datos en la colección EAGER y obtendrá muchos más datos de los que necesitan esos casos de uso y reducirá en gran medida su rendimiento. Por favor, mantenga todas las asignaciones LAZY y agregue búsquedas de combinación a sus consultas.
user1567291
6

Puede usar el inicializador perezoso de hibernación.

A continuación se muestra el código que puede consultar.
Aquí PPIDOestá el objeto de datos que quiero recuperar

Hibernate.initialize(ppiDO);
if (ppiDO instanceof HibernateProxy) {
    ppiDO = (PolicyProductInsuredDO) ((HibernateProxy) ppiDO).getHibernateLazyInitializer()
        .getImplementation();
    ppiDO.setParentGuidObj(policyDO.getBasePlan());
    saveppiDO.add(ppiDO);
    proxyFl = true;
}
Mitesh C
fuente
6

Su clase Custom AuthenticationProvider debe estar anotada con lo siguiente:

@Transaccional

Esto asegurará la presencia de la sesión de hibernación allí también.

Bilal Ahmed Yaseen
fuente
4

Para aquellos que tienen este problema con la recopilación de enumeraciones, aquí se explica cómo resolverlo:

@Enumerated(EnumType.STRING)
@Column(name = "OPTION")
@CollectionTable(name = "MY_ENTITY_MY_OPTION")
@ElementCollection(targetClass = MyOptionEnum.class, fetch = EAGER)
Collection<MyOptionEnum> options;
Sasha Shpota
fuente
Esto funciona para mi. También probé la opción de agregar @Transactional y también funciona. Pero elijo esta opción.
rick dana
2

En primer lugar, me gustaría decir que todos los usuarios que dijeron sobre pereza y transacciones tenían razón. Pero en mi caso hubo una ligera diferencia en que utilicé el resultado del método @Transactional en una prueba y eso estaba fuera de la transacción real, así que obtuve esta excepción perezosa.

Mi método de servicio:

@Transactional
User get(String uid) {};

Mi código de prueba:

User user = userService.get("123");
user.getActors(); //org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role

Mi solución a esto fue envolver ese código en otra transacción como esta:

List<Actor> actors = new ArrayList<>();
transactionTemplate.execute((status) 
 -> actors.addAll(userService.get("123").getActors()));
vk23
fuente
1

Creo que en lugar de habilitar la búsqueda ansiosa, tiene sentido reinicializar su entidad donde sea necesario para evitar una LazyInitializationExceptionexcepción

Hibernate.initialize(your entity);
Pravin
fuente
0

Para aquellos que usan JaVers , dada una clase de entidad auditada, es posible que desee ignorar las propiedades que causan la LazyInitializationExceptionexcepción (por ejemplo, utilizando la @DiffIgnoreanotación).

Esto le dice al marco que ignore esas propiedades al calcular las diferencias de objeto, por lo que no intentará leer desde la base de datos los objetos relacionados fuera del alcance de la transacción (lo que provoca la excepción).

nickshoe
fuente
0

Una práctica común es poner un valor por @Transactionalencima de su clase de servicio.

@Service
@Transactional
public class MyServiceImpl implements MyService{
...
}
Debutante
fuente
-1

Agregar la anotación

@JsonManagedReference

Por ejemplo:

@ManyToMany(cascade=CascadeType.ALL)
@JoinTable(name = "autorizacoes_usuario", joinColumns = { @JoinColumn(name = "fk_usuario") }, inverseJoinColumns = { @JoinColumn(name = "fk_autorizacoes") })
@JsonManagedReference
public List<AutorizacoesUsuario> getAutorizacoes() {
    return this.autorizacoes;
}
Mauricio
fuente