Java 如何解决;org.hibernate.LazyInitializationException:无法初始化代理-无会话“;使用转换器的弹簧控制器 我正从一个项目迁移到: 弹簧4.2.1->5.0.0 春季数据小鹅->凯 Hibernate 4.3.8->5.8.0

Java 如何解决;org.hibernate.LazyInitializationException:无法初始化代理-无会话“;使用转换器的弹簧控制器 我正从一个项目迁移到: 弹簧4.2.1->5.0.0 春季数据小鹅->凯 Hibernate 4.3.8->5.8.0,java,hibernate,spring-data,Java,Hibernate,Spring Data,在控制器方法中访问来自数据库的对象时,我正在运行获取“org.hibernate.LazyInitializationException:无法初始化代理-无会话” 以下是我的代码的精简版本: // CustomUser.java @Entity @Access(AccessType.FIELD) @Table(name = "users") public class CustomUser implements Serializable { ... @Id @Generat

在控制器方法中访问来自数据库的对象时,我正在运行获取“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;
    }
}
//CustomUser.java
@实体
@访问权限(AccessType.FIELD)
@表(name=“users”)
公共类CustomUser实现了可序列化{
...
@身份证
@GeneratedValue//省略了详细信息
@GenericGenerator//省略详细信息
@列(name=“id”,insertable=true,updateable=true,unique=true,nullable=false)
私人长id;
@列(name=“name”)
私有字符串名称;
公共字符串getName(){return name;}
}
//UserController.java
@RequestMapping(value=“/user/{userId}/”,method=RequestMethod.GET)
公共字符串showUser(@PathVariable(“userId”)CustomUser){
System.out.println(“用户名为[“+user.getName()+”]);
返回“someTemplate”;
}
//UserService.java
@服务
公共类UserServiceImpl实现UserService{
@自动连线用户存储库用户存储库;
@凌驾
公共用户findUserById(长用户ID){
返回userRepository.getOne(userId);
}
}
//UserRepository.java
公共接口UserRepository扩展了JpaRepository{}
//UserConverter.java
@组成部分
公共类UserConverter实现转换器{
@自动连线用户服务;
@凌驾
公共CustomUser转换(字符串用户ID){
CustomUser=userService.findUserById(SomeUtilClass.parseLong(userId));
返回用户;
}
}
还有一个@Configuration WebMVCConfigureAdapter类,它自动连接UserConverter实例并将其添加到FormatterRegistry

在开始升级之前,我可以点击:

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

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

我从搜索中找到的大多数信息都表明,这个异常来自于对象具有延迟加载的子对象集合(例如,如果我的CustomUser具有权限对象集合或映射到不同数据库表的某个对象)。但在这种情况下,我甚至没有这样做,这只是对象上的一个字段

我目前最好的猜测是,这是由于某种休眠会话在转换器工作后被终止,因此返回控制器时,我没有有效的会话。(尽管我也不知道为什么CustomUser对象返回时不可用,但我没有尝试获取子集合)

我已经将Hibernate注释“@Proxy(lazy=false)”添加到CustomUser.java中,如果我这样做,问题就会消失。但是,我不确定这是否是一个好的解决方案——出于性能方面的考虑,我真的不想走上急切获取所有内容的道路

我还尝试用@Transactional注释各种事物(服务方法、控制器方法等);我还没有让它发挥作用,但我对Spring还是相当陌生的,我可能在错误的地方尝试过,或者误解了它应该做什么


除了在我所有的实体类上使用“@Proxy(lazy=false)”,还有更好的方法来处理这个问题吗?

直接的问题来自使用
userRepository.getOne(userId)
。它使用
EntityManager.getReference在
SimpleParepository
中实现。此方法仅返回一个代理,该代理仅包含其id。并且仅当访问属性时,才会延迟加载这些属性。在您的案例中,这包括像
name
这样的简单属性

最直接的解决方法是使用
findOne
,它应该加载实体的所有渴望的属性,这些属性应该包括简单的属性,这样异常就会消失

请注意,这将略微改变转换器的行为。当数据库中不存在id时,当前版本不会失败,因为它从不检查。新的将获得一个空的可选值,您必须将其转换为null或引发异常

但是隐藏还有(或者可能有)一个更大的问题:实体仍然是分离的,因为在没有明确的事务划分的情况下,事务只跨单个存储库调用。因此,如果以后要访问延迟加载的属性,可能会再次遇到相同的问题。我认为有多种选择可供考虑:

  • 保持原样,注意不要访问延迟加载的属性

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

  • 使用
    @Transactional
    标记控制器,并(再次)在控制器中加载用户


  • 直接的问题来自使用
    userRepository.getOne(userId)
    。它使用
    EntityManager.getReference在
    SimpleParepository
    中实现。此方法仅返回一个代理,该代理仅包含其id。并且仅当访问属性时,才会延迟加载这些属性。在您的案例中,这包括像
    name
    这样的简单属性

    最直接的解决方法是使用
    findOne
    ,它应该加载实体的所有渴望的属性,这些属性应该包括简单的属性,这样异常就会消失

    请注意,这将稍微改变conv的行为