简体   繁体   English

Spring MVC @Controller:由于“ component-scan”标签的“ use-default-filter”而导致的事务性问题

[英]Spring MVC @Controller: transactional issue due to “use-default-filter” of “component-scan” tag

I'm kinda new to Spring MVC, so I'm creating a small web application just to try it. 我是Spring MVC的新手,所以我正在创建一个小型Web应用程序以进行尝试。

I am using Spring 4.2 and Hibernate 5. 我正在使用Spring 4.2和Hibernate 5。

The web app has a Spring servlet-context.xml and a Spring application-context.xml . 该Web应用程序具有Spring servlet-context.xml和Spring application-context.xml

I have a @Controller with a method that make use of a @Service . 我有一个@Controller ,其方法使用了@Service This Service has a findAll() method marked as @Transactional . 该服务具有一个标记为@TransactionalfindAll()方法。

The web.xml looks like this: web.xml看起来像这样:

...
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath:*-context.xml
    </param-value>
</context-param>

<servlet>
    <servlet-name>myDispatcher</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>myDispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
...

Here the Controller: 这里的控制器:

@Controller
public class TestOneController
{
    @Autowired
    private UserManager userManager;

    @RequestMapping("views/test")
    public ModelAndView defaultResolution()
    {
        List<User> users = userManager.findAll();
        // here I build my String msg object
        return new ModelAndView("views/resolution", "msg", msg);
    }
}

Here the Service: 这里的服务:

@Service("userManager")
public class UserManagerImpl implements UserManager
{
    @Autowired
    private UserDao userDao;

    @Override
    @Transactional(readOnly = true, propagation = Propagation.REQUIRED)
    public List<User> findAll()
    {
        return userDao.findAll();
    }
}

And finally the Dao used by the Service: 最后,该服务使用的道:

@Repository("userDao")
public class UserDaoImpl implements UserDao
{

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    @SuppressWarnings("unchecked")
    @Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
    public List<User> findAll()
    {
        Query query = sessionFactory.getCurrentSession().createQuery("from User ");
        return query.list();
    }
}

My question: I have tried several configurations, that I believed to be equivalent, but with one of them I am getting an org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread when I hit the mapped Controller method from a link in a page. 我的问题:我尝试了几种配置,我认为它们是等效的,但是使用其中一种配置,我得到的是org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread当我从A中命中映射的Controller方法时, org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread链接到页面。 Could someone explain me why this exception is raised when I use configuration 2, but not on configuration 1 and 3? 有人可以解释一下为什么在使用配置2而不是在配置1和3时会引发此异常吗?

Configuration 1 (working) 配置1(工作)

application-context.xml application-context.xml

...
<tx:annotation-driven />
<context:component-scan base-package="my.package">
    <context:exclude-filter type="annotation"
        expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<bean id="transactionManager"
    class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:hibernate.cfg.xml" />
    <property name="packagesToScan">
        <list>
            <value>my.package.entities</value>
        </list>
    </property>
</bean>

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/myDb" />
</bean>
...

servlet-context.xml servlet-context.xml

...
<context:component-scan base-package="my.package.inner.package">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<mvc:annotation-driven />
<mvc:resources mapping="/resources/**" location="/resources/" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass"
        value="org.springframework.web.servlet.view.JstlView" />
    <property name="prefix" value="/" />
    <property name="suffix" value=".jsp" />
</bean>
...

Configuration 2 (raises the exception) Same configuration as 1, with the difference of the <context:component-scan> tag of the servlet-context.xml , that has a base-package closer to the root: 配置2(引发异常)与配置1相同,只是servlet-context.xml<context:component-scan>标记不同,该标记的基本包更接近于根:

<context:component-scan base-package="my.package">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

Note that this is the same base-package for the component-scan in the application-context.xml . 请注意,这与application-context.xml的component-scan相同。

Configuration 3 (working) Same as 2 (thus with base-package="my.package" in the servlet-context.xml ), with the difference that instead of annotating my Service with @Service , I declare it explicitly as a bean in the application-context.xml : 配置3(正常工作)与2(因此在servlet-context.xml使用base-package="my.package" )相同,不同之处在于,我不使用@Service注释我的Service,而是将其显式声明为bean中的bean。 application-context.xml

...
<bean id="userManager" class="my.package.services.impl.UserManagerImpl">
</bean>
...

TL;DR Could someone explain me why I am getting an org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread when I hit the Controller mapped method with configuration 2 and not with configuration 1 and 3? TL; DR有人可以解释一下为什么我得到org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread当我使用配置2而不是配置1和3来访问Controller映射方法时, org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread吗? I was believing that all those 3 configuration were equivalent. 我相信这3种配置都是等效的。

Thank you. 谢谢。

After some hours of trial-and-error process, I found out the issue. 经过数小时的反复试验,我发现了问题所在。

In all the 3 configurations, the component-scan tag of servlet-context.xml needs another attribute: use-default-filters must be set to "false". 在所有3种配置中, servlet-context.xmlcomponent-scan标记都需要另一个属性: use-default-filters必须设置为“ false”。

<context:component-scan base-package="my.package" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

If it's set to "true" (as per default), it's essentially like having a default include filter like this one: 如果将其设置为“ true”(默认情况下),则本质上就像具有这样的默认包含过滤器:

<context:include-filter type="annotation" 
                    expression="org.springframework.stereotype.Component"/>

This means that during the package scan, it loaded also the defined @Service (and the @Repository , etc.). 这意味着在软件包扫描期间,它还加载了定义的@Service (和@Repository等)。 Since the Service was loaded by the Servlet Context, that has no <tx:annotation-driven /> tag, no Transactional context was enabled for the Service, and the Exception was raised. 由于服务是由没有<tx:annotation-driven />标记的Servlet上下文加载的,因此未为该服务启用任何事务性上下文,并且引发了异常。

In configuration 1, since the base package for scanning was restricted (including only the Controller package), the issue was bypassed (the Servlet Context only loaded the Controller, so the Service used was the one loaded by the Application Context, that has Transactional context enabled). 在配置1中,由于限制了用于扫描的基本程序包(仅包括Controller程序包),因此绕过了该问题(Servlet上下文仅加载了Controller,因此使用的服务是Application Context加载的具有Transactional上下文的Service启用)。

In configuration 3, since I used a "manual definition" of the Service bean in the right "transactional" context ( application-context.xml ), the problem was bypassed too. 在配置3中,由于我在正确的“事务”上下文( application-context.xml )中使用了Service bean的“手动定义”,因此也绕过了问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM