当我尝试获取延迟初始化实体时,我在IDE中看到以下异常消息(我无法找到它存储在代理实体中的位置,因此我无法为此异常提供整个堆栈跟踪):< / p>
Method threw 'org.hibernate.LazyInitializationException' exception. Cannot evaluate com.epam.spring.core.domain.UserAccount_$$_jvste6b_4.toString()
这是我尝试访问我想要使用的延迟初始化实体的字段后立即获得的堆栈跟踪:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:286)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185)
at com.epam.spring.core.domain.UserAccount_$$_jvstfc9_4.getMoney(UserAccount_$$_jvstfc9_4.java)
at com.epam.spring.core.web.rest.controller.BookingController.refill(BookingController.java:128)
我正在使用Spring Data,配置了JpaTransactionManager,数据库是MySql,ORM提供者是Hibernate 4.注释@EnableTransactionManagement打开,@ Transactal放在我能想象到的任何地方但没有任何作用。
这是一个关系:
@Entity
public class User extends DomainObject implements Serializable {
..
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "user_fk")
private UserAccount userAccount;
..
@Entity
public class UserAccount extends DomainObject {
..
@OneToOne(mappedBy = "userAccount")
private User user;
..
..一段配置:
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getRequiredProperty(PROP_NAME_DATABASE_DRIVER));
dataSource.setUrl(env.getRequiredProperty(PROP_NAME_DATABASE_URL));
dataSource.setUsername(env.getRequiredProperty(PROP_NAME_DATABASE_USERNAME));
dataSource.setPassword(env.getRequiredProperty(PROP_NAME_DATABASE_PASSWORD));
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
entityManagerFactoryBean.setPackagesToScan(env.getRequiredProperty(PROP_ENTITYMANAGER_PACKAGES_TO_SCAN));
entityManagerFactoryBean.setJpaProperties(getHibernateProperties());
return entityManagerFactoryBean;
}
@Bean
public JpaTransactionManager transactionManager(@Autowired DataSource dataSource,
@Autowired EntityManagerFactory entityManagerFactory) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
jpaTransactionManager.setDataSource(dataSource);
return jpaTransactionManager;
}
..这就是我想要检索UserAccount的方式:
@RequestMapping(...)
@Transactional()
public void refill(@RequestParam Long userId, @RequestParam Long amount) {
User user = userService.getById(userId);
UserAccount userAccount = user.getUserAccount();
userAccount.setMoney(userAccount.getMoney() + amount);
}
Hibernate版本是4.3.8.Final,Spring Data 1.3.4.RELEASE和MySql连接器5.1.29。
请问我是否需要其他东西。提前谢谢!
答案 0 :(得分:4)
首先,您应该了解问题的根源不是交易。我们有一个事务和一个持久化的上下文(会话)。使用@Transactional
注释,Spring会创建一个事务并打开持久化上下文。调用方法后,持久化上下文将关闭。
当您拨打user.getUserAccount()
时,您有一个包裹UserAccount
的代理类(如果您没有UserAccount
加载User
)。因此,当关闭持久性上下文时,您在调用LazyInitializationException
的任何方法时都会UserAccount
,例如toString()
。
@Transactional
仅在userService
级别上工作。要使@Transactional
工作,仅将@Transactional
注释放在方法上是不够的。您需要使用Spring Context
中的方法获取类的对象。因此,要更新资金,您可以使用其他服务方法,例如updateMoney(userId, amount)
。
如果要在控制器方法上使用@Transactional
,则需要从Spring Context
获取控制器。 Spring应该理解,它应该使用特殊方法包装每个@Transactional
方法来打开和关闭持久化上下文。其他方式是使用Session Per Request Anti模式。您需要添加一个特殊的HTTP过滤器。
https://vladmihalcea.com/the-open-session-in-view-anti-pattern/
答案 1 :(得分:1)
正如 @ v.ladynev 简要解释的那样,您的问题是您想要在持久化上下文之外初始化一个惰性关系。
我写了一篇关于此的文章,您可能会发现它很有用:http://blog.arnoldgalovics.com/2017/02/27/lazyinitializationexception-demystified/
答案 2 :(得分:0)
对于尽管存在性能问题的快速解决方案,请在服务中使用@transactional
样本:
@Transactional
public TPage<ProjectDto> getAllPageable(Pageable pageable) {
Page<Project> data = projectRepository.findAll(pageable);
TPage<ProjectDto> response = new TPage<>();
response.setStat(data, Arrays.asList(modelMapper.map(data.getContent(), ProjectDto[].class)));
return response;
}
它将在第二个查询中获取项目经理的用户详细信息。 有关更高级的解决方案,您应该阅读@galovics答案中的博客文章。
答案 3 :(得分:0)
我用下面来修复 sessionFactory.getObject().getCurrentSession() 创建查询并获取所需的对象