Jak naprawić Hibernate LazyInitializationException: nie udało się leniwie zainicjować kolekcji ról, nie można zainicjować serwera proxy - brak sesji


108

W niestandardowym AuthenticationProvider z mojego wiosennego projektu próbuję odczytać listę uprawnień zalogowanego użytkownika, ale napotykam następujący błąd:

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)

Czytając inne tematy stąd w StackOverflow, rozumiem, że dzieje się tak ze względu na sposób, w jaki tego typu atrybuty są obsługiwane przez framework, ale nie mogę znaleźć żadnego rozwiązania dla mojego przypadku. Ktoś może wskazać, co robię źle i co mogę zrobić, aby to naprawić?

Kod mojego niestandardowego dostawcy uwierzytelniania to:

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

}

Moje klasy jednostek to:

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

Pełny projekt dostępny na github

-> https://github.com/klebermo/webapp_horario_livre


Chętnie pobieraj swoje władze lub użyj OpenSessionInViewFilter.
Bart

właśnie tak próbuję spojrzeć, jak to zrobić. Próbowałem tak: List <Autorizacoes> authority = user.getAutorizacoes () , wewnątrz tej samej funkcji z alokacji UsernamePasswordAuthenticationToken, ale nadal nie działa.
Kleber Mota

2
@ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
Bart

Ok, próbuję, ale nadal nie działa. Moja klasa jednostek została zaktualizowana: github.com/klebermo/webapp_horario_livre/blob/master/src/com/… , mój obecny dostawca
Kleber Mota

Odpowiedzi:


140

Musisz dodać fetch=FetchType.EAGERwewnątrz adnotacji ManyToMany, aby automatycznie wycofywać elementy podrzędne:

@ManyToMany(fetch = FetchType.EAGER)

Lepszą opcją byłoby zaimplementowanie wiosennego menedżera transakcji poprzez dodanie do pliku konfiguracyjnego wiosny:

<bean id="transactionManager"
    class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<tx:annotation-driven />

Następnie możesz dodać adnotację @Transactional do swojej metody uwierzytelniania, na przykład:

@Transactional
public Authentication authenticate(Authentication authentication)

Spowoduje to rozpoczęcie transakcji bazy danych na czas trwania metody uwierzytelniania, umożliwiając pobranie dowolnej leniwej kolekcji z bazy danych podczas próby ich użycia.


1
Właściwie mam skonfigurowany transactionManager w mojej aplikacji i używam go w moich klasach DAO. Jeśli spróbuję użyć w metodzie uwierzytelniania z AuthenticationProvider, jak sugerujesz, pojawia się błąd. . Otrzymuję ten sam błąd, jeśli używam add fetchType = FetchType.EAGER wewnątrz mojej adnotacji ManyToMany (i mogę tego użyć tylko w jednym atrybucie - mam trzy tego samego rodzaju w mojej klasie Entity Usuario).
Kleber Mota

3
Cóż, musisz przejść przez jednostki podrzędne, których chcesz użyć w transakcji, aby uniknąć LazyInitializationException. Ponieważ twoja adnotacja transakcyjna jest na poziomie dao w metodzie ogólnej, prawdopodobnie nie będziesz chciał tego robić, więc będziesz musiał zaimplementować klasę usługi przed dao, która ma granice @Transactional, w których możesz przejść pożądane byty potomne
jcmwright80

1
Ochrona dla kogoś, kto spotka to w przyszłości; @Transaction musi być metodą publiczną. Jeśli tak nie jest, to nie zadziała. Ostrzeżenia mogą być, ale nie muszą.
Nicolas

użył typu pobierania i zadziałał idealnie, pytanie jaka jest różnica w używaniu chętnego pobierania do części licznika @transactional
Austine Gwa

1
@AustineGwa Główną różnicą jest dodanie gorliwego fetchType do sprzężenia, co oznacza, że ​​lista jednostek podrzędnych będzie zawsze wycofywana z bazy danych za każdym razem, gdy jednostka główna zostanie załadowana, więc istnieje potencjalny spadek wydajności, jeśli istnieją obszary funkcjonalności, które wymagają dane z jednostki głównej, więc korzystanie z transakcji i leniwego ładowania zapewnia większą kontrolę nad ilością wycofywanych danych, ale całkowicie zależy od aplikacji i przypadków użycia, które podejście jest dla Ciebie odpowiednie.
jcmwright80

36

Najlepszym sposobem obsługi tego LazyInitializationExceptionjest użycie JOIN FETCHdyrektywy dla wszystkich jednostek, które musisz pobrać.

W każdym razie NIE używaj następujących Anty-wzorców, zgodnie z sugestiami niektórych odpowiedzi:

Czasami projekcja DTO jest lepszym wyborem niż pobieranie jednostek, a w ten sposób nie otrzymasz żadnej LazyInitializationException.


1
fetch join jest równoznaczne z gorliwym pobieraniem. Co nie zawsze może być wykonalne ani skuteczne. Również zwykłym sposobem pobierania obiektu nie jest użycie zapytań jpql. Fakt, że sesja otwarta jest poglądem, jest antywzorem, jest odległa i szczerze mówiąc, nie zgadzam się. Oczywiście należy z niego korzystać ostrożnie, ale jest wiele doskonałych przypadków użycia, w których można z niego korzystać.
fer.marino

4
Nie, to NIE . Otwarta sesja w widoku to hack i znak, że jednostki są pobierane nawet w przypadku projekcji tylko do odczytu. Nie ma czegoś takiego jak wiele idealnie dobrych przypadków użycia, które na tym skorzystają , bez względu na to , jak bardzo będziesz próbował to uzasadnić. Nie ma usprawiedliwienia dla pobierania większej ilości danych, niż jest to naprawdę potrzebne, jak również nie ma usprawiedliwienia dla pobierania danych wyciekających poza granice warstwy usług transakcyjnych.
Vlad Mihalcea

Cześć Vlad, Czy możesz wyjaśnić, dlaczego FETCH JOIN nie jest równoznaczne z gorliwym ładowaniem. Przeglądam ten artykuł: blog.arnoldgalovics.com/2017/02/27/… . I mówi: "Lepszym pomysłem jest załadowanie relacji w momencie ładowania jednostki nadrzędnej - firmy -. Można to zrobić za pomocą funkcji Fetch Join". Jest to więc chętny do załadunku. Prawda?
Geek

2
Chęć prowadzenia oznacza dodawanie FetchType.EAGERdo swoich skojarzeń. JOIN FETCH jest przeznaczony dla FetchType.LAZYskojarzeń, które muszą być chętnie pobierane w czasie zapytania.
Vlad Mihalcea,

25

Dodanie następującej właściwości do pliku persistence.xml może tymczasowo rozwiązać problem

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

Jak powiedział @ vlad-mihalcea, jest to anty-wzór i nie rozwiązuje całkowicie problemu leniwej inicjalizacji, zainicjuj swoje powiązania przed zamknięciem transakcji i użyj zamiast tego DTO.


16

Ja też miałem ten problem, kiedy robiłem testy jednostkowe. Bardzo prostym rozwiązaniem tego problemu jest użycie adnotacji @Transactional, która utrzymuje sesję otwartą do końca wykonania.


Czy używasz Hibernate Transational czy JPA Transactional?
jDub9

1
Użyłem Hibernate
KarthikaSrinivasan

11

Powodem jest to, że gdy używasz leniwego ładowania, sesja jest zamykana.

Istnieją dwa rozwiązania.

  1. Nie używaj leniwego obciążenia.

    Ustaw lazy=falsew XML lub Ustaw w @OneToMany(fetch = FetchType.EAGER)adnotacji.

  2. Użyj leniwego obciążenia.

    Ustaw lazy=truew XML lub Ustaw w @OneToMany(fetch = FetchType.LAZY)adnotacji.

    i dodaj OpenSessionInViewFilter filterswojeweb.xml

Szczegóły Zobacz mój post.

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


1
OpenSessionInViewFilter jest również anty-wzorcem. Sugeruję również, aby nigdy nie ustawiać mapowania na EAGER, ponieważ będzie wiele przypadków, w których nie będziesz potrzebować tych danych w kolekcji EAGER i będziesz pobierać znacznie więcej danych, niż wymagają tego przypadki użycia, i znacznie zmniejszysz wydajność. Zachowaj wszystkie mapowania LENIWE i zamiast tego dodaj pobieranie dołączeń do zapytań.
user1567291

7

Twoja klasa Custom AuthenticationProvider powinna być oznaczona następującą adnotacją:

@Transactional

Zapewni to również obecność sesji hibernacji.


6

Możesz użyć hibernacji leniwego inicjatora.

Poniżej znajduje się kod, do którego możesz się odwołać.
Oto PPIDOobiekt danych, który chcę odzyskać

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

4

Dla tych, którzy mają ten problem ze zbieraniem wyliczeń, oto jak go rozwiązać:

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

To działa dla mnie. Przetestowałem też możliwość dodania @Transactional i też działa. Ale wybieram tę opcję.
rick dana

2

Przede wszystkim chciałbym powiedzieć, że wszyscy użytkownicy, którzy mówili o lenistwie i transakcjach, mieli rację. Ale w moim przypadku była niewielka różnica polegająca na tym, że użyłem wyniku metody @Transactional w teście i była to poza rzeczywistą transakcją, więc dostałem ten leniwy wyjątek.

Moja metoda obsługi:

@Transactional
User get(String uid) {};

Mój kod testowy:

User user = userService.get("123");
user.getActors(); //org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role

Moim rozwiązaniem było umieszczenie tego kodu w innej transakcji, takiej jak ta:

List<Actor> actors = new ArrayList<>();
transactionTemplate.execute((status) 
 -> actors.addAll(userService.get("123").getActors()));

1

Uważam, że zamiast umożliwiać szybkie pobieranie, sensowne jest ponowne zainicjowanie jednostki tam, gdzie jest to konieczne, aby uniknąć LazyInitializationExceptionwyjątku

Hibernate.initialize(your entity);

0

Dla tych, którzy używają JaVers , mając sprawdzoną klasę jednostki, możesz chcieć zignorować właściwości powodujące LazyInitializationExceptionwyjątek (np. Używając@DiffIgnore adnotacji).

Informuje to framework, aby ignorował te właściwości podczas obliczania różnic obiektów, więc nie będzie próbował odczytać z bazy danych powiązanych obiektów poza zakresem transakcji (powodując w ten sposób wyjątek).


0

Powszechną praktyką jest umieszczanie @Transactionalklasy powyżej klasy usług.

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

-1

Dodaj adnotację

@JsonManagedReference

Na przykład:

@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;
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.