OpenEntityManagerInViewFilter Problems

2020-02-09 18:04发布

问题:

I have scoured this site among others for answers in getting OpenEntityManagerInViewFilter to work. I have a standard User object that references a roles object with a many to many relationship as a set. When I am try to edit my user from the controller I get the dreaded lazy init exception. For the most part it seems that this should be very trivial to implement by simply adding this to your 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>

Now to things I have tried without success (these are various suggestions from around the web)

  1. Move the above declaration to very top of the web.xml
  2. Slap @Transactional around my controller method and/or whole class
  3. Obviously switching fetch type to eager works, but defeats my intentions here
  4. Playing with where I have my entityManagerFacorty defined
  5. Verified that OpenEntityManager is present in the lazy init exception, thus its being fired off

About the only thing that I have read that makes sense to me as why this isn't working is that I am loading two different sessions because of how my persistence layer is set up and the filter is grabbing the wrong one.

Heres the method in my controller where I find a user from the database and causes the lazy init exception because it didn't retrieve roles from the user object.

@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";
   }

Here is my relevant setup:

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>

Let me know if this isn't enough relevant information.

EDIT UserDao - this extends a GenericDao, I'll post this just below.

@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();
   }
}

GenericDao

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;
   }
}

回答1:

But now!!

Let me make a guess: the name of your application is: board?

Correct? then go on and read the remaining answer!

Yes you have two entity manager, and even two identical application contexts (one app context and one web context) -- So you have every bean twice!

What happened is: you have only one (relevant) spring configuration file: 'board-servlet.xml' ('persistence-spring-beans.xml' is included in that file so at least it is one big logical file)

And you create a context from this file twice in the 'web.xml':

first:

<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 load the application context specified by the files in 'contextConfigLocation' parameter.

second:

<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 create the web application context, which xml file is:

  • named by the init-param 'contextConfigLocation'
  • or, if there is no such paramter, it looks for an file named '/WEB-INF/-servlet.xml'

(For more details have a look at the Java Doc of FrameworkServlet)

In your case there is no explicitly named file, so it reads the 'board-servlet.xml' again.

What you need to do is separate them:

  • remove the <import resource="classpath:persistence-spring-beans.xml"/> from board-servlet.xml
  • change the contextConfigLocation in web.xml that it refers to classpath:persistence-spring-beans.xml and /WEB-INF/board-security.xml direcly
  • (not 100% necessary) separate the 'context:component-scan' so that a component scan in board-servlet.xml scan only for @Controller and the component scan in persistence-spring-beans.xml scan for the others (@Service, @Component, @Repository and @Dao)
  • last step: please tell me that it works now


回答2:

You DAOs look normal, only the Annotation '@Dao' is a bit suprising. In normal case one would use '@Repository' -- That is a special form of '@Component' but (and that is not very well documented) it adds some special features.

One of that special features is an exception translation, and when I remember right there, was one other feature that enhance the Entity Manager annotated bz @PersistenceContext with some extra feature that somehow deals with threads and assigning the right entity manager to the right thread.

So I would recommend to try to replace @Dao by @Repository.



回答3:

Even if I can not answer your question at the moment I can give you some hints.

First there are the some statements, you wrote in your question:

1) Move the above declaration to very top of the web.xml

2) Slap @Transactional around my controller method and/or whole class

1) The order in which the filters are applied is defined by the order of Filters in the web.xml -- So it is correct that the OpenEntityManagerInViewFilter must run before any other filter that use the Entitys. But in most cases there there are not so much filters that relay on Entities. (my the Security filter, if you have extended it) -- But what I do not know is if it is correct to put the filters after the servlet declaration

2) If the Layz Init Exception is thrown in a JSP, then this is defently wrong, because it can not influence the JSP


But what me scared a bit (except from the order in you web.xml that makes it not realy easy to read) is the sitemash filter. My first guess is that sitemash causes the problem. If it is not to complicated, then remove the sitemash filter only for an test. If the problem disapear, then you have identified the causing component.