JavaWeb入門_模仿天貓整站Tmall_SSM實踐專案
Tmall_SSM
技術棧 Spring MVC+ Mybatis + Spring + Jsp + Tomcat , 是 Java Web 入門非常好的練手專案
效果展示:
專案簡介
關聯專案
github - 天貓 JavaEE 專案
github - 天貓 SSH 專案
github - 天貓 SSM 專案
之前使用 JavaEE 整套技術和 SSH 框架來作為解決方案,實現模仿天貓網站的各種業務場景,現在開始使用 SSM 框架技術。
專案用到的技術如下:
Java:Java SE基礎
前端:HTML
,CSS
,JavaScript
,JQuery
,AJAX
,Bootstrap
J2EE:Tomcat
,Servlet
,JSP
,Filter
框架:Spring
,Spring MVC
,Mybatis
,SSM整合
資料庫:MySQL
開發工具:IDEA
,Maven
表結構
建表sql 已經放在 Github 專案的 /sql 資料夾下
表名 | 中文含義 | 介紹 |
---|---|---|
Category | 分類表 | 存放分類資訊,如女裝,平板電視,沙發等 |
Property | 屬性表 | 存放屬性資訊,如顏色,重量,品牌,廠商,型號等 |
Product | 產品表 | 存放產品資訊,如LED40EC平板電視機,海爾EC6005熱水器 |
PropertyValue | 屬性值表 | 存放屬性值資訊,如重量是900g,顏色是粉紅色 |
ProductImage | 產品圖片表 | 存放產品圖片資訊,如產品頁顯示的5個圖片 |
Review | 評論表 | 存放評論資訊,如買回來的蠟燭很好用,麼麼噠 |
User | 使用者表 | 存放使用者資訊,如斬手狗,千手小粉紅 |
Order | 訂單表 | 存放訂單資訊,包括郵寄地址,電話號碼等資訊 |
OrderItem | 訂單項表 | 存放訂單項資訊,包括購買產品種類,數量等 |
一 | 多 |
---|---|
Category-分類 | Product-產品 |
Category-分類 | Property-屬性 |
Property-屬性 | PropertyValue-屬性值 |
Product-產品 | PropertyValue-屬性值 |
Product-產品 | ProductImage-產品圖片 |
Product-產品 | Review-評價 |
User-使用者 | Order-訂單 |
Product-產品 | OrderItem-訂單項 |
User-使用者 | OrderItem-訂單項 |
Order-訂單 | OrderItem-訂單項 |
User-使用者 | User-評價 |
以上直接看可能暫時無法完全理解,結合後面具體到專案的業務流程就明白了。
開發流程
首先使用經典的 SSM 模式進行由淺入深地開發出第一個分類管理模組 ,
然後分析這種方式的弊端,再對其進行專案重構,使得框架更加緊湊,後續開發更加便利和高效率。
分類管理
Category 實體類
準備 Category 實體類,並用 Hibernate 註解標示其對應的表,欄位等資訊。
舉個例子,對於 分類 / category
的 實體類 和 表結構 設計如下:
Mapper 介面
public interface CategoryMapper { List<Category> list(); }
CategoryMapper.xml
com.how2java.tmall.mapper.CategoryMapper 對應上面的 Mapper 介面。mybatis 的 sql 是手打的,還好有逆向工程,後面重構會講。
<mapper namespace="com.caozhihu.tmall.mapper.CategoryMapper">
<select id="list" resultType="Category">
select * from category order by id desc
</select>
</mapper>
CategoryService 介面
public interface CategoryService{ List<Category> list(); }
CategoryServiceImpl 實現類
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
CategoryMapper categoryMapper;
public List<Category> list(){
return categoryMapper.list();
};
}
在 list() 方法中,通過其自動裝配的一個 CategoryMapper 物件的 list() 獲取所有的分類物件。
CategoryController 類
@Controller //聲明當前類是一個控制器
@RequestMapping("") //訪問的時候無需額外的地址
public class CategoryController {
@Autowired //自動裝配進 categoryService 介面
CategoryService categoryService;
@RequestMapping("admin_category_list")
public String list(Model model){
List<Category> cs= categoryService.list();
model.addAttribute("cs", cs);
return "admin/listCategory";
}
}
在list方法中,通過 categoryService.list() 獲取所有的 Category 物件,然後放在 "cs" 中,並服務端跳轉到“admin/listCategory” 檢視。
jdbc.properties
#資料庫配置檔案
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/tmall_ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=admin
applicationContext.xml
<beans>
<!-- 啟動對註解的識別 -->
<context:annotation-config/>
<context:component-scan base-package="com.caozhihu.tmall.service"/>
<!-- 匯入資料庫配置檔案 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置資料庫連線池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本屬性 url、user、password -->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<property name="maxActive" value="20"/>
<!-- 配置獲取連線等待超時的時間 -->
<property name="maxWait" value="60000"/>
<!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一個連線在池中最小生存的時間,單位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="validationQuery" value="SELECT 1"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<!-- 開啟PSCache,並且指定每個連線上PSCache的大小 -->
<property name="poolPreparedStatements" value="true"/>
<property name="maxPoolPreparedStatementPerConnectionSize"
value="20"/>
</bean>
<!--Mybatis的SessionFactory配置-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="typeAliasesPackage" value="com.how2java.tmall.pojo"/>
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations">
<array>
<value>classpath:com/caozhihu/tmall/mapper/*.xml</value>
<value>classpath:mapper/*.xml</value>
</array>
</property>
<!--分頁外掛-->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<value>
</value>
</property>
</bean>
</array>
</property>
</bean>
<!--Mybatis的Mapper檔案識別,mybatis-spring提供了MapperScannerConfigurer這個類,
它將會查詢類路徑下的對映器並自動將它們建立成MapperFactoryBean-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.caozhihu.tmall.mapper"/>
</bean>
</beans>
這裡只放了核心配置部分,頭部名稱空間已省略
springMVC.xml
<beans>
<!--啟動註解識別-->
<context:annotation-config/>
<context:component-scan base-package="com.caozhihu.tmall.controller">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<mvc:annotation-driven/>
<!--開通靜態資源的訪問-->
<mvc:default-servlet-handler/>
<!-- 檢視定位 例如 admin/listCategory 會被定位成 /WEB-INF/jsp/admin/listCategory.jsp-->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 對上傳檔案的解析-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
</beans>
web.xml
修改web.xml,主要提供如下功能
- 指定spring的配置檔案為classpath下的applicationContext.xml
- 設定中文過濾器
- 指定spring mvc配置檔案為classpath下的springMVC.xml
<?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_2_5.xsd"
version="2.5">
<!-- spring的配置檔案-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--中文過濾器-->
<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>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- spring mvc核心:分發servlet -->
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- spring mvc的配置檔案 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
訪問 jsp 顯示資料
Controller 中的 Model 攜帶資料跳轉到 jsp ,作為檢視,擔當的角色是顯示資料,藉助 JSTL 的 c:forEach 標籤遍歷從 CategoryController.list() 傳遞過來的集合。
管理分類剩下部分就不展開了
完整的CategoryMapper.xml
程式碼如下
<mapper namespace="com.how2java.tmall.mapper.CategoryMapper">
<select id="list" resultType="Category">
select * from category order by id desc
<if test="start!=null and count!=null">
limit #{start},#{count}
</if>
</select>
<select id="total" resultType="int">
select count(*) from category
</select>
<insert id="add" keyProperty="id" useGeneratedKeys="true" parameterType="Category" >
insert into category ( name ) values (#{name})
</insert>
<delete id="delete">
delete from category where id= #{id}
</delete>
<select id="get" resultType="Category">
select * from category where id= #{id}
</select>
<update id="update" parameterType="Category" >
update category set name=#{name} where id=#{id}
</update>
</mapper>
完整的CategoryMapper
介面程式碼如下
public interface CategoryMapper {
List<Category> list(Page page);
int total();
void add(Category category);
void delete(int id);
Category get(int id);
void update(Category category);
}
完整的CategoryService
介面程式碼如下
public interface CategoryService{
int total();
List<Category> list(Page page);
void add(Category category);
void delete(int id);
Category get(int id);
void update(Category category);
}
完整的CategoryServiceImpl
實現類程式碼就不放著了,只是實現了每個方法,並在其中呼叫對應的 CategoryMapper 方法而已
如: public List<Category> list(Page page) { return categoryMapper.list(page); }
思路流程圖
專案重構
分類管理中的CategoryMapper.xml使用很直接的SQL語句開發出來,這樣的好處是簡單易懂,便於理解。
可是,隨著本專案功能的展開和複雜度的提升,使用這種直接的SQL語句方式的開發效率較低,需要自己手動寫每一個SQL語句,而且其維護起來也比較麻煩。
所以我們做進一步的改進,主要是在分頁方式和逆向工程方面做了重構。
分頁方式
目前的分頁方式是自己寫分頁對應的limit SQL語句,並且提供一個獲取總數的count(*) SQL。 不僅如此, mapper, service, service.impl 裡都要提供兩個方法:
list(Page page);
count();
分類是這麼做的,後續其他所有的實體類要做分頁管理的時候都要這麼做,所以為了提高開發效率,把目前的分頁方式改為使用 pageHelper 分頁外掛來實現。逆向工程
目前分類管理中 Mybatis 中相關類都是自己手動編寫的,包括:Category.java, CategoryMapper.java和CategoryMapper.xml。
尤其是CategoryMapper.xml裡面主要是SQL語句,可以預見在接下來的開發任務中,隨著業務邏輯的越來越複雜,SQL語句也會越來越複雜,進而導致開發速度降低,出錯率增加,維護成本上升等問題。
為了解決手動編寫SQL語句效率低這個問題,我們對 Mybatis 部分的程式碼,使用逆向工程進行重構。
所謂的逆向工程,就是在已經存在的資料庫表結構基礎上,通過工具,自動生成Category.java, CategoryMapper.java和CategoryMapper.xml,想想就很美好是吧。
pageHelper 分頁
CategoryMapper.xml
- 去掉total SQL語句
- 修改list SQL語句,去掉其中的limit
<mapper namespace="com.how2java.tmall.mapper.CategoryMapper">
<select id="list" resultType="Category">
select * from category order by id desc
</select>
<insert id="add" keyProperty="id" useGeneratedKeys="true" parameterType="Category" >
insert into category ( name ) values (#{name})
</insert>
<delete id="delete">
delete from category where id= #{id}
</delete>
<select id="get" resultType="Category">
select * from category where id= #{id}
</select>
<update id="update" parameterType="Category" >
update category set name=#{name} where id=#{id}
</update>
</mapper>
CategoryMapper 介面
/CategoryService 介面
/ CategoryServiceImpl 類
都是
- 去掉total()方法
- 去掉list(Page page)方法
- 新增list() 方法
CategoryController.list()方法
@Controller
@RequestMapping("")
public class CategoryController {
@Autowired
CategoryService categoryService;
@RequestMapping("admin_category_list")
public String list(Model model,Page page){
PageHelper.offsetPage(page.getStart(),page.getCount());
List<Category> cs= categoryService.list();
int total = (int) new PageInfo<>(cs).getTotal();
page.setTotal(total);
model.addAttribute("cs", cs);
model.addAttribute("page", page);
return "admin/listCategory";
}
}
applicationContext.xml 配置 pagehelper 外掛
之前放的已經是修改過的了。。
就是在<!--Mybatis的SessionFactory配置--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
裡面加入
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<value>
</value>
</property>
</bean>
</array>
</property>
Mybatis 逆向工程
- OverIsMergeablePlugin
MybatisGenerator 外掛是 Mybatis 官方提供的,這個外掛存在一個問題 ,即當第一次生成了CategoryMapper.xml 之後,再次執行會導致 CategoryMapper.xml 生成重複內容,而影響正常的執行。
為了解決這個問題,需要自己寫一個小外掛類 OverIsMergeablePlugin 。 這是複製別人的,具體原理還沒研究。
public class OverIsMergeablePlugin extends PluginAdapter {
@Override
public boolean validate(List<String> warnings) {
return true;
}
@Override
public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) {
try {
Field field = sqlMap.getClass().getDeclaredField("isMergeable");
field.setAccessible(true);
field.setBoolean(sqlMap, false);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}
- generatorConfig.xml
這裡提供一部分程式碼,具體的在 github-generatorConfig.xml
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3">
<!--避免生成重複程式碼的外掛-->
<plugin type="com.caozhihu.tmall.util.OverIsMergeablePlugin"/>
<!--是否在程式碼中顯示註釋-->
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--資料庫連結地址賬號密碼-->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost/tmall_ssm"
userId="root" password="admin">
</jdbcConnection>
<!--該屬性可以控制是否強制DECIMAL和NUMERIC型別的欄位轉換為Java型別的java.math.BigDecimal,預設值為false,一般不需要配置。-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!--生成pojo類存放位置-->
<javaModelGenerator targetPackage="com.caozhihu.tmall.pojo" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!--生成xml對映檔案存放位置-->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!--生成mapper類存放位置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.caozhihu.tmall.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!--生成對應表及類名-->
<table tableName="category" domainObjectName="Category" enableCountByExample="false"
enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="true"
selectByExampleQueryId="false">
<property name="my.isgen.usekeys" value="true"/>
<property name="useActualColumnNames" value="true"/>
<generatedKey column="id" sqlStatement="JDBC"/>
</table>
- MybatisGenerator
執行即生成mapper,pojo,xml 檔案,核心程式碼如下
List<String> warnnings = new ArrayList<>();
boolean overwrite = true;
InputStream is = MybatisGenerator.class.getClassLoader().getResource("generatorConfig.xml").openStream();//獲取配置檔案對應路徑的輸入流
ConfigurationParser configurationParser = new ConfigurationParser(warnnings);
Configuration configuration = configurationParser.parseConfiguration(is);
is.close();
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(configuration, callback, warnnings);
myBatisGenerator.generate(null);
自動生成的 CategoryMapper.xml 如下,
這是外掛自動生成的xml,與我們自己手動寫的也差不了多少,主要區別在於提供了一個 id="Example_Where_Clause"的SQL,藉助這個可以進行多條件查詢。
pojo 如下,
MybatisGenerator會生成一個類叫做XXXXExample的。 它的作用是進行排序,條件查詢的時候使用。
在分類管理裡用到了排序,但是沒有使用到其條件查詢,在後續的屬性管理裡就會看到其條件查詢的用法了。
mapper 介面
與手動編寫的CategoryMapper比起來,CategoryMapper也是提供CURD一套,不過方法名發生了變化,比如:
delete
叫做deleteByPrimaryKey
, update
叫做updateByPrimaryKey
。
除此之外,修改還提供了一個updateByPrimaryKeySelective
,其作用是隻修改變化了的欄位,未變化的欄位就不修改了。
還有個改動是查詢 list ,變成了selectByExample(CategoryExample example);
CategoryServiceImpl 實現類
因為 CategoryMapper 的方法名發生了變化,所以 CategoryServiceImpl 要做相應的調整。
值得一提的是list方法:
public List<Category> list() {
CategoryExample example =new CategoryExample();
example.setOrderByClause("id desc");
return categoryMapper.selectByExample(example);
}
按照這種寫法,傳遞一個example物件,這個物件指定按照id倒排序來查詢
我查看了 xml 裡的對映, 在對應的查詢語句 selectByExample 裡面,
會判斷 orderByClause 是否為空,如果不為空就追加 order by ${orderByClause}
這樣如果設定了 orderByClause 的值為“id desc” ,執行的 sql 則會是 order by id desc
然後,我們再根據資料庫欄位,一次性生成所有的 實體類,example 類,mapper 和 xml,如果需要定製,直接在生成的東西上修改就行了,真是舒服啊。
後臺還有其他管理頁面的,比如屬性管理、產品管理等,由於篇幅原因,具體的請移步github-Tmall_SSM專案。
前臺頁面展示
此處是 SSH 跑起來截的圖,SSM 版本目前只做了後臺,前臺未做,敬請期待...
本文所講不足整個專案的 1/10 ,有興趣的朋友請移步 github 專案的地址 。
參考
天貓SSM整站學習教程 裡面除了本專案,還有 Java 基礎,前端,Tomcat 及其他中介軟體等教程, 可以註冊一個賬戶,能儲存學習記錄。