如何修复 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 这里的其他主题时,我明白这是由于框架处理这种属性的方式造成的,但我不能找到任何解决方案。有人能指出我做错了什么,以及我能做些什么来弥补吗?

我的自定义身份验证提供程序的代码是:

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


}

我的实体类是:

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

AutorizacoesUsurario.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

333016 次浏览

您需要在 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)

然后,这将在身份验证方法期间启动一个 db 事务,允许在您尝试使用这些方法时从 db 检索任何延迟收集。

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

有两种解决方案。

  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

添加注释

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

Xml中添加以下属性可以暂时解决问题

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

正如@vlad-mihalcea 所说,这是一个反模式,不能完全解决惰性初始模式问题,在关闭事务之前初始化关联,而是使用 dTO。

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

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

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

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

无论如何,不要使用以下反模式的建议,由一些答案:

有时候,使用 DTO 投影比获取实体更好,这样就不会得到任何 LazyInitializationException

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

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

Hibernate.initialize(your entity);

首先,我想说的是,所有提到懒惰和事务的用户都是正确的。但是在我的例子中,有一个细微的差别,我在测试中使用了 @ Transactional方法的 result,这是在实际事务之外的,所以我得到了这个惰性异常。

我的服务方式:

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

对于那些有这个问题的人 收集枚举在这里是如何解决它:

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

您的自定义身份验证提供程序类应该注释以下内容:

@ Transactional

这也将确保冬眠会话的存在。

对于那些使用 贾维斯的用户,给定一个经过审计的实体类,您可能希望 忽略引起 LazyInitializationException异常的属性(例如,通过使用 @DiffIgnore注释)。

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

一个常见的做法是将 @Transactional置于服务类的上方。

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

在某些情况下,您不需要将@Transactional 注释放到您的服务方法中,比如在集成测试中,您可以将@Transactional 添加到测试方法中。你可以获得器官,冬眠。当测试仅从数据库中进行选择的方法时发生 LazyInitializationException,该方法不需要是事务性的。例如,当您试图加载一个具有延迟获取关系的实体类时,如下所示,可能会导致以下情况:

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

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

@Test
@Transactional
public void verifySomethingTestSomething()  {

在将 FetchType 更改为 EAGER 之后,我仍然有同样的问题。事实证明,我使用的是来自 session 的用户实例,对象是在 DB 中序列化的(我使用 Spring session JDBC) ,所以无论我是否重新启动 Spring 启动,问题都会持续存在。我应该向仓库申请的。