【问题标题】:Hibernate LazyInitializationException even in transactionHibernate LazyInitializationException 即使在事务中
【发布时间】:2015-07-15 20:28:20
【问题描述】:

我正面临与此非常相似的问题: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

这不是我使用的唯一惰性获取集合,但它在其他任何地方都可以使用。延迟加载必须在事务中,并且在事务中(您可以看到我的服务实现)!我什至在web.xml 中添加了org.springframework.orm.hibernate4.support.OpenSessionInViewFilter

我找不到任何解决方案,请指教!

更新(我的实体的确切用途):

我有一大组 As,每个 A 都有一组 B。有一个视图可以显示所有 As,它们在列表中并显示在数据表中。在每一行的末尾都有一个按钮,用于调用和操作。在此操作中,我保存了选定的 A(在 myService 中有一个用于选定 A 的设置器)。此操作在控制器 1 中。当我想显示 A 的 Bs 时,我设置了哪个被选中并重定向到另一个视图。此视图由另一个控制器管理,这就是我将所选 A 保存到服务(会话或单例范围)的原因。

@Controller
@Scope("session")
public class Controller1 {
    ...
    public void setSelectedA(A selectedA) {
        myService.setSelectedA(selectedA);
    }
}

即使在这个方法中我也尝试到达B的集合,但是不起作用(整个服务是事务性的,我尝试只对setselectedA()getBSet()方法设置事务性注释,但没有成功) .

【问题讨论】:

  • 在检索对象 A 时初始化集合 b。
  • 我试过了,但遇到了同样的异常。
  • 把事务注解放在方法上而不是类上
  • 也试过了,没有成功。

标签: spring hibernate transactions lazy-loading


【解决方案1】:

您的服务是session 作用域(@Scope(value="session")),但它不会自动使其成为线程安全的。例如,如果您有一个购物车对象(它是同一个 servlet 会话),用户可能会刷新他的页面,并且该页面将在服务器上从不同的线程进行处理,这将是另一个 Hibernate 会话但相同的购物车(和相同的 Servlet 会话)。

问题是您在 MySessionImpl 中缓存的实体需要一个实时的 Hibernate 会话来触发 B 集的加载 - 在第一个控制器完成处理后会话关闭。

Hibernate 会话在从不同线程使用时也不能保证正常工作,因此您无法延长它们的寿命以在控制器 B 中提供延迟加载,因为它是在另一个线程中处理的。 因此,请避免在您的服务类中缓存分离的未初始化对象。因此,当您调用return a.getBSet(); 时,您正在访问附加了a 的会话,该会话在当前线程中不存在。

我会重构代码,所有操作都在具有单例范围的线程安全服务中完成(这个范围在 Spring 中是默认的),并且它的方法应该是粗粒度的 - 即创建一个尽可能多地在单一方法中执行的服务调用并且该方法被注释为@Transactional

如果您需要保留选定对象的列表(例如网络商店购物车中的文章 ID),您只需将它们的标识符(而不是实体)存储在会话范围(每个用户)bean 中,然后通过另一个控制器/线程中需要时的 ID。为了避免 A 实体的额外数据库往返,您可以在 Hibernate 中启用二级缓存。

【讨论】:

  • 在单例范围的服务中,我仍然收到错误。
  • 请将代码添加到问题中 - 您不应缓存未初始化字段的 A 值(您可以通过访问 a.getBSet() 强制初始化/加载 B 集(正如 Harshal Patil 所指出的)在你设置 A 值或存储 A 的 id 而不是 A 实例的线程中)
  • 好的,我更新了。这是我设置这个 A 的地方。我在加载 As 视图的方法中初始化 As 列表,这就是为什么我要避免急切地加载 B 集。
  • Hibernate 实体一次只能附加到一个会话,因此最好在第二个控制器中加载具有相同 ID 的 A 的单独副本。为了避免额外的数据库往返,您可以启用 A(2 级缓存)的缓存
  • 在你的情况下也忽略单件事情,它更像是选定对象的“购物车”而不是服务 - 不同的用户,不同的购物车。
猜你喜欢
  • 2017-11-21
  • 1970-01-01
  • 2019-12-09
  • 1970-01-01
  • 2019-07-11
  • 1970-01-01
  • 1970-01-01
  • 2014-07-11
  • 1970-01-01
相关资源
最近更新 更多