如何修复Hibernate LazyInitializationException:无法延迟初始化角色集合,无法初始化代理-没有会话


108

在我的spring项目的自定义AuthenticationProvider中,我尝试读取已记录用户的权限列表,但遇到以下错误:

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)

从StackOverflow的此处阅读其他主题,我理解这种情况是由于框架处理此类属性的方式而发生的,但是我无法为我的情况找到任何解决方案。有人可以指出我做错了什么,我该怎么做才能解决?

我的Custom AuthenticationProvider的代码是:

@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);
    }

}

我的实体类是:

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;
    }
}

完整项目可在github上找到

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


急切地获取您的权限或使用OpenSessionInViewFilter。
巴特2014年

正是我尝试着看怎么做。我尝试过的是:List <Autorizacoes>权限= user.getAutorizacoes(),它来自分配UsernamePasswordAuthenticationToken的同一函数,但仍然无法正常工作。
Kleber Mota 2014年

2
@ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
巴特2014年

好的,我尝试了一下,但是还是行不通。我的实体类更新:github.com/klebermo/webapp_horario_livre/blob/master/src/com/...,我目前的AuthenticationProvider:github.com/klebermo/webapp_horario_livre/blob/master/src/com/...
克莱伯莫塔

Answers:


140

您需要fetch=FetchType.EAGER在ManyToMany批注中添加以自动拉回子实体:

@ManyToMany(fetch = FetchType.EAGER)

更好的选择是通过将以下内容添加到您的spring配置文件中来实现spring transactionManager:

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

<tx:annotation-driven />

然后可以将@Transactional批注添加到您的authenticate方法中,如下所示:

@Transactional
public Authentication authenticate(Authentication authentication)

然后,这将在authenticate方法期间启动db事务,从而允许在尝试使用它们时从db中检索任何惰性集合。


1
实际上,我在应用程序中配置了transactionManager,并在DAO类中使用了它。如果我尝试像您建议的那样使用从AuthenticationProvider进行身份验证的方法,则会收到错误原因:java.lang.IllegalArgumentException:无法将com.horariolivre.security.CustomAuthenticationProvider字段com.horariolivre.security.SecurityConfig.authenticationProvider设置为$ Proxy36。如果在ManyToMany批注中使用添加的fetchType = FetchType.EAGER,则会出现相同的错误(我只能在一个属性中使用它-我的Entity类Usuario中有三个相同的类型)。
Kleber Mota

3
那么,您需要遍历要在Transaction中使用的子实体,以避免LazyInitializationException。由于您的事务批注在通用方法的dao级别上,您可能不想在那做,因此您将需要在dao之前实现一个服务类,该服务类具有@Transactional边界,您可以从中进行操作。所需的子实体
jcmwright80

1
对于将来遇到此问题的人的提示;@Transaction必须使用公共方法。如果不是,这将不起作用。可能有也可能没有任何警告。
尼古拉斯

使用了访存类型,并且效果很好,请问对@transactional计数器部分使用渴望的访存有什么区别
Austine Gwa

1
@AustineGwa主要区别在于,将急切的fetchType添加到联接中将意味着无论何时加载主实体,子实体的列表将始终从数据库中拉回,因此,如果某些功能区域仅需要来自主实体的数据,因此使用事务和延迟加载可以使您更好地控制要拉回的数据量,但这完全取决于您的应用程序和用例,哪种方法最适合您。
jcmwright80 '20

36

处理的最好方法LazyInitializationException是对JOIN FETCH需要提取的所有实体使用伪指令。

无论如何,请勿使用某些答案所建议的以下反模式:

有时,DTO投影比获取实体是更好的选择,这样一来,您将一无所获LazyInitializationException


1
fetch join等同于渴望获取。这可能并不总是可行或高效的。同样,获取对象的常用方法也不是通过jpql查询。开放式会议是一种反模式是一个长期的事实,老实说,我不同意。显然,应谨慎使用它,但有许多完美的用例可从中受益。
fer.marino

4
不,不是。View中的Open Session是骇人听闻的迹象,表明即使对于只读投影也可以获取实体。有一个这样的东西很多完全正常使用的情况下,从受益,不管你怎么努力来证明它。没有任何借口可以获取比实际需要更多的数据,也没有借口可以将获取的数据泄漏到事务服务层的边界之外。
Vlad Mihalcea

嗨,弗拉德,你能解释一下为什么FETCH JOIN不等于渴望加载。我正在阅读这篇文章:blog.arnoldgalovics.com/2017/02/27/… 。它说:“一个更好的主意是在加载父(公司)实体时加载关系。这可以通过Fetch Join(获取联接)完成”。所以这是一个渴望的加载。是不是
极客

2
渴望领导意味着增加FetchType.EAGER您的协会。JOIN FETCH用于FetchType.LAZY需要在查询时急切获取的关联。
Vlad Mihalcea

25

将以下属性添加到您的persistence.xml可能会暂时解决您的问题

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

正如@ vlad-mihalcea所说,这是一种反模式,不能完全解决延迟初始化问题,请在关闭事务之前初始化关联,并改用DTO。



11

原因是当您使用延迟加载时,会话将关闭。

有两种解决方案。

  1. 不要使用延迟加载。

    设置lazy=false为XML或设置@OneToMany(fetch = FetchType.EAGER)为注释。

  2. 使用延迟加载。

    设置lazy=true为XML或设置@OneToMany(fetch = FetchType.LAZY)为注释。

    并添加OpenSessionInViewFilter filter您的web.xml

详细信息请参阅我的帖子。

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


1
OpenSessionInViewFilter也是一种反模式。我还建议不要设置到EAGER的映射,因为在许多情况下,您不需要EAGER集合中的数据,并且您将提取比这些用例更多的数据,从而大大降低性能。请保留所有映射LAZY,然后将联接提取添加到您的查询中。
user1567291


6

您可以使用休眠惰性初始化器。

下面是您可以参考的代码。
PPIDO是我要检索的数据对象

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

4

对于那些在枚举集合中遇到此问题的人这里是解决方法:

@Enumerated(EnumType.STRING)
@Column(name = "OPTION")
@CollectionTable(name = "MY_ENTITY_MY_OPTION")
@ElementCollection(targetClass = MyOptionEnum.class, fetch = EAGER)
Collection<MyOptionEnum> options;

这对我有用。我还测试了添加@Transactional的选项,它也可以工作。但是我选择这个选项。
里克达纳

2

首先,我想说的是所有关于懒惰和事务的用户都是正确的。但就我而言,我在测试中使用了@Transactional方法的结果,但在实际事务之外,因此我有一个懒惰的异常,这是有一点不同的。

我的服务方式:

@Transactional
User get(String uid) {};

我的测试代码:

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

我的解决方案是将该代码包装在另一个事务中,如下所示:

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

1

我相信,与其启用急切的获取功能,不如在需要避免LazyInitializationException异常的情况下重新初始化您的实体是有意义的

Hibernate.initialize(your entity);

0

对于使用JaVers的用户,给定已审核的实体类,您可能希望忽略导致LazyInitializationException异常的属性(例如,通过使用@DiffIgnore注释)。

这告诉框架在计算对象差异时忽略这些属性,因此它不会尝试从数据库中读取事务范围之外的相关对象(因此会导致异常)。


0

常见的做法是将@Transactional服务等级放在首位。

@Service
@Transactional
public class MyServiceImpl implements MyService{
...
}

-1

添加注释

@JsonManagedReference

例如:

@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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.