0

Yes I've seen similar questions, but none of the answers actually led me to a solution. I'm not using the thread anywhere, and this same code works in another jhipster app, so I'm stumped why this lift+shift is causing Hibernate problems.

public UserDetailsContextMapper userDetailsContextMapper() {
    return new UserDetailsContextMapper() {
        @Override
        public UserDetails mapUserFromContext(
            DirContextOperations ctx, String username,
            Collection<? extends GrantedAuthority> authorities) {
            log.error("2 " + username + " -> " + ctx.toString());
            String lowercaseLogonName = username.toLowerCase();
            Optional<User> userFromDatabase = userRepository.findOneByLogin(lowercaseLogonName);
            if (!userFromDatabase.isPresent()) {
                User ldapUser = new User();
                ldapUser.setLogin(lowercaseLogonName);
                ldapUser.setPassword(RandomStringUtils.random(60)); // We use LDAP password, but the password need to be set
                ldapUser.setActivated(true);
                try {
                    Attribute attribute = ctx.getAttributes().get("mail");
                    ldapUser.setEmail( attribute != null && attribute.size() > 0 ? (String) attribute.get(0) : "");
                    attribute = ctx.getAttributes().get("givenname");
                    ldapUser.setFirstName(attribute != null && attribute.size() > 0 ? (String) attribute.get(0) : "");
                    attribute = ctx.getAttributes().get("sn");
                    ldapUser.setLastName(attribute != null && attribute.size() > 0 ? (String) attribute.get(0) : "");
                } catch (NamingException e) {
                    log.warn("Couldn't get LDAP details for user: " + lowercaseLogonName);
                }

                Set<Authority> newUserAuthorities = new HashSet<>();
                Authority authority = authorityRepository.getOne(AuthoritiesConstants.USER);

                newUserAuthorities.add(authority); <-- This line in the Exception

                ldapUser.setAuthorities(newUserAuthorities);
                ldapUser.setLangKey("en");
                userRepository.saveAndFlush(ldapUser);

            }

Exception:

org.hibernate.LazyInitializationException: could not initialize proxy [com.app.my.let.domain.Authority#ROLE_USER] - no Session
        at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:155)
        at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:268)
        at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73)
        at com.app.my.let.domain.Authority_$$_jvst758_1e.hashCode(Authority_$$_jvst758_1e.java)
        at java.util.HashMap.hash(HashMap.java:339)
        at java.util.HashMap.put(HashMap.java:612)
        at java.util.HashSet.add(HashSet.java:220)
        at com.app.my.let.config.SecurityConfiguration$1.mapUserFromContext(SecurityConfiguration.java:249) <-- code line above

JPA properties:

jpa:
        database-platform: org.hibernate.dialect.SQLServer2012Dialect
        database: SQL_SERVER
        show_sql: true
        format_sql: true
        properties:
            hibernate.id.new_generator_mappings: true
            hibernate.cache.use_second_level_cache: false
            hibernate.cache.use_query_cache: false
            hibernate.generate_statistics: true

EDIT - This resolved my issue:

Optional<Authority> optAuthority = authorityRepository.findById(AuthoritiesConstants.USER);

Authority authority = optAuthority.get();
Andronicus
  • 25,419
  • 17
  • 47
  • 88
patrickjp93
  • 399
  • 4
  • 20
  • When are you opening your JPA session? Does AuthorityRepository open or close a session? – Frank Riccobono Feb 11 '19 at 02:57
  • Pretty sure a session just gets opened when the app starts. There's never any code to openSession or getCurrentSession in either the old app (which works) or the new (doesn't). Added the jpa properties from the yml file up above. – patrickjp93 Feb 11 '19 at 03:06
  • Hibernate uses multiple sessions. It's bad practice to have one long-running session. I'm not familiar with JHipster specifically, but usually there is minimally one session per request and often multiple per complex request. Can you post the code of `authorityRepository.getOne`? You're adding the object to a hash set. The error most likely means that something you're using in your hashcode function is not being initialized by Hibernate before the session was closed. Does `Authority` have any associations? – Frank Riccobono Feb 11 '19 at 03:15
  • It's a generic, empty class. package com.app.my.let.repository; import com.app.my.let.domain.Authority; import org.springframework.data.jpa.repository.JpaRepository; /** * Spring Data JPA repository for the Authority entity. */ public interface AuthorityRepository extends JpaRepository { } – patrickjp93 Feb 11 '19 at 03:18
  • Then yes, that is closing the session https://stackoverflow.com/a/25710391/1245752. How is `Authority.hashCode` implemented? – Frank Riccobono Feb 11 '19 at 03:21
  • @Override public int hashCode() { return name != null ? name.hashCode() : 0; } name is a String – patrickjp93 Feb 11 '19 at 03:23
  • Could you edit your question with full stack? I can't understand why mapUserFromContext() method would be called when app is starting, it's supposed to be used for security while processing requests not at startup. – Gaël Marziou Feb 11 '19 at 07:55
  • @Gael, it's a helper class passed up from a Factory, which was how I was advised as best practice to keep relevant code together (the rememberMe functions are also in this configuration). I actually solved it by just using findById().get(). – patrickjp93 Feb 11 '19 at 22:27

1 Answers1

5

This line:

Authority authority = authorityRepository.getOne(AuthoritiesConstants.USER);

gives you just the reference to an entity. Under the hood it calls EntityManager#getReference. As sais, this is not the entity, just the reference for object-relational mapping purposes. The exception is thrown, because you are trying to cast it outside of transactional environment.

There are couple of solutions. To get the entity, you would have to use EntityManager#find or do it within transaction (use @Transactional over the method). Here you could also call Hibernate.initialize(authority) to fetch the entity from database.

Andronicus
  • 25,419
  • 17
  • 47
  • 88
  • Ah, buried in the spec doc rather than the API doc... I actually solved it by just using findById().get(), which DOES fetch the actual data. – patrickjp93 Feb 11 '19 at 22:24