Java 如何解决 Spring Data JPA 中的 LazyInitializationException?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26507446/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to resolve LazyInitializationException in Spring Data JPA?
提问by stevecross
I have to classes that have a one-to-many-relation. When I try to access the lazily loaded collection I get the LazyInitializationException
.
I searching the web for a while now and now I know that I get the exception because the session that was used to load the class that holds the collection is closed.
However I did not find a solution (or at least I did not understand them). Basically I have those classes:
我必须学习具有一对多关系的课程。当我尝试访问延迟加载的集合时,我得到了LazyInitializationException
. 我现在在网上搜索了一段时间,现在我知道我得到了异常,因为用于加载保存集合的类的会话已关闭。但是我没有找到解决方案(或者至少我不理解它们)。基本上我有这些课程:
User
用户
@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;
}
}
UserRepository
用户库
public interface UserRepository extends JpaRepository<User, Long> {}
UserService
用户服务
@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();
}
}
Job
工作
@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;
}
}
JobRepository
作业库
public interface JobRepository extends JpaRepository<Job, Long> {}
JobService
工作服务
@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;
}
}
}
}
}
App
应用程序
@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());
}
}
I have often read that the session has to be bound to the current thread, but I don't know how to do this with Spring's annotation based configurations. Can someon point me out how to do that?
我经常读到会话必须绑定到当前线程,但我不知道如何使用 Spring 的基于注释的配置来做到这一点。有人可以指出我该怎么做吗?
P.S. I want to use lazy loading, thus eager loading is no option.
PS我想使用延迟加载,因此急切加载是没有选择的。
回答by BetaRide
Change
改变
@OneToMany(mappedBy = "creator")
private Set<Job> createdJobs = new HashSet<>();
to
到
@OneToMany(fetch = FetchType.EAGER, mappedBy = "creator")
private Set<Job> createdJobs = new HashSet<>();
Or use Hibernate.initialize inside your service, which has the same effect.
或者在您的服务中使用 Hibernate.initialize,它具有相同的效果。
回答by Chaitanya
You have 2 options.
您有 2 个选择。
Option 1 :As mentioned by BetaRide, use the EAGER
fetching strategy
选项 1:如 BetaRide 所述,使用EAGER
获取策略
Option 2 :After getting the user
from database using hibernate, add the below line in of code to load the collection elements:
选项 2:user
使用 hibernate 从数据库中获取数据后,在代码中添加以下行以加载集合元素:
Hibernate.initialize(user.getCreatedJobs())
This tells hibernate to initialize the collection elements
这告诉 hibernate 初始化集合元素
回答by Predrag Maric
Basically, you need to fetch the lazy data while you are inside of a transaction. If your service classes are @Transactional
, then everything should be ok while you are in them. Once you get out of the service class, if you try to get
the lazy collection, you will get that exception, which is in your main()
method, line System.out.println(random.getCreatedJobs());
.
基本上,您需要在事务内部时获取惰性数据。如果您的服务类是@Transactional
,那么当您在其中时一切都应该没问题。一旦你离开服务类,如果你尝试get
惰性集合,你会得到那个异常,它在你的main()
方法 line 中System.out.println(random.getCreatedJobs());
。
Now, it comes down to what your service methods need to return. If userService.getRandomUser()
is expected to return a user with jobs initialized so you can manipulate them, then it's that method's responsibility to fetch it. The simplest way to do it with Hibernate is by calling Hibernate.initialize(user.getCreatedJobs())
.
现在,归结为您的服务方法需要返回什么。如果userService.getRandomUser()
期望返回一个初始化了作业的用户,以便您可以操作它们,那么该方法有责任获取它。使用 Hibernate 执行此操作的最简单方法是调用Hibernate.initialize(user.getCreatedJobs())
.
回答by Pleymor
Consider using JPA 2.1, with Entity graphs:
考虑使用JPA 2.1和实体图:
Lazy loading was often an issue with JPA 2.0. You had to define at the entity FetchType.LAZY or FetchType.EAGER and make sure the relation gets initialized within the transaction.
延迟加载通常是 JPA 2.0 的一个问题。您必须在实体 FetchType.LAZY 或 FetchType.EAGER 上定义,并确保在事务中初始化关系。
This could be done by:
这可以通过以下方式完成:
- using a specific query that reads the entity
- or by accessing the relation within business code (additional query for each relation).
- 使用读取实体的特定查询
- 或者通过访问业务代码中的关系(每个关系的附加查询)。
Both approaches are far from perfect, JPA 2.1 entity graphs are a better solution for it:
这两种方法都远非完美,JPA 2.1 实体图是更好的解决方案:
回答by Pleymor
For those who have not the possibility to use JPA 2.1 but want to keep the possibility to return a entity in their controller (and not a String/JsonNode/byte[]/void with write in response):
对于那些无法使用 JPA 2.1 但希望保留在其控制器中返回实体的可能性的人(而不是响应写入的 String/JsonNode/byte[]/void):
there is still the possibility to build a DTO in the transaction, that will be returned by the controller.
仍然有可能在事务中构建 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);
}
}
回答by oak
You should enable Spring transaction manager by adding @EnableTransactionManagement
annotation to your context configuration class.
您应该通过向@EnableTransactionManagement
上下文配置类添加注释来启用 Spring 事务管理器。
Since both services have @Transactional
annotation and default value
property of it is TxType.Required
, current transaction will be shared among the services, provided that transaction manager is on. Thus a session should be available, and you won't be getting LazyInitializationException
.
由于这两个服务都有@Transactional
注释并且value
它的默认属性是TxType.Required
,如果事务管理器打开,当前事务将在服务之间共享。因此,会话应该可用,而您将无法获得LazyInitializationException
.