我面临着一个非常类似的问题:Yet another LazyInitializationException even with OpenSessionInViewFilter
我使用Hibernate 4.2.7.Final。我有一个像这样映射的实体:
@Entity
@Table(...)
public class A {
...
@OneToMany(fetch=FetchType.LAZY, mappedBy="b")
private Set<B> bSet;
...
}
它会加载大量数据,这就是我需要在需要时加载数据的原因。所以我加载了一个包含这个控制器请求映射的页面:
@RequestMapping("/getDetails")
public ModelAndView showView(Model model) {
...
for(B b : myService.getBSet()) {...}
...
}
服务在交易中:
@Service
@Scope(value="session")
@Transactional("ora11transactionManager")
public class MyServiceImpl implements MyService {
private A a;
...
public Set<B> getBSet() {
return a.getBSet();
}
}
hibernate.cgf.xml中的事务管理器:
<bean id="ora11sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="ora11source"/>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.show_sql">${debug}</prop>
<prop key="hibernate.format_sql">false</prop>
<prop key="hibernate.connection.characterEncoding">UTF-8</prop>
<prop key="hibernate.jdbc.use_get_generated_keys">true</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
</props>
</property>
<property name="packagesToScan">
<list>
<value>mypackage</value>
</list>
</property>
</bean>
<bean id="ora11transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="ora11sessionFactory" />
</bean>
当我想加载getDetails视图时,它会抛出引用服务中该行的异常:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: <my-package>.A.bSet, could not initialize proxy - no Session
这不是我使用的唯一延迟抓取的集合,但它可以在任何其他地方使用。延迟加载必须在事务中,并且它在事务中(因为您可以看到我的服务实现)!我甚至在org.springframework.orm.hibernate4.support.OpenSessionInViewFilter
中添加了web.xml
。
我找不到任何解决方案,请指教!
更新(我的实体的确切用法):
我有一大组As,每个A都有一组B.有一个视图我可以显示所有的As,它们在一个列表中并显示在数据表中。在每行的末尾都有一个按钮,用于调用和操作。在这个动作中,我保存了所选的A(在myService中有一个选定A的setter)。此操作在controller1中。当我想显示A的B时,我设置了哪个被选中并重定向到另一个视图。此视图由另一个控制器管理,这就是我将所选A保存到服务(会话或单一作用域)的原因。
@Controller
@Scope("session")
public class Controller1 {
...
public void setSelectedA(A selectedA) {
myService.setSelectedA(selectedA);
}
}
我试图在这种方法中达到B的集合,但是不起作用(整个服务是事务性的,我试图仅将事务注释设置为setselectedA()
和getBSet()
方法,但没有成功)。
答案 0 :(得分:0)
您的服务是session
范围(@Scope(value="session")
),但它不会使其自动线程安全。例如,如果你有一个购物车对象(它是相同的servlet会话),用户可以刷新他的页面,页面将在服务器上从另一个线程处理,它将是另一个Hibernate会话但是相同的购物车(和相同的Servlet会话) )。
问题是你在MySessionImpl中缓存的实体需要一个实时的Hibernate会话来触发B set的加载 - 会话在第一个控制器完成处理后关闭。
当从不同的线程使用时,Hibernate会话也不能保证正常工作,所以你不能延长它们的寿命以在控制器B中提供延迟加载,因为它是在另一个线程中处理的。
因此,请避免在服务类中缓存未分离的未初始化对象。因此,当您致电return a.getBSet();
时,您正在访问当前线程中不存在a
的会话。
我会重构所有操作在具有范围单例的线程安全服务中完成的代码(此范围在Spring中是默认的),并且它的方法应该是粗粒度的 - 即创建一个尽可能在单个方法中执行的服务call和该方法注释为@Transactional
。
如果您需要保留所选对象的列表(例如,Web商店购物车中的文章ID),您只需要将它们的标识符(而不是实体)存储在会话作用域(每个用户)bean中,然后通过另一个控制器/线程需要时的ID。为了避免A实体的额外数据库往返,您可以在Hibernate中启用二级缓存。