【问题标题】:如何修复 Hibernate LazyInitializationException:无法延迟初始化角色集合,无法初始化代理 - 无会话
【发布时间】:2014-05-14 08:10:52
【问题描述】:

在我的 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 中的此处阅读其他主题,我了解这是由于框架处理此类属性的方式而发生的,但我无法为我的案例找到任何解决方案。有人可以指出我做错了什么以及我能做些什么来解决它?

我的自定义 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

【问题讨论】:

标签: java spring hibernate spring-mvc spring-security


【解决方案1】:

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

@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 注释:

@Transactional
public Authentication authenticate(Authentication authentication)

这将在身份验证方法的持续时间内启动一个数据库事务,允许在您尝试使用它们时从数据库中检索任何惰性集合。

【讨论】:

  • 实际上,我在我的应用程序中配置了 transactionManager,并在我的 DAO 类中使用它。如果我尝试按照您的建议在 AuthenticationProvider 中使用方法进行身份验证,我会收到错误 Caused by: java.lang.IllegalArgumentException: Can not set com.horariolivre.security.CustomAuthenticationProvider field com.horariolivre.security.SecurityConfig.authenticationProvider到 $Proxy36。如果我在 ManyToMany 注释中使用 add fetchType=FetchType.EAGER,我会得到同样的错误(我只能在一个属性中使用它——我的实体类 Usuario 中有三个相同的类型)。
  • 那么您需要遍历要在事务中使用的子实体以避免 LazyInitializationException。由于您的事务注释位于通用方法的 dao 级别,因此您可能不想在那里执行此操作,因此您需要在具有 @Transactional 边界的 dao 前面实现一个服务类,您可以在其中行走所需的子实体
  • 为将来遇到此问题的人提供提示; @Transaction 需要在公共方法上。如果不是,这将不起作用。可能有也可能没有任何警告。
  • 使用了 fetch 类型,它工作得很好,质疑使用 Eager fetch 到 @transactional 计数器部分有什么区别
  • @AustineGwa 主要的区别是将急切的 fetchType 添加到连接中意味着子实体列表总是会在主实体加载时从数据库中拉回,因此如果出现潜在的性能影响有些功能区域只需要来自主实体的数据,因此使用事务和延迟加载可以让您更好地控制被拉回的数据量,但这完全取决于您的应用程序和用例,哪种方法适合你。
【解决方案2】:

处理LazyInitializationException 的最佳方式是对您需要获取的所有实体使用JOIN FETCH 指令。

无论如何,请不要按照某些答案的建议使用以下反模式:

有时,DTO projection 是比获取实体更好的选择,这样您就不会得到任何 LazyInitializationException

【讨论】:

  • fetch join 等价于eager fetching。这可能并不总是可行或有效的。获取对象的常用方法也不是通过 jpql 查询。开放会话是视图是一种反模式这一事实是一个长期的目标,老实说,我不同意。显然,应该谨慎使用它,但有许多非常好的用例可以从中受益。
  • 不,它不是。 Open Session in View 是 hack 并且表明即使是只读投影也会获取实体。没有像许多完美的用例可以从中受益这样的事情,无论你多么努力地证明它是正确的。获取比您真正需要更多的数据没有任何借口,也没有任何借口泄露在事务服务层边界之外获取的数据。
  • 嗨,弗拉德,您能解释一下为什么 FETCH JOIN 不等同于急切加载。我正在阅读这篇文章:blog.arnoldgalovics.com/2017/02/27/…。它说“一个更好的主意是在加载父 - 公司 - 实体时加载关系。这可以通过 Fetch Join 来完成”。所以这是一个急切的加载。不是吗?
  • 渴望领先意味着将FetchType.EAGER 添加到您的关联中。 JOIN FETCH 用于FetchType.LAZY 需要在查询时快速获取的关联。
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

    我在进行单元测试时也遇到了这个问题。解决此问题的一个非常简单的方法是使用 @Transactional 注释,该注释使会话保持打开状态直到执行结束。

    【讨论】:

    • 您使用的是 Hibernate Transational 还是 JPA Transactional?
    • 我用过休眠
    【解决方案5】:

    您的自定义 AuthenticationProvider 类应使用以下注释:

    @Transactional

    这将确保那里也存在休眠会话。

    【讨论】:

    • 在我的服务类上添加@Transactional 解决了这个问题。非常感谢。
    • 很高兴它有帮助!
    【解决方案6】:

    原因是当你使用延迟加载时,会话被关闭了。

    有两种解决方案。

    1. 不要使用延迟加载。

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

    2. 使用延迟加载。

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

      并在您的web.xml 中添加OpenSessionInViewFilter filter

    详情见我的帖子。

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

    【讨论】:

    • OpenSessionInViewFilter 也是一种反模式。我还建议永远不要设置到 EAGER 的映射,因为在很多情况下,您不需要 EAGER 集合中的数据,并且您将提取比这些用例所需的更多的数据,并大大降低您的性能。请保持所有映射 LAZY 并将连接提取添加到您的查询中。
    • “并将连接提取添加到您的查询中”。这是什么意思?
    【解决方案7】:

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

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

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

    【讨论】:

      【解决方案8】:

      对于那些遇到枚举集合这个问题的人,这里是如何解决它:

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

      【讨论】:

      • 这对我有用。我还测试了添加 @Transactional 的选项,它也可以工作。但我选择了这个选项。
      【解决方案9】:

      首先我想说,所有提到懒惰和交易的用户都是对的。但在我的情况下,我在测试中使用了 @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()));
      

      【讨论】:

        【解决方案10】:

        在某些情况下,您不需要将@Transactional 注解添加到您的服务方法中,例如集成测试,您只需将@Transactional 添加到您的测试方法中即可。测试仅从数据库中选择的方法时,您可以获得 org.hibernate.LazyInitializationException,该方法不需要是事务性的。例如,当您尝试加载具有如下延迟获取关系的实体类时,可能会导致:

        @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
        private List<Item> items;
        

        所以你只将@Transactional注解添加到测试方法中。

        @Test
        @Transactional
        public void verifySomethingTestSomething()  {
        

        【讨论】:

          【解决方案11】:

          一种常见的做法是在您的服务类上方放置一个@Transactional

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

          【讨论】:

            【解决方案12】:

            我相信与其启用 Eager fetch,不如在需要避免LazyInitializationException 异常的地方重新初始化您的实体

            Hibernate.initialize(your entity);
            

            【讨论】:

              【解决方案13】:

              对于那些使用JaVers 的人,给定一个审计实体类,您可能希望ignore 导致LazyInitializationException 异常的属性(例如,通过使用@DiffIgnore 注释)。

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

              【讨论】:

                【解决方案14】:

                添加注释

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

                【讨论】:

                  猜你喜欢
                  • 2019-05-21
                  • 2017-01-30
                  • 2014-05-14
                  • 2017-08-13
                  • 1970-01-01
                  • 2016-07-06
                  • 2015-07-02
                  • 2015-04-01
                  • 2018-02-25
                  相关资源
                  最近更新 更多