仿天貓整站三個版本的對比

傻藏發表於2020-12-21

系列文章目錄

天貓整站的前端筆記

天貓整站的ee版本筆記

天貓整站的ssh版本筆記

天貓整站的ssm版本筆記

前言

主要是為了對比這三個版本對實現功能上使用的技術差別,以及對實現的一些感想。 此次對比,不涉及框架原理的對比,只是單純從實現功能上對比;畢竟,我也只是個小白,有啥錯誤建議,歡迎指正,友好交流,拒絕槓精秀優越感的人!!

二、設計模式

使用mvc的模式
即Model,View,Controller

三、設計模式下的各層對比

1.View層

一些相同點

前臺顯示,即頁面相同點:

第一點:在對後臺資料顯示上,全都是使用的jsp頁面,並沒有使用html頁面;也就是沒實現前後端分離;


第二點:在顯示的技術和資料的處理上,使用的是jstl的el表示式,其中struts專案中也沒有用struts的標籤; 但是要注意的是: 在ssh專案中,雖然使用的是el表示式,可在el表示式中獲取和顯示資料,都是來自於action類中的Action4Pojo類,而不是實體類!!!

這一點是跟ee和ssm版本獲取和顯示資料的地點不同之處!!!


第三點:使用了include標籤,例如: <%@include file="include/footer.jsp"%>這樣處理的話,讓頁面得到了重用,使頁面也更加簡潔;

1.Result

綜上所述
其檢視層使用的就是同一套jsp頁面。

資料庫
其表結構設計,基本是一樣的;
略有不同的點在於:
ssh版本和ssm版本將訂單表的主鍵id作為了訂單項表的外來鍵;
而ee版本,有其oid欄位,但是並沒有設計外來鍵約束;其ee版本之所以如此設計,是基於當訂單項生成的時候,即加入購物車的時候,其訂單沒有生成,也就是oid為null的情況,也就會違反外來鍵約束;後期設計將oid預設為null,也就不會有這個問題;

2.Model層

Model,即模型
我對這個理解是資料的傳遞和資料的儲存;

Bean類

三個版本其使用的都是JavaBean,進行資料的暫時儲存和顯示作用; 例如Category類
package com.ee.bean;
import java.util.List;

/**
 * 分類表
 */
public class Category {
    private String name;
    private int id;
//    一對多關係,實現一個分類多個產品,在頁面就是對應首頁的多行產品
    private List<Product> products;
    /*
    假設一個分類恰好對應40種產品,那麼這40種產品本來是放在一個集合List裡。
    可是,在頁面上顯示的時候,需要每8種產品,放在一列 為了顯示的方便,
    我把這40種產品,按照每8種產品方在一個集合裡的方式,拆分成了5個小的集合,
    這5個小的集合裡的每個元素是8個產品。 這樣到了頁面上,顯示起來就很方便了。
     否則頁面上的處理就會複雜不少。
     */
//    一個產品下有多個產品記錄,首頁功能
//    這裡就是八個產品放在一個集合中,然後五個小集合又放在一個集合中
    private List<List<Product>> productsByRow;

    public List<List<Product>> getProductsByRow() {
        return productsByRow;
    }

    public void setProductsByRow(List<List<Product>> productsByRow) {
        this.productsByRow = productsByRow;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }
}

其裡面主要是利用set和get方法,實現對資料的傳遞和儲存。

一點不同:

但是值得特別注意的是,在ssm版本中,其bean是由mybatis的外掛MybatisGenerator外掛自動生成的,其每個資料庫對應的列欄位是對應的基本資料型別的引用類,而不是ee和ssh版本中的基本資料型別,例如:

package com.ssm.pojo;

import java.util.List;

public class Category {
    private Integer id;

    private String name;

    /*前臺功能*/
//    一對多關係,多個產品
    private List<Product> products;
    //    一個產品的八個小產品
    private List<List<Product>>  productsByRow;

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }

    public List<List<Product>> getProductsByRow() {
        return productsByRow;
    }

    public void setProductsByRow(List<List<Product>> productsByRow) {
        this.productsByRow = productsByRow;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }
}

很多不同:

基於ssh版本的javabean是有較大不同的, 如下程式碼:
package com.ssh.pojo;

import javax.persistence.*;
import java.util.List;

@Entity//宣告為實體pojo
@Table//宣告瞭該實體bean對映指定的表(table),
public class Category {
    @Id//標註這個是表中的主鍵id
    @GeneratedValue(strategy=GenerationType.IDENTITY)//宣告瞭主鍵的生成策略,跟隨資料庫自增長
    @Column(name="id")//對映到表中的id即主鍵
    private int id;

//    前臺欄位
//    對應一個分類下多個產品
    @Transient //@Transient表示該屬性並非一個到資料庫表的欄位的對映,ORM框架將忽略該屬性.
    private List<Product> products;
//    對應前臺一個分類下的多行產品,就是將多個產品分成一行,這一行又是一個集合,然後多行組成一個分類
    @Transient
    private List<List<Product>> productsByRow;

    private  String name;

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }

    public List<List<Product>> getProductsByRow() {
        return productsByRow;
    }

    public void setProductsByRow(List<List<Product>> productsByRow) {
        this.productsByRow = productsByRow;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Category{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

這個ssh專案使用的是註解方式去表達跟資料庫表關係的,所以會有許多註解,為啥會有註解?理由如下;
在hibernate中,如圖所示:
hibernate實現資料庫對映圖

也就是說,在hibernate框架中,每一個資料庫表對應的bean物件,都有一個對映xml檔案。而這個xml檔案的命名規則是:
對應的bean類.hbm.xml;例如:一個Product類的對映檔案:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.hibernate.domain">
<!--    表示類Product對應表product-->
    <class name="Product" table="product">
        <!--    增加二級快取-->
<!--        <cache usage="read-only"/>-->
<!--        表示屬性id,對映表裡的欄位id-->
        <id name="id" column="id">
<!--            意味著id的自增長方式採用資料庫的本地方式-->
            <generator class="native"></generator>
        </id>
<!--        version元素必須緊挨著id後面-->
        <version name="version" column="ver" type="int"></version>
        <property name="name"/>
        <property name="price"/>
<!--
        使用many-to-one 標籤設定多對一關係
        name="category" 對應Product類中的category屬性
        class="Category" 表示對應Category類
        column="cid" 表示指向 category表的外來鍵
-->
<!--        表示在Product類中的型別Category的屬性關係-->
        <many-to-one name="category" class="Category" column="cid"/>
<!--表示set集合中User類的關係-->
        <set name="users" table="user_product" lazy="true">
<!--           一對多關係中,一所對應的外來鍵欄位-->
            <key column="pid"></key>
<!--            一對多關係中,多所對應的外來鍵欄位和類-->
            <many-to-many column="uid" class="User"/>
        </set>
    </class>
</hibernate-mapping>

而另一個hibernate.cfg.xml配置檔案,其作用是配置訪問資料庫要用到的驅動,url,賬號密碼等等,也就是 session-factory標籤就是sessionFactory類
例如:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<!--這玩意就是核心配置檔案-->
<hibernate-configuration>
    <session-factory>
<!--        資料庫連線配置-->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/test2?characterEncoding=UTF-8</property>
        <property name="connection.username">root</property>
        <property name="connection.password">admin</property>
<!--        sql方言及配置-->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!--        這是Hibernate事務管理方式,即每個執行緒一個事務-->
        <property name="current_session_context_class">thread</property>
<!--       這表示是否在控制檯顯示執行的sql語句 -->
        <property name="show_sql">true</property>
<!--        這表示是否會自動更新資料庫的表結構,
            有這句話,其實是不需要建立表的,因為Hibernate會自動去建立表結構-->
        <property name="hibernate.hbm2ddl.auto">update</property>

<!--            開啟c3p0連線池-->
        <property name="hibernate.connection.provider_class">
                org.hibernate.c3p0.internal.C3P0ConnectionProvider
        </property>
        <property name="hibernate.c3p0.max_size">20</property>
        <property name="hibernate.c3p0.min_size">5</property>
        <property name="hibernate.c3p0.timeout">50000</property>
        <property name="hibernate.c3p0.max_statements">100</property>
        <property name="hibernate.c3p0.idle_test_period">300</property>
        <!-- 當連線池耗盡並接到獲得連線的請求,則新增加連線的數量 -->
        <property name="hibernate.c3p0.acquire_increment">2</property>
        <!-- 是否驗證,檢查連線 -->
        <property name="hibernate.c3p0.validate">false</property>

            <!--        引入對映配置檔案-->
<!--        <mapping resource="com/hibernate.domain/Product.hbm.xml"></mapping>-->
<!--            使用註解的方式-->
        <mapping class="com.hibernate.domain.Product"></mapping>
<!--        <mapping resource="com/hibernate.domain/Category.hbm.xml"></mapping>-->
<!--            使用註解的方式-->
        <mapping class="com.hibernate.domain.Category"></mapping>
<!--        <mapping resource="com/hibernate.domain/User.hbm.xml"></mapping>-->
<!--            使用註解的方式-->
        <mapping class="com.hibernate.domain.User"></mapping>
    </session-factory>
</hibernate-configuration>

在這個專案中,使用的是註解的方式,所以這個對映的配置檔案也沒有了,至於這個hibernate.cfg.xml檔案,在下一層有分析;

Dao類

ee版本:

Dao類,則不一樣了,也正是因為對dao的處理,才有對應的框架出現。一個一個的來。 在最基本的ee版本中,直接上程式碼:
package com.ee.dao;

import com.ee.bean.User;
import com.ee.utils.DBUtil;
import com.ee.bean.Category;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 分類表
 */
public class CategoryDAO {
//   查: 獲取種類的總數量
    public int getTotal(){
        int total=0;
        /*
        try括號內的資源會在try語句結束後自動釋放,
        前提是這些可關閉的資源必須實現 java.lang.AutoCloseable 介面。
        通關檢視原始碼可以看到已經實現了AntonioCloseable介面
         */
        try(
//                獲取資料庫的連線
                Connection connection = DBUtil.getConnection();
//                獲取SQL模板
                Statement statement = connection.createStatement();
                ){
//            定義SQL語句
            String sql="select count(*) from Category";
//            傳送SQL語句,獲得一個結果集
            ResultSet resultSet = statement.executeQuery(sql);
//            遍歷這個結果集
            while (resultSet.next()){//如果資料存在
//                獲取到總數
                total=resultSet.getInt(1);
            }
        }catch (SQLException e){
            e.printStackTrace();
        }
        return total;
    }
//    查:根據id獲取種類的資訊
    public Category get(int id) {
        Category category = null;

        try (Connection c = DBUtil.getConnection(); Statement s = c.createStatement();) {

            String sql = "select * from Category where id = " + id;

            ResultSet rs = s.executeQuery(sql);

            if (rs.next()) {
                /*
                這裡儲存欄位值,是為了在servlet利用request域傳遞資料到頁面的時候用到的!!!
                * */
                category = new Category();
//                結果集是以1開始算索引的,所以獲取name欄位為2
                String name = rs.getString(2);
                category.setName(name);
                category.setId(id);
            }

        } catch (SQLException e) {

            e.printStackTrace();
        }
        return category;
    }
//    分頁查詢
    public List<Category> list(int start,int count){//第一個引數從哪個id開始查,第二個引數總共查詢多少條資料
        List<Category> categoryList=new ArrayList<Category>();
        String sql="select * from Category order by id desc limit ?,?";
        try (Connection con = DBUtil.getConnection(); PreparedStatement ps = con.prepareStatement(sql);){
            ps.setInt(1,start);
            ps.setInt(2,count);
            ResultSet rs = ps.executeQuery();
            while (rs.next()){
                Category category=new Category();
                int id = rs.getInt(1);
                String name = rs.getString(2);
                category.setId(id);
                category.setName(name);
                categoryList.add(category);
            }
        }catch (SQLException e){
            e.printStackTrace();
        }
        return categoryList;
    }
//    查:獲取所有種類的資訊
    public List<Category> list() {
    return list(0, Short.MAX_VALUE);
}

    //    查:根據種類名獲取種類的資訊,判斷該種類是否存在
    public Boolean isExitCategory(String name) {
        Boolean flag=false;
        String sql = "select * from Category where name = ?";
        try (Connection c = DBUtil.getConnection(); PreparedStatement ps = c.prepareStatement(sql)) {
            ps.setString(1, name);
            ResultSet rs =ps.executeQuery();
            if(rs.next()){
                flag=true;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        if(flag==true){
            return true;
        }else {
            return false;
        }
    }


    //    增:向種類表中插入資料
    public void add(Category category){
        String sql="insert into category values(null,?)";
        try (final Connection connection = DBUtil.getConnection(); final PreparedStatement ps = connection.prepareStatement(sql)){
            ps.setString(1,category.getName());
            ps.execute();
            ResultSet rs = ps.getGeneratedKeys();
            while (rs.next()){
                int id = rs.getInt(1);
                category.setId(id);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
//    改:向種類中修改種類名
    public void update(Category category){
        String sql="update category set name=? where id=?";

        try (Connection con = DBUtil.getConnection(); PreparedStatement ps = con.prepareStatement(sql);){
            ps.setString(1,category.getName());
            ps.setInt(2,category.getId());
            ps.execute();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return;
    }
//    刪:刪除一個種類
    public void delete(int id) {

    try (Connection c = DBUtil.getConnection(); Statement s = c.createStatement();) {

        String sql = "delete from Category where id = " + id;

        s.execute(sql);

    } catch (SQLException e) {

        e.printStackTrace();
    }
}

}

從程式碼中可以看出,主要使用的是DBUtil這個工具類,在這個ee版本的web中,正是因為這一些封裝類,才讓這個ee版本看起來不那麼臃腫並且可維護性大大提高;

其中在DBUtil類中:

package com.ee.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * 資料庫工具類
 * 初始化驅動
 */
public class DBUtil {
//    資料庫所處的ip
    static String ip="localhost";
//    資料庫所處的埠
    static int port=3306;
//    資料庫的名稱
    static String database="tmall";
//    資料庫的編碼
    static String encoding="UTF-8";
//    資料庫名字
    static String loginName="root";
//     資料庫密碼
    static String password="admin";

//    使用靜態塊載入驅動
    static {
        try{
//            載入資料庫驅動
            Class.forName("com.mysql.jdbc.Driver");
        }catch (ClassNotFoundException e){
//            如果有錯就列印出來
            e.printStackTrace();
        }
    }
//      得到資料庫的連線
    public static Connection getConnection() throws SQLException {
//        使用格式化輸出,%s字串,%d表示數字,%n表示換行
//        所以這一句完整就是jdbc:mysql://localhost:3306/tmall?characterEncoding=utf-8
        String url=String.format("jdbc:mysql://%s:%d/%s?characterEncoding=%s",ip,port,database,encoding);
//        返回資料庫的連線
        return DriverManager.getConnection(url,loginName,password);
    }

    public static void main(String[] args) throws SQLException {
        System.out.println(getConnection());
    }

}

ssh版本:

看完ee版本,接下來看ssh版本,怎樣處理Dao類的; 在ssh版本中,這個h指的是hibernate,hibernate就是一個採用ORM思想的持久層的框架,所謂持久層,我粗淺理解為對資料的crud的操作類; 話不多說上程式碼:

在其dao介面中:

package com.ssh.dao;

import org.hibernate.SessionFactory;

public interface DAO {
    public void setSessionFactory(SessionFactory sessionFactory);
}

在介面的實現類中:

package com.ssh.dao.impl;
import com.ssh.dao.DAO;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

@Repository("daoImpl")//表示給當前類命名一個別名,方便注入到其他需要用到的類中
public class DAOImpl extends HibernateTemplate implements DAO {
    //    set方式依賴注入SessionFactory
    @Resource(name="sf")//將hibernate的session工廠依賴注入到DAOImpl中
    public void setSessionFactory(SessionFactory sessionFactory){
        super.setSessionFactory(sessionFactory);
    }
}

依據前面的對Bean類的ssh專案處理方式,在這裡整合hibernate框架,最為主要的是對於sessionFactory的處理,也就是說對於hibernate框架而言,處理Dao類,是利用sessionFactory來進行處理的,其sessionFactory中獲取session,然後呼叫session中的crud的方法;

那麼,是如何處理sessionFactory的呢?

在Bean層的時候有提到,hibernate框架有兩個對映檔案,一個是實體類對應的對映檔案(即實體類名.hbm.xml)提供資料庫中物件跟表的對映關係,另一個是提供跟資料庫連線等配置資訊和載入對應的實體對映檔案等功能的主配置檔案(即hibernate.cfg.xml);

實體類對映檔案因為是使用註解,所以在Bean類中全是表關係的註解;而這個主配置檔案,在ssh專案中,是用spring解決的!!!
如下,applicationContext.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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
">
<!--啟動註解配置-->
    <context:annotation-config/>
<!--    配置註解元件掃描-->
    <context:component-scan base-package="com.ssh"/>
<!--    配置資料來源-->
    <bean name="ds" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/tmall_himybo?characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="admin"/>
    </bean>
<!--    配置session工廠-->
    <bean name="sf" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" scope="prototype">
<!--        資料來源注入-->
        <property name="dataSource" ref="ds"/>
<!--        註解掃描-->
        <property name="packagesToScan">
            <list>
                <value>com.ssh.*</value>
            </list>
        </property>

        <property name="schemaUpdate">
            <value>true</value>
        </property>

        <property name="hibernateProperties">
            <value>
                hibernate.dialect=org.hibernate.dialect.MySQLDialect
                hibernate.show_sql=false
                hbm2ddl.auto=update
            </value>
        </property>

    </bean>
<!--    配置註解事務管理驅動-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
<!--    配置註解的事務管理器-->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sf"/>
    </bean>
</beans>
結論
這樣就很清晰了,spring和hibernate框架的整合,將sessionFactory的建立權交給spring的ioc容器來處理,利用依賴注入實現呼叫session工廠的crud功能;

整體如下所示:
spring整合hibernate

除此之外,
也可以將hibernate的核心配置檔案hibernate.hbm.xml單獨拿出來,放到spring配置檔案中使用;
如下:
applicationContext.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:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
 
<!-- 建立一個用於連線的基礎bean  BasicDataSource  用於Spring自動裝載-->
	<bean id="dataSource"
		class="org.apache.commons.dbcp2.BasicDataSource">
		<!-- 配置資料庫的驅動程式,Hibernate在連線資料庫時,需要用到資料庫的驅動程式 -->
		<property name="driverClassName"
			value="com.mysql.jdbc.Driver">
		</property>
		<!-- 設定資料庫的連線url:jdbc:mysql://127.0.0.1:3306/hibernate,其中127.0.0.1表示mysql伺服器名稱,此處為本機,    hibernate是資料庫名 -->
		<property name="url"
			value="jdbc:mysql://127.0.0.1:3306/hibernate">
		</property>
		<!-- 連線資料庫的使用者名稱 -->
		<property name="username" value="root"></property>
		<!-- 連線資料庫的密碼 -->
		<property name="password" value="root"></property>
	</bean>
	
	<!-- 建立sessionFactory的 bean,用於Spring自動裝載 -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
		<!-- 屬性dataSource ,引用上面的dataSource Bean 使用者獲取與資料庫的連線資訊 -->
		<property name="dataSource">
			<ref bean="dataSource" />
		</property>
		<!-- 指定hibernate核心配置檔案的路徑 -->
		<property name="configLocation">
		<value>classpath:hibernate.cfg.xml</value>
		</property>
		<!-- 用於配置hibernate裡的一些功能屬性 -->
		<property name="hibernateProperties">
			<props>
				<!-- 以標準sql格式列印輸出sql語句 -->
				<prop key="hibernate.format_sql">true</prop>
				<!-- 設定是否列印sql語句,建議開發期間開啟該功能,便於除錯程式 -->
				<prop key="hibernate.show_sql">true</prop>
				<!-- 設定資料庫的方言 -->
				<prop key="hibernate.dialect">
					org.hibernate.dialect.MySQLDialect
				</prop>
				<!-- 主要用於:自動建立|更新|驗證資料庫表結構 
					create:
						每次載入hibernate時都會刪除上一次的生成的表,然後根據你的model類再重新來生成新表,
						哪怕兩次沒有任何改變也要這樣執行,這就是導致資料庫表資料丟失的一個重要原因。
		  create-drop :
						每次載入hibernate時根據model類生成表,但是sessionFactory一關閉,表就自動刪除。
				  update:
						最常用的屬性,第一次載入hibernate時根據model類會自動建立起表的結構(前提是先建立好資料庫),
						以後載入hibernate時根據 model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。
						要注意的是當部署到伺服器後,表結構是不會被馬上建立起來的,是要等 應用第一次執行起來後才會。
				validate :
						每次載入hibernate時,驗證建立資料庫表結構,只會和資料庫中的表進行比較,不會建立新表,但是會插入新值。
				-->
				<prop key="hibernate.hbm2ddl.auto">update</prop>
			</props>	
		</property>
	</bean>
	</beans>

hibernate.cfg.xml檔案:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<!-- Generated by MyEclipse Hibernate Tools.                   -->
<hibernate-configuration>
 
<session-factory>
 	<property name="hbm2ddl.auto">update</property>
	<property name="dialect">
		org.hibernate.dialect.MySQLDialect
	</property>
	<property name="connection.url">
		jdbc:mysql://127.0.0.1:3306/hibernate
	</property>
	<property name="connection.username">root</property>
	<property name="connection.password">root</property>
	<property name="connection.driver_class">
		com.mysql.jdbc.Driver
	</property>
	<property name="myeclipse.connection.profile">
		com.mysql.jdbc.Driver
	</property>
	<property name="format_sql">true</property>
	<property name="show_sql">true</property>
	<property name="javax.persistence.validation.mode">none</property> 
	<mapping resource="com/imooc/config/Address.hbm.xml" />
	<mapping resource="com/imooc/config/Student.hbm.xml" />
</session-factory>
</hibernate-configuration>

這個是來自部落格:單獨拿出hibernate.cfg.xml檔案

ssm版本:

與hibernate類似,在mybatis中,也是使用session工廠實現crud操作,只是mybatis中sessionFactory是叫 SqlSessionFactory,

mybatis的處理如圖所示:
mybatis流程

還有一點是mybatis是基於介面的實現呼叫的,在ssm版本中其Dao類是叫Mapper類; 例如:
package com.ssm.mapper;

import com.ssm.pojo.Category;
import com.ssm.pojo.CategoryExample;
import java.util.List;

public interface CategoryMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(Category record);

    int insertSelective(Category record);

    List<Category> selectByExample(CategoryExample example);

    Category selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(Category record);

    int updateByPrimaryKey(Category record);
}
同樣的,mybatis也有兩個配置檔案,一個是Mapper對應的xml檔案即Mapper類名.xml,專案中CategoryMapper.xml如下:
<?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.ssm.mapper.CategoryMapper">
  <resultMap id="BaseResultMap" type="com.ssm.pojo.Category">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="name" jdbcType="VARCHAR" property="name" />
  </resultMap>
  <sql id="Example_Where_Clause">
    <where>
      <foreach collection="oredCriteria" item="criteria" separator="or">
        <if test="criteria.valid">
          <trim prefix="(" prefixOverrides="and" suffix=")">
            <foreach collection="criteria.criteria" item="criterion">
              <choose>
                <when test="criterion.noValue">
                  and ${criterion.condition}
                </when>
                <when test="criterion.singleValue">
                  and ${criterion.condition} #{criterion.value}
                </when>
                <when test="criterion.betweenValue">
                  and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
                </when>
                <when test="criterion.listValue">
                  and ${criterion.condition}
                  <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
                    #{listItem}
                  </foreach>
                </when>
              </choose>
            </foreach>
          </trim>
        </if>
      </foreach>
    </where>
  </sql>
  <sql id="Base_Column_List">
    id, name
  </sql>
  <select id="selectByExample" parameterType="com.ssm.pojo.CategoryExample" resultMap="BaseResultMap">
    select
    <if test="distinct">
      distinct
    </if>
    'false' as QUERYID,
    <include refid="Base_Column_List" />
    from category
    <if test="_parameter != null">
      <include refid="Example_Where_Clause" />
    </if>
    <if test="orderByClause != null">
      order by ${orderByClause}
    </if>
  </select>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from category
    where id = #{id,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from category
    where id = #{id,jdbcType=INTEGER}
  </delete>
  <insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.ssm.pojo.Category" useGeneratedKeys="true">
    insert into category (name)
    values (#{name,jdbcType=VARCHAR})
  </insert>
  <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.ssm.pojo.Category" useGeneratedKeys="true">
    insert into category
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="name != null">
        name,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="name != null">
        #{name,jdbcType=VARCHAR},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.ssm.pojo.Category">
    update category
    <set>
      <if test="name != null">
        name = #{name,jdbcType=VARCHAR},
      </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.ssm.pojo.Category">
    update category
    set name = #{name,jdbcType=VARCHAR}
    where id = #{id,jdbcType=INTEGER}
  </update>
</mapper>

其實跟ssh專案的Bean類使用註解代替配置檔案一樣的道理,這裡配置Mapper的可以使用註解來代替;那樣就要手打了。。。ssm專案中之前Bean類有提到,使用的mybatis外掛自動生成的Mappe類和對映檔案,Bean類;

為了對比,這是沒整合前的Mapper的xml配置檔案,UserMapper.xml如下:

<?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.mybatis.dao.UserMapper">
<!--    mapper上的namespace是為了,告訴Mybatis在哪個配置檔案-->

<!--    查詢操作-->
<!--    這句話的使用的時候的意思是;
        userMapper的配置檔案中,呼叫id為findAll的方法
        執行查詢語句,將結果集封裝到User的bean物件中!!!
-->
    <select id="findAll" resultType="user">
        select *from user
    </select>

<!--    根據id查詢-->
    <select id="findById" resultType="user" parameterType="int">
        select * from user where id=#{id}
    </select>


</mapper>

其中沒整合前mybatis的核心配置檔案SqlMapperConfig.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標籤載入外部properties檔案-->
    <properties resource="jdbc.properties"></properties>
    <!--    自定義bean別名-->
    <typeAliases>
        <typeAlias type="com.mybatis.domain.User" alias="user"></typeAlias>
    </typeAliases>
<!--    配置資料來源環境-->
    <environments default="developement">
        <environment id="developement">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

<!--    載入對映檔案
        注意因為在呼叫配置檔案的時候
        只會呼叫SqlMapperConfig的配置檔案
        所以,這裡要引入對映檔案
-->
    <mappers>
        <mapper resource="com/mybatis/mapper/UserMapper.xml"></mapper>
    </mappers>

</configuration>

有一點很重要:

在Mybatis中的Mapper對應的xml檔案跟在Hibernate中的Bean所對應的實體類名.hbm.xml是不一樣的;
在Mybatis中的Mapper對應的xml檔案跟在Hibernate中的Bean所對應的實體類名.hbm.xml是不一樣的;
在Mybatis中的Mapper對應的xml檔案跟在Hibernate中的Bean所對應的實體類名.hbm.xml是不一樣的;

這個很容易弄混!!!

然鵝從上面沒有整合前的SqlMapperConfig.xml中可以看出,對於同樣的核心配置檔案,在這兩個ssm和ssh專案來說,區別不大,sqlMapperConfig.xml裡面同樣配置著資料來源和連線池,以及對應的Mapper配置檔案;當然,SqlMapperConfig.xm可以配有別名,還可以配置型別轉換器;肯定還有其它,這裡不展開了;

那麼使用SqlSessionFactory又是如何實現的持久層的crud功能的呢?

這裡的SqlMapperConfig.xml檔案同樣不存在了,因為跟ssh專案的實現類似,也是在applicationContext.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:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation=
               "http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-3.0.xsd
     http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!--    配置註解掃描-->
    <context:annotation-config/>
<!--   配置掃描的包-->
    <context:component-scan base-package="com.ssm"/>
<!--    匯入資料庫配置檔案-->
    <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="2"/>

<!--        配置獲取連線等待超過的時間-->
        <property name="maxWait" value="60000"/>
<!--        配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒-->
        <property name="timeBetweenConnectErrorMillis" 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.ssm.pojo"/>
<!--        引用的是上面配置好的阿里巴巴的連線池-->
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
<!--        分頁外掛,目前先註釋,後面重構的時候才會使用-->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
<!--                        reasonable:分頁合理化引數-->
                        <value>
                            reasonable=true
                            offsetAsPageNum=true
                            rowBoundsWithCount=true
                        </value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>

<!--    Mybatis的Mapper檔案識別-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.ssm.mapper"/>
    </bean>

<!--  配置事務管理器  -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>
結論
到這兒,就也很清晰了,spring和Mybatis框架的整合,同樣是將SqlsessionFactory的建立權交給spring的ioc容器來處理,利用依賴注入實現呼叫Sqlsession工廠的crud功能;

整體如下所示:
ssm整合

與ssh同樣,如果是將SqlMapperConfig.xml檔案單獨拿出來,放到spring配置檔案中使用;
如下:
applicationContext.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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<!--    元件掃描 掃描service和mapper-->
    <context:component-scan base-package="com.ssm">
<!--        排除controller的掃描-->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

<!--    載入properties檔案-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

<!--    配置資料來源資訊-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

<!--    配合SelSession工廠-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--        因為要運算元據庫,所以要注入dataSource-->
        <property name="dataSource" ref="dataSource"></property>
<!--        載入mybatis的核心檔案-->
        <property name="configLocation" value="classpath:sqlMapConfig-spring.xml"></property>
    </bean>

<!--    載入/掃描mapper所在的包 為mapper建立實現類-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.ssm.mapper"></property>
    </bean>

<!--    配置宣告式事務控制-->

<!--    平臺事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

<!--    配置事務增強-->
    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    
<!--    事務的aop織入-->
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.ssm.service.impl.*.*(..))"></aop:advisor>
    </aop:config>
</beans>

SqlMapperConfig-spring.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>
<!--    定義別名-->
    <typeAliases>
<!--        方式一<typeAlias type="com.ssm.domain.Account" alias="account"></typeAlias>-->
<!--        方式二-->
        <package name="com.ssm.domain"/>
    </typeAliases>

</configuration>

2.Result

綜上所述:

1.無論是Hibernate還是Mybatis整合Spring, 共同點就在於,將SessionFactory或者SqlSessionFatory的建立權交於Spring的Ioc容器進行處理的,利用依賴注入實現呼叫;


2.Hibernate的sql實現是採用了繼承HibernateTemplate實現的,而Mybatis的sql實現則是通過外掛自動生成的;假如不使用工具的話,Hibernate本身封裝好了sql語句(增刪改查,查詢也可自定義),而Mybatis需要我們自己去定義(增刪改查都要自己定義),從這裡來說的話,Hibernate的確具有更加簡便,易用的特點; 可同樣的,mybatis因為沒有封裝好語句,使對語句的呼叫更具個人化和多樣化;
3.我個人認為,正是基於第二點(mybatis中語句得自己寫,而Hibernate封裝好了一些語句,直接呼叫): mybatis採用的是,類似於ee版本的形式,也就是對於ORM的關係,不需要配置,而是直接著重於對於持久層Dao的crud實現,從mybatis有其Mapperxml檔案可見一斑;但是Hibernate對於Bean類進行了重點管理,有其對應的實體類.hbm.xml檔案也可以看出;

3.Controller層

控制層也是兩部分,第一是後臺的資料呼叫業務服務Service類,二是分發資料本身Controller類;

Service類

ee版本:

ee版本基於只是單純呼叫Dao類,所以沒有寫這個類,忽略

ssh版本:

這個版本的封裝,讓我學到了如何真正的重構一個專案,相比於ssm版本基於ee版本的bean注入,這個版本更加具有學習意義;

a.首先在Service實現類中,抽取了一個父類BaseServiceImpl,這個類包含了在Service實現類中會使用的crud方法;用到繼承;
父類BaseServiceImpl如下:

public class BaseServiceImpl extends ServiceDelegateDAO implements BaseService {
//    增加
    @Override
    public Integer save(Object object) {
        return (Integer) super.save(object);
    }

    /*
    //    以下兩句因為繼承了委派物件ServiceDelegateDAO,物件已有了update和delete
    //    修改
        @Override
        public void update(Object object) {
            dao.update(object);
        }
    //    刪除
        @Override
        public void delete(Object object) {
            dao.delete(object);
        }*/
        //    查詢
    @Override
    public Object get(Class clazz, int id) {
        return super.get(clazz,id);
    }
}

b.其次,對這個類再次進行重構;重構的點在於:
b.1:無論是哪一個基於BaseService類的子類呼叫父類方法,都能夠使BaseServiceImpl類呼叫對應的實體類物件;技術就是反射和切割字串,多型特點;
實現如下:

    //    B.1例項化子類的時候,父類構造方法肯定會被呼叫
    public BaseServiceImpl(){
//        方式一:
//        B.2故意丟擲一個異常,
        try{
//            B.3捕捉它
            throw new Exception();
        }catch (Exception e){
//            B.4使用異常,獲取到棧追蹤
            StackTraceElement stes[] = e.getStackTrace();
//            B.5在子類中拿到全限定名稱,注意這裡下標為1,就是第二個元素為呼叫的第二個子類名
            String serviceImpClassName = stes[1].getClassName();
           /* B .6利用反射通過類名(這裡是全限定名)獲取到對應類的全限定名
            (其實可以自己切割,就不用反射的,這裡沒有切割,就使用反射吧。。。。
              看了下後面的程式碼,還是得用到反射。。。。。後面切割包名也要使用到反射
              還是老老實實用反射。。。。
            )*/
            Class serviceImplClazz = null;
            try {
                serviceImplClazz = Class.forName(serviceImpClassName);
            } catch (ClassNotFoundException ex) {
                ex.printStackTrace();
            }
//            B.7呼叫SimpleName方法,得到類名
            String serviceImplSimpleName = serviceImplClazz.getSimpleName();
//            替換掉ServiceImpl這些字元,得到service呼叫的pojo類名
            String pojoSimpleName = serviceImplSimpleName.replaceAll("ServiceImpl", "");
//            獲取pojo的包名,替換掉
            String pojoPackageName=serviceImplClazz.getPackage().getName().replaceAll(".service.impl",".pojo");
//            將得到的類名和包名,進行拼接,然後得到一個全限定名
            String pojoFullName=pojoPackageName+"."+pojoSimpleName;
//            使用反射,得到了對應實體類的類物件
            try {
                clazz=Class.forName(pojoFullName);
            } catch (ClassNotFoundException ex) {
                ex.printStackTrace();
            }
        }

    }

c:將要呼叫的Dao類物件,基於依賴注入重寫了其要呼叫的物件的方法,裝飾者模式,也就是委派物件;技術繼承

/**
 * 這個是抽取了對dao各種方法的呼叫,一個對dao呼叫的委派物件
 */
public class ServiceDelegateDAO {
    @Autowired //根據型別依賴注入
    DAOImpl dao;

   /* @Resource(name = "sf")
    public void setSessionFactory(SessionFactory sessionFactory) {
        dao.setSessionFactory(sessionFactory);
    }*/

    public void setAllowCreate(boolean allowCreate) {
        dao.setAllowCreate(allowCreate);
    }

    public boolean isAllowCreate() {
        return dao.isAllowCreate();
    }

    public void setAlwaysUseNewSession(boolean alwaysUseNewSession) {
        dao.setAlwaysUseNewSession(alwaysUseNewSession);
    }
.......等等方法,還有許多方法,百多個方法
}

這是總體而言,ssh在對於Service層的重構,其中還有些細節不展開了;

ssm版本:

相比於ssh專案對Service層的多處重構,ssm專案沒啥重構了,但是程式碼看起來依然簡單,舒適;其實深究的話,其程式碼也能夠通過反射手段再次重構,這裡也不展開; 以下是CategoryService層:
package com.ssm.service.impl;

import com.ssm.mapper.CategoryMapper;
import com.ssm.pojo.Category;
import com.ssm.pojo.CategoryExample;
import com.ssm.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CategoryServiceImpl implements CategoryService {
    @Autowired
    CategoryMapper categoryMapper;

   /* @Override
    public int total() {
        return categoryMapper.total();
    }*/

   /* @Override
    public List<Category> list(Page page) {
        return categoryMapper.list(page);
    }*/
    @Override
    public List<Category> list() {
        CategoryExample example=new CategoryExample();
        example.setOrderByClause("id desc");
        return categoryMapper.selectByExample(example);
    }

    @Override
    public void add(Category category) {
        categoryMapper.insert(category);
    }

    @Override
    public void delete(int id) {
        categoryMapper.deleteByPrimaryKey(id);
    }

    @Override
    public Category get(int id) {
        return categoryMapper.selectByPrimaryKey(id);
    }

    @Override
    public void update(Category category) {
        categoryMapper.updateByPrimaryKeySelective(category);
    }

//    通過分類名字查詢分類資訊
    public boolean isExitCategory(String nameCategory){
        /*
        * 例項化查詢條件類
        * */
        CategoryExample categoryExample=new CategoryExample();
//        設定查詢條件
        categoryExample.createCriteria().andNameEqualTo(nameCategory);
//        執行這個查詢條件
        List<Category> categories = categoryMapper.selectByExample(categoryExample);
        if(!categories.isEmpty()){
            return true;
        }
        return false;
    }


}

Controller類

這個類,也是三個專案不同之處特徵;

ee版本

ee版本的Controller功能實現,交給了Web的Servlet類;除此之外,這裡還用到了filter類,也就是web的攔截器類;使用Servlet+Filter的封裝也是讓我受益匪淺; 其原理是: 1.使用一個BackServletFilter攔截器攔截所有請求,然後將訪問的地址使用字串切割,切割出兩個資訊:

一為要轉發給的Servlet類名;
二為轉發的類中對應的方法名;

2.根據得到的Servlet類名轉發到對應的Servlet類去,將轉發的類中對應的

方法儲存到request的域物件中去;

3.將所有的Servlet都繼承於一個BaseServlet類,而這個類又繼承於HttpServlet類;所以當BackServletFilter攔截器轉發到某個Servlet類的時候,就會呼叫BaseServlet類的service方法(這個是基於web基礎)。

而在這個service方法中,獲取到之前儲存到request域中的方法名,使用反射呼叫對應的方法,這樣也就實現頁面訪問了後臺功能呼叫對應方法;

4.當Servlet中的方法處理完畢之後,返回字串,而這個在BaseServlet類的的service方法獲取呼叫方法的返回值,並對返回值的字串判斷,重定向或者轉發或者輸出字串到頁面;這樣就完成了整個分發資料的過程

BaseServletFilter如下:

package com.ee.filter;
import org.apache.commons.lang.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class BackServletFilter implements Filter {
    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws ServletException, IOException {
//        獲取到servlet的request和response
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
//        System.out.println("提取到Servlet中方法攔截成功");
//        獲取到專案名稱和訪問的servlet
        String contextPath = request.getServletContext().getContextPath();
//        獲取到訪問的路徑和訪問的方法頁面
//        取出訪問的uri: /tmall/admin_category_list
        String uri = request.getRequestURI();
//        將字首移除,自己這裡沒有字首,不用移除,加上吧,以防萬一
        uri = StringUtils.remove(uri, contextPath);
        if(uri.startsWith("/admin_")){//如果是以admin_開頭的
//            取出category也就是訪問的servlet名字,並且拼接成categoryServlet
            String servletPath = StringUtils.substringBetween(uri,"_","_")+"Servlet";
//            取出訪問的方法名,例如這裡的例子list
            String method = StringUtils.substringAfterLast(uri,"_" );
//            在request域中設定method這個屬性名,屬性值就是方法名
            request.setAttribute("method",method);
//            請求轉發到取出的這個地址/categoryServlet,留頭不留體,響應體由下一個servlet完成
            req.getRequestDispatcher("/" + servletPath).forward(request, response);
//            注意了,這個必須要有!!!
            return;
        }
//        放行
        chain.doFilter(request, response);
    }

    public void init(FilterConfig config) throws ServletException {

    }

}

BaseBackServlet,如下:

public abstract class BaseBackServlet extends HttpServlet {

    public abstract String add(HttpServletRequest request, HttpServletResponse response, Page page) ;
    public abstract String delete(HttpServletRequest request, HttpServletResponse response, Page page) ;
    public abstract String edit(HttpServletRequest request, HttpServletResponse response, Page page) ;
    public abstract String update(HttpServletRequest request, HttpServletResponse response, Page page) ;
    public abstract String list(HttpServletRequest request, HttpServletResponse response, Page page) ;

    protected CategoryDAO categoryDAO = new CategoryDAO();
    protected OrderDAO orderDAO = new OrderDAO();
    protected OrderItemDAO orderItemDAO = new OrderItemDAO();
    protected ProductDAO productDAO = new ProductDAO();
    protected ProductImageDAO productImageDAO = new ProductImageDAO();
    protected PropertyDAO propertyDAO = new PropertyDAO();
    protected PropertyValueDAO propertyValueDAO = new PropertyValueDAO();
    protected ReviewDAO reviewDAO = new ReviewDAO();
    protected UserDAO userDAO = new UserDAO();

    public void service(HttpServletRequest request, HttpServletResponse response) {
        try {
            /*獲取分頁資訊*/
//            預設開始位置為0
            int start= 0;
//            預設每頁顯示資料為5頁
            int count = 5;
            try {
//                利用request域獲取客戶端傳遞過來的資料
                start = Integer.parseInt(request.getParameter("page.start"));
            } catch (Exception e) {

            }
            try {
                count = Integer.parseInt(request.getParameter("page.count"));
            } catch (Exception e) {
            }
//            將得到的資訊設定到Page中去
            Page page = new Page(start,count);

            /*藉助反射,呼叫對應的方法*/
//            這個method:客戶端請求資料被BackServetFilter攔截設定的方法
            String method = (String) request.getAttribute("method");
//            得到子類中的方法
            Method m = this.getClass().getMethod(method, javax.servlet.http.HttpServletRequest.class,
                    javax.servlet.http.HttpServletResponse.class,Page.class);
//            呼叫對應的方法,執行之
            String send = m.invoke(this,request, response,page).toString();
            System.out.println("BaseBackServlet中反射呼叫的servlet方法,返回的字串是:"+send);
            /*根據方法的返回值,進行相應的客戶端跳轉,服務端跳轉,或者僅僅是輸出字串*/
            if(send.startsWith("@")){
//                重定向,也就是所謂的客戶端跳轉
                response.sendRedirect(send.substring(1));
            } else if(send.startsWith("%")){
//                僅僅輸出字串
                response.getWriter().print(send.substring(1));
            } else {
//                轉發,也就是服務端跳轉
                request.getRequestDispatcher(send).forward(request, response);
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

流程圖如下:
在這裡插入圖片描述

結論
使用抽取父類BaseServlet+Filter實現了Controller類處理分發資料的功能;

ssh版本

對比於ssm版本來說,ssh版本的Action(Controller)類就變得異常臃腫。。。。我想這也是struts使用越來越少的原因之一吧。。。。. 其核心功能還是跟ee版本的一樣,使用一個過濾器(就是攔截器),來攔截所有請求,然後將所有請求轉發到指定的action類,但是框架之所以 框架,就是因為已經封裝好了;

這裡這個攔截器就是:

StrutsPrepareAndExecuteFilter
其全名是:
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter

這個類是將其配置到web.xml中的,如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
<!--配置Filter,讓請求都被過濾給了這個Filter-->
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>

</web-app>

而對應ee版本使用Servlet來處理分發資料不一樣,在struts中使用action類來實現這個功能,與此同時再配合一個名為struts.xml檔案來配合action實現servlet的功能,其中未整合前的struts.xml跟整合後的struts.xml檔案基本一樣的;
如下為沒整合前:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
        "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<!--    開啟動態方法呼叫-->
    <constant name="struts.enable.DynamicMethodInvocation" value="true" />
<!--    指定解碼方式為utf-8-->
    <constant name="struts.i18n.encoding" value="utf-8"></constant>
<!--    設定上傳檔案最大值10m-->
    <constant name="struts.multipart.maxSize" value="10485760"></constant>
    <package name="basicstruts" extends="struts-default">
        <!--    宣告時間攔截器-->
        <interceptors>
            <interceptor name="dateInterceptor" class="com.struts.intercept.DateInterceptor"></interceptor>
        </interceptors>

<!--        提交資料到action-->
        <action name="addProduct" class="com.struts.action.ProductAction" method="add">
            <result name="input">addProduct.jsp</result>
<!--            把資料顯示到show.jsp中-->
            <result name="s">show.jsp</result>
        </action>

<!--表單顯示的jsp-->
        <action name="upload" class="com.struts.action.UploadAction" method="upload">
            <result name="success">success.jsp</result>
        </action>


<!--配置客戶端重定向傳參-->
        <action name="addPageProduct" class="com.struts.action.ProductAction" method="addPage">
            <result name="addPage" type="redirect">
                addProduct.jsp?name=${name}
            </result>
        </action>
    </package>
</struts>

整合後的struts.xml檔案:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
        "http://struts.apache.org/dtds/struts-2.1.7.dtd">

<struts>
    <constant name="struts.i18n.encoding" value="UTF-8"></constant>
    <constant name="struts.objectFactory" value="spring"></constant>
    <package name="basicstruts" extends="struts-default">
<!--       配置登入狀態攔截器-->
        <interceptors>
<!--            這個是攔截是否是登入狀態-->
            <interceptor name="authorityInterceptor" class="com.ssh.interceptor.AuthInterceptor"></interceptor>
<!--            這個是讓搜尋框下面有四個分類的作用-->
            <interceptor name="categoryNamesBelowSearchInterceptor" class="com.ssh.interceptor.CategoryNamesBelowSearchInterceptor"></interceptor>
<!--            這個是購物車總數-->
            <interceptor name="cartTotalItemNumberInterceptor" class="com.ssh.interceptor.CartTotalItemNumberInterceptor"></interceptor>
            <!--        攔截器棧-->
            <interceptor-stack name="auth-default">
                <interceptor-ref name="authorityInterceptor"></interceptor-ref>
                <interceptor-ref name="categoryNamesBelowSearchInterceptor"></interceptor-ref>
                <interceptor-ref name="cartTotalItemNumberInterceptor"></interceptor-ref>
<!--                一旦使用了攔截器,預設攔截器會失效,(連啟動都啟動不了)所以要加上預設的-->
                <interceptor-ref name="defaultStack"></interceptor-ref>
            </interceptor-stack>
        </interceptors>
        <default-interceptor-ref name="auth-default"></default-interceptor-ref>
    </package>
</struts>

可以看出整合前和整合後,其功能都可以配置攔截器,編碼方式,檢視跳轉等等功能;
唯一不同在於這個,有一個標籤:


 <constant name="struts.objectFactory" value="spring"></constant>

根據站長所言,這是

將Action生命週期由原本的Struts進行管理,交由Spring進行管理;也就這裡不同了。。。。

這個專案其使用了註解的方式,所以其struts.xml看起來乾淨利落,但是在action類中卻並不是這樣的。。。。

首先,action類中有如下幾個功能。。。。

  1. 返回頁面的定義
  2. 單個物件的getter setter
  3. 集合物件的getter setter
  4. 分頁物件的getter setter
  5. 上傳檔案物件的getter setter
  6. Service層物件的注入
  7. 作為控制層進行的訪問路徑對映

說實話,還沒有ee版本的Controller類乾淨利落。。。。。
看著都頭大,因而言其採用全部抽取為類,然後一一繼承,繼承關係如下:

/*
	上傳				分頁			       實體類			
Action4Upload《———Action4Pagination《———Action4Pojo《———
部分方法抽取(自己抽取出來的一些重用方法)
Action4Method
		服務			      頁面				    控制層
《———Action4Service《———Action4Result《———CategoryAction
* */

這裡就不一一展示所有的抽取類,只將其Action類顯示出來,如下:

public class CategoryAction extends Action4Result{
    /*新增分類*/
    @Action("admin_category_add")
    public String add(){
//        把category物件儲存到資料庫,就是實現新增分類
        categoryService.save(category);
//        呼叫新增圖片方法
        addAndEdit();
        return "listCategoryPage";
    }

    /*刪除分類*/
    @Action("admin_category_delete")
    public String delete(){
//        為了刪除屬性和產品,持久化category
        t2p(category);

        /*刪除產品*/
//        1.查詢出該分類下的所有產品
        List<Product>  productList= productService.listByParent(category);
//        2.遍歷這些產品,一個一個刪除
        for (Product ps : productList) {
//            3.呼叫刪除產品的方法
            deleteCategoryByProduct(propertyValueService,orderItemService,
                    productImageService,productService,ps);
        }

        /*刪除屬性*/
//        1.查詢出該分類下的所有屬性
        List<Property> propertyList=propertyService.listByParent(category);
//        2.遍歷這些屬性
        for (Property py : propertyList) {
            deleteCategoryByProperty(propertyValueService,propertyService,py);
        }

//        刪除分類
        categoryService.delete(category);
//        刪除圖片
        deleteImage();
        return "listCategoryPage";
    }

    /*點選編輯轉到編輯頁面*/
    @Action("admin_category_edit")
    public String edit(){
        t2p(category);
        return "editCategory";
    }

    /*修改分類*/
    @Action("admin_category_update")
    public String update(){
//        呼叫service層的更新category方法
        categoryService.update(category);
//        當發現上傳的檔案的時候,進行檔案更新操作
        if(null!=img){
//            呼叫修改方法
            addAndEdit();
        }
        return "listCategoryPage";
    }
}
至於其分發處理資料過程是: 1.所有的訪問都會被StrutsPrepareAndExecuteFilter類攔截; 2.攔截之後根據其@Action註解中的訪問路徑,呼叫其對應的方法; 3.通過struts接收請求引數,接收與設定頁面傳遞來的實體類; 4.呼叫Spring依賴注入的Service方法,實現crud功能; 5.通過@Results註解轉發,重定向,輸出字串到對應的頁面

其流程圖:
在這裡插入圖片描述

結論
使用StrutsPrepareAndExecuteFilter類+struts.xml配置檔案處理分發資料的功能;

ssm版本

接下來就是ssm版本的Controller了。其跟struts是一樣的,也有一個用於分發和處理資料的過濾器,當然Spring中叫控制器;在Spring中同樣有一個配合控制器的.xml檔案,

先看下,沒有整合前的springmvc中的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.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
">
<!--    Controller的元件掃描-->
<!--    <context:component-scan base-package="com.spring.controller"/>-->
    <!--
    前面註釋掉的,同樣能夠實現註解掃描;
    下面這種也可以實現,但是更加具有專一性,也就是標明掃描
    帶有@Controller註解的類
    -->
    <context:component-scan base-package="com.spring">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

<!--    配置內部資源檢視解析器,就是轉發/重定向的地址字首和字尾簡寫-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--        指示內部資源檢視解析器的字首和字尾-->
        <property name="prefix" value="/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

<!--    mvc的註解驅動,就是替代處理器和介面卡-->
    <mvc:annotation-driven conversion-service="conversionService"/>
<!--開放資源的訪問-->
    <!--<mvc:resources mapping="/js/**" location="/js/"/>-->

<!--    當Controller找不到資源的時候,使用tomcat預設的servlet處理器去尋找資源-->
    <mvc:default-servlet-handler/>

<!--    宣告日期轉換器-->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.spring.converters.DateConverter"></bean>
            </list>
        </property>
    </bean>

<!--    配置檔案上傳解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--        上傳檔案總大小-->
        <property name="maxUploadSize" value="500000"/>
<!--        上傳單個檔案的大小-->
        <property name="maxUploadSizePerFile" value="50000"/>
<!--        上傳檔案的編碼型別-->
        <property name="defaultEncoding" value="UTF-8"/>
    </bean>
</beans>

看整合後的spring的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-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<!--    啟動註解識別-->
    <context:annotation-config/>
    <context:component-scan base-package="com.ssm.controller">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
<!--    元件掃描,配置mvc註解驅動-->
    <mvc:annotation-driven/>
<!--    開啟靜態資源的訪問-->
    <mvc:default-servlet-handler/>

<!--    配置內部資源檢視解析器-->
    <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">
    </bean>
<!--    攔截器-->
    <mvc:interceptors>
<!--        登入狀態-->
        <mvc:interceptor>
            <mvc:mapping path="/fore*"/>
            <bean class="com.ssm.interceptor.LoginInterceptor"/>
        </mvc:interceptor>
<!--        顯示分類和購物車數量-->
        <mvc:interceptor>
            <mvc:mapping path="/fore*"/>
            <bean class="com.ssm.interceptor.OtherInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
</beans>

基本上沒有變化,上傳檔案,內部解析器,以及註解掃描等等,當然如果不是使用註解的話,就是對於轉發地址和接收地址的配置了,這個跟struts.xml檔案大同小異,只是標籤名不一樣;
值得注意的是:

<!--    Controller的元件掃描-->
<!--    <context:component-scan base-package="com.spring.controller"/>-->
    <!--
    前面註釋掉的,同樣能夠實現註解掃描;
    下面這種也可以實現,但是更加具有專一性,也就是標明掃描
    帶有@Controller註解的類
    -->
    <context:component-scan base-package="com.spring">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

這一個標籤據站長所說,跟ssh專案中的過濾器一樣是將Controller的生命週期交給了Spring去管理,總之就是springmvc.xml檔案直接跟Controller類掛鉤,用控制器配合;
其配置的控制器名為:
DispatcherServlet
全限定名為:
org.springframework.web.servlet.DispatcherServlet

同樣也是配置在了web.xml檔案中,如下:

<!--    配置spring的前端控制器,就是分發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>

同樣的,這裡只顯示分類的Controller檔案,如下:

package com.ssm.controller;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ssm.pojo.Category;
import com.ssm.pojo.Product;
import com.ssm.pojo.Property;
import com.ssm.service.CategoryService;
import com.ssm.utils.ImageUtil;
import com.ssm.utils.Page;
import com.ssm.utils.UploadedImageFile;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.util.List;
@Controller
@RequestMapping("")//表示一級訪問的時候無需額外的地址,不寫就是預設專案根目錄
public class CategoryController extends MethodController{
    @RequestMapping("admin_category_list")
    public String list(Model model, Page page){//為方法list增加引數Page用於獲取瀏覽器傳遞過來的分頁資訊
//        使用分頁助手
        PageHelper.offsetPage(page.getStart(),page.getCount());
//        獲取當前頁的分類集合
        List<Category> cs = categoryService.list();
//        獲取分類總數
        int total = (int) new PageInfo<Category>(cs).getTotal();
//        將獲取到的分類總數設定到Page中去
        page.setTotal(total);
        model.addAttribute("cs",cs);
        model.addAttribute("page",page);
        return "admin/listCategory";
    }

    /*
    * 1. add方法對映路徑admin_category_add的訪問
    1.1 引數 Category c接受頁面提交的分類名稱
    1.2 引數 session 用於在後續獲取當前應用的路徑
    1.3 UploadedImageFile 用於接受上傳的圖片
    * */
    @RequestMapping("admin_category_add")
    public String add(Category category, HttpSession session,
                      UploadedImageFile uploadedImageFile) throws IOException {
//        呼叫service層的方法,儲存分類資訊在資料庫中
        categoryService.add(category);
//        呼叫修改圖片方法
        addAndEditByCategory(category,session,uploadedImageFile);
        return "redirect:/admin_category_list";
    }

    @RequestMapping("admin_category_delete")
    public String delete(int id,HttpSession session){
        /*刪除屬性*/
//        查詢該分類下的屬性值
        List<Property> pts = propertyService.selectByCategory(id);
//        遍歷這些屬性值
        for (Property pt : pts) {
            if(pt!=null){
                //            呼叫刪除屬性值方法
                System.out.println("屬性值id是?:"+pt.getId());
                deleteByPropertyId(pt.getId());
            }
        }

        /*刪除產品*/
        List<Product> ps = productService.selectByCategory(id);
        for (Product p : ps) {
            if(p!=null){
                //            呼叫刪除產品方法
                System.out.println("產品id是?:"+p.getId());
                deleteByProductId(p.getId(),session);
            }
        }

//        呼叫service層刪除方法
        categoryService.delete(id);
//        刪除分類圖片
        File imageFolder=new File(session.getServletContext().getRealPath("img/category"));
        File file=new File(imageFolder,id+".jpg");
        file.delete();
        return "redirect:/admin_category_list";
    }

//   點選編輯轉到的方法
    @RequestMapping("admin_category_edit")
    public String edit(int id,Model model){
//        首先通過id資訊獲取到Category表資訊
        Category category=categoryService.get(id);
        model.addAttribute("c",category);
        return "admin/editCategory";
    }

//    修改分類方法
    @RequestMapping("admin_category_update")
    public String update(Category category,HttpSession session,
                         UploadedImageFile uploadedImageFile) throws IOException {
        categoryService.update(category);
        MultipartFile image = uploadedImageFile.getImage();
//        如果上傳的圖片不為null
        if(null!=image && !image.isEmpty()){
//            呼叫修改圖片的方法
            addAndEditByCategory(category,session,uploadedImageFile);
        }
        return "redirect:/admin_category_list";
    }

//    判斷新增的分類是否存在
    @RequestMapping("admin_category_judge")
    @ResponseBody//告訴spring直接響應,不需要跳轉
    public String judge(String nameCategory){
        boolean exitCategory = categoryService.isExitCategory(nameCategory);
        if(exitCategory){
            return "success";
        }
        return null;
    }
}

其管理分發資料的過程是: 1.所有的訪問都會被DispatcherServlet類攔截; 2.攔截之後根據其@RequestMapping("路徑”)註解中的訪問路徑,進入對應的Controller類 3.通過spring處理對映請求引數,接收與設定頁面傳遞來的實體類; 4.呼叫Spring依賴注入的Service方法,實現crud功能; 5.通過springMVC中配置的內部資源檢視解析器,配置的字首字尾,根據是否有@RequestBody轉發,重定向,輸出字串到頁面;

其流程圖:
ssm版本流程

結論
使用DispatcherServlet類+springMVC.xml配置檔案處理分發資料的功能;

3.Result

綜上所述:

1.無論是Spring的Controller類+springMVC配置檔案;還是Struts2的Action類+struts.xml配置檔案;這兩個都是跟ee版本一樣的採用Servlet+Filter處理控制層的,只是:

一則將抽取的BaseServlet變成了用配置檔案或者註解的方式配合對應的Action或Controller類 二則將Servlet變成了Controller類和Action類,其功能可以看成是servlet類

四、專案中的其它

1.分頁

相同點

三個版本都使用了一個Page物件,其在於用來在頁面顯示資料 如下: Page物件:
public class Page {
    private int start;//開始頁數
    private int count;//每頁顯示的數量
    private int total;//總共有多少條資料
    private String param;//引數,為了攜帶種類id

    private static final int defaultCount = 5; //預設每頁顯示5條
//    每次建立無參物件的時候將預設顯示的資料設定為5
    public Page (){
        count = defaultCount;
    }
    public Page(int start, int count) {
        super();
        this.start = start;
        this.count = count;
    }

//    判斷是否有前一頁
    public boolean isHasPrevious(){
        if(start==0){
            return false;
        }
        return true;
    }
//    判斷是否有下一頁
    public boolean isHasNext(){
        if(start==getLast()){
            return false;
        }
        return true;
    }
//	 根據 每頁顯示的數量count以及總共有多少條資料total,計算出總共有多少頁
    public int getTotalPage(){
        int totalPage;
        // 假設總數是50,是能夠被5整除的,那麼就有10頁
        if (0 == total % count) {
            totalPage = total / count;
        } else {
            // 假設總數是51,不能夠被5整除的,那麼就有11頁
            totalPage = total / count + 1;
        }
        if(0==totalPage) {
            totalPage = 1;
        }
        return totalPage;
    }
//	最後一頁的數值是多少
    public int getLast(){
        int last;
        // 假設總數是50,是能夠被5整除的,那麼最後一頁的開始就是45
        if (0 == total % count) {
            last = total - count;
        }
        // 假設總數是51,不能夠被5整除的,那麼最後一頁的開始就是50
        else {
            last = total - total % count;
        }
        last = last<0?0:last;
        return last;
    }


    public int getStart() {
        return start;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public String getParam() {
        return param;
    }

    public void setParam(String param) {
        this.param = param;
    }

    @Override
    public String toString() {
        return "Page{" +
                "start=" + start +
                ", count=" + count +
                ", total=" + total +
                ", param='" + param + '\'' +
                '}';
    }
}

不同點

其不同點主要在於sql語句上。

在ee版本中:
SQL語句是自己寫的,使用sql方言limit關鍵字

//    分頁查詢
    public List<Category> list(int start,int count){//第一個引數從哪個id開始查,第二個引數總共查詢多少條資料
        List<Category> categoryList=new ArrayList<Category>();
        String sql="select * from Category order by id desc limit ?,?";
        try (Connection con = DBUtil.getConnection(); PreparedStatement ps = con.prepareStatement(sql);){
            ps.setInt(1,start);
            ps.setInt(2,count);
            ResultSet rs = ps.executeQuery();
            while (rs.next()){
                Category category=new Category();
                int id = rs.getInt(1);
                String name = rs.getString(2);
                category.setId(id);
                category.setName(name);
                categoryList.add(category);
            }
        }catch (SQLException e){
            e.printStackTrace();
        }
        return categoryList;
    }

在ssh版本中:
使用QBC的查詢方式

//    分頁查詢某一類資訊
    @Override
    public List<Object> listByPage(Page page) {
        DetachedCriteria dc= DetachedCriteria.forClass(clazz);
        dc.addOrder(Order.desc("id"));
        return findByCriteria(dc,page.getStart(),page.getCount());
    }

在ssm版本中:
使用了PageHelper分頁助手,其實現:
1.pom.xml中導座標;

 <pagehelper.version>5.1.2-beta</pagehelper.version>
<!-- pageHelper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>${pagehelper.version}</version>
        </dependency>

2.在mybatis核心配置檔案中配置PageHelper外掛;這裡是在applicationContext.xml檔案的SqlSession中配置;

<!--    Mybatis的SessionFactory配置-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="typeAliasesPackage" value="com.ssm.pojo"/>
<!--        引用的是上面配置好的阿里巴巴的連線池-->
        <property name="dataSource" ref="dataSource"/>
<!--        配置Mapper配置檔案類-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
<!--        分頁外掛,目前先註釋,後面重構的時候才會使用-->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
<!--                        reasonable:分頁合理化引數-->
                        <value>
                            reasonable=true
                            offsetAsPageNum=true
                            rowBoundsWithCount=true
                        </value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>

3.使用;

    @RequestMapping("admin_category_list")
    public String list(Model model, Page page){//為方法list增加引數Page用於獲取瀏覽器傳遞過來的分頁資訊
//        使用分頁助手
        PageHelper.offsetPage(page.getStart(),page.getCount());
//        獲取當前頁的分類集合
        List<Category> cs = categoryService.list();
//        獲取分類總數
        int total = (int) new PageInfo<Category>(cs).getTotal();
//        將獲取到的分類總數設定到Page中去
        page.setTotal(total);
        model.addAttribute("cs",cs);
        model.addAttribute("page",page);
        return "admin/listCategory";
    }

2.上傳檔案

相同點

ssm和ssh版本,都使用 了一個類,來儲存從頁面上獲取的資料:前者是使用一個類用到了org.springframework.web.multipart.MultipartFile這個物件; 後者是使用一個類,但是這個類的屬性名字中必須包含跟頁面上傳表單元素的name中值;

還有兩點是:

上傳頁面有兩點需要注意
1. form 的method必須是post的,get不能上傳檔案。 還需要加上enctype="multipart/form-data" 表示提交的資料是二進位制檔案
 
<form action="uploadPhoto" method="post" enctype="multipart/form-data">
 

2. 需要提供type="file" 的欄位進行上傳

不同點

ee版本:
沒有使用類了,直接寫成一個方法在BaseBackServlet中,如下:

//    返回上傳檔案的輸入流和欄位名
    public InputStream parseUpload(HttpServletRequest request, Map<String, String> params) {
        InputStream is =null;
        try {
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            // 設定上傳檔案的大小限制為10M
            factory.setSizeThreshold(1024 * 10240);
//            獲取到瀏覽器上傳的檔案的輸入流
            List items = upload.parseRequest(request);
//            遍歷輸入流,得到瀏覽器提交的資料
            Iterator iter = items.iterator();
            while (iter.hasNext()) {
//            遍歷出Item,一個Item就是對應一個瀏覽器提交的資料
                FileItem item = (FileItem) iter.next();
                /*
                * 瀏覽器指定了以二進位制的形式提交資料
                * 不能通過常規的手段獲取非File欄位
                *       所以通過item.isFormField()判斷
                *       是否是常規欄位還是提交的檔案
                * */
//                當返回是false,說明是提交的檔案,獲取上傳檔案的輸入流
                if (!item.isFormField()) {
                    // item.getInputStream() 獲取上傳檔案的輸入流
                    is = item.getInputStream();
                } else {//當返回的是true的時候,說明是常規欄位
//                    獲取到欄位名
                    String paramName = item.getFieldName();
//                    獲取到欄位值
                    String paramValue = item.getString();
//                    將欄位值轉化為位元組流,並且指定編碼,以防儲存到資料庫亂碼
                    paramValue = new String(paramValue.getBytes("ISO-8859-1"), "UTF-8");
//                    將鍵跟處理好的值存入Map集合
                    params.put(paramName, paramValue);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return is;
    }

ssh版本:
這個版本抽取了上傳類:
提交的時候,struts會自動對映到這個類,前提是之前說的,必須基於上傳表單的input標籤的name=“img”,這個name的值

package com.ssh.action;
import java.io.File;

/**
 * 處理圖片上傳
 */
public class Action4Upload {
    /*用於新增分類和圖片
     * 注意了!
     * 在客戶端jsp中,輸入框input的name屬性跟這裡
     * 定義屬性的相匹配,例如這裡
     * 在listCategory.jsp中的input的name="img"
     * 所以這裡定義的關於上傳相關的屬性都得基於"img",為了讓struts2識別
     * */
//    上傳檔案,用於接受瀏覽器提交的圖片檔案,
    protected File img;
//    上傳檔名字
    protected String imgFileName;
//    上傳檔案型別
    protected String imgContentType;

    public File getImg() {
        return img;
    }

    public void setImg(File img) {
        this.img = img;
    }

    public String getImgFileName() {
        return imgFileName;
    }

    public void setImgFileName(String imgFileName) {
        this.imgFileName = imgFileName;
    }

    public String getImgContentType() {
        return imgContentType;
    }

    public void setImgContentType(String imgContentType) {
        this.imgContentType = imgContentType;
    }

}

ssm版本:
在spring這個版本中,同樣有一個上傳類。
跟ssh版本不同的是,這個類使用了一個MultipartFile的物件,這個類的全限定名為:
org.springframework.web.multipart.MultipartFile;

還有一點是,使用spring的上傳類的話,得在springmvc配置檔案中配置上傳檔案解析器:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    </bean>

同在頁面提交的,spring會自動對映到這個上傳類

package com.ssm.utils;

import org.springframework.web.multipart.MultipartFile;
/**
 * 這個是用於spring上傳用的類
 */
public class UploadedImageFile {
    /*
    *  這裡的屬性名稱image必須和頁面中的增加分類部分中的type="file"的name值保持一致。
    *  <input id="categoryPic" accept="image/*" type="file" name="image" />
    * */
    MultipartFile image;

    public MultipartFile getImage() {
        return image;
    }

    public void setImage(MultipartFile image) {
        this.image = image;
    }
}


五、總結

捋一遍,總結

view層

其檢視層使用的就是同一套jsp頁面。

Model層

1.無論是Hibernate還是Mybatis整合Spring, 共同點就在於,將SessionFactory或者SqlSessionFatory的建立權交於Spring的Ioc容器進行處理的,利用依賴注入實現呼叫;
2.Hibernate的sql實現是採用了繼承HibernateTemplate實現的,而Mybatis的sql實現則是通過外掛自動生成的;假如不使用工具的話,Hibernate本身封裝好了sql語句(增刪改查,查詢也可自定義),而Mybatis需要我們自己去定義(增刪改查都要自己定義),從這裡來說的話,Hibernate的確具有更加簡便,易用的特點; 可同樣的,mybatis因為沒有封裝好語句,使對語句的呼叫更具個人化和多樣化;
3.我個人認為,正是基於第二點(mybatis中語句得自己寫,而Hibernate封裝好了一些語句,直接呼叫): mybatis採用的是,類似於ee版本的形式,也就是對於ORM的關係,不需要配置,而是直接著重於對於持久層Dao的crud實現,從mybatis有其Mapperxml檔案可見一斑;但是Hibernate對於Bean類進行了重點管理,有其對應的實體類.hbm.xml檔案也可以看出;

Controller層

1.無論是Spring的Controller類+springMVC配置檔案;還是Struts2的Action類+struts.xml配置檔案;這兩個都是跟ee版本一樣的採用Servlet+Filter處理控制層的,只是: 一則將抽取的BaseServlet變成了用配置檔案或者註解的方式配合對應的Action或Controller類 二則將Servlet變成了Controller類和Action類,其功能可以看成是servlet類

總結

到這裡的話,三個專案的總體對比已經做完了,此次對比,不涉及框架原理的對比,只是單純從實現功能上對比;畢竟,我也只是個小白,有啥錯誤建議,歡迎指正,友好交流,拒絕槓精秀優越感的人!!

對了!!!
要是各位覺得有用的話!!!
麻煩各位帥哥美女點個讚了!!!
我搞了四天!!!!
謝謝 了!!!

相關文章