Spring Boot 和 fetchType=Lazy

Posted

技术标签:

【中文标题】Spring Boot 和 fetchType=Lazy【英文标题】:Spring Boot and fetchType=Lazy 【发布时间】:2017-05-31 06:37:15 【问题描述】:

我的 Spring Boot 应用程序中存在延迟初始化问题。我有一个带有惰性字段Role 的实体,我在我的Spring Security (UserDetailsService) 方法中有LazyInitializationException,但在控制器中没关系。 你能向我解释一下 Spring Boot 如何与 fetch = FetchType.LAZY 一起工作吗?为什么它不能在 Spring Security UserDetailsService 和控制器方法中工作? 我没有找到任何关于此的指南。谢谢!

春季启动:

@SpringBootApplication
public class App 

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

一个实体:

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

我的服务:

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

我有 Spring Security UserDetailsS​​ervice:

@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
        
    

和我的控制器(所有方法都有效!但是必须发生异常,因为没有会话和延迟获取类型):

@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
    

【问题讨论】:

你试过这个***.com/questions/11746499/… 您好!那篇文章与我的问题没有任何共同之处。 你能解决这个问题吗?我其实也有同样的问题 【参考方案1】:

您需要一个交易来包装整个事物 - 在您的外观上 - test() 方法才能使其工作,但这并不是一个真正的好习惯。

要走的路是让您的服务将 api 方法包装在事务中,并返回一个完全加载的对象 - 包含所有子节点。这个对象可以是一个从 Users 类构造的简单 bean, 从对象构建的哈希图,甚至是你想从 rest 调用中返回的字符串。

【讨论】:

您好!默认情况下,Spring Data Jpa 在事务 readOnly=true 中包装诸如 testGetUser() 之类的方法。我在方法 getUserByLoginWithRoles 中做到了。为什么像“testGetUser”这样的方法在安全类中不起作用而在控制器中不起作用?为什么在控制器中我没有收到 Spring Boot 的延迟异常?为什么 Spring Boot 初始化了我的惰性集合? Spring Boot 是怎么做到的? 嗨。问题是事务在使用 spring data 方法后立即结束,因为您在 User 和 Role 实体之间使用惰性获取,所以 spring data 方法只会获取没有角色的用户。如果包装在更大的事务上下文中,则应该可以遍历用户角色图并从数据库中获取所有角色。在这种情况下,如果可能的话,你显然可以将惰性获取更改为渴望,或者如果你不想让这个渴望,你可以编写一个简单的 hql 查询来强制获取这个连接。

以上是关于Spring Boot 和 fetchType=Lazy的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 2. Jackson json 序列化或 Spring Data JPA FetchType.Lazy

如何在 Spring Boot 中使用 JsonIgnore 来停止无限循环? [复制]

spring boot:无法删除或更新父行:外键约束失败

如何在Jpa中使用所选实体创建行,RestController Spring Boot

JPA 左连接查询不会在 Spring Boot 中实例化子集合?

Fetch.lazy 不适用于 spring boot jpa,一定是啥问题?