如何解决Spring Data JPA中的LazyInitializationException?

问题描述 投票:0回答:8

我有两个具有一对多关系的类。当我尝试访问延迟加载的集合时,我得到

LazyInitializationException
。 我已经在网上搜索了一段时间,现在我知道我得到了异常,因为用于加载保存集合的类的会话已关闭。 但是,我没有找到解决方案(或者至少我不理解它们)。基本上我有这些课程:

用户

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private long id;

    @OneToMany(mappedBy = "creator")
    private Set<Job> createdJobs = new HashSet<>();

    public long getId() {
        return id;
    }

    public void setId(final long id) {
        this.id = id;
    }

    public Set<Job> getCreatedJobs() {
        return createdJobs;
    }

    public void setCreatedJobs(final Set<Job> createdJobs) {
        this.createdJobs = createdJobs;
    }

}

用户存储库

public interface UserRepository extends JpaRepository<User, Long> {}

用户服务

@Service
@Transactional
public class UserService {

    @Autowired
    private UserRepository repository;

    boolean usersAvailable = false;

    public void addSomeUsers() {
        for (int i = 1; i < 101; i++) {
            final User user = new User();

            repository.save(user);
        }

        usersAvailable = true;
    }

    public User getRandomUser() {
        final Random rand = new Random();

        if (!usersAvailable) {
            addSomeUsers();
        }

        return repository.findOne(rand.nextInt(100) + 1L);
    }

    public List<User> getAllUsers() {
        return repository.findAll();
    }

}

工作

@Entity
@Table(name = "job")
@Inheritance
@DiscriminatorColumn(name = "job_type", discriminatorType = DiscriminatorType.STRING)
public abstract class Job {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private long id;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User creator;

    public long getId() {
        return id;
    }

    public void setId(final long id) {
        this.id = id;
    }

    public User getCreator() {
        return creator;
    }

    public void setCreator(final User creator) {
        this.creator = creator;
    }

}

作业存储库

public interface JobRepository extends JpaRepository<Job, Long> {}

就业服务

@Service
@Transactional
public class JobService {

    @Autowired
    private JobRepository repository;

    public void addJob(final Job job) {
        repository.save(job);
    }

    public List<Job> getJobs() {
        return repository.findAll();
    }

    public void addJobsForUsers(final List<User> users) {
        final Random rand = new Random();

        for (final User user : users) {
            for (int i = 0; i < 20; i++) {
                switch (rand.nextInt(2)) {
                case 0:
                    addJob(new HelloWorldJob(user));
                    break;
                default:
                    addJob(new GoodbyeWorldJob(user));
                    break;
                }
            }
        }
    }

}

应用程序

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class App {

    public static void main(final String[] args) {
        final ConfigurableApplicationContext context = SpringApplication.run(App.class);
        final UserService userService = context.getBean(UserService.class);
        final JobService jobService = context.getBean(JobService.class);

        userService.addSomeUsers();                                 // Generates some users and stores them in the db
        jobService.addJobsForUsers(userService.getAllUsers());      // Generates some jobs for the users

        final User random = userService.getRandomUser();            // Picks a random user

        System.out.println(random.getCreatedJobs());
    }

}

我经常读到会话必须绑定到当前线程,但我不知道如何使用 Spring 的基于注释的配置来做到这一点。 有人可以指出我该怎么做吗?

附注我想使用延迟加载,因此急切加载是没有选择的。

java spring hibernate
8个回答
30
投票

基本上,您需要在事务内部获取惰性数据。如果您的服务等级是

@Transactional
,那么当您在其中时一切都应该没问题。一旦退出服务类,如果您尝试
get
惰性集合,您将得到该异常,该异常位于您的
main()
方法中,第
System.out.println(random.getCreatedJobs());
行。

现在,归结为您的服务方法需要返回什么。如果

userService.getRandomUser()
预计返回一个已初始化作业的用户,以便您可以操作它们,那么该方法有责任获取它。使用 Hibernate 执行此操作的最简单方法是调用
Hibernate.initialize(user.getCreatedJobs())


8
投票

考虑使用 JPA 2.1,以及 实体图

延迟加载通常是 JPA 2.0 的一个问题。您必须在实体 FetchType.LAZY 或 FetchType.EAGER 处进行定义,并确保关系在事务中初始化。

这可以通过以下方式完成:

  • 使用读取实体的特定查询
  • 或者通过访问业务代码中的关系(每个关系的附加查询)。

这两种方法都远非完美,JPA 2.1 实体图是更好的解决方案


4
投票

您有 2 个选择。

选项 1 : 正如 BetaRide 提到的,使用

EAGER
获取策略

选项2:使用hibernate从数据库获取

user
后,添加以下代码行来加载集合元素:

Hibernate.initialize(user.getCreatedJobs())

这告诉 hibernate 初始化集合元素


0
投票

对于那些无法使用 JPA 2.1 但希望保留在控制器中返回实体的可能性的人(而不是带有写入响应的 String/JsonNode/byte[]/void):

仍然可以在交易中构建 DTO,该 DTO 将由控制器返回。

@RestController
@RequestMapping(value = FooController.API, produces = MediaType.APPLICATION_JSON_VALUE)
class FooController{

    static final String API = "/api/foo";

    private final FooService fooService;

    @Autowired
    FooController(FooService fooService) {
        this.fooService= fooService;
    }

    @RequestMapping(method = GET)
    @Transactional(readOnly = true)
    public FooResponseDto getFoo() {
        Foo foo = fooService.get();
        return new FooResponseDto(foo);
    }
}

0
投票

您应该通过在上下文配置类中添加

@EnableTransactionManagement
注释来启用 Spring 事务管理器。

由于两个服务都有

@Transactional
注释并且其默认
value
属性为
TxType.Required
,因此只要事务管理器处于打开状态,当前事务将在服务之间共享。因此,会话应该可用,并且您不会得到
LazyInitializationException


0
投票

找到一篇很棒的文章(作者:Vlad Mihalcea),内容涉及处理

LazyInitializationException
的最佳方法:

https://vladmihalcea.com/the-best-way-to-handle-the-lazyinitializationexception/

总结

LazyInitializationException
是一种代码味道,因为它可能隐藏了使用实体而不是 DTO 投影 的事实。有时,获取实体是正确的选择,在这种情况下,
JOIN FETCH
指令是初始化 LAZY Hibernate 代理的最简单且最好的方法。


0
投票

只需将

spring.jpa.open-in-view=true
添加到您的 application.properties


-2
投票

改变

@OneToMany(mappedBy = "creator")
private Set<Job> createdJobs = new HashSet<>();

@OneToMany(fetch = FetchType.EAGER, mappedBy = "creator")
private Set<Job> createdJobs = new HashSet<>();

或者在你的服务中使用Hibernate.initialize,具有相同的效果。

© www.soinside.com 2019 - 2024. All rights reserved.