1. 程式人生 > >JavaWeb入門_模仿天貓整站Tmall_SSM實踐專案

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
框架:SpringSpring MVCMybatisSSM整合
資料庫: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,主要提供如下功能

  1. 指定spring的配置檔案為classpath下的applicationContext.xml
  2. 設定中文過濾器
  3. 指定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() 傳遞過來的集合。

listCategory.jsp 部分程式碼

localhost/admin_category_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語句,而且其維護起來也比較麻煩。
所以我們做進一步的改進,主要是在分頁方式和逆向工程方面做了重構。

  1. 分頁方式
    目前的分頁方式是自己寫分頁對應的limit SQL語句,並且提供一個獲取總數的count(*) SQL。 不僅如此, mapper, service, service.impl 裡都要提供兩個方法:
    list(Page page);
    count();
    分類是這麼做的,後續其他所有的實體類要做分頁管理的時候都要這麼做,所以為了提高開發效率,把目前的分頁方式改為使用 pageHelper 分頁外掛來實現。

  2. 逆向工程
    目前分類管理中 Mybatis 中相關類都是自己手動編寫的,包括:Category.java, CategoryMapper.java和CategoryMapper.xml。
    尤其是CategoryMapper.xml裡面主要是SQL語句,可以預見在接下來的開發任務中,隨著業務邏輯的越來越複雜,SQL語句也會越來越複雜,進而導致開發速度降低,出錯率增加,維護成本上升等問題。
    為了解決手動編寫SQL語句效率低這個問題,我們對 Mybatis 部分的程式碼,使用逆向工程進行重構。
    所謂的逆向工程,就是在已經存在的資料庫表結構基礎上,通過工具,自動生成Category.java, CategoryMapper.java和CategoryMapper.xml,想想就很美好是吧。


pageHelper 分頁

CategoryMapper.xml

  1. 去掉total SQL語句
  2. 修改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 類 都是

  1. 去掉total()方法
  2. 去掉list(Page page)方法
  3. 新增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 逆向工程
  1. 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;
    }
}
  1. 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>
  1. 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 介面
image.png

與手動編寫的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 及其他中介軟體等教程, 可以註冊一個賬戶,能儲存學習記錄。