JavaWeb入门_模仿天猫整站Tmall_SSM实践项目

Tmall_SSM

技术栈 Spring MVC+ Mybatis + Spring + Jsp + Tomcat , 是 Java Web 入门非常好的练手项目

效果展示:

模仿天猫前台
模仿天猫后台

项目简介

关联项目
github - 天猫 JavaEE 项目
github - 天猫 SSH 项目
github - 天猫 SSM 项目

之前使用 JavaEE 整套技术和 SSH 框架来作为解决方案,实现模仿天猫网站的各种业务场景,现在开始使用 SSM 框架技术。

项目用到的技术如下:
Java: Java SE基础
前端: HTML,CSS, JavaScript, JQuery,AJAX, Bootstrap
J2EE:Tomcat, Servlet, JSP, Filter
框架:SpringSpring MVCMybatisSSM整合
数据库:MySQL
开发工具: IDEA ,Maven

项目结构

表结构

建表sql 已经放在 Github 项目的 /sql 文件夹下

表名中文含义介绍
Category分类表存放分类信息,如女装,平板电视,沙发等
Property属性表存放属性信息,如颜色,重量,品牌,厂商,型号等
Product产品表存放产品信息,如LED40EC平板电视机,海尔EC6005热水器
PropertyValue属性值表存放属性值信息,如重量是900g,颜色是粉红色
ProductImage产品图片表存放产品图片信息,如产品页显示的5个图片
Review评论表存放评论信息,如买回来的蜡烛很好用,么么哒
User用户表存放用户信息,如斩手狗,千手小粉红
Order订单表存放订单信息,包括邮寄地址,电话号码等信息
OrderItem订单项表存放订单项信息,包括购买产品种类,数量等

表关系

Category-分类Product-产品
Category-分类Property-属性
Property-属性PropertyValue-属性值
Product-产品PropertyValue-属性值
Product-产品ProductImage-产品图片
Product-产品Review-评价
User-用户Order-订单
Product-产品OrderItem-订单项
User-用户OrderItem-订单项
Order-订单OrderItem-订单项
User-用户User-评价

以上直接看可能暂时无法完全理解,结合后面具体到项目的业务流程就明白了。


开发流程

首先使用经典的 SSM 模式进行由浅入深地开发出第一个分类管理模块 ,
然后分析这种方式的弊端,再对其进行项目重构,使得框架更加紧凑,后续开发更加便利和高效率。

分类管理模块

Category 实体类

准备 Category 实体类,定义对应的字段即可。
举个例子,对于 分类 / category 的 实体类 和 表结构 设计如下:

1240


Mapper 接口

public interface CategoryMapper {
    List<Category> list();
} 

CategoryMapper.xml 指定映射的 sql 和结果集

com.caozhihu.tmall.mapper.CategoryMapper 对应上面的 Mapper 接口。mybatis 的 sql 是手打的,还好有逆向工程,后面重构会讲。

<mapper namespace="com.caozhihu.tmall.mapper.CategoryMapper">
  <select id="list" resultType="Category">
    select * from   category order by id desc
  </select>
</mapper>

CategoryService 接口
public interface CategoryService{
    List<Category> list();
}

CategoryServiceImpl 实现类
@Service
public class CategoryServiceImpl  implements CategoryService {
    @Autowired
    CategoryMapper categoryMapper;
    public List<Category> list(){
        return categoryMapper.list();
    };
}

在 list() 方法中,通过其自动装配的一个 CategoryMapper 对象的 list() 方法来获取所有的分类对象。


CategoryController 控制类

@Controller //声明当前类是一个控制器
@RequestMapping("") //访问的时候无需额外的地址
public class CategoryController {
    @Autowired //自动装配进 categoryService 接口
    CategoryService categoryService;
    
    @RequestMapping("admin_category_list")
    public String list(Model model){
        List<Category> cs= categoryService.list();
        model.addAttribute("cs", cs); 
        return "admin/listCategory";
    }
}

在list方法中,通过 categoryService.list() 获取所有的 Category 对象,然后放在 "cs" 中,并服务端跳转到“admin/listCategory” 视图。


jdbc.properties 数据库配置文件

#数据库配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/tmall_ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=admin

applicationContext.xml

这里配置使用了阿里巴巴的 druid 数据库连接池,这些配置基本都是固定写法,PSCache 就是 PreparedStatement 缓存,据说可以大幅提升性能。

<beans>
    <!-- 启动对注解的识别 -->
    <context:annotation-config/>
    <context:component-scan base-package="com.caozhihu.tmall.service"/>

    <!-- 导入数据库配置文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 配置数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 基本属性 url、user、password -->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="1"/>
        <property name="minIdle" value="1"/>
        <property name="maxActive" value="20"/>
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="validationQuery" value="SELECT 1"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxPoolPreparedStatementPerConnectionSize"
                  value="20"/>
    </bean>

    <!--Mybatis的SessionFactory配置-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="typeAliasesPackage" value="com.caozhihu.tmall.pojo"/>
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations">
            <array>
                <value>classpath:com/caozhihu/tmall/mapper/*.xml</value>
                <value>classpath:mapper/*.xml</value>
            </array>
        </property>
        <!--分页插件-->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <value>
                        </value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>

    <!--Mybatis的Mapper文件识别,mybatis-spring提供了MapperScannerConfigurer这个类,
    它将会查找类路径下的映射器并自动将它们创建成MapperFactoryBean-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.caozhihu.tmall.mapper"/>
    </bean>
</beans>

这里只放了核心配置部分,头部命名空间已省略


springMVC.xml
<beans>
   <!--启动注解识别-->
   <context:annotation-config/>
   <context:component-scan base-package="com.caozhihu.tmall.controller">
       <context:include-filter type="annotation"
                               expression="org.springframework.stereotype.Controller"/>
   </context:component-scan>
   <mvc:annotation-driven/>

   <!--开通静态资源的访问-->
   <mvc:default-servlet-handler/>

   <!-- 视图定位 例如 admin/listCategory 会被定位成 /WEB-INF/jsp/admin/listCategory.jsp-->
   <bean
           class="org.springframework.web.servlet.view.InternalResourceViewResolver">
       <property name="viewClass"
                 value="org.springframework.web.servlet.view.JstlView"/>
       <property name="prefix" value="/WEB-INF/jsp/"/>
       <property name="suffix" value=".jsp"/>
   </bean>

   <!-- 对上传文件的解析-->
   <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
</beans>

web.xml

web.xml 主要提供如下功能

  1. 指定 spring 的配置文件为 classpath 下的 applicationContext.xml
  2. 设置中文过滤器
  3. 指定 spring mvc 配置文件为 classpath 下的 springMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <!-- spring的配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!--中文过滤器-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- spring mvc核心:分发servlet -->
    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- spring mvc的配置文件 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springMVC.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

访问 jsp 显示数据

Controller 中的 Model 携带数据跳转到 jsp ,作为视图,担当的角色是显示数据,借助 JSTL 的 c:forEach 标签遍历从 CategoryController.list() 传递过来的集合。

listCategory.jsp 部分代码

localhost/admin_category_list 访问效果

管理分类剩下部分就不展开了
完整的CategoryMapper.xml代码如下

<mapper namespace="com.how2java.tmall.mapper.CategoryMapper">
    <select id="list" resultType="Category">
        select * from category order by id desc
        <if test="start!=null and count!=null">
            limit #{start},#{count}
        </if>
    </select>

    <select id="total" resultType="int">
        select count(*) from category
    </select>

    <insert id="add"  keyProperty="id"  useGeneratedKeys="true" parameterType="Category" >
        insert into category ( name ) values (#{name})
    </insert>

    <delete id="delete">
        delete from category where id= #{id}
    </delete>
 
    <select id="get" resultType="Category">
        select * from category  where id= #{id}
    </select>
 
    <update id="update" parameterType="Category" >
        update category set name=#{name} where id=#{id}
    </update>
</mapper>

完整的CategoryMapper接口代码如下

public interface CategoryMapper {
     List<Category> list(Page page);
     int total();
     void add(Category category);
     void delete(int id);
     Category get(int id);
     void update(Category category);
}

完整的CategoryService接口代码如下

public interface CategoryService{
    int total();
    List<Category> list(Page page);
    void add(Category category);
    void delete(int id);
    Category get(int id);
     void update(Category category);
}

完整的CategoryServiceImpl实现类代码就不放着了,只是实现了每个方法,并在其中调用对应的 CategoryMapper 方法而已,如下:

public List<Category> list(Page page) { return categoryMapper.list(page); }

思路流程图

思路流程图

项目重构

分类管理中的 CategoryMapper.xml 使用很直接的 SQL 语句开发出来,这样的好处是简单易懂,便于理解。可是,随着本项目功能的展开和复杂度的提升,使用这种直接的SQL语句方式的开发效率较低,需要自己手动写每一个SQL语句,而且其维护起来也比较麻烦。
所以我们做进一步的改进,主要是在分页方式和逆向工程方面做了重构。

  1. 分页方式
    目前的分页方式是自己写分页对应的 limit SQL 语句,并且提供一个获取总数的 count(*) SQL。 不仅如此, mapper, service, service.impl 里都要提供两个方法:
    list(Page page);
    count();
    分类是这么做的,后续其他所有的实体类要做分页管理的时候都要这么做,所以为了提高开发效率,把目前的分页方式改为使用 pageHelper 分页插件来实现。

  2. 逆向工程
    目前分类管理中 Mybatis 中相关类都是自己手动编写的,包括:Category.java, CategoryMapper.java和CategoryMapper.xml。
    尤其是 CategoryMapper.xml 里面主要是SQL语句,可以预见在接下来的开发任务中,随着业务逻辑的越来越复杂,SQL 语句也会越来越复杂,进而导致开发速度降低,出错率增加,维护成本上升等问题。
    为了解决手动编写 SQL 语句效率低这个问题,我们对 Mybatis 部分的代码,使用逆向工程进行重构。
    所谓的逆向工程,就是在已经存在的数据库表结构基础上,通过工具,自动生成 Category.java, CategoryMapper.java 和 CategoryMapper.xml,想想就很美好是吧。


pageHelper 分页

因为使用插件可以获取总数信息和实现分页查询了,所以关于分页操作的部分配置和代码要做修改。

修改 CategoryMapper.xml
  1. 去掉 total SQL 语句
  2. 修改 list SQL 语句,去掉其中的 limit
<mapper namespace="com.how2java.tmall.mapper.CategoryMapper">
    <select id="list" resultType="Category">
        select * from category order by id desc
    </select>
 
    <insert id="add"  keyProperty="id"  useGeneratedKeys="true" parameterType="Category" >
        insert into category ( name ) values (#{name})
    </insert>

    <delete id="delete">
        delete from category where id= #{id}
    </delete>
 
    <select id="get" resultType="Category">
        select * from category  where id= #{id}
    </select>
 
    <update id="update" parameterType="Category" >
        update category set name=#{name} where id=#{id}
    </update>
</mapper>

使用 PageHelper 提供的方法进行分页查询

CategoryMapper 接口 /CategoryService 接口 / CategoryServiceImpl 类 进行如下操作:

  1. 去掉 total() 方法
  2. 去掉 list(Page page) 方法
  3. 新增 list() 方法

使用分页插件后的 CategoryController.list()方法

@Controller
@RequestMapping("")
public class CategoryController {
    @Autowired
    CategoryService categoryService;
   
    @RequestMapping("admin_category_list")
    public String list(Model model,Page page){
        PageHelper.offsetPage(page.getStart(),page.getCount());
        List<Category> cs= categoryService.list();
        int total = (int) new PageInfo<>(cs).getTotal();
        page.setTotal(total);
        model.addAttribute("cs", cs);
        model.addAttribute("page", page);
        return "admin/listCategory";
    }
}

在 applicationContext.xml 配置 pagehelper 插件

其实在上面显示的已经是配置过插件了,这里再提一下,就是在 SqlSessionFactoryBean 命名空间内设置一个 plugins 的属性。

<!--Mybatis的SessionFactory配置-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageInterceptor">
                    <property name="properties">
                        <value>
                        </value>
                    </property>
                </bean>
            </array>
</property>

Mybatis 逆向工程

MybatisGenerator 插件是 Mybatis 官方提供的,这个插件存在一个问题 ,即当第一次生成了CategoryMapper.xml 之后,再次运行会导致 CategoryMapper.xml 生成重复内容,而影响正常的运行。
为了解决这个问题,需要自己写一个小插件类 OverIsMergeablePlugin 。

OverIsMergeablePlugin

这是复制别人的,具体原理还没研究。

public class OverIsMergeablePlugin extends PluginAdapter {
    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }
    @Override
    public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) {
        try {
            Field field = sqlMap.getClass().getDeclaredField("isMergeable");
            field.setAccessible(true);
            field.setBoolean(sqlMap, false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }
}
generatorConfig.xml 指定生成策略

这里提供一部分代码,具体的在 github

<generatorConfiguration>
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!--避免生成重复代码的插件-->
        <plugin type="com.caozhihu.tmall.util.OverIsMergeablePlugin"/>
        <!--是否在代码中显示注释-->
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--数据库链接地址账号密码-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost/tmall_ssm"
                        userId="root" password="admin">
        </jdbcConnection>
        <!--该属性可以控制是否强制DECIMAL和NUMERIC类型的字段转换为Java类型的java.math.BigDecimal,默认值为false,一般不需要配置。-->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!--生成pojo类存放位置-->
        <javaModelGenerator targetPackage="com.caozhihu.tmall.pojo" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!--生成xml映射文件存放位置-->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!--生成mapper类存放位置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.caozhihu.tmall.mapper" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>

        <!--生成对应表及类名-->
        <table tableName="category" domainObjectName="Category" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="true"
               selectByExampleQueryId="false">
            <property name="my.isgen.usekeys" value="true"/>
            <property name="useActualColumnNames" value="true"/>
            <generatedKey column="id" sqlStatement="JDBC"/>
        </table>
MybatisGenerator 生成执行类

运行即生成 mapper,pojo,xml 文件,核心代码如下

List<String> warnnings = new ArrayList<>();
        boolean overwrite = true;
        InputStream is = MybatisGenerator.class.getClassLoader().getResource("generatorConfig.xml").openStream();//获取配置文件对应路径的输入流
        ConfigurationParser configurationParser = new ConfigurationParser(warnnings);
        Configuration configuration = configurationParser.parseConfiguration(is);
        is.close();

        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(configuration, callback, warnnings);
        myBatisGenerator.generate(null);

自动生成的 CategoryMapper.xml

1240

这是插件自动生成的 xml,与我们自己手动写的也差不了多少,主要区别在于提供了一个 id="Example_Where_Clause" 的 SQL,借助这个可以进行多条件查询。


自动生成的 pojo 类

1240

MybatisGenerator 会生成一个类叫做 XXXXExample 的。 它的作用是进行排序,条件查询的时候使用。
在分类管理里用到了排序,但是没有使用到其条件查询,在后续的属性管理里就会看到其条件查询的用法了。


自动生成的 mapper 接口

image.png

与手动编写的 CategoryMapper 对比,CategoryMapper 也是提供 CURD 一套,不过方法名发生了变化,比如:
delete() 叫做 deleteByPrimaryKey(),
update 叫做 updateByPrimaryKey(),
除此之外,还提供了一个 updateByPrimaryKeySelective() 方法,其作用是只更新,即只修改新插入的不为 null 的字段。(比如当前数据是 {name,age} ,插入新数据是 {newName,null},如果使用此方法,则插入之后数据变为 {newName,age} 而不是 {newName,null})

还有个改动是 list() 方法 ,变成了selectByExample(CategoryExample example);

修改 CategoryServiceImpl 实现类

因为 CategoryMapper 的方法名发生了变化,所以 CategoryServiceImpl 要做相应的调整。
值得一提的是list方法:

public List<Category> list() {
        CategoryExample example =new CategoryExample();
        example.setOrderByClause("id desc");
        return categoryMapper.selectByExample(example);
    }

按照这种写法,传递一个 example 对象,这个对象指定按照 id 倒排序来查询
我查看了 xml 里的映射, 在对应的查询语句 selectByExample 里面,
会判断 orderByClause 是否为空,如果不为空就追加 order by ${orderByClause}
这样如果设置了 orderByClause 的值为“id desc” ,执行的 sql 则会是 order by id desc

1240

然后,我们再根据数据库字段,一次性生成所有的 实体类,example 类,mapper 和 xml,如果需要定制,直接在生成的东西上修改就行了,真是舒服啊。


后台还有其他管理页面的,比如属性管理、产品管理等,由于篇幅原因,具体的请移步github-Tmall_SSM项目

前台页面展示

此处是 SSH 跑起来截的图,SSM 版本目前只做了后台,前台未做,敬请期待...
前台首页

产品页

本文所讲不足整个项目的 1/10 ,有兴趣的朋友请移步 github 项目的地址

参考

天猫SSM整站学习教程 里面除了本项目,还有 Java 基础,前端,Tomcat 及其他中间件等教程, 可以注册一个账户,能保存学习记录。

转载于:https://www.cnblogs.com/czwbig/p/9965562.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值