框架
一套规范。
实际是他人实现的一系列接口和类的集合。通入导入对应框架的jar文件(maven项目导入对应的依赖),进行适当的配置,就能使用其中的所有内容。
开发者可以省去很多模板代码,如dao中的CRUD,MVC模式下层与层之间的关联。只需要集中精力实现项目中的业务逻辑部分。
Java主流框架
Spring、SpringMVC、MyBatis、MyBatisPlus、Hibernate、JPA等。
SSH:最初是Spring+Stucts2+Hibernate组成,之后Stucts2被SpringMVC取代。
SSM:Spring+SpringMVC+MyBatis
新项目使用SpringBoot,早起的SSH项目由于维护成本高,基本不会推翻重做,但会维护一些SSM项目。
无论是SSH还是SSM,Spring、SpringMVC必不可少。从2004年推出至今,依旧是主流框架中不可获取的一部分。
Spring
概念
一个轻量级开源的Java框架。是一个管理项目中对象的容器,同时也是其他框架的粘合器,目的就是对项目进行解耦。
轻量级:对原有代码的侵入很小。
Spring的核心是IOC控制反转和AOP面向切面编程
组成
名词解释
IOC
Inversion Of Control 控制反转
DI
Dependency Injection 依赖注入
举例说明
用代码描述场景:员工食堂每天提供事物
-
食物:米饭类
public class Rice{ public Rice(){ sout("今天吃米饭"); } }
-
食物:面条类
public class Noodles{ public Noodles(){ sout("今天吃面条"); } }
-
厨师类
public class Cook{ //定义一个做饭的方法,做什么饭new什么对象,创建食物对象的权限,是由当前厨师类决定。 public void cooking(){ //new Rice(); new Noodles(); } }
-
员工:main方法
psvm(){ Cook cook = new Cook(); cook.cooking(); }
这种方式,Cook类中的cooking()方法创建什么对象,就输出什么内容。(厨师做什么,员工就吃什么)。
如果有人想要吃指定食物,就要修改源代码cooking()方法中创建的对象。
解决方案:定义一个接口:Food
public interface Food{
void info();
}
让原本的所有食物Rice类和Noodles类实现该接口
public class Rice implements Food{
@Override
public void info(){
sout("今天吃米饭");
}
}
public class Noodles implements Food{
@Override
public void info(){
sout("今天吃面条");
}
}
给厨师Cook类中cooking()方法定义一个参数:Food接口
public class Cook{
public void cooking(Food food){
food.info();
}
}
这时Cook对象调用cooking()方法时,需要提供一个Food接口类型的实现类。
psvm(){
Cook cook = new Cook();
//传递什么参数,旧调用该参数重写后的方法
cook.cooking(new Rice());
cook.cooking(new Noodles());
}
整个过程中,将创建什么食物对象的控制权,由厨师交给用户,这就是控制反转(IOC)。
厨师对象调用cooking()方法的参数,就是对于食物Food对象的依赖,是由用户注入进来的,这就是依赖注入(DI)。
这样一来,各个对象之间互相独立(没有在某个类中new另一个类的对象),降低了代码之间的耦合度。
总结:控制反转(IOC)是一种思想,就是让创建对象的控制权由自身交给第三方,控制反转这种思想,通过依赖注入(DI)的方式实现。
IOC和DI其实都是在描述控制反转,IOC是思想,DI是具体实现方式。
这里的第三方,就是Spring。Spring是一个容器,可以管理所有对象的创建和他们之间的依赖关系。
可以理解为:“Spring就是用来管理对象的,在需要用到某个对象的时候,帮我们自动创建”。
如Servlet+JSP模式写Web项目时,会在控制层Servlet中创建业务逻辑层Service对象,在Service层中创建数据访问层Dao对象。
有了Spring后,就不会出现new这些对象的代码了。
Spring需要导入对应的jar文件后,定义一个配置文件,在该配置文件中配置程序运行过程中所需的对象。
AOP
Aspect Orintend Programming 面向切面编程
Spring控制台应用
1.创建一个普通的Maven项目,不选择模板
2.添加Spring核心依赖
jdk8用5.x版本
<!-- spring-context表示spring核心容器 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
3.创建一个Java类
package com.hqyj.spring01;
/*
* 定义一个普通的Java类
* PlainOrdinaryJavaObject pojo 相当于简化的javabean
* entity vo dto pojo 都是在描述实体类
* */
public class PlainOrdinaryJavaObject {
public void fun(){
System.out.println("一个PlainOrdinaryJavaObject(普通的Java类)对象");
}
}
思考
-
1.对象在什么时候创建?
默认情况下,在解析Spring配置文件的时候,自动创建一个对象。
-
2.如果不想在解析时自动创建怎么办?
在配置文件的某个
<bean>
标签中,添加一个"lazy-init=true"属性,表示该对象设置为懒加载,在初始化Spring容器时不会创建对象,只有在调用getBean()时才会创建对象
-
3.如果设置为懒加载,是不是每次调用getBean(),都会创建一个对象呢?
默认情况下,Spring容器只会创建一个对象。
在配置文件的某个
<bean>
标签中,添加一个"scope=‘prototype’"属性,表示每次调用getBean()方法,就会创建一个对象。该属性的值默认为"singleton",表示单例模式,只会创建一个对象。
总结:默认情况下,通过<bean>
标签定义的类,在Spring容器初始化时,创建一个对象。
bean标签常用属性
属性 | 作用 |
---|---|
class | 定义类的全限定名 |
id | 定义对象的名称 |
lazy-init | 是否为懒加载。默认值为false,在解析配置文件时就会创建对象。设置为true表示懒加载,只有在getBean()时才会创建对象。 |
scope | 单例/原型模式。默认值为singleton,表示单例模式,只会创建一个对象。设置为prototype,表示原型模式,每调getBean()就创建一个对象。 |
init-method | 初始化时触发的方法。在创建完该对象时自动调用的方法。该方法只能是无参方法,该属性的值只需要写方法名即可 |
destory-method | 销毁时触发的方法。Spring容器关闭时自动调用的方法,该方法只能是无参方法。只有在单例模式下有效。 |
属性注入
给某个bean添加属性的方式有两种:构造器注入和setter注入
setter注入
这种方式注入属性时,类中必须要有set方法
在bean标签中,加入<property></property>
标签,
该标签的name属性通常表示该对象的某个属性名,但实际是setXXX()方法中的XXX单词。
如有age属性,但get方法为getNianLing(),name属性就需要写成nianLing。
该标签的value属性表示给该类中的某个属性赋值,该属性的类型为原始类型或String。
该标签的ref属性表示给该类中除String以外的引用类型属性赋值,值为Spring容器中另一个bean的id。
<!--注入Car类对象并用set方式注入其属性-->
<bean class="com.hqyj.spring01.Car" id="c">
<!--该属性是字符串或原始类型,使用value赋值-->
<property name="brand" value="宝马"></property>
<!--name并不是类中是属性名,而是该属性对应的getXXX()方法中XXX的名称-->
<!--如Car类中有color属性,但get方法名为getColo(),这里就要写为colo-->
<property name="colo" value="白色"></property>
</bean>
<!--注入Person类对象并用set方式注入其属性-->
<bean class="com.hqyj.spring01.Person" id="p1">
<property name="name" value="王海"></property>
<property name="age" value="22"></property>
<!--属性是引用类型,需要通过ref赋值,值为另外的bean的id ref即references-->
<property name="car" ref="c"></property>
</bean>
构造方法注入
这种方式注入属性时,类中必须要有相应的构造方法
在bean标签中,加入<constructor-arg></constructor-arg>
标签,
该标签的name属性表示构造方法的参数名,index属性表示构造方法的参数索引。
赋值时,原始类型和字符串用value,引用类型用ref。
<!--注入Person类对象并用构造方法注入其属性-->
<bean class="com.hqyj.spring01.Person" id="p2">
<!--constructor-arg表示构造方法参数 name是参数名 index是参数索引-->
<constructor-arg name="name" value="张明"></constructor-arg>
<constructor-arg index="1" value="20"></constructor-arg>
<constructor-arg name="car" ref="c"></constructor-arg>
</bean>
复杂属性注入
/*
* 定义电影类
* */
public class Movie {
//电影名
private String movieName;
//导演
private String director;
//时长
private int duration;
//主演
private List<String> playerList;
//类型
private String movieType;
//放映时间,最终格式为yyyy/MM/dd HH:mm:ss
private String showTime;
}
List类型的属性
<!--注入Movie对象-->
<bean class="com.hqyj.spring01.Movie" id="movie1">
<property name="movieName" value="夏洛特烦恼"></property>
<property name="director" value="闫非、彭大魔"></property>
<property name="duration" value="87"></property>
<!--List类型属性赋值-->
<property name="playerList" >
<!--使用list标签-->
<list>
<!--如果集合中保存的是引用类型,使用ref标签-->
<!--如果集合中保存的是原始类型或字符串,使用value标签-->
<value>沈腾</value>
<value>艾伦</value>
<value>马丽</value>
</list>
</property>
<property name="movieType" value="喜剧"></property>
<property name="showTime" value="2010/06/01 0:0:0"></property>
</bean>
Set类型的属性
<!--注入PetShop对象-->
<bean class="com.hqyj.spring01.test3.PetShop" id="petShop">
<property name="shopName" value="xxx宠物店"></property>
<property name="petSet">
<!--Set类型的属性-->
<set>
<!--如果中保存的是原始类型或字符串,使用value子标签-->
<!--如果保存的是引用类型,使用ref子标签加bean属性设置对应的id-->
<ref bean="p1"></ref>
<ref bean="p2"></ref>
<ref bean="p3"></ref>
</set>
</property>
</bean>
Map类型的属性
<!--注入Cinema对象-->
<bean class="com.hqyj.spring01.Cinema" id="cinema">
<property name="name" value="万达影城"></property>
<property name="timeTable">
<!--Map类型属性赋值,标明键和值的类型-->
<map key-type="java.lang.Integer" value-type="com.hqyj.spring01.Movie">
<!--entry标签表示键值对 如果键值对都是原始类型或字符串,使用key和value-->
<!--如果键值对中有引用类型,使用key-ref或value-ref-->
<entry key="1" value-ref="movie1"></entry>
<entry key="2" value-ref="movie2"></entry>
</map>
</property>
</bean>
属性值如果通过某个方法调用而来
如使用String保存yyyy/MM/dd格式的日期,需要通过SimpleDateFormat对象调用parse()方法而来
<!--
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
-->
<!--注入SimpleDateFormat对象,设置日期格式-->
<bean class="java.text.SimpleDateFormat" id="sdf">
<!--如果构造方法注入时,该构造方法只有一个参数,可以不用写name或index-->
<constructor-arg value="yyyy/MM/dd"></constructor-arg>
</bean>
<!--
Date now = new Date();
-->
<!--注入Date对象-->
<bean class="java.util.Date" id="now"></bean>
<!--注入Pet对象-->
<bean class="com.hqyj.spring01.test3.Pet" id="p1">
<property name="petType" value="哈士奇"></property>
<property name="petNickName" value="小哈"></property>
<!--使用当前时间作为该属性的值,以yyyy/MM/dd-->
<!--
sdf.format(now);
-->
<property name="petBirthday">
<!--如果某个值是通过某个bean调用了某个方法而来-->
<bean factory-bean="sdf" factory-method="format">
<!--方法的实际参数-->
<constructor-arg ref="now"></constructor-arg>
</bean>
</property>
</bean>
属性自动注入autowire
以上所有案例中,如果要在A对象中注入一个引用类型的对象B,都是手动将对象B注入到对象A中。
如在Person中注入Car对象,在Cinema中注入Movie等。
这种情况下,如果当某个被注入的bean的id更改后,所有引用了该bean的地方都要进行修改。
所以将手动注入更改为自动注入(自动装配),就无需添加相应的<property>
标签,甚至可以无需定义bean的id。
实现自动注入
在某个bean标签中,加入autowire属性,设置值为"byName"或"byType"。通常设置为"byType"。
Car类
package com.hqyj.spring01.test1;
import java.util.HashMap;
public class Car {
private String brand;
private String color;
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", color='" + color + '\'' +
'}';
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
Person类
package com.hqyj.spring01.test1;
public class Person {
private String name;
private int age;
private Car car;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", car=" + car +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Car getCar() {
return car;
}
//自动注入时,必须要有该方法
//byType方式自动注入,会自动在容器中寻找该方法参数类型
//byName方式自动注入,会自动在容器中寻找该方法setCar中的car这个id
public void setCar(Car car) {
this.car = car;
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--在容器中注入Car类型的bean-->
<bean class="com.hqyj.spring01.test1.Car" id="car">
<property name="brand" value="宝马"></property>
<property name="colo" value="白色"></property>
</bean>
<!--注入Person类的bean-->
<!--autowire="byType"表示自动检测该类中是否需要使用引用类型参数,如果容器中正好有唯一的一个对应类型的bean,就会自动赋值给对应的属性-->
<bean class="com.hqyj.spring01.test1.Person" id="person" autowire="byType">
<property name="name" value="赵明"></property>
<property name="age" value="26"></property>
</bean>
<!--autowire="byName"表示自动检测该类中的setXXX方法,如果某个setXXX方法的XXX和容器某个对象的id对应,就会自动赋值给对应的属性-->
<bean class="com.hqyj.spring01.test1.Person" id="person2" autowire="byName">
<property name="name" value="王海"></property>
<property name="age" value="26"></property>
</bean>
</beans>
Main
package com.hqyj.spring01.test1;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
Person person = context.getBean("person", Person.class);
//此时打印时,会输出姓名、年龄和Car对象
System.out.println(person);
context.close();
}
}
autowire属性的值
-
byType
- 类中要有被注入的属性的setter()方法
- 被自动注入的对象可以没有id
- Spring容器中,某个对象的类型要与该setter()方法的参数类型一致,且容器中只有一个该类型的对象。
- 如setCar(Car c),Spring就会自动在容器中寻找类型为Car的对象自动装配
-
byName
- 类中要有被注入的属性的setter()方法
- 被自动注入的对象必须要有id
- 实际是根据setXXX()方法set后的单词XXX关联
- 如setCar(Car c),Spring就会自动在容器中寻找id为car的对象自动装配
在Web项目中,可以利用自动装配,在控制层中自动装配业务逻辑层的对象,在业务逻辑层中自动装配数据访问层的对象。
属性自动注入练习
在电影院案例的基础上
1.创建一个dao层CinemaDao类,包含Cinema对象,定义一个fun()方法,输出Cinema对象
package com.hqyj.spring01.test2.dao;
import com.hqyj.spring01.test2.Cinema;
/*
* 定义数据访问层
* 模拟查询数据库得到结果
* */
public class CinemaDao {
private Cinema cinema;
public void setCinema(Cinema cinema) {
this.cinema = cinema;
}
public void fun(){
System.out.println(cinema);
}
}
2.创建一个controller层CinemaController类,包含CinemaDao对象,定义方法fun()调用CinemaDao中的fun()方法
package com.hqyj.spring01.test2.controller;
import com.hqyj.spring01.test2.dao.CinemaDao;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/*
* 定义控制层类,该层需要使用下一层dao中的方法
* */
public class CinemaController {
//这里无需创建对象,让Spring自动注入CinemaDao对象
private CinemaDao cinemaDao;
public void setCinemaDao(CinemaDao dao) {
this.cinemaDao = dao;
}
public void fun(){
cinemaDao.fun();
}
}
最终在Main方法中获取Controller对象后,调用fun()
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("movie.xml");
CinemaController controller = context.getBean("controller", CinemaController.class);
controller.fun();
}
配置文件
<!--省略movie对象-->
<!--注入Cinema对象-->
<bean class="com.hqyj.spring01.test2.Cinema" id="cinema">
<property name="name" value="万达影城"></property>
<property name="timeTable">
<!--Map类型属性赋值,标明键和值的类型-->
<map key-type="java.lang.Integer" value-type="com.hqyj.spring01.test2.Movie">
<!--entry标签表示键值对 如果键值对都是原始类型或字符串,使用key和value-->
<!--如果键值对中有引用类型,使用key-ref或value-ref-->
<entry key="1" value-ref="movie1"></entry>
<entry key="2" value-ref="movie2"></entry>
</map>
</property>
</bean>
<!--该类中有一个Cinema类型的属性,这里使用了autowire=byType自动装配,会在容器中寻找Cinema类型的对象自动赋值给属性-->
<bean class="com.hqyj.spring01.test2.dao.CinemaDao" autowire="byType"></bean>
<!--该类中有一个CinemaDao类型的属性,这里使用了autowire=byType自动装配,会在容器中寻找CinemaDao类型的对象自动赋值给属性-->
<bean class="com.hqyj.spring01.test2.controller.CinemaController" id="controller" autowire="byType"></bean>
AOP
概念
Process Oriented Programming 面向过程编程POP
Object Oriented Programming 面向对象编程OOP
Aspect Oriented Programming 面向切面编程AOP
以上都是编程思想,但AOP不是OOP和POP的替代,而是增强、拓展和延伸。主流编程思想依然是OOP。
作用
在传统的OOP思想中,我们将程序分解为不同层次的对象,通过封装、继承、多态等特性,
将对象组织成一个整体来完成功能。但在某些场景下,OOP会暴露出一些问题。
如在处理业务中,除了核心的业务代码外,通常还会添加一些如果参数验证、异常处理、事务、记录日志等操作。
这些内容会分散在各个业务逻辑中,依旧会出现大量重复操作。如果将这些重复的代码提取出来,在程序编译运行时,
再将提出来的内容应用到需要执行的地方,就可以减少很多代码量。方便统一管理,更专注于核心业务。
简单来说,就是将不同位置中重复出现的一些事情拦截到一处进行统一处理。
如图,这是用户模块的不同操作,其中除了核心业务之外,其余都是重复代码
将相同的位置进行切分
切分后
使用
一个普通业务层service类
package com.hqyj.springweb.service;
import com.hqyj.springweb.dao.HeroDao;
import com.hqyj.springweb.entity.Hero;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/*
* 一个普通业务层service类
* */
@Service
public class HeroService {
@Autowired
private HeroDao dao;
public List<Hero> queryAll() {
//在没有使用AOP时,比如要在执行前记录日志
//System.out.println("============");
//System.out.println("操作开始执行"+new Date());
System.out.println("xxx进行了查询操作");
//核心业务代码
List<Hero> list=dao.queryAll();
//在没有使用AOP时,比如要在执行后记录日志
//System.out.println("操作执行结束"+new Date());
//System.out.println("============");
return list;
}
public void delete(int id) {
//在没有使用AOP时,比如要在执行前记录日志
//System.out.println("============");
//System.out.println("操作开始执行"+new Date());
System.out.println("xxx删除了"+id);
//核心业务代码
dao.delete(id);
//在没有使用AOP时,比如要在执行后记录日志
//System.out.println("操作执行结束"+new Date());
//System.out.println("============");
}
}
提取在执行某个业务方法前后要执行的公共代码
package com.hqyj.springweb;
import org.springframework.stereotype.Component;
import java.util.Date;
/*
* 模拟各个业务中都要执行的记录日志的操作,单独定义在一个类中
* 将其注入到Spring容器中
* */
@Component
public class LogOperate {
public void start(){
System.out.println("============");
System.out.println("操作开始执行"+new Date());
}
public void end(){
System.out.println("操作执行结束"+new Date());
System.out.println("============");
}
}
在项目中添加AOP所需依赖
<!-- 切面织入包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.19</version>
<scope>runtime</scope>
</dependency>
在Spring配置文件中配置AOP
<!--配置AOP-->
<aop:config>
<!--配置切面(配置拦截后要做的事情)-->
<!--ref是提取出的公共代码所在类LogOperate在Spring容器中的id。由于使用了@Componet注解,默认id名为类名首字母小写-->
<aop:aspect ref="logOperate">
<!--配置切点(对哪个包中的哪个类、哪个方法执行的时候进行拦截)-->
<!--id是切点名,自定义-->
<aop:pointcut id="logPointcut" expression="execution(* com.hqyj.springweb.service..*.*(..))"/>
<!--配置前置增强(切点之前执行的方法)-->
<aop:before method="start" pointcut-ref="logPointcut"></aop:before>
<!--配置后置增强(切点之后执行的方法)-->
<aop:after method="end" pointcut-ref="logPointcut"></aop:after>
</aop:aspect>
</aop:config>
上面的配置代码中execution是切点指示符,小括号中是一个切点表达式,用于配置需要切入后增强处理的方法的特征
execution(* com.hqyj.springweb.service..*.*(..))
第一个*表示匹配所有方法的返回值
com.hqyj.springweb.service 表示匹配指定包
..* 表示匹配该包下的所有子包及所有类
.* 表示匹配该类下的所有方法
(..) 表示匹配所有参数类型和个数
整体表示在com.hqyj.springweb.service包中的一切类中的一切方法在执行前后,都会进入切入增强处理。
MVC
MVC设计思想并不是某个语言特有的设计思想,而是一种通用的模式。
是将一个应用分为三个组成部分:Model模型,View视图,Controller控制器
这样会降低系统的耦合度,提高它的可扩展性和维护性。
SpringMVC
在Web阶段中,控制层是由Servlet实现,传统的Servlet,需要创建、重写方法、配置映射。使用时极不方便,SpringMVC可以替换Servlet。
SpringMVC是Spring框架中位于Web开发中的一个模块,是Spring基于MVC设计模式设计的轻量级Web框架。
SpringMVC提供了一个DispatcherServlet的类,是一个Servlet。它在指定映射(通常设置为/或*.do)接收某个请求后,调用相应的模型处理得到结果,再通过视图解析器,跳转到指定页面,将结果进行渲染。
原理大致为:配置SpringMVC中的DispatcherServlet,将其映射设置为/或.do。*
如果是/表示一切请求先经过它,如果是*.do表示以.do结尾的请求先经过它,
它对该请求进行解析,指定某个Controller中的某个方法,这些方法通常返回一个字符串,
这个字符串是一个页面的名称,再通过视图解析器,将该字符串解析为某个视图的名称,跳转到该视图页面。
详细流程
使用SpringMVC
1.创建webapp项目
- 修改web.xml版本为4.0
- 创建java和resources目录
- 创建包结构
2.添加依赖
<!-- spring-webmvc -->
<!-- 这个依赖会包含spring-web和spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.23</version>
</dependency>
3.配置初始化Spring
-
在resources目录下创建Spring配置文件application.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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"> <!--设置扫描使用了注解的根包--> <context:component-scan base-package="com.hqyj.springmvc"></context:component-scan> </beans>
-
在web.xml中使用监听器ContextLoaderListener初始化Spring
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--配置全局监听器初始化Spring--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--定义全局参数读取Spring配置文件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:application.xml</param-value> </context-param> </web-app>
4.配置SpringMVC
-
在resources目录下创建配置SpringMVC的配置文件springmvc.xml
- 配置要扫描的控制层类所在的包名
- 配置内部资源视图解析器以及视图路径的前后缀
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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">
<!--扫描控制层所在的包--> <context:component-scan base-package="com.hqyj.springmvc.controller"></context:component-scan> <!--配置内部资源视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--最终控制层跳转的页面所在的路径及页面自身后缀名--> <!--jsp页面不建议直接通过浏览器访问。在WEB-INF目录下在资源,无法通过浏览器直接方法,所以将jsp保存在WEB-INF目录下,最好创建一个pages--> <property name="prefix" value="/WEB-INF/pages/"></property> <!--现阶段使用jsp输出数据,所以后缀为.jsp--> <property name="suffix" value=".jsp"></property> </bean>
- 在web.xml中配置DispatcherServlet
- 将该Servlet的请求映射设置为/,表示所有请求都会访问该Servlet,由该Servlet再进行分发
```xml
<!--配置DispatcherServlet-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--设置该Servlet的初始化参数,用于读取SpringMVC配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--设置该servlet的映射为/或*.do-->
<url-pattern>/</url-pattern>
</servlet-mapping>
5.在WEB-INF目录下创建一个pages目录,在其中创建一个welcome.jsp页面
- 通常jsp页面不允许被浏览器直接访问,需要保存在WEB-INF目录下
6.编写控制层代码
-
在controller包下创建一个类,加上@Controller注解
-
该类中定义的方法方法加入@RequestMapping()注解表示访问该方法的映射
-
该类中定义的方法返回值通常为字符串,表示某个页面的名称,也可以是另一个controller中的方法映射名
package com.hqyj.springmvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; //加入@Controller注解表示该类是一个控制层类,替换之前的servlet @Controller //该注解如果只设置请求映射,直接填字符串 @RequestMapping("/first") public class HelloController { //该注解如果还有其他参数要设置,路径用path赋值 @RequestMapping(path="/hello") public String hello(){ //返回的字符串是某个页面的名称或另一个控制层中方法的请求映射 return "welcome"; } }
将项目部署到tomcat后,访问http://localhost:8080/SpringMVC_war_exploded/first/hello,即可跳转到指定页面
SpringMVC相关注解
-
@Controller
- 只能写在类上,表示该类属于一个控制器
-
@RequestMapping(“/请求映射名”)/@RequestMapping(value=“/请求映射名”)/@RequestMapping(path=“/请求映射名”)
- 该注解可以写在类或方法上。写在类上用于区分功能模块,写在类上用于区分具体功能
- 默认写一个属性或value或path后的值,都表示访问该类或该方法时的请求映射
-
@RequestMapping(value=“/请求映射名”,method=RequestMethod.GET/POST/PUT/DELETE)
- method属性表示使用哪种请求方式访问该类或该方法
- 如果注解中不止一个属性,每个属性都需要指定属性名
-
**@GetMapping(“/请求映射名”)**相当于@RequestMapping(value=“/请求映射名”,method=RequestMethod.GET)
- post、put、delete同理
- @GetMapping只能写在方法上
-
@PathVariable
-
该注解写在某个方法的某个形参上
-
通常配合@RequestMapping(“/{path}”)获取请求时传递的参数
@RequestMapping("/{path}") public String fun(@PathVariable("path") String pageName){ return pageName; } //当前方法如果通过"localhost:8080/项目名/hello"访问,就会跳转到hello.jsp //当前方法如果通过"localhost:8080/项目名/error"访问,就会跳转到error.jsp //映射中的/{path}就是获取路径中的hello或error,将其赋值给形参 //通常用于跳转指定页面
-
-
@RequestParam(value=“传递的参数名”,defaultValue =“没有传递参数时的默认值”)
- 该注解写在某个方法的某个参数上
- 用于获取提交的数据,可以设置默认值在没有提交数据时使用
控制层中获取请求时传递的参数
-
controller中方法的形参名和表单的name或?后的参数名一致
表单或a标签
<form action="${pageContext.request.contextPath}/first/sub"> <input type="text" name="username"> <input type="text" name="password"> <input type="submit" > </form> <a href="${pageContext.request.contextPath}/first/sub?username=admin">xxx</a>
controller
public String login(String username,int password){
//此时可以正常获取
//无关数据类型,但是提交数据时必须是对应的类型,否则会有400错误
}
-
controller中方法的形参名和表单的name或?后的参数名不一致
controller
public String login(@RequestParam(value="username",defaultValue="admin")String name,@RequestParam("username")int pwd){ //如果没有提交数据时,会使用设置的默认值 }
-
如果传递的参数都是某个实体类中的属性时
User类
package com.hqyj.springmvc.entity; public class User { private String username; private String password; @Override public String toString() { return "User{" + "username='" + username + '\'' + ", password='" + password + '\'' + '}'; } public User() { } public User(String username, String password) { this.username = username; this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
controller
@RequestMapping("/login") public String login(User user){ //某个实体类的属性和提交的参数名一致时,可以直接将对象作为形参,会自动将对应参数赋值给对应属性 System.out.println(user); return ""; }
解决提交数据时的中文乱码
使用过滤器解决中文乱码。
在web.xml中配置spring-web提供的CharaterEncodingFilter过滤器
<!--定义解决中文乱码的过滤器CharacterEncodingFilter-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--设置编码格式-->
<init-param>
<!--该过滤器需要设置一个encoding属性,用于设置编码格式-->
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<!--设置将什么请求经过该过滤器,通常设置为/*表示一切请求先经过该过滤器-->
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
将数据保存到作用域中
-
在controller中的某个方法的参数列表里添加Model参数
- 调用Model对象的addAttribute(String str,Object obj)方法,将某个obj对象保存在request作用域中,命名为str
@RequestMapping("/queryAll") public String queryAll(Model model){ ArrayList list = new ArrayList<>(); list.add("qwe"); list.add(123); list.add(true); list.add("哈哈"); //将list保存到request作用域中 model.addAttribute("list",list); //这种跳转属于请求转发 return "welcome"; }
-
在controller中的某个方法的参数列表里添加ModelAndView参数,同时将该方法的返回值设置为ModelAndView类型
- 使用ModelAndView对象调用addObject(String str,Object obj),将某个obj对象保存在request作用域中,命名为str
- 使用ModelAndView对象调用setViewName(String viewName),跳转到viewName页面
package com.hqyj.springmvc.controller; import com.hqyj.springmvc.service.HeroService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping(“/hero”)
//如果要将数据保存到session中,使用该注解定义session中对象的名称
@SessionAttributes({“str1”,“str2”})
public class HeroController {@Autowired private HeroService heroService; @RequestMapping("/queryAll") public String queryAll(Model model){ model.addAttribute("list", heroService.queryAll()); return "heroList"; } @RequestMapping("/showAll") public ModelAndView queryAll(ModelAndView mav){ //默认保存查询出的对象到request中,命名为list mav.addObject("list",heroService.queryAll()); //也可以保存到session //mav.addObject("str1",heroService.queryAll()); //设置要跳转的页面 mav.setViewName("heroList"); return mav; }
}
**数据都是保存在request作用域中**,在页面中使用jsp内置对象或EL获取。
如果要保存到session中,需要在类上加入@SessionAttributes({"str1","str2"})注解,str1,str2表示保存到session中的对象的名称,再在方法中,使用
Model对象的addAttribute(String str,Object obj)将其保存在session中。
# SpringMVC中的跳转
- 控制层跳转到某个jsp页面
- 在控制层中定义方法,这种方式跳转,属于请求转发
- 如果要使用重定向跳转,在页面名之前添加"redirect:"
```java
@RequestMapping("/hello")
public String hello(){
//返回页面名称
return "hello";//请求转发
}
@RequestMapping("/hello")
public ModelAndView hello(ModelAndView mav){
//设置页面名称
mav.setViewName("hello");
return mav;
}
-
在springmvc配置文件中
<mvc:view-controller path="请求映射" view-name="页面名称"></mvc:view-controller> <!-- 访问项目根目录,跳转到welcome.jsp页面 --> <mvc:view-controller path="/" view-name="welcome"></mvc:view-controller> <!-- 这个标签使用时,会让@RequesMapping失效,如果要共存,添加以下标签 --> <!--来自于xmlns:mvc="http://www.springframework.org/schema/mvc" --> <mvc:annotation-driven></mvc:annotation-driven>
-
控制层跳转到另一个控制层中的方法
-
方法的返回值为"redirect/forward:另一个控制层中方法的映射名"
@RequestMapping("/hello") public String hello(){ return "redirect:hero";//使用重定向的方式,跳转到映射名为hero的控制层 return "forward:hero"//使用请求转发的方式,跳转到映射名为hero的控制层 }
-
jsp页面跳转另一个jsp页面
-
当前项目中jsp页面都在WEB-INF下,无法直接访问,a标签同样如此,只能通过控制层跳转页面
-
定义用于跳转页面控制层类
package com.hqyj.springmvc.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; /* * 定义一个用于跳转指定页面或controller的控制层 * */ @Controller public class ToPageController { /* 项目启动时或直接访问根目录,跳转到指定的controller */ @RequestMapping("/") public String toIndex() { return "redirect:/hero/queryAll"; } /* * 这个方法的作用:会将请求中第一个/后的单词截取出来命名为path赋值给参数page * 如 localhost:8080/web/hero,就会识别出hero,return "hero"; * 就会跳转到 /WEB-INF/pages/hero.jsp页面 * */ @RequestMapping("/{path}") public String toPage(@PathVariable("path") String page) { return page; } }
-
这时在页面中这样跳转
<%--这个路径实际是/项目名/addHero,会截取addHero,跳转到/项目名/WEB-INF/pages/addHero.jsp--%> <a href="${pageContext.request.contextPath}/addHero">添加</a>
文件上传
使用apche提供的通用文件上传组件实现。
1.导入所需依赖
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
2.向Spring容器中注入上传文件的核心类
在application.xml中注入通用多部件解析器CommonsMultipartResolver
<!--注入上传文件的核心类:通用多部件解析器CommonsMultipartResolver-->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
<!--设置上传的单个文件最大字节 10M:1024*1024*10-->
<property name="maxUploadSizePerFile" value="10485760"></property>
</bean>
3.上传页面的表单
<!--上传的表单控件使用file-->
<!--提交方式为post-->
<!--添加enctype="multipart/form-data"属性-->
<form action="/upload" method="post" enctype="multipart/form-data">
请选择图片<input type="file" name="uploadFile">
<input type="submit" value="上传">
</form>
4.控制层获取上传的文件
package com.hqyj.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@Controller
@RequestMapping("/admin")
public class UploadController {
@RequestMapping("/upload")
public String upload(@RequestParam("uploadFile") MultipartFile uploadFile) throws IOException {//uploadFile就是上传的文件对象
//获取上传的文件名
String oldName = uploadFile.getOriginalFilename();
//得到源文件的后缀名
String prefix = oldName.substring(oldName.lastIndexOf("."));
//有可能不同的人上传的文件名相同,所以获取源文件的后缀后,生成一个随机文件名,拼接新文件名。
String newName = UUID.randomUUID()+prefix;
//创建一个文件File对象,
File file = new File("d:\\上传文件夹", newName);
//将文件写入硬盘中
uploadFile.transferTo(file);
return "welcome";
}
}
如果要在某个页面中显示上传的文件,要配置一个虚拟目录
配置Spring+SpringMVC时用到的关键类
-
在web.xml中配置Spring全局监听器
-
ContextLoaderListener
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:application.xml</param-value> </context-param>
-
-
在Springmvc配置文件中配置SpringMVC内部资源视图解析器
-
InternalResourceViewResolver
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"></property> <property name="suffix" value=".jsp"></property> </bean>
-
-
在web.xml中配置SpringMVC请求分发Servlet
-
DispatcherServlet
<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:springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
-
-
在web.xml中配置字符编码过滤器
-
CharacterEncodingFilter
<filter> <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> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>#
-
SSM项目搭建补充
通过db.properties文件保存连接数据库的信息
.properties文件称为属性文件,其中的数据以键值对(key=value)的形式保存
-
在resources目录下新建db.properties文件
DB_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver DB_URL=jdbc:mysql://localhost:3306/bookdb?serverTimezone=Asia/Shanghai DB_USERNAME=root DB_PASSWORD=root
-
在application.xml中读取properties文件
<!--读取properties文件--> <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
-
读取时使用EL表达式访问其中的键
<!--Druid数据源DruidDataSource--> <bean class="com.alibaba.druid.pool.DruidDataSource" id="druidDataSource"> <property name="driverClassName" value="${DB_DRIVER_CLASS_NAME}"></property> <property name="url" value="${DB_URL}"></property> <property name="username" value="${DB_USERNAME}"></property> <property name="password" value="${DB_PASSWORD}"></property> </bean>
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>
<!--设置-->
<settings>
<!--开启驼峰命名映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启SQL日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
MyBatis基本增删改查
dao层
package com.hqyj.ssm02.dao;
import com.hqyj.ssm02.entity.BookInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface BookInfoDao {
//查询所有
List<BookInfo> queryAll();
//根据id删除
int delete(int id);
//添加
int insert(BookInfo bookInfo);
//根据id查询
BookInfo findById(int no);
//修改时,参数通常为一个完整的修改对象
int update(BookInfo bookInfo);
//批量删除
//分页查询
//关键字分页
//如果dao层中某个方法不止1个参数,需要给每个参数添加@Param("参数名")注解,给该参数命名
//命名后,才能在mybatis的sql映射文件中使用该参数,即#{参数名}
//如这里的newPrice,在sql中用#{newPrice}获取
int update(@Param("newPrice") int bookPrice,@Param("newNum") int bookNum,@Param("updateId") int bookId);
}
service层
package com.hqyj.ssm02.service;
import com.hqyj.ssm02.dao.BookInfoDao;
import com.hqyj.ssm02.entity.BookInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookInfoService {
@Autowired
private BookInfoDao bookInfoDao;
public List<BookInfo> queryAll() {
return bookInfoDao.queryAll();
}
public boolean delete(int id) {
return bookInfoDao.delete(id) > 0;
}
public void insert(BookInfo bookInfo) {
bookInfoDao.insert(bookInfo);
}
public BookInfo findById(int no) {
return bookInfoDao.findById(no);
}
public void update(BookInfo bookInfo) {
bookInfoDao.update(bookInfo);
}
}
controller层
package com.hqyj.ssm02.controller;
import com.hqyj.ssm02.entity.BookInfo;
import com.hqyj.ssm02.entity.BookType;
import com.hqyj.ssm02.service.BookInfoService;
import com.hqyj.ssm02.service.BookTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@Controller
@RequestMapping("/bookInfo")
public class BookInfoController {
@Autowired
private BookInfoService bookInfoService;
//注入BookTypeService,用于获取所有图书类型
@Autowired
private BookTypeService bookTypeService;
@RequestMapping("/queryAll")
public String queryAll(Model model) {
List<BookInfo> list = bookInfoService.queryAll();
model.addAttribute("list", list);
return "bookInfoList";
}
@RequestMapping("/delete")
public String delete(int id) {//如果在页面中传递的参数名和方法的形参名一致,会自动获取数据赋值
if (bookInfoService.delete(id)) {
//增删改执行后,使用重定向跳转
return "redirect:queryAll";
}
return "error";
}
@RequestMapping("/insert")
//如果表单提交的参数和方法的形参名一致,自动获取并赋值
//如果表单提交的所有参数正好是一个实体类对象,可以用对应的实体类对象获取
public String insert(BookInfo bookInfo){
bookInfoService.insert(bookInfo);
return "redirect:queryAll";
}
@RequestMapping("/findById")
//如果表单提交的参数名和方法的参数名不一致,使用@RequestParam("提交的参数名")
public String findById(@RequestParam("id") int no,Model model){
//查询对应的图书信息
BookInfo byId = bookInfoService.findById(no);
model.addAttribute("book",byId);
//查询所有的图书类型,保存到请求中
List<BookType> bookTypeList = bookTypeService.queryAll();
model.addAttribute("btList",bookTypeList);
return "bookEdit";
}
@RequestMapping("/update")
public String update(BookInfo bookInfo){
bookInfoService.update(bookInfo);
return "redirect:queryAll";
}
}
sql映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hqyj.ssm02.dao.BookInfoDao">
<!--查询所有-->
<select id="queryAll" resultType="com.hqyj.ssm02.entity.BookInfo">
select *
from book_info
</select>
<!--根据id查询-->
<select id="findById" resultType="com.hqyj.ssm02.entity.BookInfo">
select *
from book_info
where book_id = #{no}
</select>
<!--根据id删除-->
<delete id="delete">
delete
from book_info
where book_id = #{id}
</delete>
<!--添加-->
<insert id="insert">
insert into book_info
values (null, #{typeId}, #{bookName}, #{bookAuthor}, #{bookPrice}, #{bookNum}, #{publisherDate}, #{bookImg})
</insert>
<!--修改-->
<update id="update">
update book_info
set book_price=#{newPrice},
book_num=#{newNum},
type_id=#{typeId}
where book_id = #{updateId}
</update>
</mapper>
使用BootStrap渲染数据
下载bootstrap的文件夹和jquery,保存到webapp根目录下。
由于在web.xml中,SpringMVC的核心类DispatcherServlet(请求分发器)的映射设置成了"/",表示所有请求,包含静态资源的请求都会交给SpringMVC处理。
解决无法引入静态资源的问题
-
在webapp目录下,新建一个目录,通常命名为static。将项目中的静态资源文件都保存于此。
-
在springmvc.xml中
<!--映射静态资源目录--> <!--location表示要映射的静态资源目录--> <!--mapping表示最终通过哪种方式进行访问。这里表示只要以/static开头的请求,都可以访问静态资源目录--> <mvc:resources mapping="/static/**" location="/static/"></mvc:resources> <!--开启注解驱动,有这个标签,SpringMVC就能区分哪个是资源文件,哪个是Controller--> <mvc:annotation-driven></mvc:annotation-driven>
导入BootStrap的样式和JS文件
<%--导入bootstrap的css文件--%>
<link href="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/css/bootstrap.min.css" rel="stylesheet"
type="text/css">
<%--导入jquery--%>
<script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js"></script>
<%--导入boostrap的js文件--%>
<script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/bootstrap.min.js"></script>
创建顶部导航页面
每个页面都需要这三句话,为了方便起见,给每个页面添加顶部导航页面top.jsp
这样其他页面只需要通过<jsp:include>
导入该页面的同时,使用BootStrap的样式和JS文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%--导入bootstrap的css文件--%>
<link href="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/css/bootstrap.min.css" rel="stylesheet"
type="text/css">
<%--导入jquery--%>
<script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/jquery-3.6.2.min.js"></script>
<%--导入boostrap的js文件--%>
<script src="${pageContext.request.contextPath}/static/bootstrap-3.4.1-dist/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Brand</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
<li><a href="#">Link</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Link</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
</ul>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
</body>
</html>
列表页面
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--使用jsp动作包含一个子页面,也能使用其中的css和js文件--%>
<jsp:include page="top.jsp"></jsp:include>
<div class="row" style="width: 100%">
<div class="col-md-2"></div>
<div class="col-md-8">
<table class="table-striped table">
<tr>
<th>图书编号</th>
<th>类型编号</th>
<th>图书名称</th>
<th>图书作者</th>
<th>图书价格</th>
<th>图书库存</th>
<th>出版时间</th>
<th >操作</th>
<th><a href="${pageContext.request.contextPath}/bookAdd" class="btn btn-default btn-sm">添加</a></th>
</tr>
<c:forEach var="book" items="${list}">
<tr>
<td>${book.bookId}</td>
<td>${book.typeId}</td>
<td>${book.bookName}</td>
<td>${book.bookAuthor}</td>
<td>${book.bookPrice}</td>
<td>${book.bookNum}</td>
<td>${book.publisherDate}</td>
<td><a href="${pageContext.request.contextPath}/bookInfo/findById?id=${book.bookId}" class="btn btn-primary btn-sm">编辑</a></td>
<td><a href="${pageContext.request.contextPath}/bookInfo/delete?id=${book.bookId}" class="btn btn-danger btn-sm">删除</a></td>
</tr>
</c:forEach>
</table>
</div>
<div class="col-md-2"></div>
</div>
</body>
</html>
详情页面
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<jsp:include page="top.jsp"></jsp:include>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<form class="form-horizontal" action="${pageContext.request.contextPath}/bookInfo/update" method="post">
<%--隐藏域提交id--%>
<input type="hidden" name="bookId" value="${book.bookId}">
<div class="form-group">
<label class="col-sm-2 control-label">图书名称</label>
<div class="col-sm-10">
<input type="text" readonly class="form-control" value="${book.bookName}" name="bookName" placeholder="请输入图书名称" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">图书作者</label>
<div class="col-sm-10">
<input type="text" readonly class="form-control" value="${book.bookAuthor}" name="bookAuthor" placeholder="请输入图书作者" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">图书类型</label>
<div class="col-sm-10">
<select class="form-control" name="typeId" >
<%--遍历所有的图书类型--%>
<c:forEach items="${btList}" var="bt">
<option value="${bt.typeId}" ${bt.typeId==book.typeId?"selected":""}>${bt.typeName}</option>
</c:forEach>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">图书价格</label>
<div class="col-sm-10">
<input type="number" min="1" class="form-control" value="${book.bookPrice}" name="bookPrice" placeholder="请输入图书价格" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">图书库存</label>
<div class="col-sm-10">
<input type="number" min="1" class="form-control" value="${book.bookNum}" name="bookNum" placeholder="请输入图书库存" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">出版时间</label>
<div class="col-sm-10">
<input type="date" readonly class="form-control" value="${book.publisherDate}" name="publisherDate" required>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">修改</button>
</div>
</div>
</form>
</div>
<div class="col-md-3"></div>
</div>
</body>
</html>
添加页面
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2023/1/30
Time: 14:17
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<jsp:include page="top.jsp"></jsp:include>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<form class="form-horizontal" action="${pageContext.request.contextPath}/bookInfo/insert" method="post">
<div class="form-group">
<label class="col-sm-2 control-label">图书名称</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="bookName" placeholder="请输入图书名称" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">图书作者</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="bookAuthor" placeholder="请输入图书作者" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">图书类型</label>
<div class="col-sm-10">
<select class="form-control" name="typeId">
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">图书价格</label>
<div class="col-sm-10">
<input type="number" min="1" class="form-control" name="bookPrice" placeholder="请输入图书价格" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">图书库存</label>
<div class="col-sm-10">
<input type="number" min="1" class="form-control" name="bookNum" placeholder="请输入图书库存" required>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">出版时间</label>
<div class="col-sm-10">
<input type="date" class="form-control" name="publisherDate" required>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">添加</button>
</div>
</div>
</form>
</div>
<div class="col-md-3"></div>
</div>
</body>
<script>
/*
使用ajax查询所有图书类型
在页面中使用ajax访问后端如果要得到数据,该数据必须是JSON格式
*/
$.ajax({
//请求地址
url: "${pageContext.request.contextPath}/bookType/queryAllToJson",
//访问成功后的回调函数
success: function (res) {//这里的res是所有类型对象的集合
for (var i = 0; i < res.length; i++) {
var $opt = $("<option value='" + res[i].typeId + "'>" + res[i].typeName + "</option>")
$("select[name=typeId]").append($opt);
}
}
});
</script>
</html>
在SpringMVC中,让某个控制层中的方法返回JSON格式的数据
-
添加依赖
<!--jackson:将数据转换为JSON格式--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.14.2</version> </dependency>
-
@ResponseBody注解
该注解可以加在类或方法上
- 如果加在方法上,表示该方法的返回值类型为JSON格式
- 如果加在类上,表示该类中的所有方法的返回值类型为JSON格式
package com.hqyj.ssm02.controller; import com.hqyj.ssm02.entity.BookType; import com.hqyj.ssm02.service.BookTypeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.util.List; @Controller @RequestMapping("/bookType") public class BookTypeController { @Autowired private BookTypeService bookTypeService;
//如果controller的某个方法返回一个JSON字符串,需要使用@ResponseBody @RequestMapping("/queryAllToJson") @ResponseBody//表示该方法无论返回值是什么,都返回JSON格式字符串 public List<BookType> queryAllToJson(){ List<BookType> list = bookTypeService.queryAll(); return list; }
}
# SSM项目中使用Ajax
ajax依赖于jquery,所以先保证页面中存在jquery.js。
```js
$.ajax({
url:"访问地址",
data:{
"提交的参数名":"实际值",
"提交的参数名":"实际值"
},
type:"get/post",
success:function(res){
//成功后的回调函数,res为访问后的结果,必须是json格式
}
});
在前端页面中使用ajax访问controller时,controller的返回值必须是一个JSON格式的字符串。
所以controller中的方法上要加入@ResponseBody注解#
使用Aajx实现注册和登录
表
当前表为customer,包含字段如下
使用该表中的phone和password作为登录时的账户和密码
实体类
package com.hqyj.ssm02.entity;
/*
* 对应customer表
* */
public class SysAdmin {
//用户名adminName属性对应phone字段
private String adminName;
//密码adminPwd属性对应password字段
private String adminPwd;
//省略get/set/toString
}
注册
注册时先判断用户名是否存在,如果存在则不能注册
-
dao
/*查询用户名是否存在,返回查询到的数量*/ int findByAdminName(String adminName); /*添加*/ int insert(SysAdmin sysAdmin);
-
mapper.xml
<!--查询用户名(phone)是否存在--> <select id="findByAdminName" resultType="java.lang.Integer"> select count(*) from customer where phone = #{adminName} </select> <!--添加用户--> <insert id="insert"> insert into customer values (null, #{adminName}, #{adminPwd}, 0, null) </insert>
-
service
/* * 检查注册的用户名是否存在 * */ public boolean findAdminName(SysAdmin sysAdmin) { //查询要注册的用户名是否存在 int i = sysAdminDao.findByAdminName(sysAdmin.getAdminName()); if (i != 0) { return false; } return true; } /* * 注册 * */ public boolean register(SysAdmin sysAdmin) { return sysAdminDao.insert(sysAdmin) > 0; }
-
controller
/* * 查询名称是否存在,返回boolean类型的json字符串 * */ @RequestMapping("/findAdminName") @ResponseBody public boolean findAdminName(SysAdmin sysAdmin) { return sysAdminService.findAdminName(sysAdmin); } /* * 注册成功后跳转到登录页面 * */ @RequestMapping("/register") public String register(SysAdmin sysAdmin) { sysAdminService.register(sysAdmin); return "login"; }
-
页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <style> .cus-portrait { margin: 0 auto; width: 100px; height: 100px; border: 1px solid #337ab7; text-align: center; line-height: 100px; border-radius: 50px; } </style> </head> <body> <jsp:include page="top.jsp"></jsp:include> <div class="col-md-4"></div> <div class="col-md-4"> <div class="panel panel-primary"> <div class="panel-heading">用户注册</div> <div class="panel-body"> <form action="${pageContext.request.contextPath}/sysAdmin/register" method="post"> <div class="form-group"> <label class="warning">用户名</label> <input type="text" class="form-control" required name="adminName" placeholder="请输入用户名"> </div> <div class="form-group"> <label>密码</label> <input type="password" class="form-control" required name="adminPwd" placeholder="请输入密码"> </div> <button type="submit" class="btn btn-default">注册</button> </form> </div> </div> </div> <div class="col-md-4"></div> <script> $(function () { //用户名文本框失去焦点触发 $("input[name=adminName]").blur(function () { $.ajax({ //访问controller url:"${pageContext.request.contextPath}/sysAdmin/findAdminName", data:{ //提交数据 "adminName":$(this).val() }, success:function(res){ //res是true、false。true表示该用户名不存在 if(res){ $(".warning").text("√").css("color","green"); $("button[type=submit]").removeAttr("disabled"); }else{ $(".warning").text("该用户名已存在").css("color","red"); $("button[type=submit]").attr("disabled","disabled"); } } }); }); }); </script> </body> </html>
登录
-
dao
/*登录*/ SysAdmin login(SysAdmin sysAdmin);
-
mapper.xml
<!--登录--> <!--如果查询的字段名和实体的属性名不一致,需要自定义查询结果集映射--> <select id="login" resultMap="loginMap"> select * from customer where phone = #{adminName} and password = #{adminPwd} </select> <!--自定义返回结果集映射--> <resultMap id="loginMap" type="com.hqyj.ssm02.entity.SysAdmin"> <!--表中的phone字段对应SysAdmin对象的adminName字段--> <result property="adminName" column="phone"></result> <result property="adminPwd" column="password"></result> </resultMap>
-
service
/* * 登录 * */ public SysAdmin login(SysAdmin sysAdmin){ return sysAdminDao.login(sysAdmin); }
-
controller
@RequestMapping("/login") @ResponseBody public SysAdmin login(SysAdmin sysAdmin,Model model) { SysAdmin login = sysAdminService.login(sysAdmin); //将登录成功的对象保存到session中 model.addAttribute("sysAdmin",login); return login; }
-
页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <jsp:include page="top.jsp"></jsp:include> <div class="col-md-4"></div> <div class="col-md-4"> <div class="panel panel-primary"> <div class="panel-heading">用户登录</div> <div class="panel-body"> <div class="form-group"> <label>用户名</label> <input type="text" class="form-control" required id="name" placeholder="请输入用户名"> </div> <div class="form-group"> <label>密码</label> <input type="password" class="form-control" required id="pwd" placeholder="请输入密码"> </div> <div class="form-group"> <label class="warning">验证码</label> <span class="vcode"></span> <input type="text" class="form-control inpVcode" required placeholder="请输入验证码"> </div> <a class="btn btn-default loginBtn">登录</a> <a class="btn btn-default" href="">注册</a> </div> </div> </div> <div class="col-md-4"></div> <script> //1000~9999 var vcode = Math.floor(Math.random() * 8999 + 1000); //显示验证码 $(".vcode").text(vcode).css("font-weight", "bolder"); //定义一个boolean值用于提交数据时判断验证码是否有误,默认false var goon=false; //bind("绑定事件名",满足时触发的函数) input propertychange表示监听文本框输入事件(只要内容有变化就触发) $(".inpVcode").bind("input propertychange", function () { if (vcode != $(this).val()) {//如果验证码有误,改变警告文字 $(".warning").text("验证码输入错误").css("color","#f00"); }else{ //如果输入正确,改变boolean值为true $(".warning").text("√").css("color","#0f0"); goon=true; } }); //登录按钮单击 $(".loginBtn").click(function () { //判断验证码 if (!goon) { return; } //使用ajax提交数据进行登录 $.ajax({ url:"${pageContext.request.contextPath}/sysAdmin/login", data:{ "adminName":$("#name").val(), "adminPwd":$("#pwd").val() }, success:function (res){ if(res!=""){ location.href="${pageContext.request.contextPath}/bookInfo/queryAll"; }else{ alert("用户名或密码错误"); } } }); }); </script> </body> </html>
在SpringMVC中使用Session
方式一:@SessionAttributes注解
由于SSM项目中,没有使用servlet,所以不能通过request.getSession()方法来获取session对象。
在控制器Controller中,在类上加入@SessionAttributes注解
**@SessionAttributes({“参数1”,“参数2”})**表示在session中保存两个参数
再在某个方法中,通过Model对象调用addAttribute(“参数1”,对象)方法将指定对象保存到session中
使用和销毁
@Controller
@RequestMapping("/sysAdmin")
//如果要将数据保存到session中,先使用该注解定义session中的参数名
@SessionAttributes({"sysAdmin"})
public class SysAdminController {
@Autowired
SysAdminService sysAdminService;
@RequestMapping("/login")
@ResponseBody
public SysAdmin login(SysAdmin sysAdmin,Model model) {
SysAdmin login = sysAdminService.login(sysAdmin);
//将登录成功的对象保存到session中
model.addAttribute("sysAdmin",login);
return login;
}
/*登出时销毁session*/
@RequestMapping("/logout")
public String logout(SessionStatus sessionStatus) {
//在方法中使用SessionStatus参数表示session状态对象
//调用setComplete()方法,将session设置为完成状态
sessionStatus.setComplete();
return "redirect:/login";
}
}
方式二:HttpSession参数
给项目添加javax.servlet.api依赖,给controller中某个方法添加HttpSession参数后,获取session使用
<!--如果要使用servlet相关内容-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
使用和销毁
@Controller
@RequestMapping("/sysAdmin")
//如果要将数据保存到session中,先使用该注解定义session中的参数名
@SessionAttributes({"sysAdmin"})
public class SysAdminController {
@Autowired
SysAdminService sysAdminService;
@RequestMapping("/login")
@ResponseBody
public SysAdmin login(SysAdmin sysAdmin,HttpSession session) {
SysAdmin login = sysAdminService.login(sysAdmin);
//将登录成功的对象保存到session中
session.setAttribute("sysAdmin",login);
return login;
}
/*登出时销毁session*/
@RequestMapping("/logout")
public String logout(HttpSession session) {
session.invalidate();
return "redirect:/login";
}
}
拦截器
每次请求controller时,都要经过的一个类。
当一个项目中有过滤器、拦截器和controller时的执行流程
实现过程
1.导入servlet依赖
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
2.自定义一个类,实现拦截器HandlerInterceptor接口
其中有三个default方法可以重写
- preHandle
- 在发送请求后,DispatcherServlet解析控制器中某个RequestMapping之前执行的方法
- 该方法返回true时,请求才能继续
- postHandle
- 在preHandle方法返回值为true后执行
- 在DispatcherServlet解析控制器中某个RequestMapping之后执行
- afterCompletion
- 在preHandle方法返回true后执行
- 在解析视图后执行的方法
这里只需重写preHandle方法即可
package com.hqyj.ssm02.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
* 自定义拦截器,用户拦截未登录时的请求
* */
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
//登录成功后,会在session中保存一个名为sysAdmin的对象
Object sysAdmin = request.getSession().getAttribute("sysAdmin");
//如果有对象,说明登录成功,可以放行return true
if(sysAdmin!=null){
return true;
}else{
response.sendRedirect(request.getContextPath()+"/login");
}
System.out.println(requestURI+"试图访问,拦截成功");
return false;
}
}
3.在springmvc.xml中配置拦截器
<!--配置拦截器们-->
<mvc:interceptors>
<!--配置某个拦截器-->
<mvc:interceptor>
<!--设置要拦截的请求,这里的/**表示拦截一切请求-->
<mvc:mapping path="/**"/>
<!--设置不拦截的请求-->
<!--放行登录和注册页-->
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/register"/>
<!--放行静态资源-->
<mvc:exclude-mapping path="/static/**"/>
<!--放行用户模块-->
<mvc:exclude-mapping path="/sysAdmin/**"/>
<!--注入指定的拦截器-->
<bean class="com.hqyj.ssm02.interceptor.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
SpringBoot
Spring推出的一个Spring框架的脚手架。
不是一个新的框架,而是搭建Spring相关内容框架的平台。
它省去了Spring、SpringMVC项目繁琐的配置过程,让开发变得更加简单。
本质还是Spring+SpringMVC,可以搭配其他的ORM框架,如MyBatis、MyBatisPlus、JPA、Hibernate等。
特点
- 内置了Tomcat服务器,不需要部署项目到Tomcat中
- 内置了数据源Hikari
- 减少了jar文件依赖的配置
- SpringBoot中的配置文件可以使用yml格式文件,代替properties或xml
创建SpringBoot项目
通过IDEA创建
通过官网模板创建
点击生成,会下载一个压缩文件,解压后通过IDEA打开。
无论哪种方式,都需要重写设置Maven配置文件
创建成功后的目录结构
第一个springboot项目的helloworld
热部署
项目在开发过程中,可以不需要每次都重启,等待一段时间后会自动更新编译运行
使用
添加依赖,可以在创建的项目的时候选择,也可以中途添加
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.7.8</version>
</dependency>
开启热部署
Lombok
用于简化实体类中模板代码的工具
使用
添加依赖,可以在创建的项目的时候选择,也可以中途添加
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
安装插件(IDEA2020.2之后的版本会内置Lombok插件,无需安装)
-
IDEA插件官网Versions: Lombok - IntelliJ IDEs Plugin | Marketplace (jetbrains.com)
-
IDEA内置插件市场搜索
在某个实体类上添加注解
lombok常用注解 | 作用 |
---|---|
@AllArgsConstructor | 自动生成全参构造方法 |
@Data | 以下注解之和 |
@Setter | 自动生成set方法 |
@Getter | 自动生成get方法 |
@NoArgsConstructor | 自动生成无参构造方法 |
@ToString | 自动生成toString方法 |
@EqualsAndHashcode | 自动生成equals和hashcode方法 |
SpringBoot+MyBatis实现单表查询
1.创建好SpringBoot项目
最好在创建的时候选择以下依赖
- spring-web(必选)
- lombok
- spring-devtools
- springboot集成mybatis
- mysql驱动
都可以后续添加
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.7.8</version>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
2.在pom.xml中添加mybatis集成SpringBoot依赖和数据库驱动
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<!--springboot集成MyBatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
3.在springboot配置文件application.properties中
#注释
#.properties文件称为属性文件,数据以键值对"键=值"的形式保存
#设置项目启动端口号
#server.port=9090
#设置项目上下文路径
#server.servlet.context-path=/springbootday1
#mybatis相关配置
#开启驼峰命名映射
mybatis.configuration.map-underscore-to-camel-case=true
#打印sql语句
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#扫描mybatis的sql映射文件(将mapper.xml文件保存在resources目录下的mapper目录下)
mybatis.mapper-locations=classpath:mapper/*.xml
#数据库信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/gamedb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
mybatis的sql映射文件模板
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--设置该文件对应的dao层接口全限定名-->
<mapper namespace="">
</mapper>
4.根据数据表创建实体类、dao层接口、service、controller
Hero
@Data
public class Hero{
private Integer id;
private String name;
private String position;
private String sex;
private Double price;
private String shelfDate;
}
dao
@Repository
public interface HeroDao{
List<Hero> queryAll();
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--设置该文件对应的dao层接口全限定名-->
<mapper namespace="com.xxx.dao.HeroDao">
<select id="queryAll" resultType="com.xxx.entity.Hero">
select * from hero
</select>
</mapper>
service
@Service
public class HeroService{
@Autowired
private HeroDao heroDao;
public List<Hero> queryAll(){
return heroDao.queryAll();
}
}
controller
@Controller
@RequestMapping("/hero")
public class HeroController{
@Autowired
private HeroService heroService;
@RequestMapping("/queryAll")
@ResponseBody
public List<Hero> queryAll(){
return heroDao.queryAll();
}
}
5.在SpringBoot的启动类上,加入@MapperScan注解,扫描dao层所在根包
@SpringBootApplication
//扫描dao层所在的根包
@MapperScan("com.hqyj.first.dao")
public class FirstApplication {
public static void main(String[] args) {
SpringApplication.run(FirstApplication.class, args);
}
}
SpringBoot+LayUI实现酒店客房管理
核心知识点
-
SpringBoot项目搭建
-
核心依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
-
集成MyBatis
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.0</version> </dependency>
-
-
新注解
-
@RestController
如果某个控制器类中的所有方法都要加@ResponseBody,可以在类上加@ResponseBody,也可以用@RestController代替@ResponseBody和@Controller
-
-
LayUI
- 数据表格
- 数据接口格式
- 真假分页
- 头工具栏事件
- 行内事件
- 单元格编辑事件
- 弹出层
- layer.msg()
- layer.confirm()
- layer.open()
- 数据表格
-
ajax
$.ajax({ url:"", data:xxx, type:"get/post", success:function(){ } })
-
前后端分离
- 该案例可以将页面独立出来,成为一个前后端分离项目,也可以将页面作为静态资源保存在static目录下
-
如果设计为前后端分离,要在控制器类上加入@CrossOrigin,表示该类中的所有方法允许跨域请求
-
打包SpringBoot项目
保证项目中无错误,包含单元测试中
打包后是一个.jar文件,位于target目录中
在安装有java环境的机器中,控制台运行jar文件
java -jar 文件名.jar
组织结构图
部分功能流程图
管理员
入住
数据库部分设计
ER图
数据库表详细设计
管理员表sys_admin
客房表room
客户表customer
订单表orders
核心Java代码
实体类
订单表
/*
* 订单
* */
@Data
public class Orders {
private Integer id;
private Integer roomNo;
private Integer cusId;
//@JsonFormat是springboot集成的jackson包中的注解,用于格式化日期
//pattern指定格式日期 timezone指定时区,这里和数据库的时区保持一致
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")
private Date leaveTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")
private Date stayTime;
private Integer cost;
//显示订单的同时,要显示房间的相关信息
private Room room;
//显示订单的同时,要显示客户的相关信息
private Customer customer;
}
数据访问层
-
实现随意修改某个字段的值
int update(@Param("field") String field, @Param("value") String value, @Param("id") int id);
sql映射文件
<update id="update"> update room <set> <if test="field=='roomType'"> room_type= #{value} </if> <if test="field=='roomPrice'"> room_price= #{value} </if> <if test="field=='roomUse'"> room_use=#{value} </if> </set> where room_no=#{id} </update>
-
添加时获取自增的值
int insert(Customer customer);
sql映射文件
<!--添加顾客的同时,获取自增的id--> <insert id="insert" useGeneratedKeys="true" keyColumn="cus_id" keyProperty="cusId"> insert into customer values(null,#{cusName},#{cusPhone},#{cusIdcard}) </insert>
-
多表连接查询/条件查询
List<Orders> queryAll(); List<Orders> search(String keyword); //根据客户编号查询是否已入住 Orders isStay(Integer cusId); //根据房间号查询正在入住的订单信息 Orders findCheckInByRoomNo(Integer roomNo); //计算费用 int checkOut(@Param("roomNo") Integer roomNo,@Param("cost") Integer cost);
sql映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--设置该文件对应的dao层接口全限定名--> <mapper namespace="com.hqyj.hotel_sys.dao.OrdersDao"> <insert id="insert"> insert into orders values (null, #{roomNo}, #{cusId}, now(), null, 0) </insert>
<!--关联查询方式一:特殊的SQL语句--> <!-- <select id="queryAll" resultType="com.hqyj.hotel_sys.entity.Orders"> select o.*, cus_name as 'customer.cusName', cus_phone as 'customer.cusPhone', cus_idcard as 'customer.cusIdcard', room_type as 'room.roomType', room_price as 'room.roomPrice', room_use as 'room.roomUse' from customer c, room r, orders o where c.cus_id = o.cus_id and r.room_no = o.room_no </select> --> <!--多表连接条件查询--> <select id="search" resultMap="ordersMap"> SELECT * FROM orders o,customer c <trim prefix="where" prefixOverrides="and"> o.cus_id = c.cus_id <if test="keyword!=null"> and cus_name like concat('%',#{keyword},'%') </if> </trim> </select> <!--关联查询方式二:子查询--> <!--1.查询自身表--> <select id="queryAll" resultMap="ordersMap"> select * from orders </select> <!--自定义结果集映射,用cus_id和room_no做子查询,重新映射一次到orders对象--> <resultMap id="ordersMap" type="com.hqyj.hotel_sys.entity.Orders"> <result property="roomNo" column="room_no"></result> <result property="cusId" column="cus_id"></result> <association property="room" column="room_no" select="findRoomByNo"></association> <association property="customer" column="cus_id" select="findCustomerById"></association> </resultMap> <!--子查询一:根据房号查房间--> <select id="findRoomByNo" resultType="com.hqyj.hotel_sys.entity.Room"> select * from room where room_no = #{roomNo} </select> <!--子查询二:根据编号查客户--> <select id="findCustomerById" resultType="com.hqyj.hotel_sys.entity.Customer"> select * from customer where cus_id = #{cusId} </select>
<select id="isStay" resultType="com.hqyj.hotel_sys.entity.Orders">
select *
from orders
where cus_id = #{cusId}
and leave_time is null
</select>
<!--
根据房号查询正在入住的订单信息,包含房间价格
-->
<select id="findCheckInByRoomNo" resultType="com.hqyj.hotel_sys.entity.Orders">
select o.*, room_price as 'room.roomPrice'
from orders o,
room r
where o.room_no = r.room_no
and r.room_no = #{roomNo}
and leave_time is null
</select>
<update id="checkOut">
update orders
set leave_time=now(),
cost=#{cost}
where room_no = #{roomNo}
and leave_time is null
</update>
## 业务流程层
```java
package com.hqyj.hotel_sys.service;
import com.hqyj.hotel_sys.dao.CustomerDao;
import com.hqyj.hotel_sys.dao.OrdersDao;
import com.hqyj.hotel_sys.dao.RoomDao;
import com.hqyj.hotel_sys.entity.Customer;
import com.hqyj.hotel_sys.entity.Orders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
@Service
public class CustomerService {
@Autowired
private CustomerDao customerDao;
@Autowired
private RoomDao roomDao;
@Autowired
private OrdersDao ordersDao;
/*
* 入住
* 1.获取顾客信息:姓名、电话、身份证号,
* 根据身份证号判断是否存在,如果存在,用存在的对象
* 如果不存在,向customer表中添加一条记录 insert into customer values(null,#{cusName},#{cusPhone},#{cusIdcard})
* 添加成功时,要获取自增的id,用于订单表中使用
* 判断该顾客是否已入住:查询订单表中对应顾客编号的记录,如果退房时间为空,说明已入住
* select * from orders where cus_id=#{cusId} and leave_time is null
* 2.将对应房间的状态改为1,对room表中修改 update room set room_use=1 where room_no=#{roomNo}
* 3.向订单表中添加一条记录 insert into orders values (null,#{roomNo},#{cusId},now(),null,0)
*
* 以上3个步骤属于一个事务,要在该方法上加事务注解
* */
@Transactional//让该方法成为一个事务,如果执行中途出错,会自动回滚
public boolean checkIn(Customer customer, int roomNo) {
//1.添加客户,根据身份证号判断是否存在
Customer byIdcard = customerDao.findByIdcard(customer.getCusIdcard());
boolean b1;
if (byIdcard == null) {//不存在,调用添加
b1 = customerDao.insert(customer) > 0;
} else {
customer = byIdcard;
b1 = true;
}
//查看是否已入住
if (ordersDao.isStay(customer.getCusId()) != null) {
return false;
}
//2.修改房间状态
boolean b2 = roomDao.update("roomUse", "1", roomNo) > 0;
//3.添加订单信息
//创建订单对象
Orders orders = new Orders();
//客户编号在添加客户成功后,会自动获取
orders.setCusId(customer.getCusId());
orders.setRoomNo(roomNo);
boolean b3 = ordersDao.insert(orders) > 0;
return b1 & b2 & b3;
}
/*
* 退房
* 1.根据房间号查询对应正在入住的订单记录(同时查询出房价)
* select o.* from orders o,room r where o.room_no=r.room_no and r.room_no =#{roomNo} and leave_time is null
* 2.结算
* 添加退房时间、添加花费
* update orders set leave_time =now() ,cost=#{cost} where room_no =#{roomNo} and leave_time is null
* 3.修改房间状态为空闲
* update room set room_use = 0 where room_no=#{roomNo}
* */
@Transactional
public boolean checkOut(Integer roomNo) {
//1.根据房间号查询对应正在入住的订单记录
Orders orders = ordersDao.findCheckInByRoomNo(roomNo);
//2.计算费用
Date stayTime = orders.getStayTime();
Date now = new Date();
double l = now.getTime() - stayTime.getTime();
//转换为天数
double day = Math.ceil(l / 1000 / 3600 / 24);
double cost = orders.getRoom().getRoomPrice() * day;
//修改订单中的花费和退房时间
//update orders set leave_time=now(),cost=#{cost} where room_no=#{roomNo} and leave_time is null
boolean b1 = ordersDao.checkOut(roomNo, (int) cost) > 0;
//3.修改房间状态为空闲0
//update room set room_use where room_no = #{roomNo}
boolean b2 = roomDao.update("roomUse", "0", roomNo) > 0;
return b1 & b2;
}
}
控制层
如果某个控制器中的所有方法都需要返回JSON格式字符串,在类上加@ResponseBody
@Controller
@RequestMapping("/room")
public class RoomController {
@Autowired
private RoomService roomService;
@ResponseBody
@RequestMapping("/queryAll")
public ResultData<Room> queryAll(Integer page, Integer limit) {
//使用PageHelper分页
PageHelper.startPage(page, limit);
//正常查询所有
List<Room> rooms = roomService.queryAll();
//创建分页模型对象
PageInfo<Room> pageInfo = new PageInfo<>(rooms);
//返回的数据为分页后的集合,数量为总记录数
return new ResultData<>((int)pageInfo.getTotal(),pageInfo.getList());
}
}
LayUI数据表格
数据表格所需的数据接口格式为
{
code:0,
msg:"",
count:1000,
data:[{},{}]
}
构造满足LayUI数据表格的数据模型类ResultData
/*
* 定义一个用于LayUI数据表格对应格式的模板类
* code 状态码 0成功
* msg 提示文字
* count 数据总量
* data 数据集合
* */
@Data
public class ResultData<T> {
private Integer code;
private String msg;
private Integer count;
private List<T> data;
/*
* 定义带count和data的构造方法,用于初始化code和msg
* */
public ResultData(Integer count, List<T> data) {
code=0;
msg="";
this.count = count;
this.data = data;
}
}
最后在控制层中,将查询的方法的返回值更改为ResultData类型
@RequestMapping("/queryAll")
public ResultData<Room> queryAll() {
List<Room> rooms = roomService.queryAll();
return new ResultData<>(rooms.size(), rooms);
}
最终页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>LayUI</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="../LayUI/css/LayUI.css" media="all">
<!-- 注意:如果你直接复制所有代码到本地,上述css路径需要改成你本地的 -->
</head>
<body>
<table class="LayUI-hide" id="test" lay-filter="test"></table>
<script type="text/html" id="toolbarDemo">
<div class="LayUI-btn-container">
<button class="LayUI-btn LayUI-btn-sm" lay-event="getCheckData">获取选中行数据</button>
<button class="LayUI-btn LayUI-btn-sm" lay-event="getCheckLength">获取选中数目</button>
<button class="LayUI-btn LayUI-btn-sm" lay-event="isAll">验证是否全选</button>
<button class="LayUI-btn LayUI-btn-sm" lay-event="addRoom">添加客房</button>
</div>
</script>
<script type="text/html" id="barDemo">
<!--插值表达式-->
{{# if(d.roomUse==0){ }}
<a class="LayUI-btn LayUI-btn-normal LayUI-btn-sm" lay-event="check-in">入住</a>
<a class="LayUI-btn LayUI-btn-danger LayUI-btn-sm" lay-event="del">删除</a>
{{# }else{ }}
<a class="LayUI-btn LayUI-btn-warm LayUI-btn-sm" lay-event="check-out">退房</a>
{{# } }}
</script>
<script src="../LayUI/LayUI.js" charset="utf-8"></script>
<!-- 注意:如果你直接复制所有代码到本地,上述 JS 路径需要改成你本地的 -->
<script>
LayUI.use('table', function () {
var table = LayUI.table;
//引入Jquery
var $ = LayUI.$;
/*
* 渲染表格数据
* url:数据访问接口
* cols:表格列
* field:数据字段名
* title:表头
* cellminwidth:100 自适应宽度
* fixed:left 固定在某侧
* unresize:true 不可改变尺寸
* sort:true 排序
* edit:true 行内编辑
* */
table.render({
elem: '#test'
//设置数据接口
, url: 'http://localhost:9090/hotel/room/queryAll'
, toolbar: '#toolbarDemo' //开启头部工具栏,并为其绑定左侧模板
, defaultToolbar: ['filter', 'exports', 'print', { //自定义头部工具栏右侧图标。如无需自定义,去除该参数即可
title: '提示'
, layEvent: 'LAYTABLE_TIPS'
, icon: 'LayUI-icon-tips'
}]
, title: '用户数据表'
, cols: [[
{type: 'checkbox', fixed: 'left'}
, {field: 'roomNo', title: '房间号', cellminwidth: 100, fixed: 'left', unresize: true, sort: true}
, {field: 'roomType', title: '房间类型', cellminwidth: 100, edit: 'text'}
, {field: 'roomPrice', title: '房间单价', cellminwidth: 100, edit: 'text', sort: true}
, {
field: 'roomUse', title: '使用状态', cellminwidth: 100,
templet: function (res) {
return res.roomUse == 0 ? "<span style='color:green'>空闲中</span>" : "<span style='color:red'>使用中</span>";
}
}
, {fixed: 'right', title: '操作', toolbar: '#barDemo', cellminwidth: 100,}
]]
//开启分页组件
, page: true
//设置每页显示记录数的下拉选项
, limits: [5, 10, 20]
//默认每页显示的条数,没有设置默认为10
, limit: 10
//解析数据表格url请求后的数据
, parseData: function (res) {//res就是url对应的数据
/*
//假分页
var result;
if (this.page.curr) {
result = res.data.slice(this.limit * (this.page.curr - 1), this.limit * this.page.curr);
} else {
result = res.data.slice(0, this.limit);
}
*/
return {
"code": res.code,
"msg": res.msg,
"count": res.count,
"data": res.data
}
}
});
//头工具栏事件
table.on('toolbar(test)', function (obj) {
var checkStatus = table.checkStatus(obj.config.id);
switch (obj.event) {
case 'addRoom':
layer.open({
title: '添加客房',
type: 2,
content: 'addRoom.html',
area: ['350px', '250px'],
resize: false,
anim: 2,
/* success: function(layero, index){
console.log(layero, index);
}*/
})
break;
case 'getCheckData':
//data是所选数据的集合
var data = checkStatus.data;
for (var i = 0; i < data.length; i++) {
console.log(data[i].roomNo)
}
// layer.alert(JSON.stringify(data));
break;
case 'getCheckLength':
var data = checkStatus.data;
layer.msg('选中了:' + data.length + ' 个');
break;
case 'isAll':
layer.msg(checkStatus.isAll ? '全选' : '未全选');
break;
//自定义头工具栏右侧图标 - 提示
case 'LAYTABLE_TIPS':
layer.alert('这是工具栏右侧自定义的一个图标按钮');
break;
}
;
});
//监听单元格编辑事件
table.on('edit(test)', function (obj) {
// console.log(obj);
//layer.confirm("提示文件",function(){确认触发},function(){取消触发})
layer.confirm('确认要修改吗', function (index) {
//使用ajax提交要修改的字段名、修改后的值、要修改的编号
$.ajax({
url: "http://localhost:9090/hotel/room/update",
data: {
"field": obj.field,//要修改的字段
"value": obj.value,//修改后的值
"id": obj.data.roomNo//要修改的id
},
success: function () {
//修改成功,关闭确认框
layer.close(index);
}
});
}, function () {
//修改失败,重新加载
location.reload()
})
})
//监听行工具事件
table.on('tool(test)', function (obj) {
//data就是当前行中的数据
var data = obj.data;
// console.log(obj)
if (obj.event === 'del') {
//弹出确认框
layer.confirm('真的删除行么', function (index) {
//使用ajax提交要删除的id
$.ajax({
url: "http://localhost:9090/hotel/room/delete",
data: {
"delId": data.roomNo
},
success: function (res) {
if (res) {
//前端假删除
obj.del();
//关闭确认框
layer.close(index);
}
}
});
});
} else if (obj.event === 'check-in') {
//弹出输入顾客信息表单
layer.open({
title: '输入顾客信息',
type: 2,
content: 'addCustomer.html',
area: ['350px', '400px'],
resize: false,
anim: 2,
//弹出窗口后的回调函数
success: function (layero, index) {
//弹出成功后,在弹出页面中加入当前点击行的roomNo
//获取弹出层的body部分
var body = layer.getChildFrame('body', index);
//获取弹出层body中的隐藏域,给其赋值
body.find("input[name=roomNo]").val(data.roomNo);
}
})
} else if (obj.event === 'check-out') {
layer.confirm("确定要退房吗", function () {
$.ajax({
url: "http://localhost:9090/hotel/customer/checkOut",
data: {
"roomNo": data.roomNo
},
success: function (res) {
if (res) {
location.reload();
}
}
});
})
}
});
});
</script>
</body>
</html>
LayUI添加页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="../LayUI/css/LayUI.css" media="all">
</head>
<body>
<form class="LayUI-form" action="">
<div class="LayUI-form-item">
<label class="LayUI-form-label">房间类型</label>
<div class="LayUI-input-inline">
<input type="text" name="roomType" required lay-verify="required" placeholder="请输入房间类型" autocomplete="off" class="LayUI-input">
</div>
</div>
<div class="LayUI-form-item">
<label class="LayUI-form-label">房间单价</label>
<div class="LayUI-input-inline">
<input type="text" name="roomPrice" required lay-verify="required" placeholder="请输入房间单价" autocomplete="off" class="LayUI-input">
</div>
</div>
<div class="LayUI-form-item">
<div class="LayUI-input-block">
<button class="LayUI-btn" lay-submit lay-filter="formDemo">立即提交</button>
<button type="reset" class="LayUI-btn LayUI-btn-primary">重置</button>
</div>
</div>
</form>
<script src="../LayUI/LayUI.js" charset="utf-8"></script>
<script>
//Demo
LayUI.use('form', function(){
var form = LayUI.form;
//使用LayUI内置jquery
var $=LayUI.$;
//监听提交
form.on('submit(formDemo)', function(data){
// data.field是当前表单中的所有数据
// layer.msg(JSON.stringify(data.field));
$.ajax({
url:'http://localhost:9090/hotel/room/addRoom',
//将表单中的所有数据一起提交,实际提交的是name=value,如roomType=值&roomPrice=值
data:data.field,
//相当于
/* data:{
roomType:"",
roomPrice:""
}*/
success:function(res){
if(res){
//刷新父页面
parent.location.reload()
}
}
})
//return false时不提交表单
return false;
});
});
</script>
</body>
</html>
LayUI分页
-
假分页
查询所有,在页面中分页,适合记录比较少的情况
table.render({ //省略url等 //开启分页组件 , page: true //设置每页显示记录数的下拉选项 , limits: [5, 10, 20] //默认每页显示的条数,没有设置默认为10 , limit: 10 //解析数据表格url请求后的数据 , parseData: function (res) {//res就是url对应的数据 //假分页 var result; if (this.page.curr) { result = res.data.slice(this.limit * (this.page.curr - 1), this.limit * this.page.curr); } else { result = res.data.slice(0, this.limit); } return { "code": res.code, "msg": res.msg, "count": res.count, "data": result } } })
-
真分页
可以使用PageHelper组件
依赖
<!--分页组件SpringBoot集成PageHelper--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.6</version> </dependency>
在application.properties中配置
# 防止不合理分页 pagehelper.reasonable=true
在控制层中使用
@ResponseBody @RequestMapping("/queryAll") //这里的page和limit参数是layui数据表格自动传递 public ResultData<Room> queryAll(Integer page, Integer limit) { //使用PageHelper分页 PageHelper.startPage(page, limit); //正常查询所有 List<Room> rooms = roomService.queryAll(); //创建分页模型对象 PageInfo<Room> pageInfo = new PageInfo<>(rooms); //返回的数据为分页后的集合,数量为总记录数 return new ResultData<>((int)pageInfo.getTotal(),pageInfo.getList()); }
LayUI条件查询
页面头部工具栏中加入搜索框
<script type="text/html" id="toolbarDemo">
<div class="layui-form-item">
<div class="layui-input-inline">
<input type="text" name="keyword" required lay-verify="required" placeholder="请输入姓名关键字" autocomplete="off"
class="layui-input">
</div>
<button class="layui-btn layui-btn-primary" lay-event="search">搜索</button>
</div>
</script>
js部分
//头工具栏事件
table.on('toolbar(test)', function (obj) {
switch (obj.event) {
case 'search':
//获取输入的内容
let keyword = $("input[name=keyword]").val();
if(keyword==""){
layer.msg("输入不能为空");
return;
}
//如果要修改数据表格中的数据,只能更改url的地址后,重新加载数据表格
table.reload('test',{
url:"http://localhost:9090/hotel/orders/search?keyword="+keyword
});
break;
}
});
@Autowired
private HeroService heroService;
@Test
void test1() {
//无条件查询所有
//heroService.list().forEach(System.out::println);
//条件查询,如果条件为null表示查询所有
//heroService.list(null).forEach(System.out::println);
//根据主键查询
//Hero hero = heroService.getById(22);
//System.out.println(hero);
//根据条件查询,如果条件为null表示查询所有,会导致异常
//System.out.println(heroService.getOne(null));
}
@Test
void test2() {
//添加save(T entity);参数对象至少要有一个属性值
//添加后,主键自增的值会自动赋值给主键属性
/*
Hero hero = new Hero();
hero.setHeroName("cvb");
hero.setSex("男");
hero.setPrice(2000);
hero.setPosition("战士");
heroService.save(hero);
System.out.println(hero);
*/
//批量添加,参数为要添加的对象集合
/* Hero h1 = new Hero(0, "1", "1", "1", 100, "1999-9-9");
Hero h2 = new Hero(0, "2", "1", "1", 100, "1999-9-9");
Hero h3 = new Hero(0, "3", "1", "1", 100, "1999-9-9");
List<Hero> heroes = Arrays.asList(h1, h2, h3);
heroService.saveBatch(heroes);*/
Hero h1 = new Hero(0, "666", "1", "1", 100, "1999-9-9");
//如果添加的对象主键值已存在执行修改,不存在则添加
heroService.saveOrUpdate(h1);
}
@Test
void test3(){
//根据对象修改
Hero h1 = new Hero(999, "修改后", "修改后", "女", 100, "1999-9-9");
//如果对象主键值已存在执行修改,不存在则添加
//heroService.saveOrUpdate(h1);
//根据主键修改
heroService.updateById(h1);
//根据条件修改
//heroService.update(null);
//根据实体和条件修改,如果条件为空,会将所有数据修改为指定实体的数据
//heroService.update(h1,null);
}
@Test
void test4(){
//根据主键或带有主键值的对象删除
//heroService.removeById(1103302663);
/*Hero hero = new Hero();
hero.setId(1103302662);
heroService.removeById(hero);*/
//批量删除
//heroService.removeBatchByIds(Arrays.asList(77,88,99));
//根据条件删除,条件为null则删除全部
heroService.remove(null);
}
}
单表条件分页
原理:调用IService接口中的**page(Page page,Wrapper wrapper)**方法,创建分页模型对象和条件构造器对象
在Springboot启动类中加入mybatis分页拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
搜索框
<form class="navbar-form navbar-left" th:action="@{/question/queryAll}">
<div class="form-group">
<input type="text" class="form-control" name="keyword" placeholder="请输入题目关键字">
</div>
<button type="submit" class="btn btn-default">搜索</button>
</form>
控制层
@RequestMapping("/queryAll")
public String queryAll(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "5") Integer size,
@RequestParam(defaultValue = "") String keyword,
Model model) {
//1.创建分页模型对象
Page<Question> pageInfo = new Page<>(page, size);
//创建条件构造器对象
QueryWrapper<Question> wrapper = new QueryWrapper<>();
wrapper.like("ques_title",keyword);
//2.调用条件分页查询的方法
questionService.page(pageInfo,wrapper);
// 将分页模型对象保存到请求中
model.addAttribute("pageInfo", pageInfo);
//构造页数
List<Long> pageList = new ArrayList<>();
//每次最多显示5页
//定义最大数字
long maxPage = page + 4 > pageInfo.getPages() ? pageInfo.getPages() : page + 4;
//定义最小数字
long minPage = page - 4 < 1 ? page : maxPage - 4;
for (long i = minPage; i <= maxPage; i++) {
pageList.add(i);
}
model.addAttribute("pageList", pageList);
return "questionList";
}
分页组件
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a th:if="${pageInfo.hasPrevious}" th:href="@{/question/queryAll(keyword=${param.keyword},page=${pageInfo.current-1})}"
aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li class="pno" th:each="pno:${pageList}">
<a th:href="@{/question/queryAll(keyword=${param.keyword},page=${pno})}" th:text="${pno}"></a>
</li>
<li>
<a th:if="${pageInfo.hasNext}" th:href="@{/question/queryAll(keyword=${param.keyword},page=${pageInfo.current+1})}"
aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
请求方式设计:RESTFul风格
风格,不是标准,可以不用强制遵循。
RESTFul风格:用不同的请求方式去访问同一个URL地址时,执行不同的操作。
特点
- 通过URL就能知道当前在哪个模块
- 通过不同的请求方式决定执行什么操作
- 通过返回的状态码得到操作结果
使用RESTFul风格和普通方式对比
普通方式
localhost:8080/user/queryAll 查询所有
localhost:8080/user/queryById?id=1001 条件查询
localhost:8080/user/insert?name=ez&sex=男&age=20 添加
localhost:8080/user/update?name=ez&id=1001 修改
localhost:8080/user/delete?id=1001 删除
RESTFul风格
localhost:8080/user 查询所有get请求
localhost:8080/user/1001 条件查询get请求
localhost:8080/user 添加post请求
localhost:8080/user 修改put请求
localhost:8080/user/1001 删除delete请求
RESTFul风格具体使用
-
在请求映射的命名上,统一用小写字母的名词形式表示当前位于哪个模块。如/user、/book_info
-
访问时如果要传参,使用"/模块名/参数"方式,配合controller中的@PathVariable获取
@GetMapping("/book/{id}") public BookInfo queryById(@PathVariable("id")Integer id){ return service.findById(id); }
-
在controller的方法上,使用@XXXMapping()设置访问该方法的请求方式
- @GetMapping(“路径”) 查询
- @PostMapping(“路径”) 添加
- **@PutMapping(“路径”) ** 修改
- **@DeleteMapping(“路径”) ** 删除
- @RequestMapping(value=“路径”,method=RequestMethod.GET/POST/PUT/DELETE))
-
如果请求方式不匹配,会报405异常
-
在同一个controller中,不能出现两个请求方式和路径都一致的方法
返回值设计
前后端分离项目的控制层方法的返回值也需要进行统一。
返回值通常包含以下信息
- 传递状态,用状态码表示Integer code
- 传递消息,用字符串表示String msg
- 传递集合,用集合表示List list
- 传递对象,用对象表示Object obj
将这些信息封装到一个对象中,这个对象称为返回结果类RestResult对象
RestResult类具体设计
Postman
Postman API Platform | Sign Up for Free
Java技术栈
服务器端
JavaSE(API、OOP)、JavaEE(Servlet、Spring、SpringMVC)
tomcat
前端0
HTML+CSS+JS+JQUERY、Bootstrap、LayUI、EasyUI、VUE
数据库
MySQL、Oracle、Redis
JDBC、SpringJDBC、MyBatis、MyBatisPlus、JPA
工具
IDEA、HBuilder、VSCode、Notepad++、Sublime、Navicat、Postman
JavaEE过程
Servlet + JDBC + JSP(bootstrap) 图书商场
Spring + SpringMVC + SpringJDBC + JSP(bootstrap)
Spring + SpringMVC + MyBatis+ JSP 图书管理系统SSM
SpringBoot + MyBatis + html(LayUI) 酒店客房管理(可前后端分离)
SpringBoot + MyBatisPlus + html(Thymeleaf+bootstrap) 答题系统
SpringBoot + Spring Data JPA
项目一
SpringBoot+MyBatisPlus+Vue 仓库管理(前后端分离)
个人总结
通过这段时间对于框架的学习,使得自己对于学习编程又有了一些新的了解与体会,不仅仅是学到了新的知识,更重要的是通过框架的学习发现前面学习的大部分东西都是为了给框架做铺垫,并且在框架的学习中也学习到了许多新的知识,比如老师说的为什么没有早一点教我们的代码生成器,确实在我们眼中这种可以直接帮助我们写代码的东西当然上越早教越好,但是如果我们不能够真正的了解项目是怎么创建的,数据是怎么传输的,就算能够帮助我们打一些代码,也不如自己懂得这些来的划算,熟话说得好授人以鱼不如授人以渔,我觉得这个道理大家还是都明白的,并且在以后的工作中,需要自己对于这些项目的了解并且能够及时修改,通过这样可以对于项目更好的维护,我相信外面的那些公司也是会比较喜欢这种个人能力比较强的,虽然会用代码生成器这些东西也算是自己会用就算自己的,但是有些东西可以不敲,不写,但是不能不会、不懂,在本次的框架课程中,我也了解到了自己的许多不足之处,并且我觉得自己受益匪浅,不光光是学习上,就连在对于项目的时候看问题的角度都不一样了,这让我非常开心,希望能够在接下来的时间中,努力学习,争取找一个自己满意,家里满意的工作,更加希望在未来的工作中能当一个独当一面的人!