前言
该项目主要出自【米米商城】SSM框架项目实战
以下为学习笔记以及实战中的问题bug
该项目的完整代码可通过如下进行下载:
米米商城项目github
或者
米米商城项目含前后端(ssm+html+js+ajax+jquery).rar
基于该项目的框架以及文章
目前所需要的技术:
基础知识:
服务端:
数据库:
web服务器:Tomcat详细配置(全)
项目管理:
前端:
开发工具:idea
依赖文件信息:
配置文件的讲解可看我这些文章
congtroller调用service,service调用mapper
1. 功能简介+项目展示
项目功能:
- 密码加密、登录处理
- 增加商品、异步ajax上传文件、监听器处理商品类型
- 分页显示商品、ajax翻页显示商品、首页末页处理
- 更新商品、图片上传后即使回显、商品类型回显
- ajax删除商品、批量删除商品、多条件批量删除
- ajax多条件查询、多条件查询并分页、多条件查询范爷
- ajax多条件查询后更新、多条件查询后删除、多条件查询后批量删除
- ajax多条件查询后删除和更新停留在当前页处理
技术指标:
- spring中的ioc和aop核心思想完成对象的创建和事务的添加
- springmvc框架接收客户端的请求以及事务的自动注入
- mybatis框架 大大完成数据访问层的优化
- jsp的标签库、EL表达式实现循环等
- ajax异步刷新技术用来分页,文件上传等
- 熟悉项目的开发流程以及对接流程等
具体页面的展示如下
实现登录注册的功能
登陆成功后的页面显示为
实现分页显示商品
增删改查商品(比如批量删除,多功能多条件的查询)
通过增加商品的同时还可以回显上传的照片
(更新商品界面的时候也会回显所有的信息)
2. 数据库表
查看项目白皮书以及文档需求等
先看看需要什么数据库表格
此处有登陆界面需要账号密码
添加一个管理员数据库表AUTO_INCREMENT
自增1PRIMARY KEY
主键
CREATE TABLE admin(
a_id INT AUTO_INCREMENT PRIMARY KEY,
a_name VARCHAR(20),
a_pass VARCHAR(20)
);
INSERT INTO admin(a_id,a_name,a_pass) VALUES(1,'admin','000000');
登陆成功后,有商品栏,需要设置商品信息以及添加商品
create table product_info
(
p_id int auto_increment primary key,
p_name varchar(20),
p_content varchar(200), ##############33商品规格/简介
p_price int, ###############价格
p_image varchar(200), #############图片
p_number int, ########数量
type_id int,
p_date date,
FOREIGN KEY(type_id) REFERENCES product_type(type_id)
);
添加商品信息
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米Note2','双曲面 黑色 6GB内存 64GB闪存',2899,'xmNote2.jpg',500,1,'2018-01-04');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('红米Note5A','5.5英寸 粉色 2GB内存 16GB闪存',699,'hmNote5A.jpg',500,1,'2018-01-05');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('红米Note4X','5.5英寸 绿色 4GB内存 64GB闪存',1299,'hmNote4X.jpg',500,1,'2018-01-06');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('红米4','5英寸 金色 3GB内存 32GB闪存',999,'hm4.jpg',500,1,'2018-01-07');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('红米4X','5英寸 黑色 3GB内存 32GB闪存',899,'hm4X.jpg',500,1,'2018-01-08');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米平板3','7.9英寸 金色 4GB内存 64GB闪存',1499,'xmPad3.jpg',500,2,'2018-01-09');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米Air12','12.5英寸 银色 4GB内存 128GB闪存',3599,'xmAir12.jpg',500,2,'2018-01-18');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米Air13','13.3英寸 银色 8GB内存 256GB闪存',4999,'xmAir13.jpg',500,2,'2018-01-17');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米Pro','15.6英寸 灰色 16GB内存 256GB闪存',6999,'xmPro.jpg',500,2,'2018-01-16');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米电视4','49英寸 原装LG屏 3840×2160 真4K',3299,'xmTV4-49.jpg',500,3,'2018-01-15');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米电视4','55英寸 原装三星屏 3840×2160 真4K',3999,'xmTV4-55.jpg',500,3,'2018-01-13');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米电视4','65英寸 原装三星屏 3840×2160 真4K',8999,'xmTV4-65.jpg',500,3,'2018-01-22');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米电视4A','43英寸 FHD全高清屏 1920*1080',1999,'xmTV4A-43.jpg',500,3,'2018-01-11');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米电视4A','49英寸 FHD全高清屏 1920*1080',2299,'xmTV4A-49.jpg',500,3,'2018-01-21');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米MIX2','全陶瓷 黑色 8GB内存 128GB闪存',4699,'xmMIX2.jpg',500,1,'2018-04-01');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米Note3','全网通 蓝色 6GB内存 64GB闪存',2499,'xmNote3.jpg',500,1,'2018-03-01');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米6','玻璃金属 白色 6GB内存 128GB闪存',2899,'xm6.jpg',500,1,'2018-02-01');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米MAX2','全金属 金色 4GB内存 64GB闪存',1599,'xmMAX2.jpg',500,1,'2018-01-02');
insert into product_info(p_name,p_content,p_price,p_image,p_number,type_id,p_date) values('小米5X','全金属 金色 4GB内存 64GB闪存',1499,'xm5X.jpg',500,1,'2018-01-03');
商品还需要分类型以及添加商品类型名称
##########################商品类型表
CREATE TABLE product_type
(
type_id int auto_increment PRIMARY KEY,
type_name varchar(20)
);
####################添加数据
insert into product_type(type_name) values('手机');
insert into product_type(type_name) values('电脑');
insert into product_type(type_name) values('电视');
3. SSM框架
具体步骤为:
- 新建maven工程修改目录,修改pom.xml文件
- 添加ssm框架所有依赖
- 拷贝jdbc,properties到resources目录下
- 新建applicationContext_dao.xml文件,进行数据访问层的配置
- 新建applicationContext_service.xmlwenjian,进行业务逻辑层的配置
- 新建springmvc.xml文件,配置springmvc框架
- 新建SqlMapConfig.xml文件,进行分页插件的配置
- 使用逆向工程生成pojo和mapper的文件
- 开发业务逻辑层,实现登录等多个功能
- 开发控制器AdminAction,完成登录处理
- 发送登录请求,验证登录
3.1 新建项目
通过idea软件编辑项目
新建项目选择maven,框架选择webapp
在版本中选择version为1.0,因为1.0-SNAPSHOT为快照版
这些默认可以自已的仓库设置
新建之后的目录大致为
3.2 目录改造
==改造目录==
需要添加一些目录结构,变成如下
==改造web.xml文件==
由于使用的框架骨骼中的web.xml版本太低,无法使用EL表达式等其他
删除
需要更新更高的版本则需要将其删除,在module中添加新的web.xml
但web.xml添加完之后不显示,则添加web1.xml
之后在删除1变为
==改造index.jsp文件==
将原本这个jsp文件删除
在webapp中重新添加index.jsp
3.3 pom.xml文件
关于pom.xml文件的具体说明可看前言中的说的博文
删除原先的依赖信息 并在该处添加以下信息
在以后的开发中也可以用到这些,万能模板
<!-- 集中定义依赖版本号 -->
<properties>
<junit.version>4.12</junit.version>
<spring.version>5.2.5.RELEASE</spring.version>
<mybatis.version>3.5.1</mybatis.version>
<mybatis.spring.version>1.3.1</mybatis.spring.version>
<mybatis.paginator.version>1.2.15</mybatis.paginator.version>
<mysql.version>8.0.22</mysql.version>
<slf4j.version>1.6.4</slf4j.version>
<druid.version>1.1.12</druid.version>
<pagehelper.version>5.1.2</pagehelper.version>
<jstl.version>1.2</jstl.version>
<servlet-api.version>3.0.1</servlet-api.version>
<jsp-api.version>2.0</jsp-api.version>
<jackson.version>2.9.6</jackson.version>
</properties>
<dependencies>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<dependency>
<groupId>com.github.miemiedev</groupId>
<artifactId>mybatis-paginator</artifactId>
<version>${mybatis.paginator.version}</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
<!-- MySql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- JSP相关 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<scope>provided</scope>
<version>${jsp-api.version}</version>
</dependency>
<!-- Jackson Json处理工具包 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- 文件异步上传 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
<!-- 插件配置 -->
<!-- 每个plugins就是一个插件 -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
<!--识别所有的配置文件-->
<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>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
可看到在文件目录中有jar包形成
如果没有形成可通过刷新
3.4 jdbc.properties
通过引入数据库位置
具体代码参数有四行
可通过自行修改useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
不可修改,公共密钥,安全性的访问以及设置时区的由来
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3308/ssm?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
jdbc.username=root
jdbc.password=123456
3.5 SqlMapConfig.xml
该配置信息主要为mybatis
==万能具体的引用信息为==
<?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>
</configuration>
主要的配置信息都被引入spring中,在该配置中只需要加入我们想要的分页信息
该插件信息通过jar包的page
<configuration>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
</plugins>
</configuration>
3.6 applicationContext-dao.xml
如果依赖配置信息没有出来,会找不到spring config的文件
该配置信息主要为spring的配置信息
==万能具体的配置信息==
<?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"
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">
<!--配置属性文件-->
<!--配置数据源-->
<!--配置mybatis工厂-->
<!--配置mapper所在的包-->
</beans>
配置信息的解读
具体该项目的配置信息如下
==读取jdbc的配置文件信息==
<context:property-placeholder location="classpath:jdbc配置文件"></context:property-placeholder>
==创建数据源==
配置id主要给工厂类使用,class为数据源德鲁伊
相关参数的设定是 <property name="jdbc的name" value="${jdbc.jdbc的name}"></property>
,如果没有jdbc.
只是该电脑下中登录用户name的value值
==配置工厂类==
bean标签不需要id,因为类对象进行配置而已,
- 需要配置数据源,因为要和数据打交道,需要使用ref而不是value,因为要引用进来使用
<property name="dataSource" ref="dataSource"></property>
- 配置mybatis核心配置文件,因为有些内容是在mybatis中配置
<property name="configLocation" value="classpath:mybatis配置文件"></property>
- 配置实体类
<property name="typeAliasesPackage" value="实体类的全限定路径"></property>
- 配置mapper文件的扫描器
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="mapper文件的全限定路径"></property>
</bean>
具体完整的配置信息
<?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"
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">
<!--配置属性文件-->
<!--在资源配置文件中的根目录进行查找-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置mybatis工厂-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="dataSource"></property>
<!--配置mybatis的核心配置文件-->
<property name="configLocation" value="classpath:SqlMapConfig.xml"></property>
<!--配置pojo-->
<property name="typeAliasesPackage" value="com.bjpowernode.pojo"></property>
</bean>
<!--配置mapper所在的包-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.bjpowernode.mapper"></property>
</bean>
</beans>
3.7 applicationContext-service.xml
spring配置拆分主要是因为项目够大,该项目按照分层来拆
该配置文件主要为spring的业务逻辑开发
==具体参数说明:==
- 设置业务逻辑层的包扫描器,目的是在指定的路径下,使用@Service注解的类,Spring负责创建对象,并添加依赖==
设置包扫描器<context:component-scan base-package="service包全限定名称"></context:component-scan>
- mybatis中处理事务管理器
DataSourceTransactionManager类
,必须要为当前的对象设置数据源,用property中的dataSource属性注入 - 因为spring配置分为两个软件,如果字体变红可以在配置信息中书写
<import resource="classpath: 另外一个配置文件全限定名称"></import>
,但不建议这种操作,会发生循环注入的操作。最后初始化注入的时候都会注入属性,不会对执行造成影响 - 切面tx:cache缓存,tx是事务切面。查询的时候不能更改,所以是
read-only="true"
,但凡是增删改一定要支持事务的执行propagation="REQUIRED"
,保证增删改能够被顺利执行。如果name都没有在事务中,只需要让其他事务支持就可以<tx:method name="*" propagation="SUPPORTS"/>
。这些方法主要在mapper接口或者业务访问层的方法,或者是controller访问层,所以需要配置个切点 - 切入点的配置,切入点的表达式
execution()
指明哪些包哪些类哪些方法需要应用这些管理,刚开始*
表示任意返回值类型,指定这个包,所有的类.*
,类中所有方法.*
,参数任意(..)
。绑定是绑定切面 以及切入点。
<aop:config>
<aop:pointcut id="mypointcut" expression="execution(* com.bjpowernode.service.*.*(..))"/>
<aop:advisor advice-ref="myadvice" pointcut-ref="mypointcut"></aop:advisor>
</aop:config>
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--<import resource="classpath:applicationContext_dao.xml"></import>-->
<!-- 设置业务逻辑层的包扫描器,目的是在指定的路径下,使用@Service注解的类,Spring负责创建对象,并添加依赖-->
<context:component-scan base-package="com.bjpowernode.service"></context:component-scan>
<!-- 设置事物管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 添加事务的切面-->
<!-- 查询的时候不能更改,所以是true-->
<tx:advice id="myadvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*select*" read-only="true"/>
<tx:method name="*find*" read-only="true"/>
<tx:method name="*get*" read-only="true"/>
<tx:method name="*search*" read-only="true"/>
<tx:method name="*insert*" propagation="REQUIRED"/>
<tx:method name="*save*" propagation="REQUIRED"/>
<tx:method name="*add*" propagation="REQUIRED"/>
<tx:method name="*delete*" propagation="REQUIRED"/>
<tx:method name="*remove*" propagation="REQUIRED"/>
<tx:method name="*clear*" propagation="REQUIRED"/>
<tx:method name="*update*" propagation="REQUIRED"/>
<tx:method name="*modify*" propagation="REQUIRED"/>
<tx:method name="*change*" propagation="REQUIRED"/>
<tx:method name="*set*" propagation="REQUIRED"/>
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!-- 完成切面和切入点的织入-->
<aop:config>
<aop:pointcut id="mypointcut" expression="execution(* com.bjpowernode.service.*.*(..))"/>
<aop:advisor advice-ref="myadvice" pointcut-ref="mypointcut"></aop:advisor>
</aop:config>
</beans>
3.8 springmvc.xml
设置包扫描器、设置视图解析器(路径跳转)、注解驱动(ajax处理,数据类型转换,json返回),因为要有文件上传,还需要有文件上传核心组件配置
==参数讲解==
- 包扫描器
<context:component-scan base-package="控制器的全限定名称"></context:component-scan>
- 视图解析器核心处理器
InternalResourceViewResolver
类。前缀名要有/ 资源名/
- 上传核心组件
CommonsMultipartResolver
类 - 设置注解驱动
<mvc:annotation-driven></mvc:annotation-driven>
,一定要注意看名称是mvc结尾
完整代码演示
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 设置包扫描器-->
<context:component-scan base-package="com.bjpowernode.controller"></context:component-scan>
<!-- 设置视图解析器 /admin/ main .jsp -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/admin/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 设置文件上传核心组件-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
<!-- 设置注解驱动-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
3.9 web.xml
tomcat8以后get请求可以不用设置,主要是为了post请求,中文才能更好识别,spring有所提供,只需要设置过去即可,还需要注册springmvc框架和spring框架
3.9.1 字符编码过滤器设置
字节编码器必须要在webapp 中的顶上部
参数设置:
过滤器filter,name可以用任意
class类用CharacterEncodingFilter
init-param
配置参数,具体参数是class类中的定义
编码转换类型 原本是boolean类型
<filter>
<filter-name>encode</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>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
还需要设置filter-mapping
<filter-mapping>
<filter-name>encode</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.9.2 springmvc框架注册
用servlet注册
核心处理器是DispatcherServlet
类
具体的参数设置为指明springmvc配置
==指定classpath指明编译之后的target目录中的classes目录下查找配置文件==
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
具体在servlet中拦截哪些请求,需要mapping
<servlet-mapping>
<!--
/admin/login.action 处理登录请求的方法
/admin/main.jsp
-->
<servlet-name>springmvc</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
3.9.3 spring框架注册
用listener注册
核心配置类ContextLoaderListener
拷贝其全限定名称即可
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
将配置文件注入到ContextLoaderListener
类
第一种方法是将spring的配置文件放置在webapp中的WEB-INF中,启动项目自会加载,只需在web.xml中配置监听器即可
第二种方法指定loaderlistener监听器 <context-param>
具体参数设置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext_*.xml</param-value>
</context-param>
4. mybatis逆向工程
管理员实体类、商品类实体类、商品类型实体类
相应的mapper接口、mapper的数据库访问功能
通过mybatis的逆向工程可以自动生成以上这些
jdk版本9.0以后可能不支持,需要低于9.0
==该配置文件中的generator Config.xml解释==
主要是生成类路径的位置
需要与项目的当前路径一样
只需修改此处即可
运行生成该类的代码
可不深究
public class GeneratorSqlmap {
public void generator() throws Exception{
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
//指定 逆向工程配置文件
File configFile = new File("generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
public static void main(String[] args) throws Exception {
try {
GeneratorSqlmap generatorSqlmap = new GeneratorSqlmap();
generatorSqlmap.generator();
} catch (Exception e) {
e.printStackTrace();
}
}
}
以及指定最后的生成的数据库表
<table schema="" tableName="admin"></table>
<table schema="" tableName="product_info"></table>
<table schema="" tableName="product_type"></table>
生成的文件中路径如果有改动,需要将生成文件删除,在重新生成即可
如果不删除,重复生成会出错
生成的具体代码如图所示
- AdminMapper为接口中的数据库访问的方法
- AdminMapper.xml为接口中列名和属性的映射等(如果数据库和java对象不一样需要映射信息)
- Admin为实体类
- AdminExample为实体类查询
5. MD5加密算法
将该加密算法归为一类,放在utils中,该包有加密算法, 文件上传等算法
通过用户输入的账号密码,通过jsp反映过去进而达到一个提交的状态,提到到后台中需要将明文转换为密文
而MD5为明文转换为密文,密文不可转换为明文
MD5(message-digest algorithm 5)信息摘要算法:
- 它的长度一般是32位的16进制数字符串(如81dc9bdb52d04dc20036dbd8313ed055)
- 注册时,将密码进行md5加密,存到数据库中,防止可以看到数据库数据的人恶意篡改。登录时,将密码进行md5加密,与存储在数据库中加密过的密码进行比对
- 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;
}
通过测试函数可以检测该算法的执行是否成功
@Test
public void testMD5() {
String aa = MD5Util.getMD5("000000");
System.out.println(aa);
}
通过该密文与数据库的密文进行比较
如果比较符合就会通过
6. 登陆功能
6.1 业务逻辑@Service
==要添加这个@Service,把当前的业务逻辑层交给spring,容器启动的时候创建该类的对象==
==在业务逻辑层中,一定会有数据访问层的对象==
需要有访问对象 AdminMapper adminMapper;
通过@Autowired进行注入,让容器创建
该业务逻辑内部的具体步骤是:
- 根据传入的用户或到DB中查询相应用户对象
- 如果查询到用户对象,再进行密码的比对,注意密码是密文
根据用户名查对象。通过mapper.xml中的映射id查看
- 用来创建封装对象,如果有条件 ,则一定要创建AdminExample的对象,用来封装条件
AdminExample example = new AdminExample();
- 条件要细化,不能用 *,
select 条件 from admin where a_name ='admin'
为了这样添加用户条件,查询语句变成带条件的查询语句example.createCriteria().andANameEqualTo(name);
,拼接到的结果最后用adminMapper的映射List<Admin> list = adminMapper.selectByExample(example);
具体完整代码如图所示
接口类为
本身接口获取账号和密码的时候返回值可以为Boolean类型,但是为了做更多的选择,返回一个类的类型
public interface AdminService {
//完成登录判断
Admin login(String name, String pwd);
}
实现类为
在业务逻辑层中,一定会有数据访问层的对象
具体的代码逻辑为创建一个实体类的对象,通过对象获取输入的name名字,将其对象放在一个列表中(如果后期对象很多,列表也适应),判断列表是否有无值,有值则取第一个值,在进行密码的配对
具体密码的配对,主要通过页面输入的密码值,以及页面输入的账号查询其数据库的密码值
两者进行比较配对,如果成功则登陆成功
@Service
public class AdminServiceImpl implements AdminService {
//在业务逻辑层中,一定会有数据访问层的对象
@Autowired
AdminMapper adminMapper;
@Override
public Admin login(String name, String pwd) {
//根据传入的用户或到DB中查询相应用户对象
//如果有条件 ,则一定要创建AdminExample的对象,用来封装条件
AdminExample example = new AdminExample();
/**如何添加条件
* select * from admin where a_name ='admin'
*/
//添加用户名a_name条件
example.createCriteria().andANameEqualTo(name);
List<Admin> list = adminMapper.selectByExample(example);
if(list.size() > 0 ){
Admin admin = list.get(0);
//如果查询到用户对象,再进行密码的比对,注意密码是密文
/**
* 分析:
* admin.getApass==>c984aed014aec7623a54f0591da07a85fd4b762d
* pwd===>000000
* 在进行密码对比时,要将传入的pwd进行md5加密,再与数据库中查到的对象的密码进行对比
*/
String miPwd = MD5Util.getMD5(pwd);
if(miPwd.equals(admin.getaPass())){
return admin;
}
}
return null;
}
}
6.2 界面层@Controller
界面层的开发
==在所有的界面层,一定会有业务逻辑层的对象==
@Controlle
r控制器对象交给spring- 当前对象进行注解,注解为后台管理员
@RequestMapping("/admin")
- 在所有的界面层,一定会有业务逻辑层的对象
AdminService adminService;
,该对象由spring对象@Autowired
实现登录判断并进行相应的跳转注解以下当前的判断方法@RequestMapping("/login")
具体的算法参数,要不要当前页面的参数提交,具体参数通过login.jsp
进行查看,参数要一一对应
如果登录成功,跳转页面,页面的参数可以设置泛泛设置
具体设置在springmvc.xml中的视图分析器,可具体网上查看
有用户名和密码之后可以通过对象的查看
具体登录成功与失败,可获得当前页面的显示通过
参数一一对应
如果执行失败的返回参数通过jsp代码查看一一对应
具体的界面层代码模块通过获取业务逻辑的对象,获取输入的账号和密码,将其账号密码的参数对比,如果成功显示主页面,不成功会返回一个错误信息
具体注入的对象是一个接口,动态代理创建对象,然后获取对象的login的方法,也就是上面写的业务逻辑的方法进行获取数据,然后比对
具体代码为
@Controller
@RequestMapping("/admin")
public class AdminAction {
//切记:在所有的界面层,一定会有业务逻辑层的对象
@Autowired
AdminService adminService;
//实现登判断,并进行相应的跳转
@RequestMapping("/login")
public String login(String name , String pwd, HttpServletRequest request){
Admin admin = adminService.login(name,pwd);
if(admin != null){
request.setAttribute("admin",admin);
//登录成功
return "main";
}else{
//登录失败
request.setAttribute("errmsg","用户名或密码不正确!");
return "login";
}
}
}
以上的代码模块通过HttpServletRequest request进行传参
也可通过model的参数
具体可看我这篇文章(已经和代码结合在一块了)
springmvc中的model和HttpServletRequest的区别详细分析
6.3 测试界面
通过配置tomcat启动界面
如果tomcat启动不了
类似这种界面
或者是这种界面
可查看这篇文章
- Tomcat:HTTP状态 404 - 未找到解决方法
- maven:编译出现Process terminated解决方法
- tomcat:Caused by:java.lang.ClassNotFoundException: javax.servlet.ServletContextListener解决方法
启动之后的界面是http://localhost:8080/admin/login.jsp
登录不成功的话,看看是否端口号以及数据库没有更改为加密的密码
加密过后的密码字段也要设置为255的字段,不然会被截断
如果在这过程中还有出现这这些错误,可看我之前的文章
- 出现SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder“.的解决方法
- 出现Logging initialized using ‘class org.apache.ibatis.logging.stdout.StdOutImpl‘ adapter的解决方法
登录成功后的截图如下所示:
会进入到main的页面
登录不成功的显示如下
会通过后端的传值,传入json数据给前端,在前端进行显示
7. 显示全部商品(不分页)
登陆成功之后的界面是商品管理
点击商品管理->出现所有的商品管理的方法
并且商品管理可以执行分页管理
7.1 业务逻辑@Service
接口类
public interface ProductInfoService {
//显示全部商品(不分页)
List<ProductInfo> getAll();
}
接口实现类
注解内容和上面登录功能大同小异
温故而知新:
@Service
交给spring创建对象- 业务逻辑层中一定有数据访问层的对象,
ProductInfoMapper productInfoMapper;
通过spring中的@Autowired
注入
这个实现类和登录功能的实现逻辑不一样
登陆功能的实现类内部还要进行判断,不单止是new一个对象
而分页中查询各个数据商品不用做判断操作,可以直接在内部中new一个对象
@Service
public class ProductInfoServiceImpl implements ProductInfoService {
//切记:业务逻辑层中一定有数据访问层的对象
@Autowired
ProductInfoMapper productInfoMapper;
@Override
public List<ProductInfo> getAll() {
return productInfoMapper.selectByExample(new ProductInfoExample());
}
}
7.2 界面层@Controller
在界面层中的开发
@Controller
表明身份控制器,交给spring创建对象@RequestMapping("/prod")
路径的映射
==在界面层中,一定会有业务逻辑层的对象==
自动注入
@Autowired
ProductInfoService productInfoService;
该list中的列表中要放到product.jsp
中
则需要通过HttpServletRequest
//显示全部商品不分页
@RequestMapping("/getAll")
public String getAll(HttpServletRequest request) {
List<ProductInfo> list = productInfoService.getAll();
request.setAttribute("list", list);
return "product";
}
也可以通过model的代码格式
//显示全部商品不分页
@RequestMapping("/getAll")
public String getAll(Model model) {
List<ProductInfo> list = productInfoService.getAll();
model.addAttribute("list", list);
return "product";
}
7.3 原理分析
==分析点击的时候不会覆盖页面、如果显示商品有滚动条==
通过点击商品页面管理,链接显示在右边的框,而不是全覆盖,现实的时候为不分页完整显示
通过主页main.jsp中设置超链接的跳转到当前页面,具体跳转的链接是上面的链接模块,且不覆盖的跳转,通过设置iframe框架,通过设置一个target外部链
iframe框架如图所示,也就是整个页面分为了两块,互不干扰
具体跳转的页面链接的界面层如上文讲到,查询数据库获取到所有商品放到list集合,将list集合放置于request中的对象最后结果返回一个product.jsp的页面指示
==目标的指定页面的跳转在右边显示,而不是全部覆盖,使用的是iframe框架==
通过EL表达式进行书写返回值
显示其所有商品,如果不分页商品要有滚动条,通过设置
main.jsp中的iframe框架参数,scrolling必须为yes才有滚动条
比如frameLabelStart--frameLabelEnd
将滚动条变为分页处理,通过ajax处理
8. 显示全部商品(分页)
需求:
- 当前页显示5条数据集合
- 页码的导航显示总共多少页,当前是第几页
- 当前页码的选中框背景显示
- 每页显示第几条
8.1 分页插件PageHelper
PageHelper
分页核心处理类,主要设置拦截器,执行sql语句中有limit的限制PageInfo
分页的所有数据(当前多少页,每一页多少数据等)pagesize为当前页面的可容纳数据条数,size为当前页面实际条数
通过设置每一页的具体数量以及当前页的数量,具体通过sql的limit的限制
8.2 业务逻辑@Service
接口类
//分页
public PageInfo splitPage(int page, int pageSize);
分装的数据都在PageInfo
具体参数的应该是(当前页,可容纳数据/每页读取几条)
在分页中的数据库代码显示
伪代码格式:select * from product_info limit 起始记录数=((当前页-1)*每页的条数),每页取几条
比如select * from product_info limit 5,5
取的数据为11-15条记录
也就是只要获取当前页,每页第几条即可
接口的实现类:
业务逻辑层中一定有数据访问层的对象
@Autowired
ProductInfoMapper productInfoMapper;
通过使用使用PageHelper工具类完成分页设置
- 内部有一个函数为
startPage(pageNum,pageSize);
,将其当前页和每页总条数传参过去 - 进行数据封装该方法在
new ProductInfoExample();
- 设置排序,按主键降序排序,通过
setOrderByClause
。追加数据的时候第一条才会显示最新的数据
该类在实体类的定义为
只需设置其参数传入即可,就如同sql语句select * from product_info order by p_id desc
也就是p_id desc为上面的传参结果
- 封装其数据
List<ProductInfo> list = productInfoMapper.selectByExample(example);
- 将查询到的集合封装进==PageInfo对象==
PageInfo<ProductInfo> pageInfo = new PageInfo<>(list);
完整代码如下:
@Override
public PageInfo splitPage(int page, int pageSize) {
//商品分页一定会借助于PageHelper类,还要借助于ProductInfoExample
ProductInfoExample example=new ProductInfoExample();
//设置的字符串是字段名称和排序规则
example.setOrderByClause("p_id desc");
//切记切记:在取集合之前,使用分页工具设置当前页和每页的记录数
PageHelper.startPage(page,pageSize);
//取集合
List<ProductInfo> list=pmapper.selectByExample(example);
//将查到的集合封装进pageInfo
PageInfo<ProductInfo> pageInfo=new PageInfo<>(list);
return pageInfo;
}
8.3 界面层
最原始的界面
现在是往这个界面加入信息以及每一页切换都有想要的信息显示正确
在这之前如果出现分页不出来的情况
可看我这篇文章
出现WARN org.springframework.web.servlet.PageNotFound - No mapping for POST /prod/ajaxsplit.action解决方法
8.3.1 后端界面@Controller
每一页的页数定义成一个全局的静态变量,public static final int PAGE_SIZE = 5;
,代表每页显示的记录数
具体的核心功能如下:
//显示第1页的5条记录
//分页显示
@RequestMapping("/split")
public String split(HttpServletRequest request) {
PageInfo info = productInfoService.splitPage(1, PAGE_SIZE);
request.setAttribute("info", info);
return "product";
}
实现ajax的翻页处理,因为有ajax,所以一定有@ResponseBody
这个注解,跳过视图解析器,将其返回值转换为json数据
页面刷新展示的数据 ,所以需要通过Httpsession的数据
所有封装的数据都是通过pageinfo的数据
通过session的数据会返回到前端界面中下面的success(下面有提及到)容器,读取info的数据,本身session的数据就有了,一刷新,网址的table就会刷新,info的数据会带着表格,刷新页面的容器
//分页显示
@ResponseBody
@RequestMapping("/ajaxSplit")
public void ajaxSplit(int page, HttpSession session) {
PageInfo info = productInfoService.splitPage(page, PAGE_SIZE);
session.setAttribute("info", info);
}
8.3.2 前端界面
==商品栏目界面展示:==
var是遍历出来的每一个商品对象
==分页栏界面展示:==
向前翻页的时候会发出ajax的请求,通过取到当前页面的对象,之后取到上一页的数据prepage
。通过超链接的跳转执行。向后翻页也同理,只不过取到下一页的数据是nextpage
代码部分展示如下:
<a href="javascript:ajaxsplit(${pb.nextPage})" aria-label="Next">
<span aria-hidden="true">»</span></a>
中间显示页面的页数是通过for each结构进行执行
比如for(int i = 第一页;i<最后一页的数据;i++)
一直输出每一页,具体的格式如下:
<c:forEach begin="1" end="${pb.pages}" var="i">
<li>${i}</li></c:if>
</c:forEach>
为了让样式更加好看,每一页都有超链接,而且停留在那一页代表当前页面,会有判断是否是当前页
只要当前页不等于展示页面,存放其页面的数据。如果当前页等于展示的页面,还要加上一个背景的颜色
具体每一页的显示链接都是通过ajax进行请求
具体代码格式如下:
<c:forEach begin="1" end="${pb.pages}" var="i">
<c:if test="${pb.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="${pb.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>
具体如何获取每一页的请求都是通过ajax
此处讲讲怎么通过ajax的获取
导入jquery的函数库,ajax的分库处理
- url通过远程界面的处理
- 数据的提交是当前提交页面的数据
- 服务提交的请求可以是get和post,但是为了安全,可以用post
- 返回值,success:重新加载数据的显示,通过一个容器进行全部刷新
在当前页面上,通过找到url的路径刷新table的容器
具体代码为如下:
<!--分页的AJAX实现-->
<script type="text/javascript">
function ajaxsplit(page) {
//取出查询条件
$.ajax({
url: "${pageContext.request.contextPath}/prod/ajaxSplit.action",
type: "post",
data: {"page": page,"pname":pname,"typeid":typeid,"lprice":lprice,"hprice":hprice},
success: function () {
$("#table").load("http://localhost:8080/admin/product.jsp #table");
},
error: function (e) {
alert(e.message);
}
});
刷新整个table的容器
总共多少页面,还有当前第几页,通过数据的传输page和pagenum
这里有一个小的瑕疵,就是在最后一页的时候,如果继续点击下一页的时候,会显示第0页,再点击才会显示第一页,所以要点两下,为了改变这个,直接把第0页变成第1页,就不用点击两下了
具体代码逻辑如下:
点击第三页的时候,通过第三页的ajax请求,去控制层界面操作,再到数据层面操作,再通过数据层面返回数据给前端
8.4 结果展示
每点击一页的时候都会显示正确的页面
而且在编辑器中会有事务的生成
9. 增加商品
分析添加商品的时候需要什么数据,id是不需要的,因为是数据库自增类型,上传时间也是不需要的,可以通过获取当前的系统时间得到
接口类型
public interface ProductTypeService {
//完成查询全部商品类型的功能
public List<ProductType> getAllType();
}
接口实现类@Service("ProductTypeServiceImpl")
代表的是一个别名
@Service("ProductTypeServiceImpl")
public class ProductTypeServiceImpl implements ProductTypeService {
//必有数据访问层的mapper对象
@Autowired
private ProductTypeMapper typeMapper;
@Override
public List<ProductType> getAllType() {
return typeMapper.selectByExample(new ProductTypeExample());
}
}
9.1 监听器获取商品类型
监听器中获取所有的列表,在当前页面绑定商品的类型
放在全局域中,即使修改数据或者增加数据,商品类型都只需要获取一次即可,而不需要一直获取
全局的监听器要继承这个类ServletContextListener
,项目启动的时候,监听器就会被触发
通过该注解@WebListener
注册当前的监听器
可以通过注解进行注入,但是无法保证哪个监听器哪个创建哪个先执行
所以需要通过手动进行获取,通过容器中取出
具体通过获取配置文件的位置,通过配置文件创建一个对象
这是因为在web.xml文件中本身就已经有了一个一样的监听器,开启启动的时候不知道先注册哪个监听器
实现的是同一个类
具体的代码为如下:
只能手工从spring的Bean工厂中按名称取出类型的service
通过ClassPathXmlApplicationContext获取配置文件
获取其对象,记得进行强转换,也就是动态代理
之后通过其对象调用其方法获取所有的类型
将其所有的类型放置到全局作用域中
@WebListener
public class ProductTypeListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//取出所有的商品类型,便于在增加和更新的页面上使用,或者前端的类型查询中使用
//切记切记:只能手工从spring的Bean工厂中按名称取出类型的service
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext-*.xml");
ProductTypeService productTypeService= (ProductTypeService) context.getBean("ProductTypeServiceImpl");
List<ProductType> typeList=productTypeService.getAllType();
//放入全局应用作用域中,共新增页面,修改页面,前台的查询功能提供全部商品的类别集合
servletContextEvent.getServletContext().setAttribute("typeList",typeList);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
==下拉列表显示全部商品类型==
下拉列表框进行绑定并且循环
进行foreach循环(导入js的标签标准库)
标签库在最上面添加<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
prefix。代表下面的代码用什么引入,通常用的是c字母
商品类型的id,通过var的循环加上id号。
商品类型的名称,通过var的循环加上名称。
遍历循环整个typelist的列表,通过形参var进行遍历,遍历id以及名称
<tr>
<td class="one">类别</td>
<td>
<select name="typeId" >
<c:forEach items="${typeList}" var="type">
<option value="${type.typeId}">${type.typeName}</option>
</c:forEach>
</select>
</td>
</tr>
9.2 异步ajax图片上传并回显
具体的功能逻辑:
使用异步ajax的方式,提交之前就上传到服务器中
上传完毕之后回传到页面中显示图片(回显)
具体回传页面,通过json的数据格式,将其放置在前端的的页面
点击一个按钮,触发一个ajax的异步请求
在这之前需要一个ajax的异步请求的js文件导入<script type="text/javascript" src="${pageContext.request.contextPath }/js/ajaxfileupload.js"></script>
关于前端界面的按钮提交图片这么书写
id就是JavaScript中获取的数据名称
name就是上传到服务器中的名称
<tr>
<td class="three">图片介绍</td>
<td> <br><div id="imgDiv" style="display:block; width: 40px; height: 50px;"></div><br><br><br><br>
<%--<input type="file" id="pimage" name="pimage" onchange="fileChange()">--%>
<input type="file" id="pimage" name="pimage" onchange="fileChange()" >
<span id="imgName" ></span><br>
</td>
</tr>
涉及到里面的功能函数,具体这么书写
- url提交给服务器的名称
- secureuri 当前是否有安全协议
- fileElementId文件上传的控件属性id
- dataType服务端返回的数据类型,通过json
- 返回成功,就是success,返回图片的src路径,可以在前端进行回显。回显要有图片的标签,属性的赋值,obj是服务端的的对象。还要设置图片的宽和高。将图片追加到div中,使得有回显。
<script type="text/javascript">
function fileChange(){//注意:此处不能使用jQuery中的change事件,因此仅触发一次,因此使用标签的:onchange属性
alert("change");
$.ajaxFileUpload({
url: '/prod/ajaxImg.action',//用于文件上传的服务器端请求地址
secureuri: false,//一般设置为false
fileElementId: 'pimage',//文件上传控件的id属性 <input type="file" id="pimage" name="pimage" />
dataType: 'json',//返回值类型 一般设置为json
success: function(obj) //服务器成功响应处理函数
{
alert(obj);
$("#imgDiv").empty(); //清空原有数据
//创建img 标签对象
var imgObj = $("<img>");
//给img标签对象追加属性
imgObj.attr("src","/image_big/"+obj.imgurl);
imgObj.attr("width","100px");
imgObj.attr("height","100px");
//将图片img标签追加到imgDiv末尾
$("#imgDiv").append(imgObj);
//将图片的名称(从服务端返回的JSON中取得)赋值给文件本框
//$("#imgName").html(data.imgName);
},
error: function (e)//服务器响应失败处理函数
{
alert(e.message);
}
});
}
</script>
上传的图片可能也有这个图片的名称,后一个可能就覆盖了前一个的图片。所以上传之后也要给他一个随机的序列保证图片不会被覆盖
对于mvc引入一个文件上传的核心组件,保存在springmvc.xml的配置文件中
还可以配置最大的文件上传属性,相关的文件上传配置,也可以在bean标签内部进行配置
<!--文件上传插件-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
服务端要使用一个工具包,通过uuid生成id文件名,将其中间的-去掉,变成32位
获取提交的文件名和提交的类型
具体可以使用json的数据包格式引入
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20140107</version>
</dependency>
- 返回json的格式的话
object.toString();
ajax的上传,所以需要@ResponseBody,因为json数据格式传输
当前上传页面的数据流,要跟前端的name属性一样
提取生成的文件名(通过uuid生成的,再加上图片的后缀名)
后缀名=文件的原始名称截取后面的那一部分,通过前端的name属性调用getOriginalFilename()
获取
通过request获取图片存储的路径
之后转存的话,就是将其图片放置到该位置
File.separator就是分割线
@ResponseBody
@RequestMapping("/ajaxImg")
public Object ajaxImg(MultipartFile pimage,HttpServletRequest request){
//要进行文件上传操作
//异步上传存文件名,增加和更新用这个文件名
//取文件名
String saveFileName = FileNameUtil.getUUIDFileName() + FileNameUtil.getFileType(pimage.getOriginalFilename());
//取路径
try {
String path = request.getServletContext().getRealPath("/image_big");
//转存
pimage.transferTo(new File(path + File.separator + saveFileName));
} catch (Exception e) {
e.printStackTrace();
}
//为了在客户端显示图片,要将存储的文件名回传下去,由于是自定义的上传插件,所以此处要手工处理JSON
JSONObject object = new JSONObject();
object.put("imgurl",saveFileName);
//切记切记:JSON对象一定要toString()回到客户端
return object.toString();
}
结果展示
alert会会显出它的uuid号
显示出来的图片就可以回显
9.3 完善其他功能
其页面的主要前端显示为:
关于这个页面
- 其点击提交的时候返回的路径是在后端完善的
action="${pageContext.request.contextPath}/prod/save.action"
- 其name的属性都是一一对应好商品属性中定义的名称
<div id="addAll">
<div id="nav">
<p>商品管理>新增商品</p>
</div>
<div id="table">
<form id="myform" action="${pageContext.request.contextPath}/prod/save.action">
<table>
<tr>
<td class="one">商品名称</td>
<td><input type="text" name="pName" class="two"></td>
</tr>
<!--错误提示-->
<tr class="three">
<td class="four"></td>
<td><span id="pnameerr"></span></td>
</tr>
<tr>
<td class="one">商品介绍</td>
<td><input type="text" name="pContent" class="two"></td>
</tr>
<!--错误提示-->
<tr class="three">
<td class="four"></td>
<td><span id="pcontenterr"></span></td>
</tr>
<tr>
<td class="one">定价</td>
<td><input type="number" name="pPrice" class="two"></td>
</tr>
<!--错误提示-->
<tr class="three">
<td class="four"></td>
<td><span id="priceerr"></span></td>
</tr>
<tr>
<td class="three">图片介绍</td>
<td> <br><div id="imgDiv" style="display:block; width: 40px; height: 50px;"></div><br><br><br><br>
<input type="file" id="pimage" name="pimage" onchange="fileChange()" >
<span id="imgName" ></span><br>
</td>
</tr>
<tr class="three">
<td class="four"></td>
<td><span></span></td>
</tr>
<tr>
<td class="one">总数量</td>
<td><input type="number" name="pNumber" class="two"></td>
</tr>
<!--错误提示-->
<tr class="three">
<td class="four"></td>
<td><span id="numerr"></span></td>
</tr>
<tr>
<td class="one">类别</td>
<td>
<select name="typeId" >
<c:forEach items="${typeList}" var="type">
<option value="${type.typeId}">${type.typeName}</option>
</c:forEach>
</select>
</td>
</tr>
<!--错误提示-->
<tr class="three">
<td class="four"></td>
<td><span></span></td>
</tr>
<tr>
<td>
<input type="submit" value="提交" class="btn btn-success">
</td>
<td>
<input type="reset" value="取消" class="btn btn-default" onclick="myclose(${param.page})">
<script type="text/javascript">
function myclose(ispage) {
window.location="${pageContext.request.contextPath}/prod/split.action?page="+ispage;
}
</script>
</td>
</tr>
</table>
</form>
</div>
</div>
其后端的业务逻辑层这么书写:
接口类:
//增加
public int save(ProductInfo info);
接口实现类:
主要通过mappeer文件的功能进行调用
@Override
public int save(ProductInfo info) {
return productInfoMapper.insert(info);
}
其界面层这么书写:
大部分对象都封装在ProductInfo info这个实体类中,还需要request的对象,如果添加成功,可以在前端的界面上显示
主要是增加一个上传图片已经回显了的日期,还有一个上传日期
其他都是可以通过上面的函数进行调用传入
增加完商品后要跳转到商品的页面,也就是通过转发
具体关于转发和重定向可看我之前的文章
转发和重定向的区别及使用方法(全)
//执行增加操作
@RequestMapping("/save")
public String save(ProductInfo info, HttpServletRequest request) {
//图片处理好后,设置到商品对象中
info.setpImage(saveFileName);
info.setpDate(new Date());
//到此为止,商品对象构建完毕,有自动从表单元素注入的,有上传图片的,有上架日期
//完成数据库增加操作
int num = -1;
try {
num = productInfoService.save(info);
} catch (Exception e) {
e.printStackTrace();
}
if (num > 0)
request.getSession().setAttribute("msg", "增加成功!");
else
request.getSession().setAttribute("msg", "增加失败!");
saveFileName="";
//增删改后用重定向跳转
return "redirect:/prod/split.action";
}
上面的代码中还有一个request的界面显示,msg在前端这么定义获取
通过request进行传输,在前端界面上显示,通过alert
<script type="text/javascript">
if ("${msg}" != "") {
alert("${msg}");
}
</script>
提交成功后,前端界面会有显示
之后跳回商品的页面
显示了乱码
具体数据库的乱码可看我之前的文章
添加数据到数据库中显示乱码的解决方法
编辑器的配置utf-8也很重要
- 没配置中文字符过滤器会导致前端传给后端的中文编码问题
- 数据库url中没有中文编码导致后端传数据给数据库时中文乱码
解决成功之后的样式为:
10. 更新商品
- 点击编辑
- 进入到更新商品的界面。显示的商品和增加这条商品界面是一样的,此处有几个难点,下面将一一展开
10.1 后端
==点击编辑按钮的框==
在后端的功能逻辑这么书写:
主键id查询商品
如果是int的话,默认会自动装箱变成integer类型
具体其后端界面层的代码如下:
接口类:
//根据主键查商品
public ProductInfo getById(Integer pid);
接口实现类
@Override
public ProductInfo getById(Integer pid) {
return productInfoMapper.selectByPrimaryKey(pid);
}
界面层的实现如下:
//根据id查询回显显示商品信息
@RequestMapping("/one")
public String one(Integer pid,Integer page, Model model) {
ProductInfo info = productInfoService.getById(pid);
model.addAttribute("prod", info);
model.addAttribute("page",page);
return "update";
}
传输一个prod的数据,也就是商品的具体信息给前端,这个prod的值,可以随便取,最后丢给了update的跳转链接,通过查询id值来获取
==回显完所有的数据后,在后端更新的时候其逻辑代码如下:==
接口类:
//修改
public int update(ProductInfo info);
接口实现类:
@Override
public int update(ProductInfo info) {
return productInfoMapper.updateByPrimaryKeySelective(info);
}
界面层实现类:
参数具体:
商品实体类的参数
增加成功或者失败的时候要返回给前端的一个显示,需要通过request的请求
点击图片的时候,更新的时候有异步上传就有图片的名字,只要判断其名字即可。点击保存之后其名字才会被清空,但是更新会显的时候,其没有使用异步上传图片,其名字为空,所以实体类info就会使用隐藏表单域提供上来的数据进行回显
//更新操作
@RequestMapping("/update")
public String update(ProductInfo info,Integer page,HttpServletRequest request) {
//因为是修改,所以要判断有没有点文件上传的铵钮
if(!saveFileName.equals("")){
info.setpImage(saveFileName);
}
boolean flag = false;
//有没有时间改变
//此处要进行info对象的上架时间的更新
//首先要判断当前新更新上来的日期不能大于当前日期
if (info.getpDate() != null) {
if (info.getpDate().getTime() > System.currentTimeMillis()) {
//日期不正常了,则置为空,底层做更新处理时不做更改
info.setpDate(null);
flag=true;
}
}
//完成对象更新
int num = -1;
try {
num = productInfoService.update(info);
} catch (Exception e) {
e.printStackTrace();
}
if (num > 0) {
if (flag) {
request.getSession().setAttribute("msg", "日期不能大于当前日期");
} else {
request.getSession().setAttribute("msg", "更新成功!");
}
} else {
request.getSession().setAttribute("msg", "更新失败");
}
saveFileName="";
return "redirect:/prod/split.action?page="+page;
}
redirect重定向的时候会被sesseion的提示信息刷掉,所以尽量少用seesion的数据,再者也可以换成forward的转发方式
10.2 前端
==点击编辑==的时候,通过id去搜寻,具体通过var进行遍历
==具体其one的函数,主要是通过服务器的数据回显到前端==
具体通过location.href,提交这个请求
function one(pid,page) {
//取出查询条件
var pname = $("#pname").val();
var typeid = $("#typeid").val();
var lprice = $("#lprice").val();
var hprice = $("#hprice").val();
//向服务器提交请求,传递商品id
var str = "?pid="+pid+"&page="+page+"&pname="+pname+"&typeid="+typeid+"&lprice="+lprice+"&hprice="+hprice;
location.href = "${pageContext.request.contextPath}/prod/one.action" + str;
}
在回显的时候,类别会相应的显示其类型
在前端界面的显示中主要通过如下显示
分别回显各个类型,通过遍历,如果通过id找到的类型与当前类型一样,则标记为默认选项,否则一个个输出回显
遍历的t数据为typeList,通过var的形参进行遍历,prod是后端查询回显的数据。
<tr>
<td class="one">类别</td>
<td>
<select name="typeId">
<c:forEach items="${typeList}" var="type">
<option value="${type.typeId}"
<c:if test="${type.typeId==prod.typeId}">
selected="selected"
</c:if>
>${type.typeName}</option>
</c:forEach>
</select>
</td>
</tr>
其他数据都可以通过prod的数据格式进行回显,图片可以通过图片库中的路径进行回显,和具体的增加商品是一个意思
具体的核心代码一样,但此处push出来
<script type="text/javascript">
function fileChange(){//注意:此处不能使用jQuery中的change事件,因此仅触发一次,因此使用标签的:onchange属性
$.ajaxFileUpload({
url: '/prod/ajaxImg.action',//用于文件上传的服务器端请求地址
secureuri: false,//一般设置为false
fileElementId: 'pimage',//文件上传控件的id属性 <input type="file" id="pimage" name="pimage" />
dataType: 'json',//返回值类型 一般设置为json
success: function(obj) //服务器成功响应处理函数
{
$("#imgDiv").empty(); //清空原有数据
//创建img 标签对象
var imgObj = $("<img>");
//给img标签对象追加属性
imgObj.attr("src","/image_big/"+obj.imgurl);
imgObj.attr("width","100px");
imgObj.attr("height","100px");
//将图片img标签追加到imgDiv末尾
$("#imgDiv").append(imgObj);
//将图片的名称(从服务端返回的JSON中取得)赋值给文件本框
//$("#imgName").html(data.imgName);
},
error: function (e)//服务器响应失败处理函数
{
alert(e.message);
}
});
}
</script>
前端传输的pid请求,要与后端的pid名字保持一致
通过表单的提交,具体提交的数据更新都由后端来实现
前端只要显示好回显的数据,通过name,具体和后端的数据定义是一样的
<div id="table">
<form action="${pageContext.request.contextPath}/prod/update.action" enctype="multipart/form-data" method="post" id="myform">
<input type="hidden" value="${prod.pId}" name="pId">
<input type="hidden" value="${prod.pImage}" name="pImage">
<table>
<tr>
<td class="one">商品名称</td>
<td><input type="text" name="pName" class="two" value="${prod.pName}"></td>
</tr>
<!--错误提示-->
<tr class="three">
<td class="four"></td>
<td><span id="pnameerr"></span></td>
</tr>
<tr>
<td class="one">商品介绍</td>
<td><input type="text" name="pContent" class="two" value="${prod.pContent}"></td>
</tr>
<!--错误提示-->
<tr class="three">
<td class="four"></td>
<td><span id="pcontenterr"></span></td>
</tr>
<tr>
<td class="one">定价</td>
<td><input type="number" name="pPrice" class="two" value="${prod.pPrice}"></td>
</tr>
<!--错误提示-->
<tr class="three">
<td class="four"></td>
<td><span id="priceerr"></span></td>
</tr>
<tr>
<td class="one">图片介绍</td>
<td> <br><div id="imgDiv" style="display:block; width: 40px; height: 50px;"><img src="/image_big/${prod.pImage}" width="100px" height="100px" ></div><br><br><br><br>
<input type="file" id="pimage" name="pimage" onchange="fileChange()">
<span id="imgName"></span><br>
</td>
</tr>
<tr class="three">
<td class="four"></td>
<td><span></span></td>
</tr>
<tr>
<td class="one">总数量</td>
<td><input type="number" name="pNumber" class="two" value="${prod.pNumber}"></td>
</tr>
<!--错误提示-->
<tr class="three">
<td class="four"></td>
<td><span id="numerr"></span></td>
</tr>
<tr>
<td class="one">类别</td>
<td>
<select name="typeId">
<c:forEach items="${typeList}" var="type">
<option value="${type.typeId}"
<c:if test="${type.typeId==prod.typeId}">
selected="selected"
</c:if>
>${type.typeName}</option>
</c:forEach>
</select>
</td>
</tr>
<!--错误提示-->
<tr class="three">
<td class="four"></td>
<td><span></span></td>
</tr>
<tr>
<td>
<input type="submit" value="提交" class="btn btn-success">
</td>
<td>
<input type="reset" value="取消" class="btn btn-default" onclick="myclose(1)">
</td>
</tr>
</table>
</form>
</div>
11. 删除商品(单个)
11.1 后端
接口类:
//删除
public int delete(Integer pid);
接口实现类:
@Override
public int delete(Integer pid) {
int num=0;
try {
num = productInfoMapper.deleteByPrimaryKey(pid);
} catch (Exception e) {
e.printStackTrace();
}
return num;
}
提交数据的时候使用了ajax的请求方式
但是结束以后,解析返回的数据
但是结束的时候,没有返回的数据,而是跳转到ajax的请求数据返回结果
返回的结果要返回到商品的页面,但是之前那个算法没有返回值,所以重新写一个算法
由于该算法是ajax的异步请求返回到客户端进行显示,所以可能会乱码,要添加一个参数
由于刷新的时候会销毁request,所以要放到session中进行读取
//执行删除操作
@RequestMapping("/delete")
public String delete(Integer pid, HttpSession session) {
int num = productInfoService.delete(pid);
if (num > 0)
session.setAttribute("msg", "删除成功!");
else
session.setAttribute("msg", "删除失败!");
//增删改后用重定向跳转
return "forward:/prod/dajaxsplit.action";
}
//删除后分页显示,切记切记切记:坑:使用@ResponseBody注解,返回值不能是String,
// 如果一定要使用String,则手工封装成JSON格式
@ResponseBody
@RequestMapping(value = "/dajaxsplit",produces = "text/html;charset=UTF-8")
public Object dajaxsplit(ProductInfo info, HttpSession session) {
session.setAttribute("prod", info);
saveFileName = "";
//手工封装返回删除成功或删除失败字符串为JSON格式
String s=session.getAttribute("msg").toString();
JSONObject object=new JSONObject();
object.put("msg",s);
return object.toString();
}
其删除的界面可以通过以下代码也可以:
@ResponseBody
@RequestMapping(value = "/dajaxsplit",produces = "text/html;charset=UTF-8")
public Object dajaxsplit(HttpServletRequest request) {
request.getSession().setAttribute("prod",info);
return request.getAttribute("msg");
}
11.2 前端
点击删除的时候要有提示的删除框,因为此次ajax异步刷新的时候不是局部刷新,只是单纯刷新整个表格而已。所以提示消息会消失。
<button type="button" class="btn btn-warning" id="mydel"
onclick="del(${p.pId},${info.pageNum})">删除
</button>
具体解决方法是通过ajax的方式将其删除成功的结果返回,需要通过json的数据,从服务器端返回到客户端,判断是否删除
键pid是服务器的pid,值pid是当前前端要删除的pid数据
//单个删除
function del(pid,page) {
//弹框提示
if (confirm("您确定删除吗?")) {
//发出ajax的请求,进行删除操作
//取出查询条件
var pname = $("#pname").val();
var typeid = $("#typeid").val();
var lprice = $("#lprice").val();
var hprice = $("#hprice").val();
$.ajax({
url: "${pageContext.request.contextPath}/prod/delete.action",
data: {"pid": pid,"page": page,"pname":pname,"typeid":typeid,"lprice":lprice,"hprice":hprice},
type: "post",
dataType: "text",
success: function (msg) {
alert(msg);
$("#table").load("http://localhost:8080/admin/product.jsp #table");
}
});
}
}
12. 删除商品(批量)
12.1 后端
mapper层的开发这么书写(底层mapper没有这个函数)
//批量删除商品的功能
int deleteBatch(String []ids);
其xml对数据库的CRUD操作具体如下:
对前端数据接收过来字符串进行获取,之后获取数据库内容
遍历出来的值就叫做item,具体的类型可以为map、list、array等
<!--
//批量删除商品的功能
int deleteBatch(String []ids); 1,4,5
-->
<delete id="deleteBatch" >
delete from product_info where p_id in
<foreach collection="array" item="pid" separator="," open="(" close=")">
#{pid}
</foreach>
</delete>
接口类:
这个pids是前端ajax传输过来的数据
//批量删除
public int deleteBatch(String[] pids);
接口实现类:
@Override
public int deleteBatch(String[] pids) {
return productInfoMapper.deleteBatch(pids);
}
具体其界面层这么书写:
通过获取的字符串进行删除逗号,转换成字符数组
request或者session主要是判断删除成功与否,之后返回的msg给前端的ajax请求数据(在success中),或者通过js中的整个页面的msg进行反应
//执行批量删除操作
@RequestMapping("/deleteBatch")
public String deleteBatch(String pids, HttpSession session) {
String []ps=pids.split(",");
try {
int num = productInfoService.deleteBatch(ps);
if (num > 0)
session.setAttribute("msg", "删除成功!");
else
session.setAttribute("msg", "删除失败!");
} catch (Exception e) {
session.setAttribute("msg","商品不可删除!");
}
//增删改后用重定向跳转
return "forward:/prod/dajaxsplit.action";
}
12.2 前端
其批量删除的按钮框如下:
<input type="button" class="btn btn-warning" id="btn1"
value="批量删除" onclick="deleteBatch()">
主要通过jquery进行引入
获取复选框的选中状态,将其全选状态赋值给其他的框中
给每个复选框进行遍历,给予其赋值
每个复选框统一都叫做name,给予赋值即可
//全选复选框功能实现
function allClick() {
//获得当前点击后全选按钮的状态
var flag = $("#all").prop("checked");
//将此状态赋值给下面五个复选框
$("input[name='ck']").each(function () {
this.checked = flag;
});
}
//单个复选框点击改变全选复选框功能实现
function ckClick() {
//得到下面五个复选框的个数
var fiveLength = $("input[name='ck']").length;
//得到下面五个复选框被选中的个数
var checkedLength = $("input[name='ck']:checked").length;
//进行对比,改变全选复选框的状态
if(fiveLength == checkedLength){
$("#all").prop("checked",true);
}else{
$("#all").prop("checked",false);
}
}
单个复选框影响全选框,一个一个判断比较慢,可以通过判断比较个数进行即可
具体判断是否有被选中,可以通过checked进行判断
通过ajax中的each(功能函数进行遍历)
this.value是一个dom对象,所以强转换成jquery的函数
获取复选框,判断复选框,之后将其拼接成字符串,通过点击的时候传输给后端,主要是为了在后端中处理字符串用,如果处理成功,前端的success会返回一些结果,并且也会刷新整个table
//批量删除
function deleteBatch() {
//得到所有选中复选框的对象,根据其长度判断是否有选中商品
var cks = $("input[name='ck']:checked"); //1,4,5
//如果有选中的商品
if(cks.length == 0){
alert("请先选择将要删除的商品!");
}else{
var str = "";
var pid = "";
if(confirm("您确定要删除"+cks.length+"条商品吗?")){
// alert("可以进行删除!");
//获取其value的值,进行字符串拼接
$.each(cks,function () {
pid = $(this).val();
//进行非空判断,避免出错
if(pid != null){
str += pid+","; //145 ===>1,4,5,
}
});
//发送ajax请求,进行批量删除的提交
$.ajax({
url:"${pageContext.request.contextPath}/prod/deleteBatch.action",
data:{"pids":str},
type:"post",
dataType:"text",
success:function (msg) {
alert(msg);//批量删除成功!失败!不可删除!
//将页面上显示商品数据的容器重新加载
$("#table").load("http://localhost:8080/admin/product.jsp #table");
}
});
}
}
}
13. 多条件查询(不分页)
13.1 后端
因为数据只有这么几个,可以把这几个数据封装成一个类
public class ProductVo {
//商品名称
private String pname;
//商品类型
private Integer typeid;
//最低价格
private Integer lprice;
//最高价格
private Integer hprice;
@Override
public String toString() {
return "ProductInfoVo{" +
"pname='" + pname + '\'' +
", typeid=" + typeid +
", lprice=" + lprice +
", hprice=" + hprice +
'}';
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public Integer getTypeid() {
return typeid;
}
public void setTypeid(Integer typeid) {
this.typeid = typeid;
}
public Integer getLprice() {
return lprice;
}
public void setLprice(Integer lprice) {
this.lprice = lprice;
}
public Integer getHprice() {
return hprice;
}
public void setHprice(Integer hprice) {
this.hprice = hprice;
}
public ProductVo(String pname, Integer typeid, Integer lprice, Integer hprice, Integer page) {
this.pname = pname;
this.typeid = typeid;
this.lprice = lprice;
this.hprice = hprice;
}
public ProductVo() {
}
}
实体类中已经和数据库中定义的列名进行了绑定,也就是名字进行了映射
具体查询的数据都封装在了一个类名中
<sql id="Base_Column_List" >
p_id, p_name, p_content, p_price, p_image, p_number, type_id, p_date
</sql>
具体内部的mapper.xml逻辑如下:
如果客户端没有提交这个name,也就是null的条件
如果提交了name而没有数据的话就是” ’ ‘ “
这个空的条件,但是typeid
是-1,因为前端的配置默认为-1
其他的数据同理
- 模糊查询的时候使用
%${pname}%
,之所以使用$这个符号,是因为可以sql注入 - 小于等于
>
,大于等于<
- 返回的类型是实体类,映射的类型是上面的类型,id是方法名
<!-- 多条件查询商品
List<ProductInfo> selectCondition(ProductInfoVo vo);
-->
<select id="selectCondition" parameterType="com.manongyanjiuseng.pojo.vo.ProductVo" resultMap="BaseResultMap">
select <include refid="Base_Column_List"></include>
from product_info
<!--拼接条件-->
<where>
<!--商品名称不为空,拼接商品名称模糊查询-->
<if test="pname != null and pname !=''">
and p_name like '%${pname}%'
</if>
<!--商品类型不为空,拼接商品类型查询条件-->
<if test="typeid != null and typeid != -1">
and type_id =#{typeid}
</if>
<!--如果最低价格不为空,最高价格为空,则查询大于最低价格的所有商品-->
<if test="(lprice != null and lprice != '') and (hprice == null or hprice == '')">
and p_price >= #{lprice}
</if>
<!--如果最高价格不为空,最低价格为空,则查询小于最高价格的所有商品-->
<if test="(hprice != null and hprice !='') and (lprice == null or lprice == '')">
and p_price <= #{hprice}
</if>
<!--如果最高和最低价格都不为空,则查询介于最高价格和最低价格之间的所有商品-->
<if test="(lprice !=null and lprice !='') and (hprice != null and hprice != '')">
and p_price between #{lprice} and #{hprice}
</if>
</where>
order by p_id desc
</select>
验证上面的sql数据查询是否有误,通过一个测试文件进行测试
- 接管单元测试
@RunWith(SpringJUnit4ClassRunner.class)
- 添加配置文件,主要为了引用配置文件
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext_dao.xml","classpath:applicationContext_service.xml"})
public class Mytest2 {
@Autowired
ProductInfoMapper productInfoMapper;
@Test
public void testSelectCondition(){
ProductVo vo =new ProductVo();
List<ProductInfo> list =productInfoMapper.selectCondition(vo);
list.forEach(ProductInfo -> {
System.out.println(ProductInfo);
});
}
}
测试截图如下:
输出的时候还会输出sql的日志文件
主要是添加了这个配置文件
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
言归正传如果在界面层和逻辑数据中应该这么实现:
接口类:
//多条件分页查询
public List <ProductInfo> selectCondition(ProductVo vo);
接口实现类:
@Override
public List<ProductInfo> selectCondition(ProductVo vo){
return productInfoMapper.selectCondition(vo);
}
界面层代码:
//多条件的查询功能实现
@ResponseBody
@RequestMapping("/condition")
public void condition(ProductVo vo,HttpSession session){
List<ProductInfo> list = productInfoService.selectCondition(vo);
session.setAttribute("list", list);
}
13.2 前端
typelist是类型,在java代码中是使用监听器传的
<div id="condition" style="text-align: center">
<form id="myform">
商品名称:<input name="pname" id="pname">
商品类型:<select name="typeid" id="typeid">
<option value="-1">请选择</option>
<c:forEach items="${typeList}" var="pt">
<option value="${pt.typeId}">${pt.typeName}</option>
</c:forEach>
</select>
价格:<input name="lprice" id="lprice">-<input name="hprice" id="hprice">
<input type="button" value="查询" onclick="condition()">
</form>
</div>
具体condition点击查询的函数:
function condition() {
//取出查询条件
var pname = $("#pname").val();
var typeid = $("#typeid").val();
var lprice = $("#lprice").val();
var hprice = $("#hprice").val();
$.ajax({
type:"post",
url:"${pageContext.request.contextPath}/prod/condition.action",
data:{"pname":pname,"typeid":typeid,"lprice":lprice,"hprice":hprice},
success:function () {
//刷新显示数据的容器
$("#table").load("http://localhost:8080/admin/product.jsp #table");
}
});
}
再这之前因为本身的table 表格,都是info.list,这是因为和分页的info结合在了一起
为了凸显查询的结果,先把table改为list进行遍历
结果如下:
全部都会显示在一个页面中
14. 多条件查询(分页)
14.1 后端
将上面的实体类改造成可以分页的
多加一个页码的参数,默认的时候是第一页
public class ProductVo {
//商品名称
private String pname;
//商品类型
private Integer typeid;
//最低价格
private Integer lprice;
//最高价格
private Integer hprice;
//设置页码
private Integer page=1;
@Override
public String toString() {
return "ProductInfoVo{" +
"pname='" + pname + '\'' +
", typeid=" + typeid +
", lprice=" + lprice +
", hprice=" + hprice +
", page=" + page +
'}';
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public Integer getTypeid() {
return typeid;
}
public void setTypeid(Integer typeid) {
this.typeid = typeid;
}
public Integer getLprice() {
return lprice;
}
public void setLprice(Integer lprice) {
this.lprice = lprice;
}
public Integer getHprice() {
return hprice;
}
public void setHprice(Integer hprice) {
this.hprice = hprice;
}
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public ProductVo(String pname, Integer typeid, Integer lprice, Integer hprice, Integer page) {
this.pname = pname;
this.typeid = typeid;
this.lprice = lprice;
this.hprice = hprice;
this.page = page;
}
public ProductVo() {
}
}
多了一个页码,以下都要进行改变
接口类:
返回的类型是上面实体类的类型
//多条件分页查询
public PageInfo<ProductInfo> splitPageVo(ProductVo vo, int pageSize);
接口实现类:
@Override
public PageInfo<ProductInfo> splitPageVo(ProductVo vo, int pageSize) {
//切记切记:在取集合之前,使用分页插件一定要先设置当前页和每页的个数
PageHelper.startPage(vo.getPage(),pageSize);
//取集合
List<ProductInfo> list=productInfoMapper.selectCondition(vo);
return new PageInfo<>(list);
}
界面层的实现具体如下:
//多条件的查询功能+分页显示
@ResponseBody
@RequestMapping("/ajaxSplitpaging")
public void ajaxSplitpaging(ProductVo vo, HttpSession session) {
PageInfo info = productInfoService.splitPageVo(vo, PAGE_SIZE);
session.setAttribute("info", info);
}
最后的结果如下所示:
当点击第二页的时候,会跳转到第一页,这主要是前端界面的数据还未整改
14.2 前端
前端界面的整改跳转如下:
主要是修改url的请求路径,都保持统一,而且data的数据要在前端进行上传,进行获取
<!--分页的AJAX实现-->
<script type="text/javascript">
function ajaxsplit(page) {
//取出查询条件
var pname = $("#pname").val();
var typeid = $("#typeid").val();
var lprice = $("#lprice").val();
var hprice = $("#hprice").val();
//向服务发出ajax请求,请示page页中的所有数据,在当前页面上局部刷新显示
$.ajax({
url: "${pageContext.request.contextPath}/prod/ajaxSplitpaging.action",
data: {"page": page,"pname":pname,"typeid":typeid,"lprice":lprice,"hprice":hprice},
type: "post",
success: function () {
//重新加载显示分页数据的容器
$("#table").load("http://localhost:8080/admin/product.jsp #table");
}
});
}
function condition() {
//取出查询条件
var pname = $("#pname").val();
var typeid = $("#typeid").val();
var lprice = $("#lprice").val();
var hprice = $("#hprice").val();
$.ajax({
type:"post",
url:"${pageContext.request.contextPath}/prod/ajaxSplitpaging.action",
data:{"pname":pname,"typeid":typeid,"lprice":lprice,"hprice":hprice},
success:function () {
//刷新显示数据的容器
$("#table").load("http://localhost:8080/admin/product.jsp #table");
}
});
}
14.3 完善其他功能
==为了保证如果修改的时候查不到数据,要有一个提示语==
如果数据没有的时候,就提示暂时没有符合条件的商品提示语
==多条件查询后点击更新后停留当前页==
加入页码的编辑
在主题函数中添加page的一些信息
function one(pid,page) {
//取出查询条件
var pname = $("#pname").val();
var typeid = $("#typeid").val();
var lprice = $("#lprice").val();
var hprice = $("#hprice").val();
//向服务器提交请求,传递商品id
var str = "?pid="+pid+"&page="+page+"&pname="+pname+"&typeid="+typeid+"&lprice="+lprice+"&hprice="+hprice;
location.href = "${pageContext.request.contextPath}/prod/one.action" + str;
}
后端的显示主要是:
分页的显示,以及其他的信息,引入改造的实体类
可以放置在session,不用专门去取
先修改点击编辑的时候,多一个session的数据,以及页码信息这些数据
//根据id查询回显显示商品信息
@RequestMapping("/one")
@RequestMapping("/one")
public String one(Integer pid,ProductVo vo, Model model,HttpSession session) {
ProductInfo info = productInfoService.getById(pid);
model.addAttribute("prod", info);
//将多条件及页码放入session中,更新处理结束后分页时读取条件和页码处理
session.setAttribute("prodvo",vo);
return "update";
}
之后跳转到更新按钮,更新所有的物品,选择提交的时候,会在提交的界面层,如果在第四页提交,返回的时候应该也还在第四页
具体更新的界面,点击提交的时候会跳转到ajax的提交页面上
//带有页面信息的分页显示(适合于多条件查询)
//分页显示
@RequestMapping("/split")
public String split(HttpServletRequest request) {
PageInfo info =null;
Object vo= request.getSession().getAttribute("prodvo");
if(vo!=null){
info=productInfoService.splitPageVo((ProductVo)vo,PAGE_SIZE);
request.getSession().removeAttribute("prodvo");//用完之后清理掉
}else{
info = productInfoService.splitPage(1, PAGE_SIZE);
}
request.setAttribute("info", info);
return "product";
}
同理删除的时候也一样
在前端的核心函数上添加:
其后端删除的逻辑修改为:
编辑的时候还有回显页面,
删除的时候,删除成功才要传到分页的页面,删除失败的时候不用传到分页的处理
之所以点击编辑的时候不这样,是因为还有个回显
执行删除操作:
//执行删除操作
@RequestMapping("/delete")
public String delete(Integer pid,ProductVo vo, HttpSession session) {
int num=-1;
try{
num = productInfoService.delete(pid);
}catch(Exception e){
e.printStackTrace();
}
if (num > 0) {
session.setAttribute("msg", "删除成功!");
session.setAttribute("deleteProdVo", vo);
}
else{
session.setAttribute("msg", "删除失败!");
}
//增删改后用重定向跳转
return "forward:/prod/dajaxsplit.action";
}
跳转到分页处理的删除结束后的操作如下:
/* //删除后分页显示,切记切记切记:坑:使用@ResponseBody注解,返回值不能是String,
// 如果一定要使用String,则手工封装成JSON格式
@ResponseBody
@RequestMapping(value = "/dajaxsplit",produces = "text/html;charset=UTF-8")
public Object dajaxsplit(ProductInfo info, HttpSession session) {
session.setAttribute("prod", info);
saveFileName = "";
//手工封装返回删除成功或删除失败字符串为JSON格式
String s=session.getAttribute("msg").toString();
JSONObject object=new JSONObject();
object.put("msg",s);
return object.toString();
}*/
//删除后分页显示,切记切记切记:坑:使用@ResponseBody注解,返回值不能是String,
// 如果一定要使用String,则手工封装成JSON格式
@ResponseBody
@RequestMapping(value = "/dajaxsplit",produces = "text/html;charset=UTF-8")
public Object dajaxsplit(HttpServletRequest request) {
PageInfo info=null;
Object vo= request.getSession().getAttribute("deleteProdVo");
if(vo!=null){
info=productInfoService.splitPageVo((ProductVo)vo,PAGE_SIZE);
}else{
info = productInfoService.splitPage(1, PAGE_SIZE);
}
request.getSession().setAttribute("info", info);
return request.getAttribute("msg");
}