Как исправить Hibernate LazyInitializationException: не удалось лениво инициализировать набор ролей, не удалось инициализировать прокси-сервер - нет сеанса

104

В пользовательском 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);
    }

}

Мои классы Entity:

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 <Autorizacoes> author = user.getAutorizacoes () внутри той же функции из выделения UsernamePasswordAuthenticationToken, но все еще не работает.
Клебер Мота
2
@ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
Барт
Хорошо, я пробую, но все равно не работает. Мой класс Entity обновлен: github.com/klebermo/webapp_horario_livre/blob/master/src/com/… , мой текущий поставщик
Kleber Mota

Ответы:

135

Вам нужно либо добавить fetch=FetchType.EAGERв свои аннотации ManyToMany, чтобы автоматически извлекать дочерние объекты:

@ManyToMany(fetch = FetchType.EAGER)

Лучшим вариантом было бы реализовать Spring transactionManager, добавив в файл конфигурации Spring следующее:

<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)

Затем это запустит транзакцию базы данных на время действия метода аутентификации, позволяя извлекать любую ленивую коллекцию из базы данных по мере того, как вы пытаетесь их использовать.

jcmwright80
источник
1
Фактически, в моем приложении настроен transactionManager, и я использую его в своих классах DAO. Если я попытаюсь использовать метод проверки подлинности из AuthenticationProvider, как вы предлагаете, я получаю сообщение об ошибке, вызванной: java.lang.IllegalArgumentException: не удается установить поле com.horariolivre.security.CustomAuthenticationProvider com.horariolivre.security.SecurityConfig.authenticationProvider на $ Proxy36. . Я получаю ту же ошибку, если использую add fetchType = FetchType.EAGER внутри своей аннотации ManyToMany (и я могу использовать это только в одном атрибуте - у меня есть три таких же типа в моем классе Entity Usuario).
Клебер Мота
3
Что ж, вам нужно пройтись по дочерним объектам, которые вы хотите использовать в транзакции, чтобы избежать LazyInitializationException. Поскольку ваша транзакционная аннотация находится на уровне dao в универсальном методе, вы, вероятно, не захотите делать это там, поэтому вам нужно будет реализовать класс обслуживания перед dao, который имеет границы @Transactional, из которых вы можете пройти желаемые дочерние сущности
jcmwright80 02
Совет для тех, кто столкнется с этим в будущем; @Transaction должна быть в общедоступном методе. В противном случае это не сработает. Предупреждений может быть, а может и не быть.
Николас
использовал тип выборки, и он работал отлично, вопрос, в чем разница в использовании активной выборки для счетчика
@transactional
33

Лучший способ справиться с этимLazyInitializationException - использовать JOIN FETCHдирективу для всех сущностей, которые вам нужно получить.

В любом случае НЕ ИСПОЛЬЗУЙТЕ следующие антипаттерны, как указано в некоторых ответах:

Иногда проекция DTO - лучший выбор, чем получение сущностей, и таким образом вы ничего не получите LazyInitializationException.

Влад Михалча
источник
1
соединение выборки эквивалентно активной выборке. Что не всегда может быть осуществимо или эффективно. Также обычный способ получения объекта - не через запросы jpql. Тот факт, что открытый сеанс просмотра - это антипаттерн, маловероятен, и, честно говоря, я не согласен. Очевидно, что его следует использовать с осторожностью, но есть много прекрасных вариантов использования, которые извлекают из этого пользу.
fer.marino 07
4
Нет, это НЕ . Open Session in View - это взлом и признак того, что сущности извлекаются даже для проекций только для чтения. Не существует такой вещи, как множество прекрасных вариантов использования, которые извлекли бы из этого пользу , как бы вы ни пытались это оправдать. Нет оправдания для получения большего количества данных, чем вам действительно нужно, а также нет оправдания для утечки данных, полученных за пределами уровня транзакционного сервиса.
Влад Михалча
Привет, Влад, пожалуйста, объясните, почему FETCH JOIN не эквивалентен активной загрузке. Я просматриваю эту статью: blog.arnoldgalovics.com/2017/02/27/… . В нем говорится: «Лучше загрузить отношение во время загрузки родительской сущности - компании. Это можно сделать с помощью Fetch Join». Так что это жадная загрузка. Не так ли?
Geek
1
Стремительное лидерство означает добавление FetchType.EAGERк вашим ассоциациям. JOIN FETCH предназначен для FetchType.LAZYассоциаций, которые необходимо быстро извлекать во время запроса.
Влад Михалча 08
25

Добавление следующего свойства в ваш persistence.xml может временно решить вашу проблему

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

Поскольку @ vlad-mihalcea сказал, что это антипаттерн и не решает полностью проблему ленивой инициализации, инициализируйте свои ассоциации перед закрытием транзакции и вместо этого используйте DTO.

Мохаммад-Хоссейн Джамали
источник
16

У меня тоже была эта проблема, когда я проводил модульное тестирование. Очень простое решение этой проблемы - использовать аннотацию @Transactional, которая сохраняет сеанс открытым до конца выполнения.

КартикаСринивасан
источник
Вы используете Hibernate Transational или JPA Transactional?
jDub9
1
Я использовал Hibernate
KarthikaSrinivasan
11

Причина в том, что при использовании отложенной загрузки сессия закрывается.

Есть два решения.

  1. Не используйте ленивую загрузку.

    Установить lazy=falseв @OneToMany(fetch = FetchType.EAGER)аннотации XML или Установить .

  2. Используйте ленивую загрузку.

    Установить lazy=trueв @OneToMany(fetch = FetchType.LAZY)аннотации XML или Установить .

    и добавьте OpenSessionInViewFilter filterв свойweb.xml

Подробно см. Мой пост.

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

Saneryee
источник
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;
}
Митеш С
источник
6

Ваш класс Custom AuthenticationProvider должен быть аннотирован следующим образом:

@Transactional

Это также обеспечит наличие сеанса гибернации.

Билал Ахмед Ясин
источник
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()));
vk23
источник
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;
}
Маурисио
источник