课程学习目标
基础篇
实用篇
原理篇
课程学习前置知识
基于idea开发SpringBoot程序需要确保联网且能够加载到程序框架结构
SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程
笔记小结
- 开发SpringBoot程序可以根据向导进行联网快速制作
- SpringBoot程序需要基于JDK8进行制作
- SpringBoot程序中需要使用何种功能通过勾选选择技术
- 运行SpringBoot程序通过运行Application程序入口进行
步骤一:创建新模块,选择Spring Initializr,并配置模块相关基础信息
步骤二:选择当前模块需要使用的技术集
步骤三:开发控制器类
//Rest模式
@RestController
@RequestMapping("/books")
public class BookController {
@GetMapping
public String getById(){
System.out.println("springboot is running...");
return "springboot is running...";
}
}
最简SpringBoot程序所包含的基础文件
pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.7version>
parent>
<groupId>com.examplegroupId>
<artifactId>springboot_01_01_quickstartartifactId>
<version>0.0.1-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
project>
Application类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@SpringBootApplication启动类
参考链接:IDEA2022.01 创建Spring Boot项目_刘代码的博客-CSDN博客_idea2022创建springboot
笔记小结
- 开发SpringBoot程序可以根据向导进行联网快速制作
- SpringBoot程序需要基于JDK8进行制作
- SpringBoot程序中需要使用何种功能通过勾选选择技术
- 运行SpringBoot程序通过运行Application程序入口进行
步骤一:基于SpringBoot官网创建项目
地址:https://start.spring.io/
步骤二:基于配置进行选择
当得到压缩包,导入即可
后续具体实现参考基础步骤(一)
笔记小结
- 选择start来源为自定义URL
- 输入阿里云start地址
- 创建项目
基于阿里云创建项目,地址:https://start.aliyun.com
后续具体实现参考基础步骤(一)
注意:
- 阿里云提供的坐标版本较低,如果需要使用高版本,进入工程后手工切换SpringBoot版本
- 阿里云提供的工程模板与Spring官网提供的工程模板略有不同
笔记小结
- 创建普通Maven工程
- 继承spring-boot-starter-parent
- 添加依赖spring-boot-starter-web
- 制作引导类Application
步骤一:创建maven模块
步骤二: 继承spring-boot-starter-parent 和添加依赖spring-boot-starter-web
步骤三:制作引导类Application
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程
笔记小结
- 开发SpringBoot程序要继承spring-boot-starter-parent
- spring-boot-starter-parent中定义了若干个依赖管理
- 继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突
- 继承parent的形式也可以采用引入依赖的形式实现效果
当我们两个项目需要公用一套依赖时会导致依赖重复
SpringBoot为我们统一了版本的管理
SpringBoot会根据不同的SpringBoot版本定义一些兼容性强的版本号列表,它的执行流程如下
补充:这些依赖管理,继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突。
笔记小结
减少依赖冲突
spring-boot-starter-web.pom里面又有什么内容呢
starter和parent的区别
starter
parent
所有SpringBoot项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的
spring-boot-starter-parent各版本间存在着诸多坐标版本不同
注意:
实际开发中
- 使用任意坐标时,仅书写GAV中的G和A,V由SpringBoot提供,除非SpringBoot未提供对应版本V
- 如发生坐标错误,再指定Version(要小心版本冲突)
笔记小结
- SpringBoot工程提供引导类用来启动程序
- SpringBoot工程启动后创建并初始化Spring容器
启动方式
@SpringBootApplication
public class Springboot01QuickstartApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01QuickstartApplication.class, args);
}
}
SpringBoot的引导类是Boot工程的执行入口,运行main方法就可以启动项目
SpringBoot工程运行后初始化Spring容器,扫描引导类所在包加载bean
补充:
此时可通过获取ConfigurableApplicationContext对象获得bean对象
@SpringBootApplication public class Springboot0101QuickstartApplication { public static void main(String[] args) { ConfigurableApplicationContext ctx = SpringApplication.run(Springboot0101QuickstartApplication.class, args); BookController bean = ctx.getBean(BookController.class); System.out.println("bean======>" + bean); } }
笔记小结
- 内嵌Tomcat服务器是SpringBoot辅助功能之一
- 内嵌Tomcat工作原理是将Tomcat服务器作为对象运行,并将该对象交给Spring容器管理
- 变更内嵌服务器思想是去除现有服务器,添加全新的服务器
在spring-boot-starter-web中存有tomcat的核心对象
可使用maven依赖管理变更起步依赖项
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jettyartifactId>
dependency>
dependencies>
补充:
Jetty比Tomcat更轻量级,可扩展性更强(相较于Tomcat),谷歌应用引擎(GAE)已经全面切换为Jetty
内置服务器:
tomcat(默认) apache出品,粉丝多,应用面广,负载了若干较重的组件
jetty 更轻量级,负载性能远不及tomcat
undertow undertow,负载性能勉强跑赢tomcat
笔记小结
- SpringBoot默认配置文件application.properties
- SpringBoot中导入对应starter后,提供对应配置属性
- 书写SpringBoot配置采用关键字+提示形式书写
SpringBoot默认配置文件application.properties,通过键值对配置对应属性
修改配置
修改服务器端口
server.port=80
关闭运行日志图标(banner)
spring.main.banner-mode=off
设置日志相关
logging.level.root=debug
注意:若SpringBoot中未导入对应starter,那么则无法提供对应配置属性
SpringBoot内置属性查询
Common Application Properties (spring.io)
官方文档中参考文档第一项:Application Properties
笔记小结
- SpringBoot提供了3种配置文件的格式
- properties(传统格式/默认格式)
- yml(主流格式)
- yaml
- 配置文件间的加载优先级
- properties(最高)
- yml
- yaml(最低)
- 不同配置文件中相同配置按照加载优先级相互覆盖,不同配置文件中不同配置全部保留
1.SpringBoot提供了多种属性配置方式
application.properties
server.port=80
application.yml
server:
port: 81
application.yaml
server:
port: 82
2.SpringBoot配置文件加载顺序
补充:若加载优先级越低,则不会覆盖
3.常用配置文件种类
笔记小结
- yaml语法规则
- 大小写敏感
- 属性层级关系使用多行描述,每行结尾使用冒号结束
- 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许 使用Tab键)
- 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔 )
- # 表示注释
- 注意属性名冒号后面与数据之间有一个空格
- 字面值、对象数据格式、数组数据格式(略)
YAML(YAML Ain’t Markup Language),一种数据序列化格式
1.优点:
2.YAML文件扩展名
XML方式
<enterprise>
<name>itcastname>
<age>16age>
<tel>4006184000tel>
enterprise>
Properties方式
enterprise.name=itcast
enterprise.age=16
enterprise.tel=4006184000
Yaml方式
enterprise:
name: itcast
age: 16
tel: 4006184000
Yml方式
enterprise:
name: itcast
age: 16
tel: 4006184000
3.yaml语法规则
笔记小结
- 使用**@Value**配合SpEL读取单个数据
- 如果数据存在多层级,依次书写层级名称即可
大小写敏感
属性层级关系使用多行描述,每行结尾使用冒号结束
使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(注意:不允许使用Tab键)
属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
# 表示注释
核心规则:数据前面要加空格与冒号隔开
字面值表示方式
boolean: TRUE #TRUE,true,True,FALSE,false,False均可
float: 3.14 #6.8523015e+5 #支持科学计数法
int: 123 #0b1010_0111_0100_1010_1110 #支持二进制、八进制、十六进制
null: ~ #使用~表示null
string: HelloWorld #字符串可以直接书写
string2: "Hello World" #可以使用双引号包裹特殊字符
date: 2018-02-17 #日期必须使用yyyy-MM-dd格式
datetime: 2018-02-17T15:02:31+08:00 #时间和日期之间使用T连接,最后使用+代表时区
注意:
- yaml文件中对于数字的定义支持进制书写格式,如需使用字符串请使用引号明确标注
数组表示方式:在属性名书写位置的下方使用减号作为数据开始符号,每行书写一个数据,减号与数据间空格分隔
subject:
- Java
- 前端
- 大数据
enterprise:
name: itcast
age: 16
subject:
- Java
- 前端
- 大数据
likes: [王者荣耀,刺激战场] #数组书写缩略格式
对象数组格式
users: #对象数组格式
- name: Tom
age: 4
- name: Jerry
age: 5
users: #对象数组格式二
-
name: Tom
age: 4
-
name: Jerry
age: 5 #对象数组缩略格式
users2: [ { name: Tom , age: 4 } , { name: Jerry , age: 5 } ]
笔记小结
简单读取
- 使用**@Value**配合SpEL读取单个数据
- 如果数据存在多层级,依次书写层级名称即可
复杂读取
- 封装Environment对象
- 使用Environment对象的getProperty方法根据yml文件中的key获取对象的value
简化配置
- 在配置文件中可以使用**${属性名}**方式引用属性值
- 如果属性中出现特殊字符,可以使用双引号包裹起来作为字符解析
封装Environment对象
- 使用Environment对象封装全部配置信息
- 使用@Autowired自动装配数据到Environment对象中
封装自定义对象✳
- 使用**@ConfigurationProperties**注解绑定配置信息到封装类中
- 封装类需要定义为Spring管理的bean,否则无法进行属性注入
示例:
@Value("${name}")
public String name;
@Value("${subject[0]}")
public String subject;
@Value("${customer[0].name}")
public String customer;
@GetMapping
public String get() {
System.out.println("springboot is running...");
System.out.println(name);
System.out.println(subject);
System.out.println(customer);
return "springboot is running...";
}
baseDir: /usr/local/fire
center:
dataDir: ${baseDir}/data
tmpDir: ${baseDir}/tmp
logDir: ${baseDir}/log
msgDir: ${baseDir}/msgDir
lesson: "Spring\tboot\nlesson"
@Autowired自动装配,
自定装配后,可将数据显示出来
示例
//Environment可获取application.yml文件中的所有key:value
@Autowired
private Environment env;
…………
//通过Environment对象的getProperty方法根据yml文件中的key获取对象的value
System.out.println(env.getProperty("users[0].age"));
示例:
创建entity包,创建Database类
package com.example.entity;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
//1.定义数据模型封装yaml文件中对应的数据
//2.定义为spring管控的bean
@Component
//3.指定加载的数据---需要指定前缀
@ConfigurationProperties(prefix = "datasource")
public class Datasource {
String driveClassName;
String url;
String userName;
String password;
@Override
public String toString() {
return "Datasource{" +
"driveClassName='" + driveClassName + '\'' +
", url='" + url + '\'' +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
public String getDriveClassName() {
return driveClassName;
}
public void setDriveClassName(String driveClassName) {
this.driveClassName = driveClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
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;
}
}
注意:
- 使用**@ConfigurationProperties**注解绑定配置信息到封装类中
- 封装类需要定义为Spring管理的bean,否则无法进行属性注入(当自定义对象和application.yml文件都为springboot所管理时即可完成属性注入)
可验证查看:
…………
@Autowired
private Datasource datasource;
…………
//通过自定义对象装配指定数据
System.out.println(datasource);
笔记小结
整合第三方技术通用方式
- 导入对应的starter
- 根据提供的配置格式,配置非默认值对应的配置项
笔记小结
步骤:
- 导入测试对应的starter (SpringBoot默认自动导入)
- 测试类使用@SpringBootTest修饰(SpringBoot默认自动导入)
- 使用自动装配的形式添加要测试的对象
- 测试类如果存在于引导类所在包或子包中无需指定引导类
- 测试类如果不存在于引导类所在的包或子包中需要通过classes 属性指定引导类
SpringBoot整合JUnit
名称:@SpringBootTest
类型:测试类注解
位置:测试类定义上方
作用:设置JUnit加载的SpringBoot启动类
范例: @SpringBootTest class Springboot04JUnitApplicationTests {}
相关属性
注意:如果测试类在SpringBoot启动类的包或子包中,可以省略启动类的设置,也就是省略classes的设定
补充:通过配置classes属性可以精准的指定配置类或者 引导类加载的位置,否则会自动在测试类所在的包或父包中寻找
示例:
创建dao/BookDao接口
package com.example.dao;
public interface BookDao {
public void save();
}
创建dao/impl/BookDaoImpl
package com.example.dao.impl;
import com.example.dao.BookDao;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("book dao is running~");
}
}
补充:@Repository注解也可换为@Component注解,但此时@Repository注解一般写在数据层上
@Repository用在持久层的接口上,这个注解是将接口的一个实现类交给spring管理。
这是因为@Repository注解的作用不只是将类识别为Bean,同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型。 Spring本身提供了一个丰富的并且是与具体的数据访问技术无关的数据访问异常结构,用于封装不同的持久层框架抛出的异常,使得异常独立于底层的框架。
创建test/java/com/example/Springboot04JunitApplicationTests
@SpringBootTest
class JunitApplicationTests {
@Autowired
private BookDaoImpl bookDao;
@Test
void contextLoads() {
bookDao.save();
}
}
@SpringBootTest:测试类注解 ,设置JUnit加载的SpringBoot启动类
注意:
如果测试类在不SpringBoot启动类的包或子包中,则会报错
因为,此时取得被测试的对象是在容器中的,而我们现在没有按要求防止测试类的位置,所以不能拿到springboot容器。换句话说,因为测试要拿容器里面的bean,容器由配置类里面run方法创建,所以需要去找配置类。
所以,可通过添加注解的classes方式进行指定配置类的位置来进行 解决
笔记小结
步骤:
- 勾选MyBatis技术,也就是导入MyBatis对应的starter
- 数据库连接相关信息转换成配置 (datasource)
- 数据库SQL映射需要添加**@Mapper**被容器识别到
步骤一:创建新模块,选择Spring初始化,并配置模块相关基础信息,并选择当前模块需要使用的技术集(MyBatis、MySQL)
步骤二:设置数据源参数
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: qweasdzxc
注意:
SpringBoot版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区
jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
或在MySQL数据库端配置时区解决此问题
步骤三:定义数据层接口与映射配置
@Mapper
public interface UserDao {
@Select("SELECT * FROM tb_user WHERE id=#{id}")
public User getById(Integer id);
@Select("SELECT * FROM tb_user")
public List<User> getAll();
}
@Mapper注解
1:为了把mapper这个DAO交給Spring管理
2:为了不再写mapper映射文件
3:为了给mapper接口 自动根据一个添加@Mapper注解的接口生成一个实现类
换种方式也可以在启动类上面加MapperScan(“mapper层所在包的全名”),让springboot认识你的mapper层
步骤四:测试类中注入dao接口,测试功
@SpringBootTest
class Springboot05MybatisApplicationTests {
@Autowired
public UserDao userDao;
@Test
void contextLoads() {
System.out.println(userDao.getById(1));
System.out.println(userDao.getAll());
}
}
示例:
创建domain下的User类
package com.example.domain;
public class User {
Integer id;
String username;
String password;
String gender;
String addr;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", gender='" + gender + '\'' +
", addr='" + addr + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
domain层,通常就是用于放置这个系统中,与数据库中的表,一一对应起来的JavaBean的
创建dao包下的UserDao接口
package com.example.dao;
import com.example.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserDao {
@Select("SELECT * FROM tb_user WHERE id=#{id}")
public User getById(Integer id);
@Select("SELECT * FROM tb_user")
public List<User> getAll();
}
在测试类中进行测试
package com.example;
import com.example.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Springboot05MybatisApplicationTests {
@Autowired
public UserDao userDao;
@Test
void contextLoads() {
System.out.println(userDao.getById(1));
System.out.println(userDao.getAll());
}
}
设置application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: qweasdzxc
补充:常见MyBatis问题
- MySQL 8.X驱动强制要求设置时区
- 修改url,添加serverTimezone设定
- 修改MySQL数据库配置(略)
- 驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver
笔记小结
- 手工添加MyBatis-Plus对应的starter
- 数据层接口使用BaseMapper简化开发
- 需要使用的第三方技术无法通过勾选确定时,需要手工添加坐标
MyBatis-Plus与MyBatis区别
1.手动添加SpringBoot整合MyBatis-Plus的坐标,可以通过mvnrepository获取
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.3version>
dependency>
补充:由于SpringBoot中未收录MyBatis-Plus的坐标版本,需要指定对应的Version
可前往网站获取最新坐标:Maven Repository: com.baomidou » mybatis-plus-boot-starter (mvnrepository.com)
2.定义数据层接口与映射配置,继承BaseMapper
@Mapper
public interface UserDao extends BaseMapper<User> {
}
3.其他同SpringBoot整合MyBatis
示例:
创建domain下的User类
package com.example.domain;
public class User {
Integer id;
String username;
String password;
String gender;
String addr;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", gender='" + gender + '\'' +
", addr='" + addr + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
domain层,通常就是用于放置这个系统中,与数据库中的表,一一对应起来的JavaBean的
创建dao包下的UserDao接口
package com.example.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.domain.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao extends BaseMapper<User> {
}
设置application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: qweasdzxc
#mybatis-plus
mybatis-plus:
global-config:
db-config:
table-prefix: tb_
注意:在mybatis-plus中需要定义table-prefix,因为MyBatisPlus的映射缘故。例如mysql表名为tb_user,会被MP变为user
在测试类中进行测试
package com.example;
import com.example.dao.UserDao;
import com.example.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class Springboot05MybatisPlusApplicationTests {
@Autowired
public UserDao userDao;
@Test
void contextLoads() {
User user = userDao.selectById(1);
System.out.println(user);
List<User> list = userDao.selectList(null);
System.out.println(list);
}
}
笔记小结
- 整合Druid需要手动导入Druid对应的starter
- 根据Druid提供的配置方式进行配置
1.导入Druid对应的starter
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.6version>
dependency>
补充:由于SpringBoot中未收录Druid的坐标版本,需要指定对应的Version
可前往网站获取最新坐标:Maven Repository: com.alibaba » druid-spring-boot-starter (mvnrepository.com)
2.配置application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: root
示例:
创建domain下的User类
package com.example.domain;
public class User {
Integer id;
String username;
String password;
String gender;
String addr;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", gender='" + gender + '\'' +
", addr='" + addr + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
domain层,通常就是用于放置这个系统中,与数据库中的表,一一对应起来的JavaBean的
创建dao包下的UserDao接口
package com.example.dao;
import com.example.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserDao {
@Select("SELECT * FROM tb_user WHERE id=#{id}")
public User getById(Integer id);
@Select("SELECT * FROM tb_user")
public List<User> getAll();
}
在测试类中进行测试
package com.example;
import com.example.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Springboot07DruidApplicationTests {
@Autowired
public UserDao userDao;
@Test
void contextLoads() {
System.out.println(userDao.getById(1));
System.out.println(userDao.getAll());
}
}
设置application.yml
#方式一
#spring:
# datasource:
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
# username: root
# password: qweasdzxc
# type: com.alibaba.druid.pool.DruidDataSource
#方式二(标准写法)
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: qweasdzxc
编写测试类:
package com.example;
import com.example.dao.UserDao;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Springboot07DruidApplicationTests {
@Autowired
public UserDao userDao;
@Test
void contextLoads() {
System.out.println(userDao.getById(1));
System.out.println(userDao.getAll());
}
}
补充:当出现 Init DruidDataSource时代表Druid加载成功
笔记小结
pom.xml
配置起步依赖
application.yml
设置数据源、端口、框架技术相关配置等
dao 继承BaseMappe
设置@Mapper
dao测试类
service
调用数据层接口或MyBatis-Plus提供的接口快速开发
service测试类
controller
基于Restful开发,使用Postman测试跑通功能
页面
放置在resources目录下的static目录中
案例实现方案分析
SSMP案例制作流程解析
添加依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.15version>
dependency>
修改配置文件为yml格式
设置端口为80方便访问
笔记小结
使用Lombok依赖
Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
补充:lombok版本由SpringBoot提供,无需指定版本
Lombok:常用注解:@Data
@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
补充:为当前实体类在编译期设置对应的get/set方法,toString方法,hashCode方法,equals方法等
笔记小结
- 手工导入starter坐标(2个)
- 配置数据源与MyBatisPlus对应的配置
- 开发Dao接口(继承BaseMapper)
- 制作测试类测试Dao功能是否有效
技术实现方案
1.导入MyBatisPlus与Druid对应的starter
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.15version>
dependency>
2.配置数据源与MyBatisPlus对应的基础配置(id生成策略使用数据库自增策略)
#tomcat服务器
server:
port: 80
#Jdbc,druid
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
username: root
password: qweasdzxc
#mybatis-plus
mybatis-plus:
global-config:
db-config:
table-prefix: tb_
id-type: auto
3.制作测试类测试结果
package com.example.dao;
import com.example.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BookDaoTest {
@Autowired
private BookDao bookDao;
@Test
void testSave() {
Book book = new Book();
book.setName("玥玥");
book.setType("美女");
book.setDescription("大美女只为你着迷");
System.out.println(bookDao.insert(book));
}
@Test
void testDelete() {
System.out.println(bookDao.deleteById(1));
}
@Test
void testUpdate() {
Book book = new Book();
book.setId(3);
book.setName("杰哥");
book.setType("帅哥");
book.setDescription("大帅哥只为你着迷");
System.out.println(bookDao.updateById(book));
}
@Test
void testSelect() {
System.out.println(bookDao.selectById(2));
System.out.println(bookDao.selectList(null));
}
@Test
void testGetPage() {
}
@Test
void testGetByCondition(){
}
}
补充:
1.MyBatisPlus默认:采用驼峰映射规则,例如 Users 对应的数据库表为 users,MyUserTable 对应的数据库表为 my_user_table
2.bookDao.insert(book)插入时,记得给application.yml文件中配置 id-type: auto 的属性,防止mysql数据库自增长失效,从而插入雪花值
笔记小结
使用配置方式开启日志,设置日志输出方式为标准输出
1.为方便调试可以开启MyBatisPlus的日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
笔记小结
- 使用IPage封装分页数据
- 分页操作依赖MyBatisPlus分页拦截器实现功能
- 借助MyBatisPlus日志查阅执行SQL语句
1.分页操作需要设定分页对象IPage
@Test
void testGetPage() {
IPage<Book> iPage = new Page<>(1, 5);
bookDao.selectPage(iPage, null);
System.out.println(iPage.getRecords());
System.out.println(iPage.getCurrent());
System.out.println(iPage.getPages());
System.out.println(iPage.getSize());
System.out.println(iPage.getTotal());
}
2.IPage对象中封装了分页操作中的所有数据
3.分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能, 使用MyBatisPlus拦截器实现
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加具体的拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
我们所做的所有东西都得受spring进行管理,而spring是用来管理bena的,因此我们需要使用spring管理第三方bean的方式初始化并加载出来
@Configuration注解作用
1.告诉spring这是一个配置类,相当于spring的xml配置文件
2.被@Configuration 注解的类,会被cglib代理进行增强
3.@Configuration类允许通过调用同一类中的其他@Bean方法来定义bean之间的依赖关系,保证@Bean的对象作用域受到控制,避免多例
@Bean注解作用
用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理
笔记小结
- 使用QueryWrapper对象封装查询条件
- 推荐使用LambdaQueryWrapper对象
- 所有查询操作封装成方法调用
- 查询条件支持动态条件拼装
使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用
@Test
void testGetByCondition(){
IPage page = new Page(1,10);
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(Book::getName,"Spring");
bookDao.selectPage(page,lqw);
}
@Test
void testGetByCondition(){
QueryWrapper<Book> qw = new QueryWrapper<Book>();
qw.like("name","Spring");
bookDao.selectList(qw);
}
支持动态拼写查询条件✳
@Test
void testGetByCondition(){
String name = "Spring";
IPage page = new Page(1,10);
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(Strings.isNotEmpty(name),Book::getName,"Spring");
bookDao.selectPage(page,lqw);
}
补充:
Java中的双冒号写法::
1.表达式:person -> person.getName();可以替换成:Person::getName
示例:
@Test
void testGetByCondition() {
String name = "spring";
// String name = null;
QueryWrapper<Book> qw = new QueryWrapper<>();
qw.like(name != null, "name", name);
bookDao.selectList(qw);
}
@Test
void testGetByCondition2() {
String name = "spring";
// String name = null;
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();
lqw.like(name != null, Book::getName, name);
bookDao.selectList(lqw);
}
Service层接口定义与数据层接口定义具有较大区别,不要混用
笔记小结
- Service接口名称定义成业务名称,并与Dao接口名称进行区分
- 制作测试类测试Service功能是否有效
接口定义
public interface BookService {
boolean save(Book book);
boolean delete(Integer id);
boolean update(Book book);
Book getById(Integer id);
List<Book> getAll();
IPage<Book> getByPage(int currentPage,int pageSize);
}
实现类定义
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
public Boolean save(Book book) {
return bookDao.insert(book) > 0;
}
public Boolean delete(Integer id) {
return bookDao.deleteById(id) > 0;
}
public Boolean update(Book book) {
return bookDao.updateById(book) > 0;
}
public Book getById(Integer id) {
return bookDao.selectById(id);
}
public List<Book> getAll() {
return bookDao.selectList(null);
}
public IPage<Book> getByPage(int currentPage, int pageSize) {
IPage page = new Page<Book>(currentPage,pageSize);
return bookDao.selectPage(page,null);
}
}
补充:@Service注解用于类上,标记当前类是一个service类,加上该注解会将当前类自动注入到spring容器中
测试类定义
@SpringBootTest
public class BookServiceTest {
@Autowired
private BookService bookService;
@Test
void testGetById(){
bookService.getById(9);
}
@Test
void testGetAll(){
bookService.getAll();
}
@Test
void testGetByPage(){
bookService.getByPage(1,5);
}
… …
}
笔记小结
- 使用通用接口(ISerivce)快速开发Service
- 使用通用实现类(ServiceImpl)快速开发ServiceImpl
- 可以在通用接口基础上做功能重载或功能追加
- 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
快速开发方案
3.实现类追加功能
示例:
创建service/IBookService接口
package com.example.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.domain.Book;
/**
* 需要继承IService
**/
public interface IBookService extends IService<Book> {
//自定义方式添加接口
Boolean savaBook(Book book);
}
创建service/impl/BookServiceImpl实现类
package com.example.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.dao.BookDao;
import com.example.domain.Book;
import com.example.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 需要继承 ServiceImpl类
* 需要好实现 IBookService接口
**/
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
private final BookDao bookDao;
@Autowired
public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
//实现自定义方式的接口
@Override
public Boolean savaBook(Book book) {
return bookDao.insert(book) > 0;
}
}
创建test/java包下创建dao/service下的测试类
package com.example.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.domain.Book;
import com.example.service.impl.BookServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class BookServiceTest {
@Autowired
BookServiceImpl bookService;
@Test
void testSave() {
Book book = new Book();
book.setName("玥玥");
book.setType("美女");
book.setDescription("大美女只为你着迷");
//System.out.println(bookService.save(book));
System.out.println(bookService.savaBook(book));
}
@Test
void testDelete() {
System.out.println(bookService.removeById(1));
}
@Test
void testUpdate() {
Book book = new Book();
book.setId(3);
book.setName("杰哥");
book.setType("帅哥");
book.setDescription("大帅哥只为你着迷");
System.out.println(bookService.updateById(book));
}
@Test
void testSelect() {
System.out.println(bookService.list(null));
}
@Test
void testGetPage() {
IPage<Book> iPage = new Page<>(1, 5);
bookService.page(iPage);
System.out.println(iPage.getRecords());
System.out.println(iPage.getCurrent());
System.out.println(iPage.getPages());
System.out.println(iPage.getSize());
System.out.println(iPage.getTotal());
}
}
笔记小结
- 基于Restful制作表现层接口
- 新增:POST
- 删除:DELETE
- 修改:PUT
- 查询:GET
- 接收参数
- 实体数据:@RequestBody
- 路径变量:@PathVariable
功能测试
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService bookService;
@GetMapping
public List<Book> getAll(){
return bookService.list();
}
}
表现层接口开发
@RestController
@RequestMapping("/books")
public class BookController {
//此处是IBookService接口
@Autowired
private IBookService bookService;
//增加
@PutMapping
public Boolean save(@RequestBody Book book) {
return bookService.save(book);
}
//删除
@DeleteMapping("/{id}")
public Boolean delete(@PathVariable Integer id) {
return bookService.removeById(id);
}
//修改
@PostMapping
public Boolean update(@RequestBody Book book) {
return bookService.updateById(book);
}
//查询--单个
@GetMapping("/{id}")
public Book getById(@PathVariable Integer id) {
return bookService.getById(id);
}
//查询--所有
@GetMapping
public List<Book> getAll() {
return bookService.list();
}
//查询--分页
@GetMapping("/{currentPage}/{pageSize}")
public IPage<Book> getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
return bookService.getPage(currentPage, pageSize);
}
}
postman测试
笔记小结
- 设计统一的返回值结果类型便于前端开发读取数据
- 返回值结果类型可以根据需求自行设定,没有固定格式
- 返回值结果模型类用于后端与前端进行数据格式统一,也称为前 后端数据协议
1.设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为前后端数据协议
创建controller/utils下的R类
@Data
public class R{
private Boolean flag;
private Object data;
}
@Data
public class R{
private Boolean flag;
private Object data;
public R(){
}
public R(Boolean flag){
this.flag = flag;
}
public R(Boolean flag,Object data){
this.flag = flag;
this.data = data;
}
}
2.表现层接口统一返回值类型结果
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IBookService bookService;
@PostMapping
public R save(@RequestBody Book book){
Boolean flag = bookService.insert(book);
return new R(flag);
}
@PutMapping
public R update(@RequestBody Book book){
Boolean flag = bookService.modify(book);
return new R(flag);
}
@DeleteMapping("/{id}")
public R delete(@PathVariable Integer id){
Boolean flag = bookService.delete(id);
return new R(flag);
}
@GetMapping("/{id}")
public R getById(@PathVariable Integer id){
Book book = bookService.getById(id);
return new R(true,book);
}
@GetMapping
public R getAll(){
List<Book> bookList = bookService.list();
return new R(true ,bookList);
}
@GetMapping("/{currentPage}/{pageSize}")
public R getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){
IPage<Book> page = bookService.getPage(currentPage, pageSize);
return new R(true,page);
}
}
- 单体项目中页面放置在resources/static目录下
- created钩子函数用于初始化页面时发起调用
- 页面使用axios发送异步请求获取数据后确认前后端是否联通
1.前端发送异步请求,调用后端接口
//列表
getAll() {
axios.get("/books").then((res)=>{
console.log(res.data);
});
}
补充:页面使用axios发送异步请求获取数据后确认前后端是否联通
2.列表页
//列表
getAll() {
axios.get("/books").then((res)=>{
this.dataList = res.data.data;
});
}
补充:将查询数据返回到页面,利用前端数据双向绑定进行数据展示
3.弹出添加窗口
//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
}
4.清除数据
//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
this.resetForm();
}
补充:弹出添加Div时清除表单数据
//重置表单
resetForm() {
this.formData = {};
}
5.添加
//添加
handleAdd () {
//发送异步请求
axios.post("/books",this.formData).then((res)=>{
//如果操作成功,关闭弹层,显示数据
if(res.data.flag){
this.dialogFormVisible = false;
this.$message.success("添加成功");
}else {
this.$message.error("添加失败");
}
}).finally(()=>{
this.getAll();
});
}
补充:请求方式使用POST调用后台对应操作,添加操作结束后动态刷新页面加载数据,根据操作结果不同,显示对应的提示信息
6.取消添加
//取消
cancel(){
this.dialogFormVisible = false;
this.$message.info("操作取消");
}
7.删除
handleDelete(row) {
// console.log(row);
//1.弹出提示框
this.$confirm("此操作永久删除当前信息,是否继续?", "提示", {type: "info"}).then(() => {
//2.做删除业务
axios.delete("/books/" + row.id).then((res) => {
if (res.data.flag) {
this.$message.success("删除成功");
} else {
this.$message.error("数据同步失败,自动刷新");
}
}).finally(() => {
//2.重新加载数据
this.getAll();
});
}).catch(() => {
//3.取消删除
this.$message.info("取消操作");
});
}
注意:请求方式使用Delete调用后台对应操作
补充:删除操作需要传递当前行数据对应的id值到后,删除操作结束后动态刷新页面加载数据,根据操作结果不同,显示对应的提示信息,删除操作前弹出提示框避免误操作(防手抖)
8.弹出修改窗口
//弹出编辑窗口
handleUpdate(row) {
//弹出编辑框时获取实时数据
axios.get("/books/" + row.id).then((res) => {
if (res.data.flag && res.data.data != null) {
this.dialogFormVisible4Edit = true;
this.formData = res.data.data;
} else {
this.$message.error("数据同步失败,自动刷新");
}
}).finally(() => {
//2.重新加载数据
this.getAll();
});
}
注意:res.data.flag && res.data.data != null 逻辑判断
补充:加载要修改数据通过传递当前行数据对应的id值到后台查询数据,利用前端数据双向绑定将查询到的数据进行回显
9.修改
//修改
handleEdit() {
axios.put("/books", this.formData).then((res) => {
//判断当前操作是否成功
if (res.data.flag) {
//1.关闭弹层
this.dialogFormVisible4Edit = false;
this.$message.success("修改成功");
} else {
this.$message.error("修改失败");
}
}).finally(() => {
//2.重新加载数据
this.getAll();
});
}
注意:请求方式使用PUT调用后台对应操作
补充:修改操作结束后动态刷新页面加载数据(同新增). 根据操作结果不同,显示对应的提示信息(同新增 )
10.取消添加和修改
cancel(){
this.dialogFormVisible = false;
this.dialogFormVisible4Edit = false;
this.$message.info("操作取消");
}
笔记小结
- 使用注解**@RestControllerAdvice**定义SpringMVC异常处理 器用来处理异常的
- 异常处理器必须被扫描加载,否则无法生效
- 表现层返回结果的模型类中添加消息属性用来传递消息到页面
11.对异常进行统一处理,出现异常后,返回指定信息
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(Exception.class)
public R doOtherException(Exception ex){
//记录日志
//发送消息给运维
//发送邮件给开发人员,ex对象发送给开发人员
ex.printStackTrace();
return new R(false,null,"系统错误,请稍后再试!");
}
注意:ex.printStackTrace();异常打印
补充:
一旦项目中发生了异常,就会进入使用了RestControllerAdvice注解类中使用了ExceptionHandler注解的方法,我们可以在这里处理全局异常,将异常信息输出到指定的位置。并对所有的错误信息进行归置。
@RestControllerAdvice是什么
@RestControllerAdvice是一个组合注解,由@ControllerAdvice、@ResponseBody组成,而@ControllerAdvice继承了@Component,因此@RestControllerAdvice本质上是个Component,用于定义@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。
@RestControllerAdvice的特点:
- 通过@ControllerAdvice注解可以将对于控制器的全局配置放在同一个位置。
- 注解了@RestControllerAdvice的类的方法可以使用**@ExceptionHandler、@InitBinder、@ModelAttribute**注解到方法上。
- @RestControllerAdvice注解将作用在所有注解了**@RequestMapping**的控制器的方法上。
- @ExceptionHandler:用于指定异常处理方法。当与@RestControllerAdvice配合使用时,用于全局处理控制器里的异常。
- @InitBinder:用来设置WebDataBinder,用于自动绑定前台请求参数到Model中。
- @ModelAttribute:本来作用是绑定键值对到Model中,当与@ControllerAdvice配合使用时,可以让全局的@RequestMapping都能获得在此处设置的键值对
参考链接:RestControllerAdvice注解与全局异常处理 - 掘金 (juejin.cn)
修改表现层返回结果的模型类,封装出现异常后对应的信息
@Data
public class R{
private Boolean flag;
private Object data;
private String msg;
public R(Boolean flag,Object data,String msg){
this.flag = flag;
this.data = data;
this.msg = msg;
}
}
可以在表现层Controller中进行消息统一处理
@PostMapping
public R save(@RequestBody Book book) throws IOException {
Boolean flag = bookService.insert(book);
return new R(flag , flag ? "添加成功^_^" : "添加失败-_-!");
}
目的:国际化
页面消息处理,没有传递消息加载默认消息,传递消息后加载指定消息
//添加
handleAdd () {
//发送ajax请求
axios.post("/books",this.formData).then((res)=>{
//如果操作成功,关闭弹层,显示数据
if(res.data.flag){
this.dialogFormVisible = false;
this.$message.success(res.data.msg);
}else {
this.$message.error(res.data.msg);
}
}).finally(()=>{
this.getAll();
});
}
笔记小结
- 使用el分页组件
- 定义分页组件绑定的数据模型
- 异步调用获取分页数据
- 分页数据页面回显
1.页面使用el分页组件添加分页功能
<div class="pagination-container">
<el-pagination
class="pagiantion"
@current-change="handleCurrentChange"
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
layout="total, prev, pager, next, jumper"
:total="pagination.total">
el-pagination>
div>
2.定义分页组件需要使用的数据并将数据绑定到分页组件
data:{
pagination: { //分页相关模型数据
currentPage: 1, //当前页码
pageSize:10, //每页显示的记录数
total:0, //总记录数
}
}
3.替换查询全部功能为分页功能
getAll() {
axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res) => {
});
}
4.分页查询
//查询--分页
@GetMapping("/{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
return new R(true, bookService.getPage(currentPage, pageSize), "查询成功~");
}
注意:使用路径参数传递分页数据或封装对象传递数据
5.加载分页数据
getAll() {
axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res) => {
this.pagination.total = res.data.data.total;
this.pagination.currentPage = res.data.data.current;
this.pagination.pagesize = res.data.data.size;
this.dataList = res.data.data.records;
});
}
6.分页页码值切换
//切换页码
handleCurrentChange(currentPage) {
this.pagination.currentPage = currentPage;
this.getAll();
}
7.删除功能维护
@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage,@PathVariable int pageSize){
IPage<Book> page = bookService.getPage(currentPage, pageSize);
//如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
if( currentPage > page.getPages()){
page = bookService.getPage((int)page.getPages(), pageSize);
}
return new R(true, page);
}
补充:对查询结果进行校验,如果当前页码值大于最大页码值,使用最大页码值作为当前页码值重新查询。此方法不能有效解决。项目开发中,当删除一项时,可直接跳转到第一页。
笔记小结
- 定义查询条件数据模型(当前封装到分页数据模型中)
- 异步调用分页功能并通过请求参数传递数据到后台
- 后台通过分页查询时携带条件
1.查询条件数据封装
pagination: { //分页相关模型数据
currentPage: 1, //当前页码
pageSize:10, //每页显示的记录数
total:0, //总记录数
name: "",
type: "",
description: ""
}
2.页面数据模型绑定
<div class="filter-container">
<el-input placeholder="图书类别" v-model="pagination.type" class="filter-item"/>
<el-input placeholder="图书名称" v-model="pagination.name" class="filter-item"/>
<el-input placeholder="图书描述" v-model="pagination.description" class="filter-item"/>
<el-button @click="getAll()" class="dalfBut">查询el-button>
<el-button type="primary" class="butT" @click="handleCreate()">新建el-button>
div>
3.组织数据成为get请求发送的数据
getAll() {
//1.获取查询条件,拼接查询条件
param = "?name="+this.pagination.name;
param += "&type="+this.pagination.type;
param += "&description="+this.pagination.description;
console.log("-----------------"+ param);
axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param)
.then((res) => {
this.dataList = res.data.data.records;
});
}
定义查询条件数据模型(当前封装到分页数据模型中)
4.Controller接收参数
@GetMapping("{currentPage}/{pageSize}")
public R getAll(@PathVariable int currentPage,@PathVariable int pageSize,Book book) {
System.out.println("参数=====>"+book);
IPage<Book> pageBook = bookService.getPage(currentPage,pageSize);
return new R(null != pageBook ,pageBook);
}
补充:请求参数和形式参数同名,自动注入,或者直接模型类中的属性和请求参数相同,也会直接注入到模型类中。
SpringMVC会按请求参数名(name)和POJO属性名进行自动匹配,自动为该对象填充属性值
这就是,POJO获取请求参数。
5.业务层接口功能开发
public interface IBookService extends IService<Book> {
IPage<Book> getPage(Integer currentPage,Integer pageSize,Book queryBook);
}
@Service
public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService {
public IPage<Book> getPage(Integer currentPage,Integer pageSize,Book queryBook){
IPage page = new Page(currentPage,pageSize);
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(Strings.isNotEmpty(queryBook.getName()),Book::getName,queryBook.getName());
lqw.like(Strings.isNotEmpty(queryBook.getType()),Book::getType,queryBook.getType());
lqw.like(Strings.isNotEmpty(queryBook.getDescription()),
Book::getDescription,queryBook.getDescription());
return bookDao.selectPage(page,lqw);
}
}
6.Controller调用业务层分页条件查询接口
@GetMapping("{currentPage}/{pageSize}")
public R getAll(@PathVariable int currentPage,@PathVariable int pageSize,Book book) {
IPage<Book> pageBook = bookService.getPage(currentPage,pageSize,book);
return new R(null != pageBook ,pageBook);
}
7.页面回显数据
getAll() {
//1.获取查询条件,拼接查询条件
param = "?name="+this.pagination.name;
param += "&type="+this.pagination.type;
param += "&description="+this.pagination.description;
console.log("-----------------"+ param);
axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param)
.then((res) => {
this.pagination.total = res.data.data.total;
this.pagination.currentPage = res.data.data.current;
this.pagination.pagesize = res.data.data.size;
this.dataList = res.data.data.records;
});
}
异步调用分页功能并通过请求参数传递数据到后台
DROP TABLE IF EXISTS `tb_book`;
CREATE TABLE `tb_book` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of tbl_book
-- ----------------------------
INSERT INTO `tb_book` VALUES (1, '计算机理论', 'Spring实战 第5版', 'Spring入门经典教程,深入理解Spring原理技术内幕');
INSERT INTO `tb_book` VALUES (2, '计算机理论', 'Spring 5核心原理与30个类手写实战', '十年沉淀之作,手写Spring精华思想');
INSERT INTO `tb_book` VALUES (3, '计算机理论', 'Spring 5设计模式', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手');
INSERT INTO `tb_book` VALUES (4, '计算机理论', 'Spring MVC+MyBatis开发从入门到项目实战', '源码级剖析Spring框架,适合已掌握Java基础的读者');
INSERT INTO `tb_book` VALUES (5, '计算机理论', '轻量级Java Web企业应用实战', '源码级剖析Spring框架,适合已掌握Java基础的读者');
INSERT INTO `tb_book` VALUES (6, '计算机理论', 'Java核心技术卷I基础知识(原书第11版)', 'Core Java第11版,Jolt大奖获奖作品,针对Java SE9、10、11全面更新');
INSERT INTO `tb_book` VALUES (7, '计算机理论', '深入理解Java虚拟机', '5个维度全面剖析JVM,大厂面试知识点全覆盖');
INSERT INTO `tb_book` VALUES (8, '计算机理论', 'Java编程思想(第4版)', 'Java学习必读经典,殿堂级著作!赢得了全球程序员的广泛赞誉');
INSERT INTO `tb_book` VALUES (9, '计算机理论', '零基础学Java(全彩版)', '零基础自学编程的入门图书,由浅入深,详解Java语言的编程思想和核心技术');
INSERT INTO `tb_book` VALUES (10, '市场营销', '直播就该这么做:主播高效沟通实战指南', '李子染、李佳琦、薇娅成长为网红的秘密都在书中');
INSERT INTO `tb_book` VALUES (11, '市场营销', '直播销讲实战—本通', '和秋叶—起学系列网络营销书籍');
INSERT INTO `tb_book` VALUES (12, '市场营销', '直播带货:淘宝、天猫直播从新手到高手', '—本教你如何玩转直播的书,10堂课轻松实现带货月入3W+');
SET FOREIGN_KEY_CHECKS = 1;
1.创建模块时勾选功能
2.在pom.xml文件中手动添加依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.15version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
3.在项目模块中创建如下目录
4.创建domain/Book类
package com.example.domain;
import lombok.Data;
@Data
public class Book {
private Integer id;
private String type;
private String name;
private String description;
}
5.创建dao/BookDao接口
package com.example.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.domain.Book;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BookDao extends BaseMapper<Book> {
}
6.创建service/IBookService接口
package com.example.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.domain.Book;
/**
* 需要继承IService
**/
public interface IBookService extends IService<Book> {
//自定义方式添加接口
Boolean savaBook(Book book);
Book getById(Integer id);
IPage<Book> getPage(int currentPage, int pageSize, LambdaQueryWrapper<Book> lqw);
}
7.创建service/impl/BookServiceImpl实现类
package com.example.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.dao.BookDao;
import com.example.domain.Book;
import com.example.service.IBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 需要继承 ServiceImpl类
* 需要好实现 IBookService接口
**/
@Service
public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService {
@Autowired
private BookDao bookDao;
//实现自定义方式的接口
@Override
public Boolean savaBook(Book book) {
return bookDao.insert(book) > 0;
}
@Override
public Book getById(Integer id) {
return bookDao.selectById(id);
}
@Override
public IPage<Book> getPage(int currentPage, int pageSize, LambdaQueryWrapper<Book> lqw) {
IPage<Book> iPage = new Page<>(currentPage, pageSize);
bookDao.selectPage(iPage, lqw);
return iPage;
}
}
8.创建controller/utils/R类
package com.example.controller.utils;
import lombok.Data;
@Data
public class R {
Boolean flag;
Object data;
private String msg;
public R(Boolean flag) {
this.flag = flag;
}
public R(Boolean flag, Object data) {
this.flag = flag;
this.data = data;
}
public R(Boolean flag, Object data, String msg) {
this.flag = flag;
this.data = data;
this.msg = msg;
}
}
9.创建controller/utils/ProjectExceptionAdvice类
package com.example.controller.utils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
//作为springMVC的异常处理
//@ControllerAdvice
@RestControllerAdvice
public class ProjectExceptionAdvice {
//拦截所有的异常处理
@ExceptionHandler
public R doException(Exception e) {
//记录日志
//通知运维
//通知开发
e.printStackTrace();//注意,打印日志,此处实际开发时容易忽略
return new R(false, null, "服务器故障,请稍后再试");
}
}
注意:ex.printStackTrace();异常打印
10.创建controller/BookController类
package com.example.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.controller.utils.R;
import com.example.domain.Book;
import com.example.service.IBookService;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@RestController
@RequestMapping("/books")
public class BookController {
//此处是IBookService接口
@Autowired
private IBookService bookService;
//增加
@PostMapping
public R save(@RequestBody Book book) throws IOException {
boolean flag;
//健壮性判断
if (book.getType().trim().length() == 0) {
flag = false;
return new R(flag, null, flag ? "添加成功~" : "图书类别不能为空!");
} else if (book.getName().trim().length() == 0) {
flag = false;
return new R(flag, null, flag ? "添加成功~" : "图书名称不能为空!");
} else if (book.getDescription().trim().length() == 0) {
flag = false;
return new R(flag, null, flag ? "添加成功~" : "描述不能为空!");
}
flag = bookService.save(book);
return new R(flag, null, flag ? "添加成功~" : "添加失败");
}
//删除
@DeleteMapping("/{id}")
public R delete(@PathVariable Integer id) {
boolean flag = bookService.removeById(id);
return new R(flag, null, flag ? "删除成功~" : "数据同步失败,自动刷新");
}
//修改
@PutMapping
public R update(@RequestBody Book book) {
boolean flag = bookService.updateById(book);
return new R(flag, null, flag ? "修改成功~" : "数据同步失败,自动刷新");
}
//查询--单个
@GetMapping("/{id}")
public R getById(@PathVariable Integer id) {
Book book = bookService.getById(id);
boolean flag;
if (book != null) {
flag = true;
} else {
flag = false;
}
return new R(true, bookService.getById(id), flag ? "查询成功~" : "数据同步失败,自动刷新");
}
//查询--所有
@GetMapping
public R getAll() {
return new R(true, bookService.list(), "查询成功~");
}
// //查询--分页
// @GetMapping("/{currentPage}/{pageSize}")
// public R getPage(@PathVariable int currentPage, @PathVariable int pageSize) {
// IPage page = bookService.getPage(currentPage, pageSize);
// //如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
// if (currentPage > page.getPages()) {
// currentPage = (int) page.getPages();
// page = bookService.getPage(currentPage, pageSize);
// }
// return new R(true, page, "查询成功~");
// }
//查询--分页
@GetMapping("/{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage, @PathVariable int pageSize, Book queryBook) {
LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>();
lqw.like(Strings.isNotEmpty(queryBook.getType()), Book::getType, queryBook.getType());
lqw.like(Strings.isNotEmpty(queryBook.getName()), Book::getName, queryBook.getName());
lqw.like(Strings.isNotEmpty(queryBook.getDescription()), Book::getDescription, queryBook.getDescription());
IPage<Book> page = bookService.getPage(currentPage, pageSize, lqw);
//如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
if (currentPage > page.getPages()) {
currentPage = (int) page.getPages();
page = bookService.getPage(currentPage, pageSize, lqw);
}
return new R(true, page, "查询成功~");
}
}
11.创建resources/static/pages/books.html
DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<title>基于SpringBoot整合SSM案例title>
<meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
<link href="../plugins/elementui/index.css" rel="stylesheet">
<link href="../plugins/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link href="../css/style.css" rel="stylesheet">
head>
<body class="hold-transition">
<div id="app">
<div class="content-header">
<h1>图书管理h1>
div>
<div class="app-container">
<div class="box">
<div class="filter-container">
<el-input class="filter-item" placeholder="图书类别" style="width: 200px;"
v-model="pagination.type">el-input>
<el-input class="filter-item" placeholder="图书名称" style="width: 200px;"
v-model="pagination.name">el-input>
<el-input class="filter-item" placeholder="图书描述" style="width: 200px;"
v-model="pagination.description">el-input>
<el-button @click="getAll()" class="dalfBut">查询el-button>
<el-button @click="handleCreate()" class="butT" type="primary">新建el-button>
div>
<el-table :data="dataList" current-row-key="id" highlight-current-row size="small" stripe>
<el-table-column align="center" label="序号" type="index">el-table-column>
<el-table-column align="center" label="图书类别" prop="type">el-table-column>
<el-table-column align="center" label="图书名称" prop="name">el-table-column>
<el-table-column align="center" label="描述" prop="description">el-table-column>
<el-table-column align="center" label="操作">
<template slot-scope="scope">
<el-button @click="handleUpdate(scope.row)" size="mini" type="primary">编辑el-button>
<el-button @click="handleDelete(scope.row)" size="mini" type="danger">删除el-button>
template>
el-table-column>
el-table>
<div class="pagination-container">
<el-pagination
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
:total="pagination.total"
@current-change="handleCurrentChange"
class="pagiantion"
layout="total, prev, pager, next, jumper">
el-pagination>
div>
<div class="add-form">
<el-dialog :visible.sync="dialogFormVisible" title="新增图书">
<el-form :model="formData" :rules="rules" label-position="right" label-width="100px"
ref="dataAddForm">
<el-row>
<el-col :span="12">
<el-form-item label="图书类别" prop="type">
<el-input v-model="formData.type"/>
el-form-item>
el-col>
<el-col :span="12">
<el-form-item label="图书名称" prop="name">
<el-input v-model="formData.name"/>
el-form-item>
el-col>
el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述">
<el-input type="textarea" v-model="formData.description">el-input>
el-form-item>
el-col>
el-row>
el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="cancel()">取消el-button>
<el-button @click="handleAdd()" type="primary">确定el-button>
div>
el-dialog>
div>
<div class="add-form">
<el-dialog :visible.sync="dialogFormVisible4Edit" title="编辑检查项">
<el-form :model="formData" :rules="rules" label-position="right" label-width="100px"
ref="dataEditForm">
<el-row>
<el-col :span="12">
<el-form-item label="图书类别" prop="type">
<el-input v-model="formData.type"/>
el-form-item>
el-col>
<el-col :span="12">
<el-form-item label="图书名称" prop="name">
<el-input v-model="formData.name"/>
el-form-item>
el-col>
el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述">
<el-input type="textarea" v-model="formData.description">el-input>
el-form-item>
el-col>
el-row>
el-form>
<div class="dialog-footer" slot="footer">
<el-button @click="cancel()">取消el-button>
<el-button @click="handleEdit()" type="primary">确定el-button>
div>
el-dialog>
div>
div>
div>
div>
body>
<script src="../js/vue.js">script>
<script src="../plugins/elementui/index.js">script>
<script src="../js/jquery.min.js" type="text/javascript">script>
<script src="../js/axios-0.18.0.js">script>
<script>
var vue = new Vue({
el: '#app',
data: {
dataList: [],//当前页要展示的列表数据
dialogFormVisible: false,//添加表单是否可见
dialogFormVisible4Edit: false,//编辑表单是否可见
formData: {
type: "",
name: "",
description: ""
},//表单数据
rules: {//校验规则
type: [{required: true, message: '图书类别为必填项', trigger: 'blur'}],
name: [{required: true, message: '图书名称为必填项', trigger: 'blur'}]
},
pagination: {//分页相关模型数据
currentPage: 1,//当前页码
pageSize: 10,//每页显示的记录数
total: 0,//总记录数
type: "",
name: "",
description: ""
}
},
//钩子函数,VUE对象初始化完成后自动执行
created() {
//调用查询全部数据的操作
this.getAll();
},
methods: {
//列表
// getAll() {
// //发送异步请求
// axios.get("/books").then((res)=>{
// // console.log(res.data);
// this.dataList = res.data.data;
// });
// },
//分页查询
getAll() {
//组织参数,拼接url请求地址
// console.log(this.pagination.type);
param = "?type=" + this.pagination.type;
param += "&name=" + this.pagination.name;
param += "&description=" + this.pagination.description;
// console.log(param);
//发送异步请求
axios.get("/books/" + this.pagination.currentPage + "/" + this.pagination.pageSize + param).then((res) => {
this.pagination.pageSize = res.data.data.size;
this.pagination.currentPage = res.data.data.current;
this.pagination.total = res.data.data.total;
this.dataList = res.data.data.records;
});
},
//切换页码
handleCurrentChange(currentPage) {
//修改页码值为当前选中的页码值
this.pagination.currentPage = currentPage;
//执行查询
this.getAll();
},
//弹出添加窗口
handleCreate() {
this.dialogFormVisible = true;
//每次打开窗口重置数据
this.resetForm();
},
//重置表单
resetForm() {
this.formData = {
type: "",
name: "",
description: ""
}
},
//添加
handleAdd() {
// axios({
// method: "put",
// url: "/books",
// data: this.formData
// }).then(response => {
// console.log(response.data)
// }).finally(() => {
// //2.重新加载数据
// this.getAll();
// })
axios.post("/books", this.formData).then((res) => {
//判断当前操作是否成功
if (res.data.flag) {
//1.关闭弹层
this.dialogFormVisible = false;
this.$message.success(res.data.msg);
} else {
this.$message.error(res.data.msg);
}
}).finally(() => {
//2.重新加载数据
this.getAll();
});
},
//取消
cancel() {
//关闭添加表单
this.dialogFormVisible = false;
//关闭编辑表单
this.dialogFormVisible4Edit = false;
this.$message.info("当前操作取消");
},
// 删除
handleDelete(row) {
// console.log(row);
this.$confirm("此操作永久删除当前信息,是否继续?", "提示", {type: "info"}).then(() => {
axios.delete("/books/" + row.id).then((res) => {
if (res.data.flag) {
this.$message.success(res.data.msg);
} else {
this.$message.error(res.data.msg);
}
}).finally(() => {
//2.重新加载数据
this.getAll();
});
}).catch(() => {
this.$message.info("取消操作");
});
},
//弹出编辑窗口
handleUpdate(row) {
//弹出编辑框时获取实时数据
axios.get("/books/" + row.id).then((res) => {
if (res.data.flag && res.data.data != null) {
this.dialogFormVisible4Edit = true;
this.formData = res.data.data;
} else {
this.$message.error(res.data.msg);
}
}).finally(() => {
//2.重新加载数据
this.getAll();
});
},
//修改
handleEdit() {
axios.put("/books", this.formData).then((res) => {
//判断当前操作是否成功
if (res.data.flag) {
//1.关闭弹层
this.dialogFormVisible4Edit = false;
this.$message.success(res.data.msg);
} else {
this.$message.error(res.data.msg);
}
}).finally(() => {
//2.重新加载数据
this.getAll();
});
},
//条件查询
}
})
script>
html>