【问题标题】:OpenEntityManagerInViewFilter ProblemsOpenEntityManagerInViewFilter 问题
【发布时间】:2011-08-09 21:19:55
【问题描述】:

我已经在这个网站中搜索过如何让 OpenEntityManagerInViewFilter 工作的答案。我有一个标准的用户对象,它引用了一个具有多对多关系的角色对象作为一个集合。当我尝试从控制器编辑我的用户时,我得到了可怕的惰性初始化异常。在大多数情况下,只需将其添加到您的 web.xml 中,这似乎很容易实现:

<filter>
    <filter-name>oemInViewFilter</filter-name>
    <filter-class>
        org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
    </filter-class>
</filter>

<filter-mapping>
    <filter-name>oemInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

现在我尝试过但没有成功的事情(这些是来自网络的各种建议)

  1. 将上述声明移至 web.xml 的最顶部
  2. 在我的控制器方法和/或整个类周围拍@Transactional
  3. 显然将 fetch 类型切换为渴望工作,但在这里违背了我的意图
  4. 在我定义了 entityManagerFacorty 的地方玩
  5. 已验证 OpenEntityManager 存在于惰性初始化异常中,因此已被触发

关于我读过的唯一一件对我有意义的事情,因为我正在加载两个不同的会话,因为我的持久层是如何设置的,而过滤器正在抓取错误的会话。

这是我的控制器中的方法,我从数据库中找到一个用户并导致惰性初始化异常,因为它没有从用户对象中检索角色。

@RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
   public String edit(@PathVariable final Integer id, final ModelMap modelMap)
   {
      final User user = userDao.find(id); ******This causes the lazy init exception

      if (user != null)
      {
         modelMap.addAttribute("userInstance", user);
         modelMap.addAttribute("validRoles",  new HashSet<Role>(roleDao.findAll()));
         return "/user/edit";
      }
      return "redirect:/user/list";
   }

这是我的相关设置:

Web.xml:

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         id="WebApp_ID" version="2.5">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/board-servlet.xml  *****This file references the file with entityManager declared*****
            /WEB-INF/board-security.xml
        </param-value>
    </context-param>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>board</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>board</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.css</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.js</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.jpg</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.png</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.gif</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <url-pattern>*.ico</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <filter>
        <filter-name>oemInViewFilter</filter-name>
        <filter-class>
            org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
        </filter-class>
    </filter>

    <filter-mapping>
        <filter-name>oemInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <filter>
        <filter-name>sitemesh</filter-name>
        <filter-class>
            com.opensymphony.module.sitemesh.filter.PageFilter
        </filter-class>
    </filter>

    <filter-mapping>
        <filter-name>sitemesh</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

board-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">

  ****This is what pulls in my entityManager  
 <import resource="classpath:persistence-spring-beans.xml"/> 

    <mvc:annotation-driven/>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="100000"/>
    </bean>

    <bean id="messageSource"
          class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basename" value="classpath:message"/>
        <property name="defaultEncoding" value="UTF-8"/>
    </bean>

</beans>

persistence-spring-beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

    <context:annotation-config/>
    <context:component-scan base-package="com.something" use-default-filters="true"/>
    <aop:aspectj-autoproxy proxy-target-class="true"/>

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
        <property name="persistenceUnitName" value="prodPersistenceUnit"/>
        <property name="dataSource" ref="c3p0PostgresDataSource"/>
        <property name="packagesToScan" value="com.something.persistence.dto"/>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
        </property>
    </bean>

    <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <bean id="c3p0PostgresDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="org.postgresql.Driver"/>
        <property name="jdbcUrl" value="jdbc:postgresql://localhost:5432/yellow_hammer"/>
        <property name="user" value="yellow"/>
        <property name="password" value="hammer"/>
        <property name="initialPoolSize" value="3"/>
        <property name="minPoolSize" value="3"/>
        <property name="maxPoolSize" value="50"/>
        <property name="idleConnectionTestPeriod" value="200"/>
        <property name="acquireIncrement" value="1"/>
        <property name="maxStatements" value="0"/>
        <!-- 0 means: statement caching is turned off.  -->
        <property name="numHelperThreads" value="3"/>
        <!-- 3 is default -->
    </bean>
</beans>

如果相关信息不足,请告诉我。

编辑 UserDao - 这扩展了一个 GenericDao,我将在下面发布。

@Dao
public class UserDao extends GenericDao<User>
{
   public User findByUsernameAndPassword(final String username, final String password)
   {
      final Query query = entityManager.createQuery("from User user " + "where user.username = :user " + "and user.password = :pass ")
         .setParameter("user", username)
         .setParameter("pass", password);

      return uniqueResult(query);
   }

   public List<User> findByRole(final Role roleIn)
   {
      if (roleIn == null)
      {
         return null;
      }

      final Query query = entityManager.createQuery("select user from User user, Role role where role = :roleParam ").
         setParameter("roleParam", roleIn);

      return query.getResultList();
   }
}

通用道

public class GenericDao<T extends BaseDto>
{
   protected Class<T> entityClass;

   @PersistenceContext
   protected EntityManager entityManager;

   public GenericDao()
   {
      final ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
      this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
   }

   public T find(final Integer id)
   {
      return entityManager.find(entityClass, id);
   }

   public List<T> findAll()
   {
      final Query query = entityManager.createQuery("from " + entityClass.getSimpleName());
      return query.getResultList();
   }

   public T save(final T t)
   {
      if (t != null)
      {
         return t.getId() != null && t.getVersion() != null ? update(t) : create(t);
      }

      return null;
   }

   private T create(final T t)
   {
      entityManager.persist(t);
      return t;
   }

   private T update(final T t)
   {
      return entityManager.merge(t);
   }

   public void delete(T t)
   {
      t = entityManager.merge(t);
      entityManager.remove(t);
   }

   protected T uniqueResult(final Query query)
   {
      final List results = query.getResultList();
      if (results.isEmpty())
      {
         return null;
      }
      else if (results.size() == 1)
      {
         return entityClass.cast(results.get(0));
      }

      // TODO send notification, multiple results found
      return null;
   }
}

【问题讨论】:

  • 我假设 userDao 是由 Spring 注入的?您是否尝试过调试日志记录?您应该能够从那里看到打开/关闭事件,它有助于将 %t 模式添加到 appender 输出中以查看线程名称以及如果您同时进行其他(想想 ajax)事情。
  • 一个问题:惰性初始化异常抛出在哪里?是真的被这条线扔了:final User user = userDao.find(id); 还是我理解错了?
  • @Ralf - 是的,它是由该行抛出的,如果我在它查找后立即调试并查看我的对象,并且您查看用户对象中的角色变量,则错误已被抛出.那对我来说没有意义。我认为它会在控制器完成后以及它在前往视图的途中被抛出。它相当混乱。
  • @sauce - 请张贴UserDao 的来源 -- 顺便说一句:这是 Ralph 而不是 Ralf -- 如果您拼写错误,我将不会收到有关评论的消息
  • @Ralph - 感谢您的提示,并对错过的咒语感到抱歉。我发布了我的 userdao 和 genericDao。我所有的 dao 都扩展了这个,它提供了一个非常基本的 crud。我注入我的 genricdao 的 entitymanager 是罪魁祸首吗?这是否与我的 web.xml 中的过滤器引用的 entityManger 相同?

标签: hibernate spring spring-mvc


【解决方案1】:

但是现在!!

让我猜一下:你的应用名称是:board?

对吗?然后继续阅读剩下的答案!

是的,您有两个实体管理器,甚至有两个相同的应用程序上下文(一个应用程序上下文和一个 Web 上下文)——所以每个 bean 都有两次!

发生的事情是:你只有一个(相关的)spring 配置文件: 'board-servlet.xml'('persistence-spring-beans.xml' 包含在该文件中,因此至少它是一个大的逻辑文件)

然后你在 'web.xml' 中两次从这个文件创建一个上下文:

第一:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/board-servlet.xml  *****This file references the file with entityManager declared*****
        /WEB-INF/board-security.xml
    </param-value>
</context-param>
...
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

ContextLoaderListener 加载 'contextConfigLocation' 参数中的文件指定的应用程序上下文。

秒:

<servlet>
    <servlet-name>board</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

Dispatcher Servlet 创建 web 应用上下文,xml 文件是:

  • 由 init-param 'contextConfigLocation' 命名
  • 或者,如果没有这样的参数,它会查找名为“/WEB-INF/-servlet.xml”的文件

(更多详情请查看FrameworkServlet的Java Doc)

在您的情况下,没有明确命名的文件,因此它会再次读取“board-servlet.xml”。

你需要做的是将它们分开:

  • board-servlet.xml 中删除&lt;import resource="classpath:persistence-spring-beans.xml"/&gt;
  • web.xml 中的contextConfigLocation 更改为直接引用classpath:persistence-spring-beans.xml/WEB-INF/board-security.xml
  • (不是 100% 必要的) 将 'context:component-scan' 分开,以便 board-servlet.xml 中的组件扫描仅扫描 @Controllerpersistence-spring-beans.xml 中的组件扫描扫描其他( @Service@Component@Repository@Dao)
  • 最后一步:请告诉我它现在可以工作了

【讨论】:

  • 在您的第一步中,它说从 'board-servlet.xml 中删除“。这是一个错字吗?我应该尝试从 board-servlet.xml 中删除什么?我应该删除 ?而且这对我来说更有意义,并认为它是这样的。我会试着摆弄它,直到我收到回复。谢谢!!跨度>
  • @sauce:它是: -- 转义不正确 -- 抱歉
  • 解决了!!这是一个不错的设计缺陷,也加载了所有的 bean 两次,所以那是双倍的棒。非常感谢你的帮助!!我真的很感激。
【解决方案2】:

你们的 DAO 看起来很正常,只有 Annotation '@Dao' 有点令人惊讶。在正常情况下,人们会使用“@Repository”——这是“@Component”的一种特殊形式,但是(并且没有很好的文档记录)它添加了一些特殊功能。

其中一个特殊功能是异常转换,当我记得那里时,另一个功能是增强实体管理器注释 bz @PersistenceContext 一些额外的功能,以某种方式处理线程并将正确的实体管理器分配给正确的线程。

所以我建议尝试将@Dao 替换为@Repository

【讨论】:

  • Dao 是我们设置的自定义注解,和 Repository 是一回事。对我来说,说“道”,“偏好”更有意义。但作为故障排除步骤,我将其替换为存储库,但仍然没有运气。
  • @sauce:即使这不是问题的原因,请注意这一点,稍后可能会检查额外的东西是否也适用于@Dao。
【解决方案3】:

即使我现在不能回答你的问题,我也可以给你一些提示。

首先有一些陈述,您在问题中写道:

1) 将上述声明移至 web.xml 的最顶部

2) 在我的控制器方法和/或整个类周围拍@Transactional

1) 过滤器的应用顺序由 web.xml 中的过滤器顺序定义——因此 OpenEntityManagerInViewFilter 必须在使用实体的任何其他过滤器之前运行是正确的。但在大多数情况下,没有那么多过滤器可以中继实体。 (我的安全过滤器,如果你扩展了它) -- 但我不知道将过滤器放在 servlet 声明之后是否正确

2) 如果在 JSP 中抛出 Layz Init Exception,那么这显然是错误的,因为它不会影响 JSP


但是我有点害怕(除了 web.xml 中的顺序,这使得它不太容易阅读)是 sitemash 过滤器。我的第一个猜测是 sitemash 会导致问题。如果它不是很复杂,那么删除 sitemash 过滤器只是为了测试。如果问题消失,那么您已经确定了导致的组件。

【讨论】:

  • 我删除了 sitemesh 过滤器,但仍然是同样的问题。嗯。我对此的唯一想法是,我从一个有类似问题的论坛中得到这个,是我的持久层打开了一个与我的 webapp 不同的会话,因此过滤器应用于 webapps 会话而不是我的持久层会话。但是 webapp 只是我项目的一部分,所以我需要弄清楚如何分开。
  • @sauce 明确抛出异常的地方后,你可以忘记关于sitemash的部分
猜你喜欢
  • 1970-01-01
  • 2016-03-30
  • 1970-01-01
  • 1970-01-01
  • 2011-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多