仿天猫整站三个版本的对比

系列文章目录

天猫整站的前端笔记

天猫整站的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框架中,每一个数据库表对应的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功能;

整体如下所示:
仿天猫整站三个版本的对比

除此之外,
也可以将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是基于接口的实现调用的,在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功能;

整体如下所示:
仿天猫整站三个版本的对比

与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转发,重定向,输出字符串到页面;

其流程图:
仿天猫整站三个版本的对比

结论
使用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类

总结

到这里的话,三个项目的总体对比已经做完了,此次对比,不涉及框架原理的对比,只是单纯从实现功能上对比;毕竟,我也只是个小白,有啥错误建议,欢迎指正,友好交流,拒绝杠精秀优越感的人!!

对了!!!
要是各位觉得有用的话!!!
麻烦各位帅哥美女点个赞了!!!
我搞了四天!!!!
谢谢 了!!!

上一篇:linq:求分组后的每组最大值、最小值、平均值等、Aggregate聚合函数


下一篇:移动端H5页面 input 获取焦点时,虚拟键盘挡住input输入框解决方法