1

I have a problem with lazy initialization in my Spring Boot application. I have an entity with lazy field Role, and I they have LazyInitializationException in My Spring Security (UserDetailsService) method, but in controller it's ok. Can you explaine to me how Spring Boot works with fetch = FetchType.LAZY ? Why it does not work Spring Security UserDetailsService and work in controller methods? I did not find any guide about this. Thanks!

Spring Boot :

@SpringBootApplication
public class App {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(App.class, args);
    }
}

An Entity:

@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
public class Users {
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "Users_Role", joinColumns = @JoinColumn(name = "User_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
    private Set<Role> roles = new HashSet<Role>();
}

My Service:

@Transactional(readOnly = true)//does not matter
 public Users getUserByLogin(String login) {
        return usersRepository.findOneByLogin(login);
    }

    @Transactional(readOnly = true)
    public Users getUserByLoginWithRoles(String login) {
        Users oneByLogin = usersRepository.findOneByLogin(login);
        logger.debug("User was initialize with Roles: " + oneByLogin.getRoles().size()); // force initialize of roles and it works!
        return oneByLogin;
    }

   @Transactional(readOnly = true)//does not matter
    public Users testGetUser() {
        Users oneByLogin = usersRepository.getOne(1L);
        return oneByLogin;
    }

    @Transactional(readOnly = true)//does not matter
    public Users testFindUser() {
        Users oneByLogin = usersRepository.findOne(1L);
        return oneByLogin;
    }

And I have Spring Security UserDetailsService:

@Service
    public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private Services services;

    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
        Users user;
        Users userFetchedViaGet = services.testGetUser();
        Users userFetchedViaCustomMethod = services.getUserByLogin(login);
        Users userFetchedViaFind = services.testFindUser();
        Users userFetchedWithRoles = services.getUserByLoginWithRoles(login);
        try {
            userFetchedViaGet.getRoles().add(new Role("test"));
        } catch (Exception e) {
            e.printStackTrace();//LazyInitializationException: failed to lazily initialize a collection of role: , could not initialize proxy - no Session
        }
        try {
            userFetchedViaCustomMethod.getRoles().add(new Role("test"));
        } catch (Exception e) {
            e.printStackTrace();//LazyInitializationException: failed to lazily initialize a collection of role: , could not initialize proxy - no Session
        }
        try {
            userFetchedViaFind.getRoles().add(new Role("test")); //LazyInitializationException: failed to lazily initialize a collection of role: , could not initialize proxy - no Session
        } catch (Exception e) {
            e.printStackTrace();
        }
        //some code
        }
    }

And My Controller (All methods works! But there is have to excpetion happen, because there is no session and Lazy fetch type):

@RequestMapping(value = "/test", method = RequestMethod.GET)
    public String test() {      
        Users userFetchedViaGet = services.testGetUser();
        Users userFetchedViaCustomMethod = services.getUserByLogin("ADMIN");
        Users userFetchedViaFind = services.testFindUser();
        Users userFetchedWithRoles = services.getUserByLoginWithRoles("ADMIN");
        try {
            userFetchedViaGet.getRoles().add(new Role("test"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            userFetchedViaCustomMethod.getRoles().add(new Role("test"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            userFetchedViaFind.getRoles().add(new Role("test"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        //some code
    }
m_br0_89
  • 85
  • 2
  • 8

1 Answers1

0

you need a transaction wrapping the entire thing - over your facade - the test() method for this to work, but this isn't really a good practice.

the way to go is to have your service wrap the api method in a transaction, and return an object that is fully loaded - with all child nodes. this object can be a simple bean that is constructed from the Users class, a hashmap built from the objects or even a string like you wanted to return from the rest call.

  • Hello! Spring Data Jpa wrapping methods like testGetUser() in transaction readOnly=true by default. And I did it in method getUserByLoginWithRoles. Why the same methods like "testGetUser" doesn't work in Security class and work in controller? And why in controller I don't receive Lazy exception with Spring Boot? Why Spring Boot initialized my lazy collections? How Spring Boot does it? – m_br0_89 Jan 16 '17 at 22:06
  • 1
    hi. the thing is that the transaction ends right after using the spring data method, since you're using a lazy fetch between the User and the Role entities, the spring data method would only fetch the user without its roles. if wrapped in a larger transaction context it should be possible to traverse the user-role graph and fetch all roles from the database. you can obviously change the lazy fetch to eager if possible in this case, or write a simple hql query that forces fetching this join if you don't want to make this eager. – Richard Hendricks Jan 17 '17 at 06:21