接上篇《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%\bin (WIN10系統直接添加一行)
配置完后,在cmd中輸入:mvn -v 如果正常輸出maven版本信息則表示OK;
1.2.2 設置Maven的本地倉庫的下載位置(默認是在系統盤(一般為C):\Users\當前用戶名\.m2\repository),如果不改變則會導致系統盤分區容量不足,建議及時修改
打開%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=\u68A6\u5728\u65C5\u9014
如上代碼所示,所有的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{
width: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="width: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="width: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="width: 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周時間,本來計划元旦前發出但由於工作原因未能及時總結,故拖到現在才發表)
