Spring控制器中的JDBCTemplate

3

我需要做一些不仅仅是实体列表的报告,而是更自由形式的查询,硬编码SQL,并返回任意JSON。

我尝试使用JdbcTemplate来实现这一点,并使用我在网上找到的代码(例如这里)构建它。

这都在一个名为ReportViewController的类中。

我的代码(带有日志记录/调试信息,稍后会提到):

private JdbcTemplate jdbcTemplate;
private DataSource dataSource;
private UUID uuid;


public ReportViewController() {
    this.uuid = UUID.randomUUID();
}


public void setDataSource(DataSource dataSource) {
    System.out.println("Setting data source");
    this.dataSource = dataSource;
    if (this.dataSource == null) {
        System.out.println("failed to set my dataSource properly");
    }
    System.out.println("uuid: " + this.uuid);
}

@RequestMapping(value = "/report/{reportId}", method = RequestMethod.POST)
@ResponseBody
public String runNewUntypedSearch(@PathVariable Integer reportId, WebRequest request, HttpSession session) {
    //View v = View.findView(reportId); 

    System.out.println("uuid: " + this.uuid);
    if (this.dataSource == null) {
        System.out.println("lost my dataSource somewhere along the way");
    }

    this.jdbcTemplate = new JdbcTemplate(this.dataSource);

    List<Map<String, Object>> l = null;
    try {
        l = this.jdbcTemplate.queryForList("select 1 as one");

        JSONSerializer s = new JSONSerializer();
        return s.serialize(l);

    } catch (Exception e) {
        System.out.println("querying failed -- " + e.toString());
        return "";
    }
} 

我的问题是,尽管控制台显示正在调用setDataSource(),但当我运行runNewUntypedSearch()时,这些属性为空。

因此,怀疑我所谈论的对象存在不良操作,我让这个控制器在其构造函数中创建一个UUID,并将其作为日志的一部分输出,如上所示。

控制台输出显示:

Setting data source
hash: bc679ef1-fbdc-4ef9-9457-dd79889f973e
...
hash: bbe2b03a-92ff-4517-add8-8d9ff6480cc8
lost my dataSource somewhere along the way

...然后Spring响应浏览器并显示"内部错误",以及一个堆栈跟踪,指示我正在尝试实例化jdbcTemplate的空指针。

这让我得出结论,被告知设置其dataSource的任何对象都不是与作为URL映射方法挂钩的相同实例。服务器运行的“真实”实例没有调用getDataSource()

可能吗?为什么这不起作用?

编辑:

web.xml(已编辑以删除我的客户端名称):

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">


    <display-name>myProjectName</display-name>

    <description>Roo generated application</description>


    <!-- Enable escaping of form submission contents -->
    <context-param>
        <param-name>defaultHtmlEscape</param-name>
        <param-value>true</param-value>
    </context-param>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:META-INF/spring/applicationContext*.xml</param-value>
    </context-param>

    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>



    <filter>
        <filter-name>HttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>



    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

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



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

    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Handles Spring requests -->
    <servlet>
        <servlet-name>myProjectName</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/webmvc-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

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

    <session-config>
        <session-timeout>90</session-timeout>
    </session-config>

    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/uncaughtException</location>
    </error-page>

    <error-page>
        <error-code>404</error-code>
        <location>/resourceNotFound</location>
    </error-page>
</web-app>

applicationContext.xml(已编辑以删除客户端名称和Roo生成的注释)

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd         http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">


    <context:property-placeholder location="classpath*:META-INF/spring/*.properties"/>


    <context:spring-configured/>


    <context:component-scan base-package="com.myProjectName">
        <context:exclude-filter expression=".*_Roo_.*" type="regex"/>
        <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>


    <bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource">
        <property name="driverClassName" value="${database.driverClassName}"/>
        <property name="url" value="${database.url}"/>
        <property name="username" value="${database.username}"/>
        <property name="password" value="${database.password}"/>
        <property name="testOnBorrow" value="true"/>
        <property name="testOnReturn" value="true"/>
        <property name="testWhileIdle" value="true"/>
        <property name="timeBetweenEvictionRunsMillis" value="1800000"/>
        <property name="numTestsPerEvictionRun" value="3"/>
        <property name="minEvictableIdleTimeMillis" value="1800000"/>
        <property name="validationQuery" value="SELECT 1"/>
    </bean>
    <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
    <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
        <property name="persistenceUnitName" value="persistenceUnit"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean class="org.springframework.mail.javamail.JavaMailSenderImpl" id="mailSender">
        <property name="host" value="${email.host}"/>
    </bean>
    <bean class="org.springframework.mail.SimpleMailMessage" id="templateMessage">
        <property name="from" value="${email.from}"/>
        <property name="subject" value="${email.subject}"/>
    </bean>
</beans>

webmvc-config.xml(已经编辑以删除我的客户端名称):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd     http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

    <!-- The controllers are autodetected POJOs labeled with the @Controller annotation. -->
    <context:component-scan base-package="com.myProjectName" use-default-filters="false">
        <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>


    <!-- Turns on support for mapping requests to Spring MVC @Controller methods
         Also registers default Formatters and Validators for use across all @Controllers -->
    <mvc:annotation-driven conversion-service="applicationConversionService"/>

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources -->
    <mvc:resources location="/, classpath:/META-INF/web-resources/" mapping="/resources/**"/>

    <!-- Allows for mapping the DispatcherServlet to "/" by forwarding static resource requests to the container's default Servlet -->
    <mvc:default-servlet-handler/>

    <!-- register "global" interceptor beans to apply to all registered HandlerMappings -->
    <mvc:interceptors>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" p:paramName="lang"/>
    </mvc:interceptors>

    <!-- selects a static view for rendering without the need for an explicit controller -->
    <mvc:view-controller path="/login"/>
    <mvc:view-controller path="/admin" view-name="admin"/>
    <mvc:view-controller path="/" view-name="index"/>
    <mvc:view-controller path="/uncaughtException"/>
    <mvc:view-controller path="/resourceNotFound"/>
    <mvc:view-controller path="/dataAccessFailure"/>

    <!-- Resolves localized messages*.properties and application.properties files in the application to allow for internationalization. 
        The messages*.properties files translate Roo generated messages which are part of the admin interface, the application.properties
        resource bundle localizes all application specific messages such as entity names and menu items. -->
    <bean class="org.springframework.context.support.ReloadableResourceBundleMessageSource" id="messageSource" p:basenames="WEB-INF/i18n/messages,WEB-INF/i18n/application" p:fallbackToSystemLocale="false"/>

    <!-- store preferred language configuration in a cookie -->
    <bean class="org.springframework.web.servlet.i18n.CookieLocaleResolver" id="localeResolver" p:cookieName="locale"/> 

    <!-- resolves localized <theme_name>.properties files in the classpath to allow for theme support -->
    <bean class="org.springframework.ui.context.support.ResourceBundleThemeSource" id="themeSource"/>

    <!-- store preferred theme configuration in a cookie -->
    <bean class="org.springframework.web.servlet.theme.CookieThemeResolver" id="themeResolver" p:cookieName="theme" p:defaultThemeName="standard"/>

    <!-- This bean resolves specific types of exceptions to corresponding logical - view names for error views. 
         The default behaviour of DispatcherServlet - is to propagate all exceptions to the servlet container: 
         this will happen - here with all other types of exceptions. -->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" p:defaultErrorView="uncaughtException">
        <property name="exceptionMappings">
            <props>
                <prop key=".DataAccessException">dataAccessFailure</prop>
                <prop key=".NoSuchRequestHandlingMethodException">resourceNotFound</prop>
                <prop key=".TypeMismatchException">resourceNotFound</prop>
                <prop key=".MissingServletRequestParameterException">resourceNotFound</prop>
            </props>
        </property>
    </bean>

    <!-- allows for integration of file upload functionality -->
    <bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>
  <bean class="com.mavenmanagement.web.ApplicationConversionServiceFactoryBean" id="applicationConversionService"/> 
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" id="tilesViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
  </bean>
    <bean class="org.springframework.web.servlet.view.tiles2.TilesConfigurer" id="tilesConfigurer">
    <property name="definitions">
      <list>
        <value>/WEB-INF/layouts/layouts.xml</value>
        <!-- Scan views directory for Tiles configurations -->
        <value>/WEB-INF/views/**/views.xml</value>
      </list>
    </property>
  </bean>
</beans>

我发现注释扫描的过滤器会在应用程序上下文级别阻止 @Controller,并在 webmvc-config 级别启用它们。所以这不会导致双重实例化。


1
在Web层使用JdbcTemplate非常不好。请参考:http://en.wikipedia.org/wiki/Separation_of_concerns - Sean Patrick Floyd
不要将列表变量命名为“l”! - Xorty
另外:您可以直接注入JdbcTemplates而不是DataSources。我认为甚至可以安全地在线程之间共享它们,但如果不行,原型范围将会处理这个问题。 - millimoose
@Inerdial,是的,您可以直接注入JdbcTemplate,它是线程安全的。不需要prototype作用域,这对解决问题没有帮助。 - Tomasz Nurkiewicz
我尝试将JdbcTemplate注入到我的方法中,似乎可以工作,但查询失败并出现异常“未指定数据源”。因此,我认为这不是我的整个解决方案。 - Dan Ray
2个回答

3

@Tomasz Nurkiewicz和@Inerdial在评论中提供的提示让我想到了一种完全不同的方法,这种方法更简单,并且效果非常好,但我没有在任何地方看到它的文档。

在applicationContext.xml的底部,我实例化了一个JdbcTemplate对象:

   <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 
    <property name="dataSource" ref="dataSource"/> 
   </bean>

然后我只需将其 @AutoWire 到需要的控制器中:
@Autowired
private JdbcTemplate jdbcTemplate;

有了这个,我现在可以与完全配置的模板对象进行交互,位置在this.jdbcTemplate。

这个控制器类为什么会实例化两次呢?因为我从applicationContext.xml启动它,试图配置它的数据源。然后被@Controller注释版本创建,但没有被配置。所以问题就出在这里。

我还按照评论中的建议,将这个查询函数移到了一个类似于model的类型中,并从我的视图控制器调用它。显然,在视图控制器中进行数据库交互是不合适的。


实际上,这在Spring文档中被记录为最佳实践:[http://docs.spring.io/spring/docs/4.0.0.RC1/spring-framework-reference/html/jdbc.html#jdbc-JdbcTemplate-idioms] - mcanti

2
首先,在Spring MVC应用程序中有两个应用程序上下文,一个是通过web.xmlContextLoaderListener引导的主要上下文,另一个是由每个DispatcherServlet(通常只有一个)创建的子上下文。
您的控制器bean在两个上下文中都被创建,您正在主上下文中创建设置数据源的控制器,但Spring MVC使用在子上下文中创建的控制器。
显然,您应该只在子上下文中拥有单个副本。
  • 如果您使用普通的XML配置,则控制器bean定义必须放置在*-servlet.xml上下文中,而不是applicationContext.xml(如果您坚持默认命名)。

  • 如果您使用组件扫描,请确保主上下文不会扫描控制器包或使用@Controller注释的类。

如果您确定只创建了单个实例,它应该可以工作。请注意,控制器可以访问DataSource(子上下文可以访问主上下文),但反之则不行。

注1

在类似情况下,仅需打印:

System.out.println(this);

在控制器中,toString()默认应该为不同实例提供独特的结果。
注2:
您的错误处理应该得到改进。如果这不仅仅是示例目的,请始终记录完整的异常堆栈跟踪,并避免返回虚假结果,如空字符串,这往往会隐藏错误。

我告诉你,这是我的第一个Java项目,我用Roo创建了它,但我真的不太理解XML配置文件中发生的事情。但我认为很明显这个对象(也许整个应用程序?天哪)被实例化了两次。我将编辑我的问题以包括我的配置文件,你介意看一下吗? - Dan Ray
此外,Roo没有给我一个 *-servlet.xml 文件。它建立了一个 web.xml 和一个 applicationContext.xml。 - Dan Ray
哦,我刚刚发现了一个名为webmvc-config.xml的文件.... 它确实设置了注释扫描... - Dan Ray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接