與SSH(Struts/Spring/Hibernate/)一樣,Spring+SpringMVC+MyBatis也有一個簡稱SSM,Spring實現業務對象管理,Spring MVC負責請求的轉發和視圖管理, MyBatis作為數據對象持久化引擎。這樣搭配的優點是:輕量、自由度高、Spring與Spring MVC契合度更好。通過一個商品管理示例完成SSM框架的集成,可以將前面學習過的一些內容整合起來,使用到的知識包含:Spring、Spring MVC、MyBatis、JSR303校驗、分頁、文件上傳、路徑處理等。
1.1、請勾選“Create a simple project”,創建一個簡單的項目,這裡不使用模板。也可以使用模板,選擇WebApp,如果使用模板這裡就不應該勾選。如下圖所示:
1.2、填寫好包名、項目名,選擇打包類型為:war,如下圖所示:
1.3、項目創建好後可能會發現有錯誤,選擇項目,右鍵“屬性properties”->"層面Project Facets"->"Java"修改版本號為1.7,默認為1.5或其它版本,先去掉“Dynamic Web Module”保存後再勾選,選擇版本為3.0,再按箭頭所示操作,步驟如下圖所示:
1.4、刪除WebContent後會發現項目的pom.xml文件報錯,是因為找不到指定位置的web.xml文件引起的。再進入項目的屬性,選擇“Deployment Assembly”項目部署項,刪除“src/test/java”、“src/test/resources”與“WebContent”目錄,因為這三項不需要部署出去。
1.5、新建完成後發現有錯誤,是因為沒有JavaEE Server Runtime引起的,在項目上右鍵屬性選擇“Java Build Path”項,點擊“Add Library...”添加引用。也可以不選擇Server Runtime可以在Maven中直接引用。目錄結構如下所示:
提示:如果您是第一次使用Maven,詳細的步驟請查看另一篇隨筆:《Spring整合MyBatis(Maven+MySQL)一》。
打開MySQL數據庫,創建一個表,這裡以goods表為例,一個用於存放商品的表,共4個字段id表示編號,name表示商品名稱,picture表示圖片,price表示價格。SQL腳本如下:
/* Navicat MySQL Data Transfer Source Server : localhost Source Server Version : 50536 Source Host : localhost:3306 Source Database : db1 Target Server Type : MYSQL Target Server Version : 50536 File Encoding : 65001 Date: 2016-07-20 10:13:58 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `goods` -- ---------------------------- DROP TABLE IF EXISTS `goods`; CREATE TABLE `goods` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(200) NOT NULL, `price` decimal(10,2) DEFAULT '0.00', `picture` varchar(100) DEFAULT 'default.jpg', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of goods -- ---------------------------- INSERT INTO `goods` VALUES ('1', 'G7 中原G7三合一濃醇咖啡固體飲料1200', '66.50', '1.jpg'); INSERT INTO `goods` VALUES ('2', '百草味東北松子200gx2袋 堅果炒貨零', '42.90', '2.jpg'); INSERT INTO `goods` VALUES ('3', '奈津香 桂圓干500gx2袋莆田特產5A桂', '39.90', '3.jpg'); INSERT INTO `goods` VALUES ('4', '益達尊享護齒裝草本40粒+冰檸40粒+西', '25.90', '4.jpg'); INSERT INTO `goods` VALUES ('5', '猴坑茶業2016新茶原產地手工太平猴魁特', '168.00', '5.jpg'); INSERT INTO `goods` VALUES ('6', '嘻鱿記 休閒零食 麻辣香辣奶香炭燒 5種', '39.80', '6.jpg'); INSERT INTO `goods` VALUES ('7', '榮業鴻福五分瘦臘腸 香港土特產香腸臘味', '126.80', '7.jpg'); INSERT INTO `goods` VALUES ('8', '蓓琳娜(BELLINA)3L PDO特級初搾橄榄油', '178.00', '8.jpg'); INSERT INTO `goods` VALUES ('10', '榮業鴻福五分瘦臘腸 香港土特產香腸臘味', '30.60', 'b454b44f-868e-4efe-ae17-91e9e6a58390.jpg');
表結構如下所示:
項目主要依賴的jar包有Spring核心包、Spring AOP包、Spring MVC包、MyBatis ORM包、MyBatis-Spring適配包、JSTL、JUnit、Log4j2等,具體的pom.xml文件如下:
<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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zhangguo</groupId> <artifactId>SSMall</artifactId> <version>0.0.3</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.0.RELEASE</spring.version> </properties> <dependencies> <!--Spring框架核心庫 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <!-- aspectJ AOP 織入器 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <!--mybatis-spring適配器 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <!--Spring java數據庫訪問包,在本例中主要用於提供數據源 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!--mysql數據庫驅動 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!--log4j日志包 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.6.1</version> </dependency> <!-- mybatis ORM框架 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <!-- JUnit單元測試工具 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <!--c3p0 連接池 --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- Servlet核心包 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <!--JSP --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope> </dependency> <!-- jackson --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.5.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.5.2</version> </dependency> <!--JSR303 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.2.Final</version> </dependency> <!--文件上傳 --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <!-- FreeMarker --> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.23</version> </dependency> </dependencies> </project>
如果是第一次依賴相關的包,則需要下載時間,請耐心等待,如果下載失敗請手動下載(http://search.maven.org/)後復制到本地的資源庫中。依賴後的項目結果如下:
為了實現與數據庫中的books表進行關系映射新建一個Goods商品類,具體代碼如下:
package com.zhangguo.ssmall.entities; import java.io.Serializable; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; /** * 商品實體 * */ public class Goods implements Serializable { /** * */ private static final long serialVersionUID = 1L; /* * 編號 */ private int id; /* * 名稱 */ @Pattern(regexp="^[^><&#]{1,50}$",message="{pattern}") @NotNull(message="{notNull}") private String name; /* * 價格 */ @Min(value=1,message="必須大於或等於1") private double price; /* * 圖片 */ private String picture; public Goods() { } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public String getPicture() { return picture; } public void setPicture(String picture) { this.picture = picture; } @Override public String toString() { return "id:"+getId()+",name:"+getName()+",price:"+getPrice()+",picture:"+getPicture(); } }
為了實現校驗,在成員變量上設置了一些注解信息。
這個項目中我們采用接口與xml結合的形式完成關系與對象間的映射,在接口中定義一些數據訪問的方法,在xml文件中定義實現數據訪問需要的sql腳本。商品數據訪問映射接口如下:
package com.zhangguo.ssmall.mapper; import java.util.List; import org.apache.ibatis.annotations.Param; import com.zhangguo.ssmall.entities.Goods; public interface GoodsDAO { /** * 獲得商品信息並分頁 */ public List<Goods> getGoodsPager(@Param("skip") int skip,@Param("size") int size); /** * 獲得單個商品通過編號 */ public Goods getGoodsById(int id); /** * 獲得商品總數 */ public int getGoodsCount(); /* * 新增加商品 */ public int insert(Goods entity); /** * 刪除商品 */ public int delete(int id); /** * 修改商品 */ public int update(Goods entity); }
為MyBatis ORM創建的映射文件GoodsMapper.xml(命名盡量都遵循一個規則,便於掃描,這裡約定以實體名+Mapper)如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--命名空間應該是對應接口的包名+接口名 --> <mapper namespace="com.zhangguo.ssmall.mapper.GoodsDAO"> <!--獲得商品信息並分頁 --> <select id="getGoodsPager" resultType="Goods"> select id,name,price,picture from goods limit #{skip},#{size} </select> <!-- 獲得單個商品通過編號 --> <select id="getGoodsById" parameterType="int" resultType="Goods"> select id,name,price,picture from goods where id=#{id} </select> <!--獲得商品總數 --> <select id="getGoodsCount" resultType="int"> select count(*) from goods </select> <!--新增加商品 --> <insert id="insert" parameterType="Goods"> insert into goods(name,price,picture) values(#{name},#{price},#{picture}); </insert> <!-- 刪除商品 --> <delete id="delete"> delete from goods where id=#{id} </delete> <!-- 修改商品 --> <update id="update" parameterType="Goods"> update goods set name=#{name},price=#{price},picture=#{picture} where id=#{id} </update> </mapper>
為了保證數據訪問正常,使用JUnit進行單元測試,在另一個源代碼目錄src/test/java下添加一個名為TestGoods的測試用例,編寫完成的測試用例如下:
package com.zhangguo.ssmall.test; import java.util.List; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import com.zhangguo.ssmall.entities.Goods; import com.zhangguo.ssmall.mapper.GoodsDAO; import junit.framework.Assert; public class TestGoods{ @Test public void getGoodsPagerTest() { int skip=4; int size=2; SqlSession session=MyBatisUtil.getSession(); try { GoodsDAO bookdao=session.getMapper(GoodsDAO.class); List<Goods> goods=bookdao.getGoodsPager(skip, size); Assert.assertEquals(2, goods.size()); } finally { session.close(); } } @Test public void getGoodsByIdTest() { SqlSession session=MyBatisUtil.getSession(); try { GoodsDAO bookdao=session.getMapper(GoodsDAO.class); Goods goods=bookdao.getGoodsById(1); Assert.assertEquals(1, goods.getId()); } finally { session.close(); } } @Test public void getGoodsCountTest() { SqlSession session=MyBatisUtil.getSession(); try { GoodsDAO bookdao=session.getMapper(GoodsDAO.class); Assert.assertEquals(9, bookdao.getGoodsCount()); } finally { session.close(); } } @Test public void insertTest() { SqlSession session=MyBatisUtil.getSession(); try { Goods entity=new Goods(); entity.setName("正宗無錫陽山水蜜桃新鮮水果水密桃12個6斤裝江浙滬皖順豐包郵"); entity.setPrice(108); entity.setPicture("nopic.jpg"); GoodsDAO bookdao=session.getMapper(GoodsDAO.class); Assert.assertEquals(1, bookdao.insert(entity)); } finally { session.close(); } } @Test public void deleteTest() { SqlSession session=MyBatisUtil.getSession(); try { GoodsDAO bookdao=session.getMapper(GoodsDAO.class); Assert.assertEquals(1, bookdao.delete(12)); } finally { session.close(); } } @Test public void update() { SqlSession session=MyBatisUtil.getSession(); try { GoodsDAO bookdao=session.getMapper(GoodsDAO.class); Goods entity=bookdao.getGoodsById(12); entity.setName("正宗無錫陽山水蜜桃新鮮水果水密桃12個6斤裝"); entity.setPrice(107); entity.setPicture("nopicture.jpg"); Assert.assertEquals(1, bookdao.update(entity)); } finally { session.close(); } } }
MyBatis訪問數據庫的工具類如下:
package com.zhangguo.ssmall.test; import java.io.InputStream; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public abstract class MyBatisUtil { public static SqlSessionFactory getSqlSessionFactory(){ // 獲得環境配置文件流 InputStream config = MyBatisUtil.class.getClassLoader().getResourceAsStream("MyBatisCfg.xml"); // 創建sql會話工廠 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config); return factory; } //獲得會話 public static SqlSession getSession(){ return getSqlSessionFactory().openSession(true); } /** * 獲得得sql會話 * @param isAutoCommit 是否自動提交,如果為false則需要sqlSession.commit();rollback(); * @return sql會話 */ public static SqlSession getSession(boolean isAutoCommit){ return getSqlSessionFactory().openSession(isAutoCommit); } }
MyBatis配置文件MyBatisCfg.xml如下所示:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="db.properties"></properties> <typeAliases> <package name="com.zhangguo.ssmall.entities" /> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="${mysql.driver}" /> <property name="url" value="${mysql.url}" /> <property name="username" value="${mysql.uid}" /> <property name="password" value="${mysql.password}" /> </dataSource> </environment> </environments> <mappers> <mapper resource="com/zhangguo/ssmall/mapper/GoodsMapper.xml" /> </mappers> </configuration> View Code配置文件中使用到了db.properties屬性文件,該文件用於存放數據庫連接信息,文件內容如下:
#mysql mysql.driver=com.mysql.jdbc.Driver mysql.url=jdbc:mysql://localhost:3306/db1 mysql.uid=root mysql.password=root
運行測試,一切正常,測試結果如下:
這裡需要注意的是MyBatis配置文件的內容在後面與Spring整合後是會變化的,使用JUnit測試並未使用到Spring框架。
7.1、在源代碼的根目錄下修改db.properties文件,用於存放數據庫連接信息,文件內容如下:
#mysql mysql.driver=com.mysql.jdbc.Driver mysql.url=jdbc:mysql://localhost:3306/db1 mysql.uid=root mysql.password=root mysql.acquireIncrement=5 mysql.initialPoolSize=10 mysql.minPoolSize=5 mysql.maxPoolSize=20
7.2、在源代碼的根目錄下新建 applicationContext.xml文件,用於整合MyBatis與Spring,非常關鍵,具體的內容如下:
<?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:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <!--1 引入屬性文件,在配置中占位使用 --> <context:property-placeholder location="classpath*:db.properties" /> <!--2 配置C3P0數據源 --> <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <!--驅動類名 --> <property name="driverClass" value="${mysql.driver}" /> <!-- url --> <property name="jdbcUrl" value="${mysql.url}" /> <!-- 用戶名 --> <property name="user" value="${mysql.uid}" /> <!-- 密碼 --> <property name="password" value="${mysql.password}" /> <!-- 當連接池中的連接耗盡的時候c3p0一次同時獲取的連接數 --> <property name="acquireIncrement" value="${mysql.acquireIncrement}"></property> <!-- 初始連接池大小 --> <property name="initialPoolSize" value="${mysql.initialPoolSize}"></property> <!-- 連接池中連接最小個數 --> <property name="minPoolSize" value="${mysql.minPoolSize}"></property> <!-- 連接池中連接最大個數 --> <property name="maxPoolSize" value="${mysql.maxPoolSize}"></property> </bean> <!--3 會話工廠bean sqlSessionFactoryBean --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 配置文件路徑 --> <property name="configLocation" value="classpath:MyBatisCfg.xml"></property> <!-- 數據源 --> <property name="dataSource" ref="datasource"></property> <!-- sql映射文件路徑 --> <property name="mapperLocations" value="classpath*:com/zhangguo/ssmall/mapper/*Mapper.xml"></property> </bean> <!--4 自動掃描對象關系映射 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--指定會話工廠,如果當前上下文中只定義了一個則該屬性可省去 --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> <!-- 指定要自動掃描接口的基礎包,實現接口 --> <property name="basePackage" value="com.zhangguo.ssmall.mapper"></property> </bean> <!--5 聲明式事務管理 --> <!--定義事物管理器,由spring管理事務 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="datasource"></property> </bean> <!--支持注解驅動的事務管理,指定事務管理器 --> <tx:annotation-driven transaction-manager="transactionManager" /> <!--6 容器自動掃描IOC組件 --> <context:component-scan base-package="com.zhangguo.ssmall"></context:component-scan> <!--7 aspectj支持自動代理實現AOP功能 --> <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy> </beans>
從配置文件中可以看出第3點會話工廠配置中指定了MyBatis配置文件的位置與名稱,其實也可以省去,在這裡可以通過屬性配置好。但個人認為當多個框架整合在一起時最後將配置文件分開,便於修改。修改後的MyBatisCfg.xml文件內容如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="db.properties"></properties> <settings> <!--指定mybatis使用日志組件 --> <setting name="logImpl" value="LOG4J2" /> <!--開啟全局的二級緩存 --> <setting name="cacheEnabled" value="false" /> <!--開啟延時加載,如果有關聯關系,則默認不會獲取數據 延遲加載的全局開關。當開啟時,所有關聯對象都會延遲加載。 特定關聯關系中可通過設置fetchType屬性來覆蓋該項的開關狀態。 在association中指定fetchType="eager(立即)" 或者 lazy(延遲) 默認:false --> <setting name="lazyLoadingEnabled" value="true" /> <!--true時,對任意延遲屬性的調用會使帶有延遲加載屬性的對象完整加載; false,每種屬性將會按需加載。 默認為:true--> <setting name="aggressiveLazyLoading" value="false" /> </settings> <typeAliases> <package name="com.zhangguo.ssmall.entities" /> </typeAliases> <!-- <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="${mysql.driver}" /> <property name="url" value="${mysql.url}" /> <property name="username" value="${mysql.uid}" /> <property name="password" value="${mysql.password}" /> </dataSource> </environment> </environments> <mappers> <mapper resource="com/zhangguo/ssmall/mapper/GoodsMapper.xml" /> </mappers>--> </configuration>
中間有一大段注釋了,是因為MyBatis-Spring適配器已完成了這部分內容的工作,注釋不刪除的原因是因為JUnit測試時還要使用,其它也可以使用兩個不同的文件。
修改web.xml文件,注冊加載Spring容器所需的監聽器;注冊Spring MVC前置控制器Servlet,中間還設置了Servlet3.0上傳所需的參數;添加了一個全局的編碼過濾器。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <listener> <description>Spring容器加載監聽器</description> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <description>設置Spring加載時的配置文件位置,默認位置在WEB-INF/lib目錄下</description> <param-name>contextConfigLocation</param-name> <param-value>classpath*:applicationContext.xml</param-value> </context-param> <!--Spring MVC 前置Servlet,中心控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <!--Spring MVC配置文件路徑 --> <param-value>classpath*:springmvc-servlet.xml</param-value> </init-param> <!-- 啟動動優先級,越小越早加載 --> <load-on-startup>1</load-on-startup> <!--Servlet3.0以上文件上傳配置 --> <multipart-config> <!--上傳文件的最大限制5MB --> <max-file-size>5242880</max-file-size> <!--請求的最大限制20MB --> <max-request-size>20971520</max-request-size> <!--當文件的大小超過臨界值時將寫入磁盤 --> <file-size-threshold>0</file-size-threshold> </multipart-config> </servlet> <!-- Servlet訪問的路徑映射,所有的訪問都必須經過調度用的前置控制品 --> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--編碼過濾器 --> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <!-- 路徑映射 --> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
在src/main/java源代碼目錄下添加applicationContext.xml文件,用於配置Spring,內容在上一節中已列出。
在src/main/java源代碼目錄下添加Spring MVC配置文件springmvc-servlet.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd"> <!-- 自動掃描包,實現支持注解的IOC --> <context:component-scan base-package="com.zhangguo.ssmall" /> <!-- Spring MVC不處理靜態資源 --> <mvc:default-servlet-handler /> <!-- 支持mvc注解驅動 --> <mvc:annotation-driven enable-matrix-variables="true" /> <!-- 配置映射媒體類型的策略 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="removeSemicolonContent" value="false" /> </bean> <!-- 內部視圖解析器,JSP與JSTL模板 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver"> <!--指定視圖渲染類 --> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <!--自動添加到路徑中的前綴 --> <property name="prefix" value="/WEB-INF/views/jstl" /> <!--自動添加到路徑中的後綴 --> <property name="suffix" value=".jsp" /> <!--設置所有視圖的內容類型,如果視圖本身設置內容類型視圖類可以忽略 --> <property name="contentType" value="text/html;charset=UTF-8" /> <!-- 優先級,越小越前 --> <property name="order" value="2" /> </bean> <!-- FreeMarker視圖解析器與屬性配置 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver"> <!--是否啟用緩存 --> <property name="cache" value="true" /> <!--自動添加到路徑中的前綴 --> <property name="prefix" value="" /> <!--自動添加到路徑中的後綴 --> <property name="suffix" value=".html" /> <!--指定視圖渲染類 --> <property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView" /> <!-- 設置是否暴露Spring的macro輔助類庫,默認為true --> <property name="exposeSpringMacroHelpers" value="true" /> <!-- 是否應將所有request屬性添加到與模板合並之前的模型。默認為false。 --> <property name="exposeRequestAttributes" value="true" /> <!-- 是否應將所有session屬性添加到與模板合並之前的模型。默認為false。 --> <property name="exposeSessionAttributes" value="true" /> <!-- 在頁面中使用${rc.contextPath}就可獲得contextPath --> <property name="requestContextAttribute" value="rc" /> <!--設置所有視圖的內容類型,如果視圖本身設置內容類型視圖類可以忽略 --> <property name="contentType" value="text/html;charset=UTF-8" /> <!-- 優先級,越小越前 --> <property name="order" value="1" /> </bean> <!-- 配置FreeMarker細節 --> <bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"> <!-- 模板路徑 --> <property name="templateLoaderPath" value="/WEB-INF/views/ftl" /> <property name="freemarkerSettings"> <props> <!-- 刷新模板的周期,單位為秒 --> <prop key="template_update_delay">5</prop> <!--模板的編碼格式 --> <prop key="defaultEncoding">UTF-8</prop> <!--url編碼格式 --> <prop key="url_escaping_charset">UTF-8</prop> <!--此屬性可以防止模板解析空值時的錯誤 --> <prop key="classic_compatible">true</prop> <!--該模板所使用的國際化語言環境選項 --> <prop key="locale">zh_CN</prop> <!--布爾值格式 --> <prop key="boolean_format">true,false</prop> <!--日期時間格式 --> <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop> <!--時間格式 --> <prop key="time_format">HH:mm:ss</prop> <!--數字格式 --> <prop key="number_format">0.######</prop> <!--自動開啟/關閉空白移除,默認為true --> <prop key="whitespace_stripping">true</prop> </props> </property> </bean> <!--文件上傳解析器 --> <!--Spring MVC默認不能識別multipart格式的文件內容 --> <bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"> </bean> </beans>
在包com.zhangguo.ssmall.services下添加GoodsService.java文件,該文件是一個服務接口,內容如下:
package com.zhangguo.ssmall.services; import java.util.List; import com.zhangguo.ssmall.entities.Goods; /** * 商品業務接口 * */ public interface GoodsService { //分頁 List<Goods> getGoodsPager(int pageNO, int size); //獲得單個商品對象 Goods getGoodsById(int id); //獲得商品總數 int getGoodsCount(); //添加 int insert(Goods entity); //刪除單個 int delete(int id); //刪除多個 int deletes(int[] ids); //更新 int update(Goods entity); }
在包com.zhangguo.ssmall.services下添加類GoodsServiceImpl.java,實現接口GoodsService,用於完成商品業務邏輯,由於是示例代碼所以比較空;中間使用了兩個注解一個是@Service,用於提供給需要服務的類自動裝配,當Spring IOC容器啟動時被掃描到該類型會自動添加實例到Spring容器中;另一個注解是@Resource用於完成自動裝配功能,在Spring容器中找到GoodsDAO類型的對象,代碼如下:
package com.zhangguo.ssmall.services; import java.util.List; import javax.annotation.Resource; import org.springframework.stereotype.Service; import com.zhangguo.ssmall.entities.Goods; import com.zhangguo.ssmall.mapper.GoodsDAO; /** * 商品業務實現 * */ //自動添加到Spring容器中 @Service public class GoodsServiceImpl implements GoodsService{ //自動裝配 @Resource GoodsDAO goodsdao; //分頁 @Override public List<Goods> getGoodsPager(int pageNO, int size) { int skip=(pageNO-1)*size; return goodsdao.getGoodsPager(skip, size); } //獲得單個產品對象 @Override public Goods getGoodsById(int id) { return goodsdao.getGoodsById(id); } //獲得商品總數 @Override public int getGoodsCount() { return goodsdao.getGoodsCount(); } //添加 @Override public int insert(Goods entity) { return goodsdao.insert(entity); } //刪除單個 @Override public int delete(int id) { return goodsdao.delete(id); } //刪除多個 @Override public int deletes(int[] ids) { int rows=0; for (int id : ids) { rows+=delete(id); } return rows; } //更新 @Override public int update(Goods entity) { return goodsdao.update(entity); } }
定義GoodsController控制器,映射訪問路徑,需要使用到的商品服務使用自動裝配完成,代碼如下:
package com.zhangguo.ssmall.controllers; import javax.annotation.Resource; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import com.zhangguo.ssmall.services.GoodsService; @Controller @RequestMapping("/goods") public class GoodsController { @Resource GoodsService goodsService; /* * 產品列表與分頁Action */ @RequestMapping("/list") public String list(Model model,@RequestParam(required=false,defaultValue="1") int pageNO){ int size=5; model.addAttribute("size",size); model.addAttribute("pageNO",pageNO); model.addAttribute("count",goodsService.getGoodsCount()); model.addAttribute("goods", goodsService.getGoodsPager(pageNO, size)); return "goods/list"; } }
參數size表示每頁記錄數,pageNO表示當前頁號,處於第幾頁,count表示總記錄數。
在views/jstl/goods目錄下添加視圖list.jsp頁面,頁面的內容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link href="<c:url value="/styles/main.css"/>" type="text/css" rel="stylesheet" /> <title>商品管理</title> </head> <body> <div class="main"> <h2 class="title"><span>商品管理</span></h2> <form action="deletes" method="post"> <table border="1" width="100%" class="tab"> <tr> <th><input type="checkbox" id="chbAll"></th> <th>編號</th> <th>產品名</th> <th>價格</th> <th>類型</th> <th>操作</th> </tr> <c:forEach var="entity" items="${goods}"> <tr> <th><input type="checkbox" name="id" value="${entity.id}"></th> <td>${entity.id}</td> <td>${entity.name}</td> <td><img src="<c:url value="/images/${entity.picture}"/>" height="40"/></td> <td>${entity.price}</td> <td> <a href="delete/${entity.id}" class="abtn">刪除</a> <a href="edit/${entity.id}" class="abtn">編輯</a> </td> </tr> </c:forEach> </table> <div id="pager"></div> <p> <a href="add" class="abtn out">添加</a> <input type="submit" value="批量刪除" class="btn out"/> </p> <p >${message}</p> <!--分頁 --> <script type="text/javascript" src="<c:url value="/scripts/jQuery1.11.3/jquery-1.11.3.min.js"/>" ></script> <link href="<c:url value="/scripts/pagination22/pagination.css"/>" type="text/css" rel="stylesheet" /> <script type="text/javascript" src="<c:url value="/scripts/pagination22/jquery.pagination2.2.js"/>" ></script> <script type="text/javascript"> //初始化分頁組件 var count=${count}; var size=${size}; var pageNO=${pageNO}; $("#pager").pagination(count, { items_per_page:size, current_page:pageNO-1, next_text:"下一頁", prev_text:"上一頁", num_edge_entries:2, load_first_page:false, callback:handlePaginationClick }); //回調方法 function handlePaginationClick(new_page_index, pagination_container){ location.href="list?pageNO="+(new_page_index+1); } var defaultSrc="<c:url value="/images/default.jpg"/>"; $(".tab img").bind("error",function(){ $(this).prop("src",defaultSrc); }); </script> </form> </div> </body> </html>
為了實現分頁,添加了一個jQuery插件pagination,該插件的詳細參數如下所示:
官網 https://github.com/gbirke/jquery_pagination jQuery Pagination插件 參考資料:https://github.com/gbirke/jquery_pagination jquery paginate插件可以達到我們的要求,使用如下 使用: 1.首先定義一個頁碼容器 <div id="Pagination"></div> 2.設置屬性 $("#Pagination").pagination(122, { items_per_page:20, callback:handlePaginationClick }); 3.定義一個相應的回調函數 function handlePaginationClick(new_page_index, pagination_container) { //new_page_index:頁碼,從0開始, pagination_container:容器對象 return false;}這樣就定制了一個簡單的分頁控件,如需更多功能,請參照如下屬性: callback:回調函數,就是點擊頁碼除法的js事件,如上面定義的handlePaginationClick函數,可以通過ajax讀取數據,或者控制數據的顯隱 current_page:當前頁碼 items_per_page:每頁的條數,用於計算總頁數 link_to:當callback回調函數return true時頁碼會轉到此處定義的鏈接,其中我們可以用__id__傳入頁碼值 num_display_entries:展示頁碼的總數,默認為11,如果設置為0則簡單的只顯示“前一頁,後一頁” next_text:下一頁文本 next_show_always:是否總顯示下一頁 prev_text:前一頁 prev_show_always:是否總顯示前一頁 num_edge_entries:顯示最前幾條,最後幾條 load_first_page:初始化插件時是否調用回調函數 #我們還可以通過代碼調用分頁功能: $("#News-Pagination").trigger('setPage', [4]); // Go to the next page $("#News-Pagination").trigger('nextPage'); // Go to the previous page $("#News-Pagination").trigger('prevPage'); View Code測試運行結果:
頁面中有一個簡單處理加載圖片失敗的事件,當圖片加載出錯時使用default.jpg圖,如編號為38的圖片就是默認圖。
為了實現刪除與多刪除功能,修改控制器,增加2個action,delete請求處理方法用於刪除單個記錄,id是路徑變量指定要刪除的商品編號;pageNO是請求參數,保持狀態的目的是為了刪除後讓頁面繼續停留在某一頁,不過這裡有問題的是當某一頁的內容只有一條記錄裡就需要重新計算了;rediredtAttributes是為了保持重定向後的message值。
/* * 刪除單個產品對象Action */ @RequestMapping("/delete/{id}") public String delete(Model model,@PathVariable int id,@RequestParam(required=false,defaultValue="1") int pageNO,RedirectAttributes redirectAttributes){ if(goodsService.delete(id)>0) { redirectAttributes.addFlashAttribute("message", "刪除成功!"); }else{ redirectAttributes.addFlashAttribute("message", "刪除失敗!"); } return "redirect:/goods/list?pageNO="+pageNO; } /* * 刪除多個產品對象Action */ @RequestMapping("/deletes") public String deletes(Model model,@RequestParam int[] id,@RequestParam(required=false,defaultValue="1") int pageNO,RedirectAttributes redirectAttributes){ //執行刪除 int rows=goodsService.deletes(id); if(rows>0) { redirectAttributes.addFlashAttribute("message", "刪除"+rows+"行記錄成功!"); }else{ redirectAttributes.addFlashAttribute("message", "刪除失敗!"); } return "redirect:/goods/list?pageNO="+pageNO; }
為了配合刪除,修改list.jsp頁面,修改後的list.jsp頁面如下所示:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link href="<c:url value="/styles/main.css"/>" type="text/css" rel="stylesheet" /> <title>商品管理</title> </head> <body> <div class="main"> <h2 class="title"><span>商品管理</span></h2> <form action="<c:url value="/goods/deletes?pageNO=${pageNO}"/>" method="post"> <table border="1" width="100%" class="tab"> <tr> <th><input type="checkbox" id="chbAll"></th> <th>編號</th> <th>產品名</th> <th>價格</th> <th>類型</th> <th>操作</th> </tr> <c:forEach var="entity" items="${goods}"> <tr> <th><input type="checkbox" name="id" value="${entity.id}"></th> <td>${entity.id}</td> <td>${entity.name}</td> <td><img src="<c:url value="/images/${entity.picture}"/>" height="40"/></td> <td>${entity.price}</td> <td> <a href="<c:url value="/goods/"/>delete/${entity.id}?pageNO=${pageNO}" class="abtn">刪除</a> <a href="<c:url value="/goods/"/>edit/${entity.id}" class="abtn">編輯</a> <a href="<c:url value="/goods/"/>upPicture/${entity.id}" class="abtn">上傳</a> </td> </tr> </c:forEach> </table> <div id="pager"></div> <p> <a href="add" class="abtn out">添加</a> <input type="submit" value="批量刪除" class="btn out"/> </p> <p >${message}</p> <!--分頁 --> <script type="text/javascript" src="<c:url value="/scripts/jQuery1.11.3/jquery-1.11.3.min.js"/>" ></script> <link href="<c:url value="/scripts/pagination22/pagination.css"/>" type="text/css" rel="stylesheet" /> <script type="text/javascript" src="<c:url value="/scripts/pagination22/jquery.pagination2.2.js"/>" ></script> <script type="text/javascript"> //初始化分頁組件 var count=${count}; var size=${size}; var pageNO=${pageNO}; $("#pager").pagination(count, { items_per_page:size, current_page:pageNO-1, next_text:"下一頁", prev_text:"上一頁", num_edge_entries:2, load_first_page:false, callback:handlePaginationClick }); //回調方法 function handlePaginationClick(new_page_index, pagination_container){ location.href="<c:url value="/goods/"/>list?pageNO="+(new_page_index+1); } var defaultSrc="<c:url value="/images/default.jpg"/>"; $(".tab img").bind("error",function(){ $(this).prop("src",defaultSrc); }); </script> </form> </div> </body> </html>
運行結果如下所示:
基中的多刪除功能可以改進為一次性讓數據庫刪除完成。
在控制器中添加2個action,一個是add用於完成添加頁面展示,一個是addSave用於完成添加保存處理,代碼如下:
/* * 添加商品 */ @RequestMapping("/add") public String add(Model model){ model.addAttribute("entity", new Goods()); return "goods/add"; } /* * 添加商品保存 */ @RequestMapping("/addSave") public String addSave(Model model,@ModelAttribute("entity") @Valid Goods entity,BindingResult bindingResult){ //如果模型中存在錯誤 if(!bindingResult.hasErrors()){ if(goodsService.insert(entity)>0) { return "redirect:/goods/list"; } } model.addAttribute("entity", entity); return "goods/add"; }
這裡有一個問題是因為使用了JSR303校驗,當保存對象是需要在參數前注解@ModelAttribute("entity") @Valid,用於激活校驗,否則頁面將不會有錯誤展示,非常奇怪的問題;我在第五章中並沒有發現該問題。
為了配合Bean Validation,定義的Goods Bean需要注解,內容如下:
/* * 名稱 */ @Pattern(regexp="^[^><&#]{1,50}$",message="{pattern}") @NotNull(message="{notNull}") private String name; /* * 價格 */ @Min(value=1,message="必須大於或等於1") private double price;
這裡的錯誤消息來源一個是直接寫在注解中,另一個來自消息文件;{pattern}來自消息文件ValidationMessages.properties,在src/main/java目錄下新建該文件,文件內容如下:
pattern=格式錯誤 notNull=不允許為空
這裡需注意的是,默認情況下中文會顯示成utf-8編碼格式如:
pattern=\u683C\u5F0F\u9519\u8BEF notNull=\u4E0D\u5141\u8BB8\u4E3A\u7A7A
為了正常顯示,可以安裝一個插件,讓屬性文件支持正常顯示中文,插件名稱是properties-editor,點擊“Helo”->“Marketplace”,搜索插件名稱,顯示內容如下:
點擊Install,進入下一步:
完成後在properties文件上右鍵選擇“Open With”,具體步驟如下:
在views/jstl/goods目錄下新增加add.jsp頁面,頁面內容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <link href="<c:url value="/styles/main.css" />" type="text/css" rel="stylesheet" /> <title>新增商品</title> </head> <body> <div class="main"> <h2 class="title"><span>新增商品</span></h2> <form:form action="addSave" modelAttribute="entity"> <fieldset> <legend>商品</legend> <p> <label for="name">商品名稱:</label> <form:input path="name" size="50"/> <form:errors path="name" cssClass="error"></form:errors> </p> <p> <label for="price">商品價格:</label> <form:input path="price"/> <form:errors path="price" cssClass="error"></form:errors> </p> <p> <input type="submit" value="保存" class="btn out"> </p> </fieldset> </form:form> <p >${message}</p> <form:errors path="*"></form:errors> <p> <a href="<c:url value="/goods/list" />" class="abtn out">返回列表</a> </p> </div> </body> </html>
運行結果:
與新增加類似,在控制器下新增兩個action,一個用於展示編輯,有一個用於執行編輯後保存,代碼如下所示:
/* * 編輯商品 */ @RequestMapping("/edit/{id}") public String edit(Model model,@PathVariable int id){ model.addAttribute("entity", goodsService.getGoodsById(id)); return "goods/edit"; } /* * 編輯商品保存 */ @RequestMapping("/editSave") public String editSave(Model model,@ModelAttribute("entity") @Valid Goods entity,BindingResult bindingResult){ //如果模型中存在錯誤 if(!bindingResult.hasErrors()){ if(goodsService.update(entity)>0) { return "redirect:list"; } } model.addAttribute("entity", entity); return "/goods/edit"; }
在views/jstl/goods目錄下新增加edit.jsp頁面,頁面內容如下:
<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <link href="<c:url value="/styles/main.css" />" type="text/css" rel="stylesheet" /> <title>編輯商品</title> <base href="<c:url value="/" />" /> </head> <body> <div class="main"> <h2 class="title"> <span>編輯商品</span> </h2> <form:form action="goods/editSave" modelAttribute="entity"> <fieldset> <legend>商品</legend> <p> <label for="name">商品名稱:</label> <form:input path="name" size="50" /> <form:errors path="name" cssClass="error"></form:errors> </p> <p> <label for="price">商品價格:</label> <form:input path="price" /> <form:errors path="price" cssClass="error"></form:errors> </p> <p> <form:hidden path="picture" /> <form:hidden path="id" /> <input type="submit" value="保存" class="btn out"> </p> </fieldset> </form:form> <p >${message}</p> <form:errors path="*"></form:errors> <p> <a href="goods/list" class="abtn out">返回列表</a> </p> </div> </body> </html>
運行結果:
這裡使用Servlet3.0實現文件上傳,相關配置已經在前面的配置文件中設置好了,在控制器中增加兩個action,代碼如下:
/** * 上傳圖片 */ @RequestMapping("/upPicture/{id}") public String upPicture(Model model,@PathVariable int id){ model.addAttribute("entity", goodsService.getGoodsById(id)); return "goods/upfile"; } /* * 上傳圖片保存 */ @RequestMapping("/upPictureSave/{id}") public String upPictureSave(Model model,@PathVariable int id,MultipartFile picFile,HttpServletRequest request){ Goods entity=goodsService.getGoodsById(id); //如果選擇了文件 if(picFile!=null){ //如果文件大小不為0 if(picFile.getSize()>0){ //獲得上傳位置 String path=request.getServletContext().getRealPath("/images"); //生成文件名 String filename=UUID.randomUUID().toString()+picFile.getOriginalFilename().substring(picFile.getOriginalFilename().lastIndexOf(".")); File tempFile=new File(path, filename); try { //保存文件 picFile.transferTo(tempFile); //更新數據 entity.setPicture(filename); goodsService.update(entity); //轉向列表頁 return "redirect:/goods/list"; } catch (Exception e) { e.printStackTrace(); } } } model.addAttribute("entity", entity); return "goods/upfile"; }
在views/jstl/goods目錄下新增加upfile.jsp頁面,頁面內容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <link href="<c:url value="/styles/main.css" />" type="text/css" rel="stylesheet" /> <title>上傳圖片</title> </head> <body> <div class="main"> <h2 class="title"> <span>上傳圖片</span> </h2> <form action="<c:url value="/goods/upPictureSave/${entity.id}" />" method="post" enctype="multipart/form-data"> <fieldset> <legend>商品</legend> <p> <label for="name">商品名稱:</label> ${entity.name} </p> <p> <label for="price">商品價格:</label>${entity.price} </p> <p> <label for="title">商品圖片:</label> <input type="file" name="picFile" /> </p> <p> <input type="submit" value="上傳" class="btn out"> </p> </fieldset> </form> <p >${message}</p> <p> <a href="<c:url value="/goods/list" />" class="abtn out">返回列表</a> </p> </div> </body> </html>
運行結果如下:
為了將MyBatis與Hibernate Validation的日志信息展示在控制中,需要添加log4j2的引用,這部分內容在pom.xml中已配置完成了,另外在項目的根目錄下需要添加一個log4j2的配置文件log4j2.xml,內容如下:
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="off" monitorInterval="1800"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> </Appenders> <Loggers> <Root level="debug"> <AppenderRef ref="Console" /> </Root> </Loggers> </Configuration>
在webapp目錄下添加index.jsp,首頁是這個程序的入口,只完成了轉發功能,頁面內容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <jsp:forward page="goods/list"></jsp:forward>
所有頁面基本都引用了同一個樣式表styles/main.css文件,文件內容如下:
@CHARSET "UTF-8"; * { margin: 0; padding: 0; font-family: microsoft yahei; font-size: 14px; } body { padding-top: 20px; } .main { width: 90%; margin: 0 auto; border: 1px solid #777; padding: 20px; border-radius: 5px; } .main .title { font-size: 20px; font-weight: normal; border-bottom: 1px solid #ccc; margin-bottom: 15px; padding-bottom: 5px; color: #006ac1; } .main .title span { display: inline-block; font-size: 20px; color: #fff; padding: 0 8px; background: orangered; border-radius: 5px; } a { color: #006ac1; text-decoration: none; } a:hover { color: orangered; } .tab td, .tab, .tab th { border: 1px solid #777; border-collapse: collapse; } .tab td, .tab th { line-height: 26px; height: 26px; padding-left: 5px; } .abtn { display: inline-block; height: 18px; line-height: 18px; background: #006ac1; color: #fff; padding: 0 5px; border-radius: 5px; } .btn { height: 18px; line-height: 18px; background: #006ac1; color: #fff; padding: 0 8px; border: 0; border-radius: 5px; } .abtn:hover, .btn:hover { background: orangered; color: #fff; } p { padding: 5px 0; } fieldset { border: 1px solid #ccc; padding: 5px 10px; } fieldset legend { margin-left: 10px; font-size: 16px; } a.out,input.out { height: 23px; line-height: 23px; } form{ margin:10px 0; } .error { color:red } View Code最終的控制器GoodsController.java文件內容如下:
package com.zhangguo.ssmall.controllers; import java.io.File; import java.util.UUID; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import com.zhangguo.ssmall.entities.Goods; import com.zhangguo.ssmall.services.GoodsService; @Controller @RequestMapping("/goods") public class GoodsController { @Resource GoodsService goodsService; /* * 產品列表與分頁Action */ @RequestMapping("/list") public String list(Model model,@RequestParam(required=false,defaultValue="1") int pageNO){ int size=5; model.addAttribute("size",size); model.addAttribute("pageNO",pageNO); model.addAttribute("count",goodsService.getGoodsCount()); model.addAttribute("goods", goodsService.getGoodsPager(pageNO, size)); return "goods/list"; } /* * 刪除單個產品對象Action */ @RequestMapping("/delete/{id}") public String delete(Model model,@PathVariable int id,@RequestParam(required=false,defaultValue="1") int pageNO,RedirectAttributes redirectAttributes){ if(goodsService.delete(id)>0) { redirectAttributes.addFlashAttribute("message", "刪除成功!"); }else{ redirectAttributes.addFlashAttribute("message", "刪除失敗!"); } return "redirect:/goods/list?pageNO="+pageNO; } /* * 刪除多個產品對象Action */ @RequestMapping("/deletes") public String deletes(Model model,@RequestParam int[] id,@RequestParam(required=false,defaultValue="1") int pageNO,RedirectAttributes redirectAttributes){ //執行刪除 int rows=goodsService.deletes(id); if(rows>0) { redirectAttributes.addFlashAttribute("message", "刪除"+rows+"行記錄成功!"); }else{ redirectAttributes.addFlashAttribute("message", "刪除失敗!"); } return "redirect:/goods/list?pageNO="+pageNO; } /* * 添加商品 */ @RequestMapping("/add") public String add(Model model){ model.addAttribute("entity", new Goods()); return "goods/add"; } /* * 添加商品保存 */ @RequestMapping("/addSave") public String addSave(Model model,@ModelAttribute("entity") @Valid Goods entity,BindingResult bindingResult){ //如果模型中存在錯誤 if(!bindingResult.hasErrors()){ if(goodsService.insert(entity)>0) { return "redirect:/goods/list"; } } model.addAttribute("entity", entity); return "goods/add"; } /* * 編輯商品 */ @RequestMapping("/edit/{id}") public String edit(Model model,@PathVariable int id){ model.addAttribute("entity", goodsService.getGoodsById(id)); return "goods/edit"; } /* * 編輯商品保存 */ @RequestMapping("/editSave") public String editSave(Model model,@ModelAttribute("entity") @Valid Goods entity,BindingResult bindingResult){ //如果模型中存在錯誤 if(!bindingResult.hasErrors()){ if(goodsService.update(entity)>0) { return "redirect:list"; } } model.addAttribute("entity", entity); return "/goods/edit"; } /** * 上傳圖片 */ @RequestMapping("/upPicture/{id}") public String upPicture(Model model,@PathVariable int id){ model.addAttribute("entity", goodsService.getGoodsById(id)); return "goods/upfile"; } /* * 上傳圖片保存 */ @RequestMapping("/upPictureSave/{id}") public String upPictureSave(Model model,@PathVariable int id,MultipartFile picFile,HttpServletRequest request){ Goods entity=goodsService.getGoodsById(id); //如果選擇了文件 if(picFile!=null){ //如果文件大小不為0 if(picFile.getSize()>0){ //獲得上傳位置 String path=request.getServletContext().getRealPath("/images"); //生成文件名 String filename=UUID.randomUUID().toString()+picFile.getOriginalFilename().substring(picFile.getOriginalFilename().lastIndexOf(".")); File tempFile=new File(path, filename); try { //保存文件 picFile.transferTo(tempFile); //更新數據 entity.setPicture(filename); goodsService.update(entity); //轉向列表頁 return "redirect:/goods/list"; } catch (Exception e) { e.printStackTrace(); } } } model.addAttribute("entity", entity); return "goods/upfile"; } }
通個該示例將前面幾章的內容整合起來,鞏固了前幾章的內容;示例中還可以嘗試使用FreeMarker視圖;示例中沒有前端驗證都是後台驗證,可以使用jQuery擴展插件Validate實現前端校驗;有些功能可以結合AJAX完成更加合理;路徑是要非常小心的,後台重定向時,前台提交表單的路徑,可以使用base標簽和c:url。內容比較簡單,適合初學,只是希望能起到拋磚引玉、以小見大的作用,謝謝閱讀!
點擊下載
github代碼下載或預覽