首页 文章

如何使用转换器的Spring控制器解决“org.hibernate.LazyInitializationException:无法初始化代理 - 无会话”

提问于
浏览
0

我正在从以下项目迁移项目:

  • Spring 4.2.1 - > 5.0.0

  • Spring Data Gosling - >凯

  • Hibernate 4.3.8 - > 5.8.0

当我在控制器方法中访问来自我的数据库的对象时,我正在运行“org.hibernate.LazyInitializationException:无法初始化代理 - 无会话” .

这是我的代码的精简版:

// CustomUser.java
@Entity
@Access(AccessType.FIELD)
@Table(name = "users")
public class CustomUser implements Serializable {
    ...
    @Id
    @GeneratedValue//details omitted
    @GenericGenerator//details omitted
    @Column(name = "id", insertable = true, updatable = true, unique = true, nullable = false)
    private Long id;

    @Column(name = "name")
    private String name;

    public String getName() { return name; }
}

// UserController.java
@RequestMapping(value = "/user/{userId}/", method = RequestMethod.GET)
public String showUser(@PathVariable("userId") CustomUser user) {
    System.out.println("user name is [" + user.getName() + "]");
    return "someTemplate";
}

// UserService.java
@Service
public class UserServiceImpl implements UserService {
    @Autowired UserRepository userRepository;

    @Override
    public User findUserById(Long userId) {
        return userRepository.getOne(userId);
    }
}

// UserRepository.java
public interface UserRepository extends JpaRepository<CustomUser, Long> { }

// UserConverter.java
@Component
public class UserConverter implements Converter<String, CustomUser> {
    @Autowired UserService userService;

    @Override
    public CustomUser convert(String userId) {
        CustomUser user = userService.findUserById(SomeUtilClass.parseLong(userId));
        return user;
    }
}

还有一个@Configuration WebMvcConfigurerAdapter类,它自动装配UserConverter实例并将其添加到FormatterRegistry .

在开始此升级之前,我可以点击:http://server:port/user/123/

并且Spring将采用“123”字符串,UserConverter :: convert方法将触发并点击Postgres数据库以查找具有该id的用户,并且我将在控制器的“showUser”方法中找回CustomUser对象 .

但是,现在我得到了org.hibernate.LazyInitializationException . 当我尝试在“showUser”方法中打印出用户名时 - 甚至只是“println(user)”而不访问字段时,会发生这种情况 .

我从搜索中得到的大部分信息都表明,这个异常来自于一个对象有一个延迟加载的子对象集合(比如我的CustomUser有一个Permission对象的集合或映射到不同数据库的东西)表) . 但在这种情况下,我甚至没有这样做,这只是对象上的一个字段 .

我最好的猜测是,这是因为某种类型的休眠会话在转换器完成其工作后被终止,所以然后回到控制器中我没有有效的会话 . (虽然我再也不知道为什么CustomUser对象回来不可用,我不是要尝试获取子集合) .

我已将Hibernate注释“@Proxy(lazy = false)”添加到我的CustomUser.java中,如果我这样做,问题就会消失 . 但是,我不确定这是一个很好的解决方案 - 出于性能原因,我真的不认为我想要急切地 grab 一切 .

我也尝试用@Transactional注释各种事物(服务方法,控制器方法等);我没有得到这个工作,但我仍然相当新的 Spring 天,我可能会在错误的地方尝试或误解应该做什么 .

在我的所有Entity类中,有没有更好的方法来处理这个问题而不仅仅是“@Proxy(lazy = false)”?

1 回答

  • 5

    直接问题来自 userRepository.getOne(userId) 的使用 . 它使用 EntityManager.getReferenceSimpleJpaRepository 中实现 . 此方法仅返回一个仅包含其ID的代理 . 只有当一个属性被访问时,才会延迟加载 . 这包括简单的属性,例如 name .

    立即修复是使用 findOne ,它应该加载实体的所有热切属性,这些属性应包含简单属性,因此异常消失 .

    请注意,这会稍微改变转换器的行为 . 当数据库中不存在id时,当前版本不会失败,因为它永远不会检查 . 新的将获得一个空的Optional,你必须将它转换为null或抛出异常 .

    但是存在(或可能存在)更大的隐藏问题:实体仍然是分离的,因为在没有显式事务划分的情况下,事务只跨越单个存储库调用 . 因此,如果您以后想要访问延迟加载的属性,则可能会再次遇到相同的问题 . 我看到了各种选择:

    • 保持原样,意识到您不能访问延迟加载的属性 .

    • 确保在调用转换器之前启动事务 .

    • @Transactional 标记控制器并在控制器中再次加载用户 .

相关问题