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

Tho*_*ler 0 java hibernate spring-data

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

  • 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

但是,现在我得到了org.hibernate.LazyInitializationException.甚至只是"的println(用户)",而无需访问现场 - 当我试图在"showUser"的方法来打印出用户的名字出现这种情况的.

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

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

我已经添加了Hibernate的注解"@Proxy(懒惰=假)"我CustomUser.java,如果我这样做,问题消失.但是,我不知道这是一个很好的解决方案 - 由于性能原因,我真的不觉得我要下去的热切地获取一切的道路.

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

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

Jen*_*der 6

当前的问题来自于使用userRepository.getOne(userId).它在SimpleJpaRepository使用中实现EntityManager.getReference.此方法仅返回一个仅包含其ID的代理.只有当一个属性被访问时,才会延迟加载.这包括像name您的情况一样的简单属性.

立即修复是使用findOne哪个应该加载实体的所有应该包含简单属性的渴望属性,因此异常消失.

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

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

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

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

  3. 在控制器中标记控制器@Transactional并将用户(再次)加载到控制器中.