ChatGPT解决这个技术问题 Extra ChatGPT

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

在我的 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

急切地获取您的权限或使用 OpenSessionInViewFilter。
这正是我试图看看该怎么做。我尝试过的是:List authority = user.getAutorizacoes(),在分配 UsernamePasswordAuthenticationToken 的相同函数内,但仍然不起作用。
@ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
好的,我尝试了,但仍然无法正常工作。我的实体类已更新:github.com/klebermo/webapp_horario_livre/blob/master/src/com/…,我当前的 AuthenticationProvider:github.com/klebermo/webapp_horario_livre/blob/master/src/com/…

N
Neil

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

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

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

在视图中打开会话

hibernate.enable_lazy_load_no_trans

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


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

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

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

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


这被 Hibernate 专家认为是一种反模式stackoverflow.com/users/1025118/vlad-mihalcea
K
KarthikaSrinivasan

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


您使用的是 Hibernate Transactional 还是 JPA Transactional?
我用过休眠
C
Community

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

有两种解决方案。

不要使用延迟加载。在 XML 中设置 lazy=false 或在注解中设置 @OneToMany(fetch = FetchType.EAGER)。使用延迟加载。在 XML 中设置 lazy=true 或在注解中设置 @OneToMany(fetch = FetchType.LAZY)。并在您的 web.xml 中添加 OpenSessionInViewFilter 过滤器

详情见我的帖子。

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


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

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

@Transactional

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


在我的服务类上添加 @Transactional 解决了这个问题。非常感谢。
很高兴它有帮助!
S
Sasha Shpota

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

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

这对我有用。我还测试了添加 @Transactional 的选项,它也可以工作。但我选择了这个选项。
K
Kevin Cruijssen

您可以使用休眠延迟初始化程序。

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

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

v
vk23

首先我想说所有关于惰性和事务的用户都是对的。但在我的情况下,我在测试中使用了@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()));

D
Deb

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

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

m
mcvkr

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

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

因此,您将 @Transactional 注释添加到仅测试方法中。

@Test
@Transactional
public void verifySomethingTestSomething()  {

N
Neuron

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

Hibernate.initialize(your entity);

n
nickshoe

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

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


d
devilcius

将 FetchType 更改为 EAGER 后,我仍然遇到同样的问题。原来我正在使用会话中的用户实例,并且该对象在 DB 中序列化(我使用 Spring session JDBC),所以无论我是否重新启动 Spring Boot,问题仍然存在。我应该从存储库中请求它。


P
Pang

添加注释

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