SSM整合实例 ---- 简单的后台

springMVC

javaWeb—SSM简单例子


这里参考的是B站的MM商城项目


昨天已经分享过MVC的简单操作,同时前天也简单分享了SSM整合的简单思路,做项目之前先将SSM的简单结构创建好之后,再进行code的编写

这里需要实现的MM商城后台管理系统的商品管理模块

商品管理的功能

  1. 作为的一个综合的项目,首先具有用户登录的功能,同时为了保证文件安全,密码会使用加密算法

  2. 分页展示商品,ajax翻页显示商品,首页末页的处理【本身的瑕疵】

  3. 增加商品,异步ajax商城文件,监听器处理商品类型

  4. 更新商品,图片上传后即时回显,商品类型回显【ajax异步上传】

  5. ajax删除商品,批量删除商品,多条件批量删除

  6. ajax多条件查询后更新和删除

  7. ajax多条件查询并分页【模糊查询】

  8. ajax多条件查询后删除和更新提留在当前页

模块的技术指标

  1. SSM框架的使用
  2. jsp的使用
  3. AJAX异步刷新

其他的官方的就不说了,只要会这三个部分就会可以了,前端还没有上框架,所以这里就还是用HTML+ CSS + Javascript;这里AJAX使用JQuery来实现;数据库使用的是Mysql;项目管理工具是maven

建立数据库表

admin 表

分析这个模块,作为后台管理系统,那么登录的都是后台的管理员administrator;这里就先简单弄一点,就一个用户名和密码即可;再来一个自然主键

USE cfengbase;
CREATE TABLE admin(
	admin_id INT PRIMARY KEY AUTO_INCREMENT,
    admin_name VARCHAR(25),
    admin_pwd  VARCHAR(200)
);

商品信息表 product_info和商品类型表product_type

商品的类型也需要创建的,毕竟商品的类型很多,包括的是type_id和type_name;主键有具体意义

CREATE TABLE product_type(
	type_id INT PRIMARY KEY, #不自增
    type_name VARCHAR(20)
);

这里也做简单一点,就来商品名称,商品介绍,定价、商品图片、商品数量【库存】自然主键

USE cfengbase;
CREATE TABLE product_info(
	pro_id INT PRIMARY KEY AUTO_INCREMENT, 
    pro_name VARCHAR(20),
    pro_content VARCHAR(200),  #商品的简介
    pro_price INT,
    pro_image VARCHAR(200),
    pro_count INT,
    type_id INT, #商品的类型,外键
    pro_date DATE, #商品上架的日期
    FOREIGN KEY(type_id) REFERENCES product_type(type_id)
);

创建项目基本结构

首先就是使用maven建立一个web项目,这里简单创建,接下来就是配置pom.xml;这里之前博主已经配置过模板了,两个部分: 首先就是加入依赖,第二就是加入资源插件,便于Dao下面的mapper文件的移动

修改pom.xml文件

关于依赖还是再说一次吧,从数据库访问层开始【springmvc依赖加入会自动加入context和web】

Mybatis: mybaits依赖,mysql-connecter依赖,整合SM的mybatis-spring依赖,外部插件pageHelper依赖,数据库连接池druid依赖

Spring : aop实现框架spring-aspects依赖,spring事务:spring-tx,spring-jdbc依赖,DI的@Resource依赖javax-annotation-api;

SpringMVC : spring-mvc依赖,jackson依赖jackson-core和jackson-databind;servlet和jsp的依赖(provided)

另外就是需要编写资源插件

  <build>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
      <!-- 还要配置上原来编译资源的路径-->
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.*</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
  </build>

编写springmvc的配置文件

这些配置文件都放在conf包下,mvc中需要完成的功能: 组件扫描器—>扫描controller包和controllerAdvice所在的handler包; annotation-driven:jackson的使用和静态资源的防止冲突;还有就是静态资源的扫描—> 使用resources:静态资源放在static下面; 注册拦截器:interceptors中就每一个interceptor中就拦截的url和拦截器类

   <context:component-scan base-package="Jning.controller"/>

    <!-- 除了上面的扫描Controller的,还需要一个扫描异常的,也就是ControllerAdvice -->
    <context:component-scan base-package="Jning.handler"/>

    <!-- 声明springmvc中的视图解析器,帮助开发人员设置视图文件的路径 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 和注解ResponseBodu配合使用,内部将对象转为json格式,还可以解析异常类的ExceptionHandler;其实就是表示表明要使用注解,spring事务也需要
          但是是不同的命名空间
     -->
    <mvc:annotation-driven/>

    <!-- 解决静态资源覆盖问题,注册defaultServletHandler -->
    <!--    <mvc:default-servlet-handler/>-->
    <!-- 这里是后台路径,所以加上/,会带上根路径 location表示的是目录位置; mapping代表的是访问的url地址 -->
    <mvc:resources mapping="/static/**" location="/static/"/>

    <!-- 声明拦截器,拦截器可以有多个 -->
    <mvc:interceptors>
        <!-- 声明一个拦截器 -->
        <mvc:interceptor>
            <!-- 指定拦截器的拦截的url得知,可以通配,**代表任意字符 -->
            <mvc:mapping path="/test/**"/>
            <!-- 声明对象; 其实就是和在web.xml中配置过滤器相同,要指明class和url -->
            <bean class="Jning.handler.MyInterceptorHandler"/>
        </mvc:interceptor>
    </mvc:interceptors>
声明文件上传核心对象CommonsMultipartResolver

项目中需要上传文件的时候,springmvc框架提供了这个组件来更方便完成这个功能

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

配置mybatis的主配置文件

这个配置文件需要完成的功能就是mapper的指定,typeAilas的制定,日志文件setttings制定,还有就是使用pageHelper加入插件plugin

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <package name="cfeng.entity"/>
    </typeAliases>

    <!-- 使用pageHelper,要加入插件 -->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
    <!-- 以后的环境都是用druid了 -->

    <mappers>
        <!--        <mapper resource="cfeng\dao\StudentDao.xml"/>  直接按照包导入就可-->
        <package name="cfeng\dao"/>
    </mappers>

配置wen.xml

这个文件主要完成的功能就是注册中央调度器,还有就是注册监听器–创建spring容器;还有就是注册过滤器进行字符集过滤

  <!-- 字符集过滤器 -->
  <filter>
    <description>字符集过滤器</description>
    <filter-name>encodingFilter</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>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

关于监听器和中央适配器就不再赘述了;都是使用的web的jar包

<servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:conf/dispatcherServletConfig.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <!-- 注册监听器 -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <!-- 指定监听器扫描的spring配置文件的位置 -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:conf/applicationContext.xml</param-value>
  </context-param>

只是额外提一点,监听器默认是在WEB-INF中寻找spring的主配置文件,所以需要使用context-param来进行路径的配置,contextConfigLocation

配置数据库文件和spring主配置文件

    <!-- spring 整合mybatis : 声明数据源对象,sqlSessionFactory对象,Dao对象 -->
    <!-- 数据源对象,声明init和destroy的method代表的是创建和销毁连接池的方法,使用druid,其中要使用数据库的配置文件-->
    <context:property-placeholder location="classpath:conf/db.properties"/>

    <bean id="myDatasource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="password" value="${jdbc.password}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="maxActive" value="20"/>
    </bean>
    <!-- mybatis-spring为了整合提供的sqlSessionFactory对象,这里类型是加上Bean,创建sqlSession需要配置文件和datasource
         因为之前是所有的都在mybatis主配置文件中,但是现在将数据库的信息,也就是environment交给了druid;对象的属性注入
    -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="myDatasource"/>
        <property name="configLocation" value="classpath:conf/mybatis.xml"/>
    </bean>

    <!-- 整合mybatis还需要的就是dao对象,这里使用的是扫描器对象,扫描mapper文件的,所以名称就是mapperScanner,
          使用package为包扫描包下面所有的mapper文件,注意这是扫描配置文件,所以是configurer,不是bean
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value="Jning.dao"/>
    </bean>

    <!-- 接下来就是声明组件扫描器,扫描service包下的对象注解,创建service对象 -->
    <context:component-scan base-package="Jning.service"/>

    <!-- 完成spring的事务, 需要有事务的管理器,mybatis的是datasource,如果使用注解的方式,要用driven,如果使用aop的AspectJ也行 -->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 某个特定数据库的事务 -->
        <property name="dataSource" ref="myDatasource"/>
    </bean>

    <!-- 如果使用注解就加入annotation-driven即可,指定扫描器;如果使用AspectJ,需要使用tx的advice指明通知method;可以通配
          同时配置aop中的config,用来引入通知并且配置切入点表达式;因为上面的通知只有方法名,没有具体指明位置,这样advice才有效
     -->
    <!-- id自定义,表示配置内容-->
    <tx:advice id="myadvise" transaction-manager="dataSourceTransactionManager">
        <tx:attributes> <!--表示要配置的事务的属性 method是作用的方法,切入点 -->
            <tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT"
                       rollback-for="java.lang.NullPointerException"/>
        </tx:attributes>
    </tx:advice>

    <!-- 配置aop -->
    <aop:config>
        <!-- 配置切入点表达式,指出哪些类需要应用事务  id 切入点表达式的名称   切入点表达式:指出切入点表达式-->
        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>

        <!-- 配置增强器: 关联pointcut和上面的advisce -->
        <aop:advisor advice-ref="myadvise" pointcut-ref="servicePt"/>
    </aop:config>

</beans>

主要是mybatis整合的3个对象; 引入配置文件需要property-placeholder;同时为了方便实现AOP和IOC,要组件扫描器扫描

这里如果要进行aop,那么为了注解实现aop,需要使用自动aspectJ的自动代理的配置 aspectj-autoproxy

配置文件有了,接下来就是包的结果,首先就是基本的controller,service,dao,还有如果有拦截器或者异常的统一处理,handler包;还有就是对密码进行加密的,这些输入工具,就是util包

同时为了进行页面安全,在WEB-INF下面建立一个view包保存安全的视图;同时在web根目录下创建static用来存储静态资源,使用resources解析,从而减少对于Tomcat的依赖

本来需要自己动手写前端页面的,这里就不自己写了,直接下载了资源,就当一次纯粹的后端人
在这里插入图片描述

Mybatis逆向工程 【少用】

Mybatis逆向工程是一个可以快速根据数据库表帮助生成pojo实体类和mapper接口以及mapper接口和映射文件【pojo包和mapper包】; 相当于之前的entity和dao; 这是一个插件,需要下载; 并且只是支持单表的操作,关联查询需要自己写。emmm……越来越少……

所以说,还是自己写好,但是这里只是想熟悉以下,就用一次吧,以后就不用了【有点死板,这样子】

加入依赖mybatis-generator

这是mybatis的一个项目,需要下载,那么这里就加入依赖

<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.4.0</version>
</dependency>

创建配置文件generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!--指定了驱动jar包的位置,这个是针对下载Jar包的方式,因为用了maven所以这个就用不上了-->
    <!-- <classPathEntry location="D:/mvn_repository_new/mysql/mysql-connector-java/5.1.45/mysql-connector-java-5.1.45.jar"/>-->

    <context id="DB2Tables" targetRuntime="MyBatis3">
        <commentGenerator>
            <property name="suppressDate" value="false"/>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <!--数据库链接URL,用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/cfengbase?servertimezone=GMT%2B8"
                        userId="cfeng"
                        password="*********">
        </jdbcConnection>
        <!--指定生成entity实体类的具体位置-->
        <javaModelGenerator targetPackage="cfeng.entity" targetProject="./src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!--指定生成mybatis映射xml文件的包名和位置-->
        <sqlMapGenerator targetPackage="cfeng.dao" targetProject="./src/main/java">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!--指定生成mapper接口的具体位置-->
        <javaClientGenerator targetPackage="cfeng.dao" targetProject="./src/main/java" type="XMLMAPPER">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>

        <!--要生成entity/mapper的表名及自定义的DO名-->
        <!--<table tableName="users" domainObjectName="User"/>-->

        <!--<table tableName="product_brand" domainObjectName="ProductBrand" />-->

        <!--mybatis generator代码生成器在默认的情况下会生成对于表实体类的一个Examle类, 可以更改生成器的配置可避免生成Examle类,
        enableCountByExample,enableUpdateByExample,enableDeleteByExample,enableSelectByExample等配置为false后, 就不会生成生成Examle类了 -->

        <table tableName="admin" enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"/>

        <table tableName="product_info" enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"/>

        <table tableName="product_type" enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"/>

        <!--或者直接写表名-->
        <!--<table schema="" tableName="product_type"></table>-->

    </context>
</generatorConfiguration>

加入插件pom中

<build>
    <!-- 逆向工程的插件 -->
    <plugins>
      <!-- mybatis逆向工程插件 -->
      <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.4.0</version>
        <dependencies>

          <!-- 逆向工程的核心依赖 -->
          <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.4.0</version>
          </dependency>

          <!-- 数据库连接池 -->
          <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
          </dependency>

          <!-- MySQL驱动 -->
          <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
      </dependencies>
        <configuration>
          <!--配置文件的路径-->
          <configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
          <overwrite>true</overwrite>
        </configuration>
      </plugin>
    </plugins>
  一定要把依赖的jar包放在插件中才能起作用 --- 点击maven的生成插件就可以了

之后点击maven中的插件就可以自动生成了;这样对于快速搭建一个测试项目还是很有好处的

[INFO] --------------------------< cfeng:MMmallssm >---------------------------
[INFO] Building MMmallssm Maven Webapp 1.0-SNAPSHOT
[INFO] --------------------------------[ war ]---------------------------------
[INFO] 
[INFO] --- mybatis-generator-maven-plugin:1.4.0:generate (default-cli) @ MMmallssm ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.284 s
[INFO] Finished at: 2022-01-18T22:34:37+08:00
[INFO] ------------------------------------------------------------------------

这个只是因为这个demo简单,不需要复杂的操作,所以就用这个插件了……使用就按照上面三步就可以了;不需要单独记忆

package cfeng.dao;

import cfeng.entity.Admin;

public interface AdminMapper {
    int deleteByPrimaryKey(Integer adminId);

    int insert(Admin record);

    int insertSelective(Admin record);

    Admin selectByPrimaryKey(Integer adminId);

    int updateByPrimaryKeySelective(Admin record);

    int updateByPrimaryKey(Admin record);
}

缺陷很明显: 生成的代码都是固定的并且是单表的操作,如果开发人员自己要写sql语句,就不要和这个放在一起,因为放在一起重新生成的时候就没有了😂

我觉得其实就逆向工程生成实体类就可以了,实体类的效果还行,并且实体类简单固定

HD加密算法

在计算机网络部分会详细看这个算法,这里就简单看看项目中如何使用; 学习学习即可

package cfeng.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Util {
    /**
     * 1.MD5(message-digest algorithm 5)信息摘要算法,
     *   它的长度一般是32位的16进制数字符串(如81dc9bdb52d04dc20036dbd8313ed055)
     * 2.由于系统密码明文存储容易被黑客盗取
     * 3.应用:注册时,将密码进行md5加密,存到数据库中,防止可以看到数据库数据的人恶意篡改。
     *       登录时,将密码进行md5加密,与存储在数据库中加密过的密码进行比对
     * 4.md5不可逆,即没有对应的算法,从产生的md5值逆向得到原始数据。
     *   但是可以使用暴力破解,这里的破解并非把摘要还原成原始数据,如暴力枚举法。
     *
     */
    public final static String getMD5(String str){
        try {
            MessageDigest md = MessageDigest.getInstance("SHA");//创建具有指定算法名称的摘要
            md.update(str.getBytes());                    //使用指定的字节数组更新摘要
            byte mdBytes[] = md.digest();                 //进行哈希计算并返回一个字节数组

            String hash = "";
            for(int i= 0;i<mdBytes.length;i++){           //循环字节数组
                int temp;
                if(mdBytes[i]<0)                          //如果有小于0的字节,则转换为正数
                    temp =256+mdBytes[i];
                else
                    temp=mdBytes[i];
                if(temp<16)
                    hash+= "0";
                hash+=Integer.toString(temp,16);         //将字节转换为16进制后,转换为字符串
            }
            return hash;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }
}

MD5算法不支持逆向得到原来的数据,所以这里进行验证就只能将用户的密码加密和存储的加密的密码进行对比;这里直接使用java提供的security中的MessageDigest; 这里md.update()将以原来的字符串进行修改;所以关键的步骤就是

MessageDigest md = MessageDigest.getInstance("SHA");
md.update(str.getBytes());
byte mdBytes[] = md.digest();

后面的两步可以直接使用digest的有参方法
byte mdBytes[] = md.digest(str.getBytes());  //得到的加密字节码都是相同的

这里就是调用java自带的security包的MessageDigest获得一个加密算法,然后利用其update对原理啊的byte进行修改;修改后得到一个全新的bytes;这里是不可逆的;后面再进行修改

因为得到的

功能开发

管理员登录

开发业务逻辑层,实现登录的判断;

//业务层是依赖dao的,所以有属性,需要自动注入
package cfeng.service.impl;

import cfeng.dao.AdminDao;
import cfeng.entity.Admin;
import cfeng.service.AdminService;
import cfeng.util.MD5Util;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service
public class AdminServiceImpl implements AdminService {

    @Resource
    private AdminDao adminDao;

    @Override
    public Admin login(String userName,String passward) {
        //根据用户名查询数据库表【这里应该设置为UNIQUE],大意了
        List<Admin> adminList = adminDao.selectByName(userName);
        //判断是否有用户,这里其实不需要用List
        if(adminList.size() > 0) {
            //得到用户
            Admin admin = adminList.get(0);
            //将用户登录密码和加密的密码比对
            String logpwd = MD5Util.getMD5(passward);
            if(admin.getAdminPwd().equals(logpwd)) {
                //登录密码正确,可以登录
                return admin;
            }
        }
        return null; //登录失败返回空
    }
}

编写控制器AdminAction,完成登录处理

//姐买你控制层依赖service,所以再将service自动注入
package cfeng.service.impl;

import cfeng.dao.AdminDao;
import cfeng.entity.Admin;
import cfeng.service.AdminService;
import cfeng.util.MD5Util;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service
public class AdminServiceImpl implements AdminService {

    @Resource
    private AdminDao adminDao;

    @Override
    public Admin login(String userName,String passward) {
        //根据用户名查询数据库表【这里应该设置为UNIQUE],大意了
        List<Admin> adminList = adminDao.selectByName(userName);
        //判断是否有用户,这里其实不需要用List
        if(adminList.size() > 0) {
            //得到用户
            Admin admin = adminList.get(0);
            //将用户登录密码和加密的密码比对
            String logpwd = MD5Util.getMD5(passward);
            if(admin.getAdminPwd().equals(logpwd)) {
                //登录密码正确,可以登录
                return admin;
            }
        }
        return null; //登录失败返回空
    }
}

这里验证就很简单,没有使用令牌机制,本来应该getSesion保存的,这里就先简单一点,之后可以加上拦截器

登录的验证

【这里其实可以使用ajax,这里使用的是全局刷新,最开始errormsg是空,后面信息填入】

使用IDEA自动部署要仔细,博主之前将项目部署错了,导致发生错误

商品分页

这里就是一个有点忘记的知识点: 超链接a的属性target可以制定窗口跳转的位置,默认情况下就是打开的新的页面来显示;target可以指定为当前页面的子窗口

<a href="${pageContext.request.contextPath}/prod/getAll.action" target="myright" >

然后使用iframe指定一个子窗口

<iframe frameborder="0" scrolling="no" name="myright" width="1235px" height="700px" ></iframe>

分页要实现的就是

  1. 当前页面是几条数据
  2. 页码的导航页面,一共是多少页,当前多少页

分页要依赖的就是pageHelper插件,这个插件需要再mybatis的主配置文件中进行注册

之后就可以使用其中的方法,pageSize就是页面显示几条,pageNum就是页数,size表示当前页的条数,最后一页可能不够

PageHelper.startPage(1,3);
List<Student> list = dao.selectStudents();
list.forEach(System.out::println);

这是之前没有集成spring的时候使用插件的过程,就是先用startPage来进行拦截之后再调用dao的方法进行查询

除了PageHelper的方法,还有PageInfo工具类封装的就是当前页的信息

public class PageInfo<T> extends PageSerializable<T> {
    public static final int DEFAULT_NAVIGATE_PAGES = 8;
    public static final PageInfo EMPTY = new PageInfo(Collections.emptyList(), 0);
    /**
     * 当前页
     */
    private int pageNum;
    /**
     * 每页的数量
     */
    private              int      pageSize;
    /**
     * 当前页的数量
     */
    private              int      size;
…………………… 还有查询出来的List集合也封装在里面; 所以分页操作直接返回这个结果最好

所以使用插件来进行分页操作的开发就是主要使用这个两个类,因为是再dao执行之前,所以这里就在业务逻辑层进行编写即可

@Service //一定要创建对象
public class ProductInfoServiceImpl implements ProductInfoService {

    @Resource
    private ProductInfoDao productInfoDao;

    @Override
    public PageInfo queryAllSplit(int pageNum, int pageSize) {
        //首先调用pageHelper的插件进行拦截处理
        PageHelper.startPage(pageNum,pageSize);
        //调用查询操作,这里应该是降序的操作,因为最新的在最前面,这里查询会自动拦截加上LIMIT
        List<ProductInfo> list = productInfoDao.selectAll();
        //将查询出的结果封装到pageInfo中
        PageInfo<ProductInfo> pageInfo = new PageInfo<>(list);
        return pageInfo;
    }
}
SQL中直接放入DESC报错

这是因为pageHelper是一种拼接的方式,在查询原本的语句之前,还会统计次数;也就是SELECT COUNT(0) ;如果直接加入DESC,就会报错

SELECT COUNT(0) FROM product_info DESC; 这显然是错误的,因为这是统计数量的,是不能加DESC的
    
ou have an error in your SQL syntax; check the manual that corresponds to your MySQL server

这里的解决办法最简单的就是使用pageHelper提供的功能,startPage还有一个重载的方法;有3个变量,第三个变量就是排序的方式; 还有就是再调用一次orderBy方法; 中间不能有逗号,这里也是直接拼接

PageHelper.startPage(pageNum,pageSize,"pro_id DESC");//指明排序的方式为降序,注意这也是直接拼接上,所以中间不要有逗号

这里直接使用构造方法就可以将查询出的list结果放到pageInfo中;直接将这个结果返回即可

这里action中对于这个ajax请求,不需要页面,只是需要数据而已,所以直接将页面的数据pageInfo放到Session域中; 注意是Session会话作用域,而不是请求作用域,因为Http无状态,将数据放到域中,这样再次访问product.jsp的时候,就可以从会话域中取出info的数据

不需要使用页面,使用页面就是转发定位到新的页面,这里只需要重新加载,所以使用JQuery的load方法,就可以获得数据

//action

    //注意这里是ajax请求,不需要返回页面
    @RequestMapping(value = "/ajaxsplit.action")
    public void getAllSplit(int page, HttpSession session) { //ajax上传的数据有page
        //这里直接将返回的info对象放入会话作用域而不是 请求作用域
        session.setAttribute("info",productInfoService.queryAllSplit(page,PAGE_SIZE));
        //相当于是用户的在服务器的柜子,所以当再次访问product的时候可以获得数据 【所以这里就直接使用EL的简化版,因为第一次是请求作用域】
    }
}

分页底部显示导航栏,并且显示页面的前端代码

这部分内容是前端必须会的,可以点击向前向后的页码实现翻页,也可以直接点击按钮实现翻页;这里就是使用的ajax异步刷新

pageInfo中封装的信息有很多很多,除了基本的查询的符合当前页的数据之外,还有size、prePage等多个属性帮助实现操作

<div id="bottom">
                        <div>
                            <nav aria-label="..." style="text-align:center;">
                                <ul class="pagination">
                                    <li>
                                            <%--                                        <a href="${pageContext.request.contextPath}/prod/split.action?page=${info.prePage}" aria-label="Previous">--%>
                                        <a href="javascript:ajaxsplit(${info.prePage})" aria-label="Previous">

                                            <span aria-hidden="true">«</span></a>
                                    </li>
                                    <c:forEach begin="1" end="${info.pages}" var="i">
                                        <c:if test="${info.pageNum==i}">
                                            <li>
                                                    <%--                                                <a href="${pageContext.request.contextPath}/prod/split.action?page=${i}" style="background-color: grey">${i}</a>--%>
                                                <a href="javascript:ajaxsplit(${i})"
                                                   style="background-color: grey">${i}</a>
                                            </li>
                                        </c:if>
                                        <c:if test="${info.pageNum!=i}">
                                            <li>
                                                    <%--                                                <a href="${pageContext.request.contextPath}/prod/split.action?page=${i}">${i}</a>--%>
                                                <a href="javascript:ajaxsplit(${i})">${i}</a>
                                            </li>
                                        </c:if>
                                    </c:forEach>
                                    <li>
                                        <%--  <a href="${pageContext.request.contextPath}/prod/split.action?page=1" aria-label="Next">--%>
                                        <a href="javascript:ajaxsplit(${info.nextPage})" aria-label="Next">
                                            <span aria-hidden="true">»</span></a>
                                    </li>
                                    <li style=" margin-left:150px;color: #0e90d2;height: 35px; line-height: 35px;">总共&nbsp;&nbsp;&nbsp;<font
                                            style="color:orange;">${info.pages}</font>&nbsp;&nbsp;&nbsp;页&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                                        <c:if test="${info.pageNum!=0}">
                                            当前&nbsp;&nbsp;&nbsp;<font
                                            style="color:orange;">${info.pageNum}</font>&nbsp;&nbsp;&nbsp;页&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                                        </c:if>
                                        <c:if test="${info.pageNum==0}">
                                            当前&nbsp;&nbsp;&nbsp;<font
                                            style="color:orange;">1</font>&nbsp;&nbsp;&nbsp;页&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                                        </c:if>
                                    </li>
                                </ul>
                            </nav>
                        </div>
                    </div>

prePage得到前一页的页码;nextPage得到后页的页码,这些都是放到ajax中;pages就是总的页数,然后进行遍历,每一个页码就是一个href,并且作为一个li

<c:forEach begin="1" end="${info.pages}" var="i">
            <c:if test="${info.pageNum==i}">
                         <li>
                          <%--                                                <a href="${pageContext.request.contextPath}/prod/split.action?page=${i}" style="background-color: grey">${i}</a>--%>
                   <a href="javascript:ajaxsplit(${i})"
                    style="background-color: grey">${i}</a>
            </li>
 </c:if> ----- 判断是否为当前页,为当前页,就会加上样式grey

所以说jsp还是有很大缺陷,这可读性太差了;这里就是取出每一页的页码,并且作为超链接,加入样式得到

这里的页码跳转不是简单的超链接,而是使用的异步ajax,这里需要做处理

<a href="javascript:ajaxsplit(${i})  --- 不跳转,但是执行ajaxSplipt方法
    
<a href="javascript:void(0)" --- 不跳转,啥都不干
JQuery的load方法

使用的方法就是将指定的内容加载到一个容器

$(selector).load(url,data,funtion)

这里表明,当点击之后执行js的方法,这样就不用定义按钮,超链接的样式好看一点

<script type="text/javascript">
    function ajaxsplit(page) {
        //异步ajax分页请求
      $.ajax({
        url:"${pageContext.request.contextPath}/prod/ajaxsplit.action",
            data:{"page":page},
            type:"post",
            success:function () {
                //重新加载分页显示的组件table
                //location.href---->http://localhost:8080/admin/login.action
                $("#table").load("http://localhost:8080/MMmallssm/admin/login.action #table");//刷新当前页面的table的div
            }  //因为这里所有的数据都是再table这个div位置显示,那么就只需要局部属性以下页面即可
        })
    };
</script>

这里介绍几个比较典型的问题

前台报404,但是访问地址没错; 没加@ResponseBody

@RestController : 都是直接返回响应,不会跳转视图的时候使用

如果该Controller类下所有的接口都是返回application/json格式数据的接口,则可以直接将类上的@Controller注解替换成 @RestController 注解。@RestController注解是一个组合注解是由@Controller注解和@ResponseBody注解组成

前台报错404,当然就是先检查是否路径写错,这里有一个很棒的调试方法;就直接在Firefox中点击F12,进入网络,可以将404的发送的请求给编辑重新发送一次;

博主仔细检查,发现前台没有错误,后台最开始的代码

//@ResponseBody  
@RequestMapping("/ajaxsplit.action")
    public void ajaxsplit(Integer page, HttpSession session) {
        PageInfo info = pservice.queryAllSplit(page, PAGE_SIZE);
        session.setAttribute("info",info);
        return ;
    }

这里设计的是分页查询,这里不需要返回任何的数据和页面,只是将数据放到用户的session中,ajax再次将请求发送给product.jsp从session中取出数据;所以最开始就没有写body

@ResponseBody注解 : 作用就是将Controller返回的对象通过适当的HttpMessageConverter转换为特定格式,写入到响应体,如果不加注解,mvc会默认返回一个视图; 这个时候将当前的url路径做一些处理【在当前的路径的上一级路径追加路径,各种解析— 博主的这个路径最开始被使用视图解析器解析,之后又追加一个模块名,总之各种奇怪的路径,然后找不到视图,就报错】

所以这里给出的建议就是 : 处理器方法要么返回视图,要么就要数据入响应体返回; 也就是要一个正常的响应给用户; 所以如果没有视图跳转的处理器方法都加上@ResponseBody注解,即便不需要返回任何的数据

这和之前的原生的servlet相同,之前要么通过请求转发重定向让其他的页面响应;要么就是直接通过response的writer来直接响应

整合Mybatis出现org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)

这里的意思就是mybaits没有找到对应的mapper映射文件,整合要求mapper文件和接口名必须一致,如果不一致就会报错; 所以不要随便使用逆向工程,下次就不用了

注意改名之后使用maven重新编译,之前的classes下面的文件给去掉

一定要注意这个ResponseBody注解,今天被默认的视图解析搞麻了,还以为是IDEA的问题😂

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值