zoukankan      html  css  js  c++  java
  • JAVA WEB快速入门之从编写一个基于SpringMVC框架的网站了解Maven、SpringMVC、SpringJDBC

    接上篇《JAVA WEB快速入门之通过一个简单的Spring项目了解Spring的核心(AOP、IOC)》,了解了Spring的核心(AOP、IOC)后,我们再来学习与实践Maven、SpringMVC、SpringJDBC(即:SSM中的S(Spring)S(SpringMVC)),暂不涉及ORM部份(即:M(Mybatis)),Mybatis将在下一篇文章中继续给大家分享。我相信通过之前几篇文章的学习与实践,已基本熟悉了搭建JSP网站及把AOP IOC应用到项目中,已具备编写JSP 普通WEB网站了,故从本文开始,一些之前讲述过的步骤不再一一说明,只讲重点及结果。

    (提示:本文内容有点长,涉及的知识点也比较多,若是新手建议耐心看完!)

     一、了解Maven并基于Maven构建一个空的SpringMVC网站:

     1.1Maven是什么?

      Maven 翻译为"专家"、"内行",是 Apache 下的一个纯 Java 开发的开源项目。基于项目对象模型(缩写:POM)概念,Maven利用一个中央信息片断能管理一个项目的构建、报告和文档等步骤。

      主要功能有:构建、文档生成、报告、依赖、SCMs、发布、分发、邮件列表

      Maven 提倡使用一个共同的标准目录结构,Maven 使用约定优于配置的原则,大家尽可能的遵守这样的目录结构,如下图示(来源网络):(假定 ${basedir} 表示工程目录)

      

      Maven 有以下三个标准的生命周期:(每个生命周期中都包含着一系列的阶段(phase)。这些 phase 就相当于 Maven 提供的统一的接口,然后这些 phase 的实现由 Maven 的插件来完成。)
      clean:项目清理的处理 、default(或 build):项目部署的处理、site:项目站点文档创建的处理

      详细说明请参见:http://www.runoob.com/maven/maven-build-life-cycle.html

    1.2搭建Maven环境

      1.2.1 从官网下载地址:http://maven.apache.org/download.cgi 中选择对应版本点击下载(开发一般以WINDOWS为主,故下载apache-maven-x.x.x-bin.zip),下载后解压到对应的目录,然后配置如下环境变量:

        新增变量名: MAVEN_HOME,变量值:maven解压根目录

            编辑系统变量 Path,添加变量值:%MAVEN_HOME%in (WIN10系统直接添加一行)

        配置完后,在cmd中输入:mvn -v 如果正常输出maven版本信息则表示OK;

      1.2.2 设置Maven的本地仓库的下载位置(默认是在系统盘(一般为C):Users当前用户名.m2 epository),如果不改变则会导致系统盘分区容量不足,建议及时修改

      打开%MAVEN_HOME%conf 目录下的settings.xml,修改localRepository元素的内容为自定义的repository目录,如:

     <localRepository>E:/LocalMvnRepositories</localRepository>
    

      然后设置IDE(eclipse或idea)MAVEN的存储位置,如下是eclipse的修改截图,idea同理

      依次打开:windows->perferences->Maven(右边列表节点)->User Settings,修改Global Settings 、User Settings的路径(将settings.xml COPY到指定的目录,然后这里设置这个目录),如下图示:【若没有maven选项可能需要手动安装maven插件,详见:https://www.cnblogs.com/dtting/p/8254103.html

      

      点击update Settings按钮完成更新,最后Apply即可

      1.2.3 settings.xml中设置镜像中央仓库URL,如下:(阿里云仓库)

      <mirror>
          <id>alimaven</id>
          <name>aliyun maven</name>
          <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
          <mirrorOf>central</mirrorOf>        
        </mirror>
      </mirrors>
    

      1.2.4 使用maven命令创建一个空的maven webapp,在cmd中执行如下命令:(请先cd切换到指定的项目创建根目录再执行如下命令)

      mvn archetype:generate

      然后根据每步提示进行交互处理,一般依次输入:archetypeArtifactId(如果要搭建MVC,则选择maven-archetype-webapp)、gourpId、artifactId、version、package,如下图示:

      

       当然如果不想一步步的按照向导来操作,可以带上完整参数来进行操作,例如:

    mvn archetype:generate
    -DgroupId=cn.zuowenjun.java
    -DartifactId=mvnspringmvc2 
    -DarchetypeArtifactId=maven-archetype-webapp 
    -DinteractiveMode=false
    

     执行完成后会输出build success字样,就表示构建maven webapp项目成功,本地文件目录就会生成相关的文件,如果需要使用IDE打开,可以在eclipse中通过:File->Open Projects from File systm or archive->选择import source路径(命令生成的项目根目录)->finish即可。

     1.2.5 通过IDE(eclipse) maven插件来构建项目,操作步骤为:

       File->New->Maven Project ->向导界面(Create a simple Proejct不要勾选,其余项按需设置)->向导界面(Select an Archetype:选择maven-archetype-webapp,如下图示)->设置->finish即可

       

      如果发现Archetype(即:项目原型模板,与VS中创建某个项目类似)的Version比较低,可以使用右下角的"Add Archetype"添加最新的Archetype(如上图示出现的1.4版本就是我加的),这里我们仍选择1.0,下一步就出现如下图示,设置相关信息即可

      

    两个踩坑点:(不论是用mvn命令行还是maven插件创建的webapp项目存在两个问题,需要处理)

      1.构建WebApp项目资源目录显示不全,缺少java等目录,问题原因是默认的项目是使用的JRE1.5,我们只需改成当前最新的版本即可(如:1.8),参考:https://blog.csdn.net/Sunny1994_/article/details/79058685

      2.改完后可能还会报:Description Resource Path Location Type Java compiler level does not match the version of the installed Java project facet. mvnspringmvc Unknown Faceted Project Problem (Java Version Mismatch),这是由于编译的JDK版本与项目的JDK版本不一致造成的,通过:在项目上右键Properties-》Project Facets,在打开的Project Facets页面中的Java下拉列表中,选择相应版本,如下图示:

      

      3.报缺少javax.servlet.http.HttpServlet父类,需要在pom文件中添加一下JAR包依赖,配置如下:(version请按需要配置)

    	<dependencies>
    		<dependency>
    			<groupId>javax.servlet</groupId>
    			<artifactId>javax.servlet-api</artifactId>
    			<version>3.1.0</version>
    		</dependency>
    	</dependencies>

        解决后,最后再buid proejct如无报错,说明空的maven project已创建OK。

    1.3添加springMVC、SpringJDBC相关依赖、配置web.xml,实现SpringMVC项目开发环境

       1.3.1在pom.xml中添加springMVC、SpringJDBC依赖,如下:(这里将版本号单独统一配置在properties中)

    	<properties>
    		<spring.version>5.1.3.RELEASE</spring.version>
    	</properties>
    	
    	<dependencies>
    		<dependency>
    			<groupId>junit</groupId>
    			<artifactId>junit</artifactId>
    			<version>3.8.1</version>
    			<scope>test</scope>
    		</dependency>
    		<dependency>
    			<groupId>javax.servlet</groupId>
    			<artifactId>javax.servlet-api</artifactId>
    			<version>3.1.0</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-core</artifactId>
    			<version>${spring.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-web</artifactId>
    			<version>${spring.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-webmvc</artifactId>
    			<version>${spring.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-jdbc</artifactId>
    			<version>${spring.version}</version>
    		</dependency>
    	</dependencies>
    

     如下是完整示例POM文件内容:(比较完整,涉及springMVC、springJDBC、数据驱动【这里是sqlserver】、jsp视图【JSTL、EL】)

    <project xmlns="http://maven.apache.org/POM/4.0.0"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<groupId>cn.zuowenjun.java</groupId>
    	<artifactId>mvnspringmvc</artifactId>
    	<packaging>war</packaging>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>mvnspringmvc Maven Webapp</name>
    	<url>http://maven.apache.org</url>
    
    
    	<properties>
    		<spring.version>5.1.3.RELEASE</spring.version>
    	</properties>
    
    	<dependencies>
    		<dependency>
    			<groupId>junit</groupId>
    			<artifactId>junit</artifactId>
    			<version>3.8.1</version>
    			<scope>test</scope>
    		</dependency>
    		<!-- JSP视图所需依赖 -->
    		<dependency>
    			<groupId>javax.servlet</groupId>
    			<artifactId>javax.servlet-api</artifactId>
    			<version>3.1.0</version>
    		</dependency>
    		<dependency>
    			<groupId>javax.servlet.jsp</groupId>
    			<artifactId>jsp-api</artifactId>
    			<version>2.2</version>
    		</dependency>
    		
    		<!-- JSP JSTL所需依赖 -->
    		<dependency>
    			<groupId>javax.servlet.jsp.jstl</groupId>
    			<artifactId>jstl-api</artifactId>
    			<version>1.2</version>
    			<exclusions>
    				<exclusion>
    					<groupId>javax.servlet</groupId>
    					<artifactId>servlet-api</artifactId>
    				</exclusion>
    				<exclusion>
    					<groupId>javax.servlet.jsp</groupId>
    					<artifactId>jsp-api</artifactId>
    				</exclusion>
    			</exclusions>
    		</dependency>
    		<dependency>
    			<groupId>org.glassfish.web</groupId>
    			<artifactId>jstl-impl</artifactId>
    			<version>1.2</version>
    			<exclusions>
    				<exclusion>
    					<groupId>javax.servlet</groupId>
    					<artifactId>servlet-api</artifactId>
    				</exclusion>
    				<exclusion>
    					<groupId>javax.servlet.jsp</groupId>
    					<artifactId>jsp-api</artifactId>
    				</exclusion>
    				<exclusion>
    					<groupId>javax.servlet.jsp.jstl</groupId>
    					<artifactId>jstl-api</artifactId>
    				</exclusion>
    			</exclusions>
    		</dependency>
    
    		<!-- springMVC所需依赖  -->
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-core</artifactId>
    			<version>${spring.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-web</artifactId>
    			<version>${spring.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-webmvc</artifactId>
    			<version>${spring.version}</version>
    		</dependency>
    		
    		<!-- Spring JDBC【数据访问】所需依赖  -->
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-jdbc</artifactId>
    			<version>${spring.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>com.microsoft.sqlserver</groupId>
    			<artifactId>mssql-jdbc</artifactId>
    			<version>7.0.0.jre8</version>
    		</dependency>
    		
    	</dependencies>
    	<build>
    		<finalName>mvnspringmvc</finalName>
    	</build>
    </project>
    

      

     1.3.2在web.xml中配置DispatcherServlet及ContextLoaderListener,如下:(如下完整的web.xml,有改造过,因为默认的web的声明头有问题,另外如果不配置contextConfigLocation,那么springContext的配置文件默认路径:[servlet-name(是DispatcherServlet配置的名称)]-servlet.xml)

    <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
        http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    
    	<display-name>Archetype Created Web Application</display-name>
    
    	<servlet>
    		<servlet-name>springmvc</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<param-value>classpath:springmvc-servlet.xml</param-value>
    		</init-param>
    		<load-on-startup>1</load-on-startup>
    	</servlet>
    
    	<!-- Map all requests to the DispatcherServlet for handling -->
    	<servlet-mapping>
    		<servlet-name>springmvc</servlet-name>
    		<url-pattern>/</url-pattern>
    	</servlet-mapping>
    	<context-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>classpath:springmvc-servlet.xml</param-value>
    	</context-param>
    
    	<listener>
    		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    	</listener>
    
    	<!-- 解决中文请求乱码问题 -->
    	<filter>
    		<filter-name>CharacterEncoding</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-mapping>
    		<filter-name>CharacterEncoding</filter-name>
    		<url-pattern>/*</url-pattern>
    	</filter-mapping>
    
    	<!-- 定义默认首页,欢迎页 -->
    	<welcome-file-list>
    		<welcome-file>index.jsp</welcome-file>
    	</welcome-file-list>
    
    	<!-- 定义错误处理页面,此处只定义404,其余的通过@ControllerAdvice+@ExceptionHandler来处理 -->
    	<error-page>
    		<error-code>404</error-code>
    		<location>/WEB-INF/errors/notfound.jsp</location>
    	</error-page>
    </web-app>
    

     知识扩展说明:springMVC异常统一处理有多种方式,可参见:https://www.cnblogs.com/bloodhunter/p/4825279.html  、 https://www.cnblogs.com/junzi2099/p/7840294.html 、https://blog.csdn.net/butioy_org/article/details/78718405

     1.3.3在src/main/resources中创建springmvc-servlet.xml(即:contextConfigLocation配置的路径文件),这个是spring Context配置文件与上一篇介绍的Beans.xml作用类似,具体配置如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <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:mvc="http://www.springframework.org/schema/mvc" 
    	xmlns:tx="http://www.springframework.org/schema/tx"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
    		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
    		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
    		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
    
    	<!-- 对包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能 ,多个包名用逗号分隔 -->
    	<context:component-scan
    		base-package="cn.zuowenjun.java.mvc"></context:component-scan>
    	
    	<mvc:annotation-driven>
    <!-- 		<mvc:argument-resolvers></mvc:argument-resolvers> -->
    <!-- 		<mvc:async-support></mvc:async-support> -->
    <!-- 		<mvc:message-converters></mvc:message-converters> -->
    <!-- 		<mvc:path-matching/> -->
    <!-- 		<mvc:return-value-handlers></mvc:return-value-handlers> -->
    	</mvc:annotation-driven>
    	
    	<bean
    		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    		<property name="prefix" value="/WEB-INF/jsp/" />
    		<property name="suffix" value=".jsp" />
    	</bean>
    	
    	<!-- 配置静态资源,如html,图片,js,css等,多个资源位置可用逗号分隔 -->
    	 <mvc:resources mapping="/pages/**" location="/WEB-INF/pages/" />
    	
    	<!-- 可配置读取外部配置文件,如果有配置jdbc.properties,则下面的dataSource的相关property可以使用${xxx}占位符,这里不演示 -->
    	<!--<context:property-placeholder location="classpath:jdbc.properties" /> -->
    	
    	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    		<property name="driverClassName"  value="com.microsoft.sqlserver.jdbc.SQLServerDriver"></property>
    		<property name="url" value="jdbc:sqlserver://ip:port;DatabaseName=testDB"></property>
    		<property name="username" value="dbuser"></property>
    		<property name="password" value="password"></property>
    	</bean>
    	
    	
    	
    	<!-- 配置事务管理器  -->
        <bean id="txManager"
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource" />
        </bean>
        
    <!--     事务扫描开始(开启@Tranctional) 此示例暂不启用-->
    <!--     <tx:annotation-driven transaction-manager="txManager" /> -->
    	
    </beans>
    

    对如下配置作简要说明:

     context:component-scan:自动扫描包并将标记@Component(组件),@Service(服务),@Controller(控制器),@Repository(数据仓库)的Bean注册到Spring IOC容器中,这样就无需手动在xml进行单独配置了;详见:https://www.cnblogs.com/fightingcoding/p/component-scan.html

     mvc:annotation-driven:简化配置自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter两个bean,这些是spring MVC为@Controller分发请求所必须的。里面也包含一些子元素的配置,详见:https://starscream.iteye.com/blog/1098880

     mvc:resources:配置处理静态资源,配置的url路径将只会由default默认的servlet来处理,而不再由DispatcherServlet处理,详见:https://www.cnblogs.com/linnuo/p/7699401.html

     注册Bean:InternalResourceViewResolver,视图解析器,用于找到视图文件;

     注册Bean:DriverManagerDataSource,指定SpringJDBC的数据源驱动相关连接信息;

     注册Bean:DataSourceTransactionManager,配置事务管理器,用于事务处理;

     如上步骤都操作完后,就可以Buid Project,如果构建成功,则说明SpringMVC项目环境已OK;可以看到项目目录如下图示:

    (其中那些包都是我为后面写代码时提前创建的)

    二、编写SpringMVC示例代码(演示:发博文,写评论),了解SpringMVC及SpringJDBC:

      2.1.定义Model:(在cn.zuowenjun.java.mvc.model包中分类定义:Post、PostComment 两个数据模型)

    //Post.java
    package cn.zuowenjun.java.mvc.model;
    
    import java.util.Date;
    
    public class Post {
    	private int id;
    	private String title;
    	private String content;
    	private String author;
    	private Date createTime;
    	
    	public int getId() {
    		return id;
    	}
    	public void setId(int id) {
    		this.id = id;
    	}
    	
    	public String getTitle() {
    		return title;
    	}
    	public void setTitle(String title) {
    		this.title = title;
    	}
    	
    	public String getContent() {
    		return content;
    	}
    	public void setContent(String content) {
    		this.content = content;
    	}
    	
    	public String getAuthor() {
    		return author;
    	}
    	public void setAuthor(String author) {
    		this.author = author;
    	}
    	
    	public Date getCreateTime() {
    		return createTime;
    	}
    	public void setCreateTime(Date createTime) {
    		this.createTime = createTime;
    	}
    	
    	
    
    }
    
    
    
    //PostComment.java
    
    package cn.zuowenjun.java.mvc.model;
    
    import java.util.Date;
    
    public class PostComment {
    	private int id;
    	private int postid;
    	private String content;
    	private String createby;
    	private Date createTime;
    	
    	public int getId() {
    		return id;
    	}
    	public void setId(int id) {
    		this.id = id;
    	}
    	
    	public int getPostid() {
    		return postid;
    	}
    	public void setPostid(int postid) {
    		this.postid = postid;
    	}
    	
    	public String getContent() {
    		return content;
    	}
    	public void setContent(String content) {
    		this.content = content;
    	}
    	
    	public String getCreateby() {
    		return createby;
    	}
    	public void setCreateby(String createby) {
    		this.createby = createby;
    	}
    	
    	public Date getCreateTime() {
    		return createTime;
    	}
    	public void setCreateTime(Date createTime) {
    		this.createTime = createTime;
    	}
    	
    	
    
    }
    

      非常简单的POJO风格的两个类,只有成对的getter、setter方法。

      2.2定义Dao接口:(在cn.zuowenjun.java.mvc.dao包中分别定义:PostDao、PostCommentDao 两个DAO接口,注意JAVA与C#的接口定义规范有所不同,C#中要求以I开头,而JAVA中并没有此要求,JAVA只要要求实现类以Impl后缀结尾)

    //PostDao.java
    package cn.zuowenjun.java.mvc.dao;
    
    import java.util.Date;
    import java.util.List;
    
    import cn.zuowenjun.java.mvc.model.Post;
    
    public interface PostDao {
    	Post get(int id);
    	List<Post> getList(Date frmDate,Date toDate);
    	int create(Post post);
    	Boolean delete(int id);
    	Boolean update(Post post);
    	
    }
    
    
    //PostCommentDao.java
    package cn.zuowenjun.java.mvc.dao;
    
    import java.util.List;
    
    import cn.zuowenjun.java.mvc.model.PostComment;
    
    public interface PostCommentDao {
    	PostComment get(int id);
    	List<PostComment> getList(int postId);
    	Boolean create(PostComment postCmmt);
    	Boolean delete(int id);
    	Boolean update(PostComment postCmmt);
    }
    

     如上代码所示,DAO接口主要定义了增、删、改、查(查单个,查多个),这是大部份的常见场景,根据需要定义。

     2.3实现Dao接口:(在cn.zuowenjun.java.mvc.dao.impl包中分别定义:BaseDao、PostDaoImpl、PostCommentDaoImpl,其中BaseDao是抽像类,主要是约束构造函数的入参及提供通用的获取JdbcTemplate实例对象)

     BaseDao抽象类定义:

    package cn.zuowenjun.java.mvc.dao.impl;
    
    import javax.sql.DataSource;
    
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
    
    public abstract class BaseDao {
    	private JdbcTemplate jdbcTemplateObj;
    	private NamedParameterJdbcTemplate namedParamJdbcTemplate;
    	
    	public BaseDao(DataSource dataSource) {
    		this.jdbcTemplateObj=new JdbcTemplate(dataSource);
    		this.namedParamJdbcTemplate=new NamedParameterJdbcTemplate(dataSource);
    	}
    	
    	protected JdbcTemplate getJdbcTemplate() {
    		return this.jdbcTemplateObj;
    	}
    	
    	protected NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
    		return this.namedParamJdbcTemplate;
    	}
    	
    	
    }

    BaseDao抽象类中分别实例化了两个数据访问对象:JdbcTemplate、NamedParameterJdbcTemplate,之所以这样做是因为我会分别在PostDaoImpl演示使用NamedParameterJdbcTemplate来进行CRUD操作,PostCommentDaoImpl演示使用JdbcTemplate来进行CRUD操作。通过对比代码来发现两者的区别以及优势。

    PostDaoImpl类定义:

    package cn.zuowenjun.java.mvc.dao.impl;
    
    import java.util.*;
    
    import javax.sql.DataSource;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.dao.DataAccessException;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.namedparam.*;
    import org.springframework.jdbc.support.*;
    import org.springframework.transaction.*;
    import org.springframework.transaction.support.*;
    
    import cn.zuowenjun.java.mvc.dao.*;
    import cn.zuowenjun.java.mvc.model.Post;
    
    public class PostDaoImpl extends BaseDao implements PostDao {
    
    	private PlatformTransactionManager transactionManager;
    	
    	@Autowired
    	public PostDaoImpl(DataSource dataSource,PlatformTransactionManager transactionManager) {
    		super(dataSource);
    		this.transactionManager=transactionManager;
    	}
    
    	@Override
    	public Post get(int id) {
    		String sql="select * from TA_TestPost where id=:id";
    		MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource();
    				mapSqlParameterSource.addValue("id", id);
    		return this.getNamedParameterJdbcTemplate().queryForObject(sql, mapSqlParameterSource,new BeanPropertyRowMapper<>(Post.class));
    	}
    
    	@Override
    	public List<Post> getList(Date frmDate, Date toDate) {
    		String sql="select * from TA_TestPost where createTime between :frmDate to :toDate";
    		Map<String, Object> paramMap = new HashMap<>();
    		paramMap.put("frmDate", frmDate);
    		paramMap.put("toDate", toDate);
    		
    		return this.getNamedParameterJdbcTemplate().query(sql, paramMap,new BeanPropertyRowMapper<>(Post.class));
    	}
    
    	@Override
    	public int create(Post post) {
    		String sql="insert into TA_TestPost(title, content, author, createTime) values(:title,:content,:author,getdate())";
    		BeanPropertySqlParameterSource beanPropParam=new BeanPropertySqlParameterSource(post);
            KeyHolder keyHolder = new GeneratedKeyHolder();
            
    		int r= this.getNamedParameterJdbcTemplate().update(sql, beanPropParam, keyHolder);
    		if(r>0) {
    			System.out.println("create is ok!");
    			return keyHolder.getKey().intValue();
    		}
    		else {
    			System.out.println("create is failed!");
    			return -1;
    		}
    	}
    
    	@Override
    	public Boolean delete(int id) {
    		TransactionDefinition def = new DefaultTransactionDefinition();
    	    TransactionStatus status = transactionManager.getTransaction(def);
    		try {
    			Map<String, Object> paramMap = new HashMap<>();
    			paramMap.put("postid",id);
    			NamedParameterJdbcTemplate namedJdbcTemplate=this.getNamedParameterJdbcTemplate();
    			namedJdbcTemplate.update("delete from TA_TestPost where id=:postid",paramMap);
    			namedJdbcTemplate.update("delete from TA_TestPostComment where postid=:postid", paramMap);
    			transactionManager.commit(status);
    			
    			System.out.println("delete is ok!");
    			
    			return true;
    			 
    		}catch(DataAccessException daEx) {
    			System.out.printf("delete is failed,Exception:%s",daEx.getMessage());
    			transactionManager.rollback(status);
    			return false;
    		}
    	}
    
    	@Override
    	public Boolean update(Post post) {
    		String sql="update TA_TestPost set title=:title,content=:content,author=:author,createTime=getdate() where id=:id";
    		BeanPropertySqlParameterSource beanPropParam=new BeanPropertySqlParameterSource(post);
    		int r= this.getNamedParameterJdbcTemplate().update(sql, beanPropParam);
    		if(r>0) {
    			System.out.println("update is ok!");
    			return true;
    		}
    		else {
    			System.out.println("update is failed!");
    			return false;
    		}
    	}
    
    }

     涉及知识要点说明:

    1.NamedParameterJdbcTemplate支持的常用SQL参数类型有:MapSqlParameterSource(键值对),Map<String, ?>,BeanPropertySqlParameterSource(把一个Bean所有属性映射成参数,推荐这种)

    2.NamedParameterJdbcTemplate 使用的SQL语句中参数的命名规则为:冒号+参数名,如: :content,有点类似C#中的ADO.NET的参数化类型(@content),只是前缀指示符不同而矣。

    3.若执行新增一条记录,且需要返回自增的ID,这时可以通过传入GeneratedKeyHolder的实例,最后使用该实例的变量获取自增ID,如上面的create方法中的一样,最终使用:keyHolder.getKey().intValue()获取到自增ID;

    4.查询返回的结果若要映射为实体对象(POJO、JavaBean),则需要自定义实现RowMapper<T>,然后传入自定义的RowMapper的变量,当然可以使用Spring JDBC 的默认实现类:BeanPropertyRowMapper(内部使用反射, 可能在某些场景下性能不佳),如果只需要返回某列的值,则可以直接指定映射的类型,如:String.class

    5.若想查询返回一个实体对象或实体对象列表,应该使用queryForObject、query的重载方法含RowMapper<T>的参数,注意:queryForList 只能返回某一列的值,不能直接返回实体对象列表,因为最后的一个参数Class<T> requiredType最终内部转化为了:SingleColumnRowMapper

    6.使用事务需要PlatformTransactionManager、TransactionDefinition、TransactionStatus,如上面的delete方法;

    PostCommentDaoImpl类定义:

    package cn.zuowenjun.java.mvc.dao.impl;
    
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.List;
    
    import javax.sql.DataSource;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.RowMapper;
    
    import cn.zuowenjun.java.mvc.dao.PostCommentDao;
    import cn.zuowenjun.java.mvc.model.PostComment;
    import cn.zuowenjun.java.mvc.model.mapper.PostCommentMapper;
    
    public class PostCommentDaoImpl extends BaseDao implements PostCommentDao {
    
    	@Autowired
    	public PostCommentDaoImpl(DataSource dataSource) {
    		super(dataSource);
    		// TODO Auto-generated constructor stub
    	}
    
    	@Override
    	public PostComment get(int id) {
    		String sql = "select * from TA_TestPostComment where id=?";
    
    		// 第一种:基于定义好的实现了RowMapper的PostCommentMapper实例
    		// return this.getJdbcTemplate().queryForObject(sql,new Object[] {id},new
    		// PostCommentMapper());
    
    		// 第二种:直接使用匿名内部类(可以理解为与C#的委托类型)
    //		return this.getJdbcTemplate().queryForObject(sql,new Object[]{id},new RowMapper<PostComment>() {
    //
    //			@Override
    //			public PostComment mapRow(ResultSet rs, int rowNum) throws SQLException {
    //				PostComment model=new PostComment();
    //				model.setId(rs.getInt("id"));
    //				model.setPostid(rs.getInt("postid"));
    //				model.setContent(rs.getString("content"));
    //				model.setCreateby(rs.getString("createby"));
    //				model.setCreateTime(rs.getDate("createtime"));
    //				return model;
    //			}
    //		});
    
    		// 第三种:直接使用Lambda表达式,这个与C#lambda就比较像了,因为JAVA抄自C#
    		return this.getJdbcTemplate().queryForObject(sql, new Object[] { id }, (rs, i) -> {
    			PostComment model = new PostComment();
    			model.setId(rs.getInt("id"));
    			model.setPostid(rs.getInt("postid"));
    			model.setContent(rs.getString("content"));
    			model.setCreateby(rs.getString("createby"));
    			model.setCreateTime(rs.getDate("createtime"));
    			return model;
    		});
    
    	}
    
    	@Override
    	public List<PostComment> getList(int postId) {
    		String sql = "select * from TA_TestPostComment where postid=?";
    		return this.getJdbcTemplate().query(sql, new Object[] { postId }, new PostCommentMapper());
    	}
    
    	@Override
    	public Boolean create(PostComment postCmmt) {
    		String sql = "insert into TA_TestPostComment(id, postid, content, createby, createTime) values(?,?,?,?,?)";
    		int r = this.getJdbcTemplate().update(sql, new Object[] { postCmmt.getId(), postCmmt.getPostid(),
    				postCmmt.getContent(), postCmmt.getCreateby(), postCmmt.getCreateTime() });
    
    		if (r > 0) {
    			System.out.println("create is ok!");
    			return true;
    		} else {
    			System.out.println("create is failed!");
    			return false;
    		}
    	}
    
    	@Override
    	public Boolean delete(int id) {
    		String sql = "delete from TA_TestPostComment where id=?";
    		int r = this.getJdbcTemplate().update(sql, new Object[] { id });
    		if (r > 0) {
    			System.out.println("delete is ok!");
    			return true;
    		} else {
    			System.out.println("delete is failed!");
    			return false;
    		}
    	}
    
    	@Override
    	public Boolean update(PostComment postCmmt) {
    		String sql = "update TA_TestPostComment set postid=?,content=?,createby=?,createTime=getdate() where id=?";
    		int r = this.getJdbcTemplate().update(sql, (pss) -> {
    			pss.setInt(1, postCmmt.getPostid());
    			pss.setString(2, postCmmt.getContent());
    			pss.setString(3, postCmmt.getCreateby());
    			pss.setInt(4, postCmmt.getId());
    		});
    
    		if (r > 0) {
    			System.out.println("update is ok!");
    			return true;
    		} else {
    			System.out.println("update is failed!");
    			return false;
    		}
    
    	}
    
    }
    

    PostCommentMapper类定义:

    package cn.zuowenjun.java.mvc.model.mapper;
    
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import org.springframework.jdbc.core.RowMapper;
    
    import cn.zuowenjun.java.mvc.model.PostComment;
    
    public class PostCommentMapper implements RowMapper<PostComment> {
    
    	@Override
    	public PostComment mapRow(ResultSet rs, int rowNum) throws SQLException {
    		PostComment model=new PostComment();
    		model.setId(rs.getInt("id"));
    		model.setPostid(rs.getInt("postid"));
    		model.setContent(rs.getString("content"));
    		model.setCreateby(rs.getString("createby"));
    		model.setCreateTime(rs.getDate("createtime"));
    		
    		return model;
    	}
    
    }
    

      

    涉及知识要点说明:

     1.JdbcTemplate支持的常见SQL参数类型有:Object[](NamedParameterJdbcTemplate不直接支持,可通过getJdbcTemplate获得JdbcTemplate,然后继续使用JdbcTemplate的CRUD用法),PreparedStatementCreator、PreparedStatementSetter

     2.JdbcTemplate返回结果常见处理转换类型有:RowMapper<T>、RowCallbackHandler、ResultSetExtractor<T> ,注意涉及数据索引都是从1开始

     3.无论是NamedParameterJdbcTemplate 还是JdbcTemplate的入参SQL参数类型、返回结果参数处理类型大部份都是函数式接口(标注了@FunctionalInterface),意味着我们可以直接使用匿名内部类或Lambda表达式来传参,如上面代码中的get方法,分别演示了使用PostCommentMapper、new RowMapper<PostComment>(){...}匿名内部类、(rs, i) -> {...}Lambda表达式,其它同理;

     4.JdbcTemplate的SQL语句中的参数命名规则为:?,与使用原生的JDBC 进行参数化查询用法相同

     NamedParameterJdbcTemplate 与JdbcTemplate 都实现JdbcOperations接口;

    2.4定义Service接口:(在cn.zuowenjun.java.mvc.service包中分别定义了:PostService、PostCommentService、UserService 三个接口)

    //PostService.java
    package cn.zuowenjun.java.mvc.service;
    
    import java.util.Date;
    import java.util.List;
    
    import cn.zuowenjun.java.mvc.model.Post;
    
    public interface PostService {
    	
    	Post get(int id);
    	List<Post> getList(Date frmDate,Date toDate);
    	List<Post> getAll();
    	int create(Post post);
    	Boolean delete(int id);
    	Boolean update(Post post);
    }
    
    //PostCommentService.java
    package cn.zuowenjun.java.mvc.service;
    
    import java.util.List;
    
    import cn.zuowenjun.java.mvc.model.PostComment;
    
    public interface PostCommentService {
    	PostComment get(int id);
    	List<PostComment> getList(int postId);
    	Boolean create(PostComment postCmmt);
    	Boolean delete(int id);
    	Boolean update(PostComment postCmmt);
    }
    
    //UserService .java
    package cn.zuowenjun.java.mvc.service;
    
    public interface UserService {
    	String login(String uid,String pwd);
    	String logout();
    	String getLoginUserName();
    }
    

     如上代码所示,接口中只是定义了业务所需要的方法,可能大家看到觉得与dao接口很类似(示例代码中确实是相同的),但其实它们服务的对象不同,dao可能相比service更原子化,更单一 一些,dao只需要为单个表提供数据读写服务,而service则是为表现层(UI)提供真实的服务场景,而这些场景有可能是复杂的,包含多个dao的数据源或操作多个dao,同时还承担了业务数据的验证逻辑处理转换等功能,service层我理解是业务服务层(BLL),如果按照DDD来划分,我认为service层则是应用服务层或领域服务层

    2.5实现Service接口:(在cn.zuowenjun.java.mvc.service.impl包中分别定义PostServiceImpl、PostCommentServiceImpl、UserServiceImpl类,它们实现了对应的service接口)

    //PostServiceImpl.java
    package cn.zuowenjun.java.mvc.service.impl;
    
    import java.security.*;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import cn.zuowenjun.java.mvc.model.Post;
    import cn.zuowenjun.java.mvc.service.PostService;
    import cn.zuowenjun.java.mvc.dao.*;
    
    @Service
    public class PostServiceImpl implements PostService {
    
    	@Autowired
    	private PostDao postDao;
    	
    	@Override
    	public Post get(int id) {
    		return postDao.get(id);
    	}
    
    	@Override
    	public List<Post> getList(Date frmDate, Date toDate) {
    		long frmDateVal=frmDate.getTime();
    		long toDateVal=toDate.getTime();
    		
    		if(frmDateVal>toDateVal) {
    			throw new InvalidParameterException("开始时间需<=结束时间");
    		}
    		return postDao.getList(frmDate, toDate);
    	}
    	
    	@Override
    	public List<Post> getAll() {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    		try {
    			return getList(sdf.parse("1900-1-1"), sdf.parse("2099-12-1"));
    		} catch (ParseException e) {
    			return null;
    		}
    	}
    	
    
    	@Override
    	public int create(Post post) {
    		String result=verifyModel(post,true);
    		if(!result.isEmpty()) {
    			throw new InvalidParameterException(result);
    		}
    		return postDao.create(post);
    	}
    
    	@Override
    	public Boolean delete(int id) {
    		return postDao.delete(id);
    	}
    
    	@Override
    	public Boolean update(Post post) {
    		String result=verifyModel(post,true);
    		if(!result.isEmpty()) {
    			throw new InvalidParameterException(result);
    		}
    		return postDao.update(post);
    	}
    	
    	private String verifyModel(Post post,Boolean isNew) {
    		StringBuilder errMsgBuilder=new StringBuilder();
    		
    		if(!isNew && post.getId()<=0) {
    			errMsgBuilder.append("ID不能为空!");
    		}
    		
    		if(post.getTitle().trim().isEmpty()) {
    			errMsgBuilder.append("标题不能为空!");
    		}
    		
    		if(post.getContent().trim().isEmpty()) {
    			errMsgBuilder.append("内容不能为空!");
    		}
    		
    		if(post.getAuthor().trim().isEmpty()) {
    			errMsgBuilder.append("作者不能为空!");
    		}
    		
    		return errMsgBuilder.toString();
    	}
    }
    
    //PostCommentServiceImpl.java
    package cn.zuowenjun.java.mvc.service.impl;
    
    import java.security.InvalidParameterException;
    import java.util.Date;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import cn.zuowenjun.java.mvc.dao.PostCommentDao;
    import cn.zuowenjun.java.mvc.model.PostComment;
    import cn.zuowenjun.java.mvc.service.PostCommentService;
    
    @Service
    public class PostCommentServiceImpl implements PostCommentService {
    
    	@Autowired
    	private PostCommentDao postCommentDao;
    	
    	@Override
    	public PostComment get(int id) {
    		return postCommentDao.get(id);
    	}
    
    	@Override
    	public List<PostComment> getList(int postId) {
    		return postCommentDao.getList(postId);
    	}
    
    	@Override
    	public Boolean create(PostComment postCmmt) {
    		String result=verifyModel(postCmmt,true);
    		if(!result.isEmpty()) {
    			throw new InvalidParameterException(result);
    		}
    		postCmmt.setCreateTime(new Date());
    		return postCommentDao.create(postCmmt);
    	}
    
    	@Override
    	public Boolean delete(int id) {
    		return postCommentDao.delete(id);
    	}
    
    	@Override
    	public Boolean update(PostComment postCmmt) {
    		String result=verifyModel(postCmmt,false);
    		if(!result.isEmpty()) {
    			throw new InvalidParameterException(result);
    		}
    		
    		return postCommentDao.update(postCmmt);
    	}
    	
    	private String verifyModel(PostComment postCmmt,Boolean isNew) {
    		StringBuilder errMsgBuilder=new StringBuilder();
    		
    		if(!isNew && postCmmt.getId()<=0) {
    			errMsgBuilder.append("ID不能为空!");
    		}
    		
    		if(postCmmt.getPostid()<=0) {
    			errMsgBuilder.append("文章ID不能为空!");
    		}
    		
    		if(postCmmt.getContent().trim().isEmpty()) {
    			errMsgBuilder.append("内容不能为空!");
    		}
    		
    		if(postCmmt.getCreateby().trim().isEmpty()) {
    			errMsgBuilder.append("回复者不能为空!");
    		}
    		
    		return errMsgBuilder.toString();
    	}
    
    }
    
    
    //UserServiceImpl.java
    package cn.zuowenjun.java.mvc.service.impl;
    
    import cn.zuowenjun.java.mvc.service.UserService;
    import java.util.*;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.stereotype.Service;
    import org.springframework.web.context.request.*;
    
    @Service
    public class UserServiceImpl implements UserService {
    
    	@Override
    	public String login(String uid, String pwd) {
    		if(uid.isEmpty() || pwd.isEmpty()) {
    			return "用户名与密码都不能为空!";
    		}
    		
    		ResourceBundle userRes= ResourceBundle.getBundle("user");
    		String configUid= userRes.getString("user.userid");
    		String configPwd=userRes.getString("user.password");
    		
    		if(configUid.equals(uid) && configPwd.equals(pwd)) {
    			
    			String configUName=userRes.getString("user.username");
    			
    			HttpSession session= getRequest().getSession();
    			session.setAttribute("loginUid", uid);
    			session.setAttribute("loginUname",configUName);
    			return null;
    		}else{
    			return "用户名或密码不正确!";
    		}
    	}
    
    	@Override
    	public String logout() {
    		try {
    			getRequest().getSession().removeAttribute("loginUid");
    			return null;
    		}catch(Exception ex) {
    			return ex.getMessage();
    		}
    		
    	}
    
    	@Override
    	public String getLoginUserName() {
    		Object loginUnameObj= getRequest().getSession().getAttribute("loginUname");
    		if(loginUnameObj==null) {
    			return null;
    		}else{
    			return  (String)loginUnameObj;
    		}
    	}
    
    	private HttpServletRequest getRequest() {
    		HttpServletRequest  request= ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
    		return request;
    	}
    }
    

     注意:登录服务中我使用了user.properties属性配置文件,username中的中文自动转码了。

    user.userid=admin
    user.password=www.zuowenjun.cn.java
    user.username=u68A6u5728u65C5u9014  

    如上代码所示,所有的service实现类都标注了@Service注解,之前的dao实现类也都标注了@Repository,目的是为了实现spring容器的自动扫描并注册到IOC容器中,正如我在上面讲到的springmvc-servlet.xml配置文件中说的一样;另外service实现类除了调用dao完成数据的操作,另外还有业务数据的校验,比如代码中的:verifyModel方法等,当然实际的大型项目中可能业务逻辑复杂得多,模型验证也可以通过注解来实现,与C#中在System.ComponentModel.DataAnnotations下的相关特性类似,大家有兴趣可以查阅相关资料;

    2.6设计Controller及View:

     2.6.1创建一个BlogController类,用于处理管理博文(查看、发表、修改、删除)、评论(发表)的相关ACTION,如下:

    package cn.zuowenjun.java.mvc.controller;
    
    import java.io.IOException;
    import java.security.InvalidParameterException;
    import java.text.*;
    import java.util.*;
    
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.propertyeditors.CustomDateEditor;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.ui.ModelMap;
    import org.springframework.web.bind.WebDataBinder;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.context.request.WebRequest;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.support.RedirectAttributes;
    
    import cn.zuowenjun.java.mvc.model.Post;
    import cn.zuowenjun.java.mvc.model.PostComment;
    import cn.zuowenjun.java.mvc.service.*;
    /**
     * 
     * @author zuowenjun.cn
     *refer-mavendepconfig:https://blog.csdn.net/qq_29227939/article/details/52063869
     *refer-EL:http://www.cnblogs.com/dongfangshenhua/p/6731421.html
     */
    @Controller
    @RequestMapping("/blog")
    public class BlogController {
    	
    	@Autowired
    	private PostService postService;
    	
    	@Autowired
    	private PostCommentService postCommentService;
    	
    	@RequestMapping()
    	public ModelAndView list() {
    		List<Post> postList= postService.getAll();
    		ModelAndView mv=new ModelAndView();
    		mv.addObject("posts",postList);
    		mv.setViewName("bloglist");
    
    		return mv;
    	}
    	
    	@RequestMapping(path="/querylist",method=RequestMethod.POST)
    	public ModelAndView list(@RequestParam(required=true) Date frmDate,@RequestParam(required=true) Date toDate,ModelAndView mv) {
    		List<Post> postList=postService.getList(frmDate, toDate);
    		mv.setViewName("bloglist");
    		mv.addObject("posts",postList);
    		return mv;
    	}
    	
    	@RequestMapping("/post/{postid}")
    	public String detail(@PathVariable String postid,ModelMap model) {
    		int pid=Integer.parseInt(postid);
    		model.put("post", postService.get(pid));
    		model.put("comments", postCommentService.getList(pid));
    		
    		return "blogdetail";
    	}
    
    	
    	@RequestMapping(path="/savecomment",method=RequestMethod.POST)
    	public String saveComment(@ModelAttribute() PostComment postComment,RedirectAttributes redirectAttr) {
    		String resultMsg="评论保存成功";
    		if(!postCommentService.create(postComment)) {
    			resultMsg="评论保存失败,请稍后重试";
    		}
    		redirectAttr.addFlashAttribute("msg", resultMsg);
    		
    		return "redirect:/blog/post/" + postComment.getPostid();
    	}
    	
    	@RequestMapping(path="/editpost/{postid}",method=RequestMethod.GET)
    	public ModelAndView editPost(@PathVariable(required=true) int postid) {
    		ModelAndView mv=new ModelAndView();
    		Post post=null;
    		post=postService.get(postid);
    		if(post==null) {
    			throw new InvalidParameterException("无效的postid");
    		}
    		mv.addObject("post", post);
    		mv.setViewName("blogedit");
    		
    		return mv;
    	}
    	
    	@RequestMapping("/editpost")
    	public String createPost(Map<String,Object> viewDataMap) {
    		Post post=new Post();
    		viewDataMap.put("post", post);
    		return "blogedit";
    	}
    	
    	@RequestMapping(path="/editpost",method=RequestMethod.POST)
    	public String updatePost(@ModelAttribute("post") Post post,@RequestParam("doAction") String action,Model model,
    			HttpServletResponse reponse) throws IOException {
    		
    		String result="保存成功!";
    		if(action.equals("delete")) { //删除操作
    			if(!postService.delete(post.getId())){
    				result="删除失败,请重试!";
    			}else {
    				String jsResult="<script>alert('删除成功!');self.close();</script>";
    				reponse.setContentType("text/html;charset=utf-8");
    				reponse.getWriter().append(jsResult);
    				return null;
    			}
    		}
    		else { //编辑操作
    			
    			if(post.getId()<=0) { //新增博文逻辑
    				int postId= postService.create(post);
    				if(postId>0) {
    					post.setId(postId);
    				}else {
    					result="保存失败,请重试!";
    				}
    			}else if(!postService.update(post)) { //更新博文逻辑
    				result="保存失败,请重试!";
    			}
    		}
    		model.addAttribute("result", result);
    		return "blogedit";
    	}
    
    	
    	
    	@InitBinder
    	public void initBinder(WebDataBinder binder, WebRequest request) {
    		
    		//转换日期
    		DateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");
    		binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));// CustomDateEditor为自定义日期编辑器
    	}
    	
    }
    

     创建AccountController类,用于提供登录、登出的途径,代码如下:

    package cn.zuowenjun.java.mvc.controller;
    
    import java.io.IOException;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.servlet.ModelAndView;
    
    import cn.zuowenjun.java.mvc.service.UserService;
    
    /**
     * seeparambind:http://www.cnblogs.com/xiaoxi/p/5695783.html
     * 
     */
    @Controller
    @RequestMapping("/account")
    public class AccountController {
    	
    	@Autowired
    	private UserService userService;
    	
    	@RequestMapping("/signin")
    	public String signIn() {
    		return "signin";
    	}
    	
    	@RequestMapping(path="/signin",method=RequestMethod.POST)
    	public ModelAndView signIn(@RequestParam(required=true) String uid,@RequestParam(required=true) String pwd) {
    		String loginResult=userService.login(uid, pwd);
    		ModelAndView mv= new ModelAndView();
    		if(loginResult==null || loginResult.isEmpty()) {//登录成功跳转
    			mv.setViewName("redirect:/blog");
    		}
    		else {
    			mv.setViewName("signin");
    			mv.addObject("message",loginResult==null || loginResult.isEmpty()?"登录成功":"登录失败:" + loginResult);
    		}
    		
    		return mv;
    	}
    	
    	@RequestMapping("/signout")
    	public void signOut(HttpServletRequest request, HttpServletResponse response) throws IOException {
    		userService.logout();
    		response.sendRedirect(request.getContextPath() + "/account/signin");
    	}
    }
    

    温馨提示:由于是DEMO演示,故我在controller中尽可能的使用不同的方式来完成各种逻辑,以便大家看到应用的效果,实际项目中不会是这样的。

    关于Controller类涉及如下知识点说明:

      a.命名规范,统一使用资源名(一般是名词)+controller结尾,如示例:BlogController,AccountController,尽量符合REST的风格,同时类上标注:@Controller,以便告诉spring容器这是一个Controller的bean;

      b.@RequestMapping:指定映射的请求路径,与ASP.NET MVC的Route特性有异由同工之效,详细用法可参考:https://www.iteye.com/news/32657/ 、https://www.cnblogs.com/jpfss/p/8047628.html

      c.Action获取请求参数(按照ASP.NET MVC的说法就是:Model Binding),详细用法可参考:https://www.cnblogs.com/xiaoxi/p/5695783.html

      d.Action指定视图以及为View指定Model数据(传值给view):可以使用ModelAndView、Model、ModelMap、Map<String,Object>、@ModelAttribute(作用于ACTION上)、HttpServletRequest(其实前面的几种方法底层最终都是通过HttpServletRequest.setAttribute来实现的),注意除了可以直接返回ModelAndView,因为它包含了setView的方法,其余的都应该返回String的viewName,可以对照上面的示例代码看一下,也可参见:https://blog.csdn.net/u012190514/article/details/80237885

      e:Controller间或Action间的跳转,使用:redirect:ACTION路径,ACTION传参多种方法详见:https://blog.csdn.net/jaryle/article/details/52263717,,我主要想特别说明的是RedirectAttributes,它不用URL传参,而是采用一次性session的模式,类似ASP.NET MVC中的TempData

      2.6.2 创建相关UI视图页面(根据InternalResourceViewResolver配置的视图解析位置及后缀名(/WEB-INF/jsp/、.jsp)在/WEB-INF/jsp/创建相关的jsp文件),分别有:signin.jsp(登录)、bloglist.jsp(博客列表主页)、blogdetail.jsp(博文详情,评论)、blogedit.jsp(博文编辑,新增、修改、删除),具体代码如下:

     登录:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8" isELIgnored="false" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
    
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>梦在旅途演示博客-登录</title>
    <style type="text/css">
    	#loginbox{
    		300px;
    		margin:100px auto 0 auto;
    		padding:50px;
    		border:5px groove gray;
    	}
    	
    	#loginbox div{
    		margin:20px auto;
    	}
    	
    	.txtcenter{
    		text-align:center;
    	}
    </style>
    </head>
    <body>
    	<form method="post">
    	<div id="loginbox">
    		<div><h3>欢迎,请登录!</h3></div>
    		<div>用户ID:<input type="text" id="txtuid" name="uid" /></div>
    		<div>密     码:<input type="password" id="txtpwd" name="pwd" /></div>
    		<div>
    			<input type="submit" id="btnSubmit" value="登 录" />
    			<input type="reset" id="btnReset" value="重置" />
    		</div>
    	</div>
    	</form>
    	<div class="txtcenter">
    		<c:if test="${message!=null}">
    			<p>${message}</p>
    		</c:if>
    	</div>
    	<p class="txtcenter">Copyright 2018  zuowj.cnblogs.com and zuowenjun.cn demo.</p>
    </body>
    </html>
    

    博客列表主页、博文详情,评论:

    <!-- bloglist.jsp -->
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    	pageEncoding="UTF-8" isELIgnored="false"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
    
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>博客列表</title>
    </head>
    <body>
    	<div style="text-align: right;">
    		<span>[ ${sessionScope.loginUid }(${sessionScope.loginUname }),
    		<a href="${pageContext.request.contextPath}/account/signout">[退出]</a> ]</span>   
    		<a href="${pageContext.request.contextPath}/blog/editpost" target="_blank">[ +发表博文 ]</a>
    	</div>
    	<div>
    		<form method="post" action="${pageContext.request.contextPath }/blog/querylist">
    			<fieldset>
    				<legend>范围查询:</legend>
    				<span>开始时间:</span> <input type="text" name="frmDate" 
    					value="${param.frmDate }"
    					placeholder="yyyy-MM-dd" />  
    				<span>结束时间:</span> <input type="text"
    					name="toDate" placeholder="yyyy-MM-dd"  
    					value="${param.toDate}" />
    				<button id="btnquery">查询</button>
    			</fieldset>
    		</form>
    	</div>
    	<c:choose>
    		<c:when test="${posts!=null && posts.size()>0}">
    			<c:forEach items="${posts}" var="item">
    				<div>
    					<h2>
    						<a href="${pageContext.request.contextPath}/blog/post/${item.id }"
    							target="_blank">${item.title}</a>  
    							<a href="${pageContext.request.contextPath}/blog/editpost/${item.id }" 
    							target="_blank" style="color:red;font-size:16px;">[修改]</a>
    					</h2>
    					<h4>
    						作者:${item.author },时间:
    						<fmt:formatDate value="${item.createTime}" pattern="yyyy-MM-dd" />
    					</h4>
    					<p>${item.content }</p>
    				</div>
    				<hr />
    			</c:forEach>
    		</c:when>
    		<c:otherwise>
    			<p style="color:red;">没有任何博客文章记录!</p>
    		</c:otherwise>
    	</c:choose>
    
    </body>
    </html>
    
    
    
    <!-- blogedit.jsp -->
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    	pageEncoding="UTF-8" isELIgnored="false"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
    
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>博文详情:${post.title }</title>
    </head>
    <body>
    	<div>
    		<h2>
    			${post.title}
    		</h2>
    		<h4>
    			作者:${post.author },
    			时间:<fmt:formatDate value="${post.createTime}" pattern="yyyy-MM-dd" />
    		</h4>
    		<p>${post.content }</p>
    	</div>
    	<hr/>
    	<div>
    		<c:choose>
    			<c:when test="${comments!=null && fn:length(comments)>0 }">
    				<c:forEach items="${ comments}" var="item">
    					<div style="margin:10px auto;">
    						<div style="border-bottom:solid 1px gray;margin-bottom:5px;">
    						${item.createby } 回复于:<fmt:formatDate value="${item.createTime}" pattern="yyyy-MM-dd HH:mm" />
    						</div>
    						<div>
    							${item.content }
    						</div>
    					</div>
    				</c:forEach>
    			</c:when>
    			<c:otherwise>
    				<p>暂无相关评论!</p>
    			</c:otherwise>
    		</c:choose>
    		<div>
    			<form method="post" action="../savecomment">
    			<h3>发表新评论:</h3>
    			<p>评论人:<input type="text" id="createby" name="createby" /></p>
    			<p>评论内容:</p>
    			<p><textarea rows="5" style="100%" id="content" name="content"></textarea>
    			</p>
    			<p>
    				<button id="btnreply">提交评论</button>
    				<input type="hidden" name="postid" value="${post.id }" />
    			</p>
    			</form>
    			<div>
    				<c:if test="${msg!=null}">
    					<p>提交评论结果:${msg}</p>
    				</c:if>
    			</div>
    		</div>
    	</div>
    </body>
    </html>
    

    博文编辑:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
    	pageEncoding="UTF-8" isELIgnored="false" import="cn.zuowenjun.java.mvc.model.*" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
    <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
    
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>
    <%-- ${post.id>0?"编辑"+ post.title:"新增博文" } --%>
    <%
    	Post post=(Post)request.getAttribute("post");
    	if(post==null){
    		out.print("post is null!");
    		return;
    	}
    	
    	if(post.getId()>0){
    		out.print("编辑"+ post.getTitle());
    	}else{
    		out.print("新增博文");
    	}
    %>
    </title>
    </head>
    <body>
    	<form:form modelAttribute="post" method="POST" id="mainForm"
    		action="${pageContext.request.contextPath }/blog/editpost">
    		<div>文章标题:</div>
    		<div>
    			<form:input path="title" />
    		</div>
    		<div>作者:</div>
    		<div>
    			<form:input path="author" />
    		</div>
    		<div>文章内容:</div>
    		<div>
    			<form:textarea path="content" rows="10" style="100%;" />
    		</div>
    		<div>
    			<button type="button" id="btnSave" data-action="update" onclick="javascript:doSubmit(this);">保存</button>
    			<c:if test="${post.id>0 }">
    			<button type="button" id="btnDelete" data-action="delete" style="color:red;" onclick="javascript:doSubmit(this);">删除</button>
    			</c:if>
    			<form:hidden path="id"/>
    			<input type="hidden" name="doAction" id="_doAction" />
    		</div>
    	</form:form>
    	<c:if test="${result!=null && fn:length(result)>0 }">
    		<p>操作结果:${result}</p>
    	</c:if>
    	<script type="text/javascript">
    		function doSubmit(btn){
    			if(!confirm("你确定要" + btn.innerText +"吗?")){
    				return false;
    			}
    			var actionVal=btn.getAttribute("data-action");
    			//alert(actionVal);
    			document.getElementById("_doAction").setAttribute("Value",actionVal);
    			document.getElementById("mainForm").submit();
    		}
    	</script>
    </body>
    </html>
    

    jsp视图涉及知识点说明:

     a.JSP EL表达式:简化JAVA代码在前台的使用,具体用法详见:http://www.runoob.com/jsp/jsp-expression-language.html

     b.JSP JSTL(标准标签库):是一个JSP标签集合,它封装了JSP应用的通用核心功能,支持通用的、结构化的任务,比如迭代,条件判断,XML文档操作,国际化标签,SQL标签等,具体用法详见:http://www.runoob.com/jsp/jsp-jstl.html

     个人认为如果需要用好JSP视图,就需要熟悉EL及JSTL这两个利器,如果是采用前后端分离那就另当别论; 

    2.7.为springMVC网站增加统一拦截器,实现统一的身份登录状态验证

    2.7.1定义一个实现了HandlerInterceptor接口的拦截器类:LoginValidationInterceptor,然后重写preHandle方法,在里面根据session来判断登录状态,若没有登录则跳转至登录页面,代码如下:

    package cn.zuowenjun.java.mvc.service.impl;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    
    public class LoginValidationInterceptor implements HandlerInterceptor {
    	
    	@Override
    	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    			throws Exception {
    			
    			String url = request.getRequestURI();
    			if(url.indexOf("/signin")>0) {//登录页面无需验证登录,放行
    				return true;
    			}
    			
    			if(request.getSession().getAttribute("loginUid")==null) {//检测到未登录,转到登录页面
    				response.sendRedirect(request.getContextPath() + "/account/signin");
    				return false;
    			}else {
    				return true;
    			}
    	}
    	
    	//其余postHandle、afterCompletion方法未重写,直接使用默认实现,这与C#有区别(C#8.0中也会有默认实现)
    
    }
    

    2.7.2在springmvc-servlet.xml中注册拦截器,并指定拦截URL的匹配路径,如下:

    	<!-- 配置拦截器 -->
    	<mvc:interceptors>
    		<mvc:interceptor>
    			<mvc:mapping path="/**"/><!-- 拦截所有请求, /表示只拦截非JSP的请求,/*只拦截一级目录,/**拦截所有目录 -->
    			<bean class="cn.zuowenjun.java.mvc.service.impl.LoginValidationInterceptor"></bean>
    		</mvc:interceptor>
    	</mvc:interceptors>
    

     如上两步即完成了拦截器的定义及配置,这样当有请求过来后就会进入拦截器,当然也可以使用JSP WEB中的filter过滤器来实现,两者的区别,详见:https://blog.csdn.net/xiaoyaotan_111/article/details/53817918

    2.8.为springMVC网站增加自定义错误页面

    2.8.1统一处理错误有多种方法,如:@ExceptionHandler、@ResponseStatus、@ControllerAdvice+@ExceptionHandler,当然这些背后都是使用了默认的HandlerExceptionResolver的实现,详见:http://www.cnblogs.com/xinzhao/p/4902295.html,本文采用@ControllerAdvice+@ExceptionHandler的方式来进行统一处理异常,代码如下:

    package cn.zuowenjun.java.mvc.service.impl;
    
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.http.*;
    import org.springframework.http.converter.HttpMessageNotReadableException;
    import org.springframework.web.*;
    
    /**
     * 
     * @author Zuowenjun
     *refer http://www.cnblogs.com/xinzhao/p/4902295.html
     */
    @ControllerAdvice
    public class WebExceptionHandler {
    	
    
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(HttpMessageNotReadableException.class)
        public void handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        }
        
        /**
         * 405 - Method Not Allowed
         */
        @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
        @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
        public void handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
            
        }
        
    
        /**
         * 415 - Unsupported Media Type
         */
        @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
        @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
        public void handleHttpMediaTypeNotSupportedException(Exception e) {
    
        }
    
    //    /**
    //     * 500 - Internal Server Error
    //     */
    //    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    //    @ExceptionHandler(Exception.class)
    //    public void handleException(Exception e) {
    //    	
    //    }
        
        // 创建ModleAndView,将异常和请求的信息放入到Model中,指定视图名字,并返回该ModleAndView
        @ExceptionHandler(Exception.class)
        public ModelAndView handleError(HttpServletRequest req, Exception exception) {
          ModelAndView mv = new ModelAndView();
          mv.addObject("exception", exception);
          mv.addObject("url", req.getRequestURL());
          mv.setViewName("error");
          
          return mv;
        }
    }
    

    对应的error.jsp 、notfound.jsp视图页面代码如下:(其中notfound.jsp中重新设置了staus,目的是为了兼容IE,如果是404,那么IE将会显示IE的默认404页面)

    <!--error.jsp-->
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    	pageEncoding="UTF-8" isELIgnored="false" import="java.io.*,java.lang.*" %>
    <%!
    
    	//获取完整堆栈信息
    	String getStackTrace(Throwable throwable){
    	StringWriter stringWriter=new StringWriter();
    	PrintWriter printWriter=new PrintWriter(stringWriter);
    
    	try {
    		throwable.printStackTrace(printWriter);
    		return stringWriter.toString();
    	}finally {
    		printWriter.close();
    	}
    }
    
    %>
    <%
    	Exception ex= (Exception)request.getAttribute("exception");
    %>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>发生错误!</title>
    </head>
    <body>
    	<h2>对不起,处理请求时发生错误!</h2>
    	<p>错误信息:${exception.getMessage() }</p>
    	<p>错误详情:<%=getStackTrace(ex) %></p>
    	<p>请求URL:${url}</p>
    	<p><a href="javascript:history.back();">[返回]</a></p>
    </body>
    </html>
    
    
    <!--notfound.jsp-->
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    	pageEncoding="UTF-8" isELIgnored="false"%>
    <%
    	response.setStatus(200);
    %>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>404-页面不存在!</title>
    </head>
    <body>
    	<div style=" 600px; margin-top: 100px;margin-left:auto;margin-right:auto;">
    		<p>你访问的资源不存在,可能被外星人吃掉了!</p>
    		<p>请求URL:<span id="rawUrl"></span></p>
    	</div>
    	
    	<script type="text/javascript">
    		window.onload=function(){
    			document.getElementById("rawUrl").innerHTML=window.location.href;
    		};
    	</script>
    </body>
    </html>  

    效果展示:

    登录:

      

    博客主页:

    博文详情及评论:

    编辑博文:

    当页面出现错误时:

              404页面:

    最后小结

    1.本文内容涵盖了:Maven、SpringMVC、SpringJDBC,相关的常见知识点都有在示例代码中呈现出来了,有利于理解;

    2.本文的springMVC Demo网站虽然简单,但基本把相关的功能都完成了,大家可以参照示例代码进行学习与深入,源代码Git地址:https://github.com/zuowj/mvnspringmvc

    3.springMVC打包WAR包的方式请参见:https://www.cnblogs.com/qlqwjy/p/8231032.html,多项目部署到同一个tomcat的方法请参见:https://blog.csdn.net/dreamstar613/article/details/75282962/,如果出现打包或部署相关错误请根据错误描述自行百度,可能坑有点多;

    比如:打包时可能报编译JDK的问题或类型无法解析问题,这时可能需要配置maven的编译插件,详见:https://www.cnblogs.com/softidea/p/6256543.html

    如果报:Cannot change version of project facet Dynamic Web Module to 3.0.详见:https://blog.csdn.net/xiongyouqiang/article/details/79130656

    4.本文中对于一些关键的知识点都有说明,同时有与ASP.NET MVC 进行对照说明,以便JAVA,.NET开发者可以相互快速了解与上手;

    5.由于目前都是使用前后端分离的模式,同时由于目前都是使用ORM,且虽然目前的手动配置依赖相比之前的JSP WEB手动引入依赖包要方便,但还是有点效率低下,故下一篇计划讲解: 基于springMVC实现Reset API,熟悉:Spring Boot+Mybatis+SpringMVC,敬请期待,谢谢!

    注:文章若有不足之处欢迎评论交流,谢谢!(本文从开始写到发表断断续续的差不多耗时了3周时间,本来计划元旦前发出但由于工作原因未能及时总结,故拖到现在才发表)

  • 相关阅读:
    BZOJ4036 HAOI2015按位或(概率期望+容斥原理)
    洛谷p2661信息传递题解
    洛谷P1434滑雪题解及记忆化搜索的基本步骤
    二分图最大匹配
    线段树

    图论基本算法
    并查集
    RMQ--ST表
    矩阵快速幂和矩阵乘法
  • 原文地址:https://www.cnblogs.com/zuowj/p/10095753.html
Copyright © 2011-2022 走看看