Як виправити сплячий режим LazyInitializationException: не вдалося ліниво ініціалізувати колекцію ролей, не вдалося ініціалізувати проксі - немає сеансу


108

У користувацькому 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);
    }

}

Мої сутності:

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.
Барт,

це саме те, що я намагаюся подивитися, як це зробити. Я спробував наступне: перелічити <Autorizacoes> Authority = user.getAutorizacoes () , всередині тієї самої функції з виділення UsernamePasswordAuthenticationToken, але все одно не працює.
Kleber Mota,

2
@ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
Барт,

Гаразд, я намагаюся це, але все одно не працюю. Клас My Entity оновлений: github.com/klebermo/webapp_horario_livre/blob/master/src/com/… , мій поточний AuthenticationProvider: github.com/klebermo/webapp_horario_livre/blob/master/src/com/…
Kleber Mota,

Відповіді:


140

Вам потрібно додати fetch=FetchType.EAGERвсередину своїх анотацій ManyToMany, щоб автоматично витягувати дочірні сутності:

@ManyToMany(fetch = FetchType.EAGER)

Кращим варіантом було б реалізувати весняний транзакційний менеджер, додавши до вашого конфігураційного файлу весни таке:

<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
Насправді в моїй програмі налаштовано транзакційний менеджер, і я використовую його у своїх класах DAO. Якщо я намагаюся використовувати в методі Authenticate з AuthenticationProvider , як ви пропонуєте, я отримую повідомлення про помилку , викликаної: java.lang.IllegalArgumentException: Може не набір com.horariolivre.security.CustomAuthenticationProvider поле com.horariolivre.security.SecurityConfig.authenticationProvider до $ Proxy36 . Я отримую ту саму помилку, якщо використовую add fetchType = FetchType.EAGER всередині моєї анотації ManyToMany (і я можу використовувати це лише в одному атрибуті - у мене є три однакові в моєму класі Entity Usuario).
Kleber Mota,

3
Ну, вам потрібно пройти дочірні сутності, які ви хочете використовувати в межах транзакції, щоб уникнути LazyInitializationException. Оскільки ваша транзакційна анотація знаходиться на рівні дао за загальним методом, ви, ймовірно, не захочете цього робити там, тому вам потрібно буде реалізувати клас служби перед дао, який має межі @Transactional, у межах яких ви можете ходити по бажані дочірні організації
jcmwright80,

1
Protip для тих, хто стикається з цим на майбутнє; @Transaction має бути відкритим методом. Якщо ні, це не спрацює. Застереження можуть бути, а можуть і не бути.
Ніколас

використовував тип вибору, і він спрацював ідеально, запитайте, яка різниця у використанні нетерплячого вибору для частини @transactional counter
Austine Gwa

1
@AustineGwa Основна відмінність полягає в тому, що додавання нетерплячого fetchType до об'єднання означатиме, що список дочірніх сутностей завжди буде витягуватися з БД при кожному завантаженні основної сутності, тому існує потенційне погіршення продуктивності, якщо є області функціональності, які вимагають лише дані від основної сутності, тож використання транзакцій та лінивого завантаження дає вам більше контролю над обсягом даних, що витягуються назад, але це повністю залежить від вашої програми та випадків використання, який підхід вам підходить.
jcmwright80

36

Найкращий спосіб обробити це LazyInitializationException- використовувати JOIN FETCHдирективу для всіх сутностей, які вам потрібно взяти.

У будь-якому випадку, НЕ використовуйте такі анти-шаблони, як пропонують деякі відповіді:

Іноді проекція DTO є кращим вибором, ніж отримання об’єктів, і таким чином, ви не отримаєте жодної LazyInitializationException.


1
отримати приєднання еквівалентно нетерплячому завантаженню. Що може бути не завжди здійсненним та ефективним. Також звичайний спосіб отримання об'єкта не здійснюється через запити jpql. Той факт, що відкрите засідання є поглядом, є антипроцесором, - це довгий шлях, і, чесно кажучи, я не погоджуюсь. Очевидно, що його слід використовувати з обережністю, але є багато цілком чудових випадків використання, які від цього виграють.
fer.marino

4
Ні, це НЕ . Open Session in View - це хак і ознака того, що сутності отримуються навіть для проекцій лише для читання. Не існує такого поняття, як безліч абсолютно чудових випадків використання, які б виграли від цього , як би ви не намагалися це виправдати. Немає виправдання для отримання більшої кількості даних, ніж вам насправді потрібно, а також немає виправдання для витоку даних, вилучених поза межами рівня транзакційного сервісу.
Влад Міхалче

Вітаю, Владе, чи можете ви пояснити, чому FETCH JOIN не еквівалентно нетерплячому завантаженню. Я переглядаю цю статтю: blog.arnoldgalovics.com/2017/02/27/… . І там сказано: "Краща ідея - завантажити відношення під час завантаження материнської компанії - компанії - сутності. Це можна зробити за допомогою" Приєднання до отримання ". Тож це нетерпляче завантаження. Чи не так?
Geek

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.


16

У мене теж була ця проблема, коли я проводив модульне тестування. Дуже простим рішенням цієї проблеми є використання анотації @Transactional, яка тримає сеанс відкритим до кінця виконання.


Ви використовуєте Hibernate Transational або JPA Transactional?
jDub9,

1
Я використовував Hibernate
KarthikaSrinivasan

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

7

До вашого класу Custom AuthenticationProvider слід додавати такі відомості:

@Transactional

Це також забезпечить наявність сеансу сплячого режиму.


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, і він також працює. Але я обираю цей варіант.
rick dana 03

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.