Java Web -- SpringBoot框架 - 2023.9.16 -
一. 创建 spring 项目
File --> new project --> spring Initializr
Location: D:\teo\springboot-web-quickstart
Language: java
Type: Maven
Group: com.ssyy99
Artifact: boot-web-quickstart // 写完这个 上面的 name 属性 会跟着变
Packaging: Jar
Version: 2.7.15
Dependencies: web --> Spring Web --> Create
# vim pom.xml // 存放 项目的 信息
<parent> // 所有工程的 父工程
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
src/main/java/com/ssyy99/SpringbootWebQuickstartApplication.java // 启动类
二. 创建 springboot 入门程序
@RestController // 注解 定义 此类为 请求处理类
@RequestMapping("/hello") // 注解 定义 此方法为 请求处理方法 并且 浏览器访问/hello 调用此方法
注: No usages(没有用法)通常指与指定的变量、方法或类相关联的代码中未找到对其使用的任何内容。
它意味着该变量、方法或类未被其他部分调用或引用。
# vim com/ssyy99/controller/HelloController.java
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
System.out.println("hello world");
return "hello teo";
}
}
http://localhost:8080/hello
三. 参数的接收:❶ ❷ ❸ ❹ ❺ ❻ ❼ ❽ ❾ ❿
❶. 简单参数的接收 // 简单参数就是 传递的比较少 只有 1个 或 2个
postman请求: http://127.0.0.1:8080/hello?name=Tom&age=10 // 请求时携带的两个参数 name =Tom age=10
get http://127.0.0.1:8080/simpleParam?name=Tom&age=22
post http://127.0.0.1:8080/simpleParam // 传递参数在body 里面设置 name 和age
第一种方法 // 使用 HttpServletRequest 类 接收 传递过来的参数 name 和 age
@RestController
public class RequestController {
@RequestMapping ("/simpleParam")
public String simpleParam(HttpServletRequest request){
String name = request.getParameter("name");
String ageStr = request.getParameter("age");
int age = Integer.parseInt(ageStr);
System.out.println(name + ":" + age);
return "OK";
}
}
第二种方法 // springboot 方式 接收 请求 只要 传过来的名和接收的保持一致即 name 和 age 一致
@RestController
public class RequestController {
@RequestMapping ("/simpleParam")
public String simpleParam(String name,Integer age){
System.out.println(name + ":" + age);
return "OKey";
}
}
// 发送名和 接收的名字 不一致 需要使用 @RequestParam注解
@RestController
public class RequestController {
@RequestMapping ("/simpleParam")
public String simpleParam(@RequestParam(value = "name", required = false) String username,@RequestParam(name = "age") Integer age){
System.out.println(username + ":" + age);
return "OKey";
}
}
注:
@RequestParam(name = "name", required = false)
// 将请求参数绑定到 控制器方法的 参数上。它有以下几个属性:
@RequestParam 属性:
name 或 value:指定请求参数的名称,如果省略,则默认使用方法参数的名称。 "name" 是 客户端 发过来的实际参数 名字
required: 指定请求参数是否必须,如果为 true,则请求中必须包含该参数,否则会报错。默认为 true。
defaultValue:指定请求参数的默认值,如果设置了该值,则 required 会自动设为 false。
❷. 简单实体参数接收 // 用于参数较多 10个 20个的时候 把所有参数封装到一个标准类当中 在使用该类的对象调用即可
首先创建一个 标准类 pojo/User 里面的属性对应的 客户端发来的 所有 参数 get/set/tostring方法 不会问机器人
@RestController
public class RequestController {
@RequestMapping("/simplePojo")
public String simplePojo(User user){
System.out.println(user);
return "okey!!111";
}
}
❸. 复杂实体参数接收 // 用于复杂的参数 address.province address.city
首先创建一个标准类 pojo/Address 里面的属性 有province 及 city
利用上面的 pojo/User 类 在该类中添加 private Address address; 属性 并 重新添加 get/set/tostring
❹. 数组 和 集合参数 接收
// 数组 请求的参数名与形参中数组变量名想通 可以直接使用数组封装
@RequestMapping("/arrayParam")
public String arrayParam(String[] hobby){
System.out.println(Arrays.toString(hobby));
return "okey!!!!3333";
}
// 集合 请求的参数名与形参中数组变量名想通 通过 @RequestParam绑定参数关系
@RequestMapping("/listParam")
public String listParam(@RequestParam List<String> hobby){
System.out.println(hobby);
return "okey!!!!4444";
}
❺. 时间参数 接收
@DataTimeFormat 注解用于将一个字符串转换为一个日期或时间对象,或者将一个日期或时间对象格式化为一个字符串。它有以下属性:
iso:使用 ISO 标准的日期时间格式,例如 DateTimeFormat.ISO.DATE 表示 yyyy-MM-dd 的格式。
pattern:使用自定义的日期时间格式,例如 pattern = “yyyy/MM/dd HH:mm:ss” 表示 2023/09/03 08:31:08 的格式。
style:使用预定义的日期时间样式,例如 style = “MM” 表示中等长度的日期和时间。
fallbackPatterns:当主要的 iso、pattern 或 style 属性无法解析时,使用备用的自定义格式。
http://127.0.0.1:8080/dateParam?updateTime=2023-09-03 08:40:06 // 注意时间格式
@RequestMapping("/dateParam")
public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime){
System.out.println(updateTime);
return "ookk!!!!555";
}
❻. json参数接收 // 只能是post
http://127.0.0.1:8080/jsonParam // postman 中 设置
body --> raw --> JSON
{
"name":"Janice",
"age":10,
"address":{
"province":"shanxi",
"city":"xian"
}
}
创建标准类 User 此类中要与json中的key 保持一致 和 Address 要有 province 及 city 及get set tostring方法
public class User {
private String name;
private Integer age;
private Address address;
}
public class Address {
private String province;
private String city;
}
@RequestMapping("/jsonParam")
public String jsonParam(@RequestBody User user){
System.out.println(user);
return "okey!!! 6666";
}
注:
@RequestBody 是一个 Spring 注解,它用于将 HttpRequest 的请求体映射到一个 Java 对象。
它通常与 POST 方法一起使用,接收来自前端的 JSON 数据
❼. 路径参数
http://127.0.0.1:8080/path/1 // 1个
http://127.0.0.1:8080/path/1/janice // 多个
@RequestMapping("/path/{id}")
public String pathParam(@PathVariable Integer id){
System.out.println(id);
return "oooo!!!!7777";
}
@RequestMapping("/path/{id}/{name}")
public String pathParam(@PathVariable Integer id,@PathVariable String name){
System.out.println(id);
System.out.println(name);
return "oooo!!!!8888";
}
注:
@PathVariable 写在方法参数上, 是一个 Spring 注解,它用于将 URI 路径中的变量绑定到控制器方法的参数上。
它适合用于 RESTful web 服务,其中 URL 包含一些值
@RestController 写在类上, 由 @Controller 和 @ResponseBody 注解的组合 定义 此类为 请求处理类
@ResponseBody 作用是将方法返回值直接响应,如果返回值类型是 实体对象、集合 将会转换为JSON格式响应
@RequestBody 写在方法参数上,是一个 Spring 注解,它用于将 HttpRequest 的请求体映射到一个 Java 对象。
它通常与 POST 方法一起使用,接收来自前端的 JSON 数据
@RequestParam
@RequestParam(name = "name", required = false)
写在方法参数上 将请求参数绑定到 控制器方法的 参数上。它有以下几个属性:
@RequestParam 属性:
name 或 value:指定请求参数的名称,如果省略,则默认使用方法参数的名称。 "name" 是 客户端 发过来的实际参数 名字
required: 指定请求参数是否必须,如果为 true,则请求中必须包含该参数,否则会报错。默认为 true。
defaultValue:指定请求参数的默认值,如果设置了该值,则 required 会自动设为 false。
@RequestMapping("/hello") // 注解 卸载方法前面 定义 此方法为 请求处理方法 并且 浏览器访问/hello 调用此方法
四. 参数的返回: // 定义一个 参数返回的结果类 Result com/ssyy99/pojo/Result.java
统一响应结果: 返回Result.success 方法
http://127.0.0.1:8080/hello
http://127.0.0.1:8080/getAddr
http://127.0.0.1:8080/listAddr
@RestController
public class HelloController {
@RequestMapping("/hello")
public Result hello(){
System.out.println("hello world");
return Result.success("hello teo....");
}
@RequestMapping("/getAddr")
public Result getAddr(){
Address addr =new Address();
addr.setProvince("jilin");
addr.setCity("dashitou");
return Result.success(addr);
}
@RequestMapping("/listAddr")
public Result listAddr(){
List<Address> list = new ArrayList<>();
Address addr1 = new Address();
addr1.setProvince("吉林");
addr1.setCity("大石头");
Address addr2 = new Address();
addr2.setProvince("海南");
addr2.setCity("东方");
list.add(addr1);
list.add(addr2);
return Result.success(list);
}
五. 基于 xml 文件 返回数据
1. # vim pom.xml
<dependencies>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
</dependencies>
2. com/ssyy99/uitls/XmlParserUtils.java // 导入工具类
XmlParserUtils.java
parse(String file, Class<T> targetClass) // parse 在使用的时候传递两个参数1. 指定解析的文件2. 解析完之后封装到哪个类
3. com/ssyy99/pojo/Emp.java // 导入员工的实体类
4. resources/emp.xml // 要解析的 xml 文件
5. resources/static/ // 导入前端页面 这里没有给名字 资料有
@RestController
public class EmpController {
@RequestMapping("/listEmp")
public Result list(){
// 1. 加载并解析emp.xml 返回一个文件路径 用 String file 接收
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
System.out.println(file);
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
// 2. 对数据进行转换处理 gender job
empList.stream().forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("讲师");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就业指导");
}
});
return Result.success(empList);
}
}
类加载器 lassLoader() 类加载器,它负责加载 Java 类的字节码,并将其转换为一个 Java 类对象。
this.getClass().getClassLoader() 获得类加载器
this.getClass().getClassLoader().getResource("emp.xml").getFile() 是一个Java方法,用于获取一个特定资源的URL。
这个方法会首先获取当前类的类加载器(this.getClass().getClassLoader()),然后调用这个类加载器的 getResource 方法。
getResource 方法会查找并返回一个表示该资源的URL对象。这里的“资源”指的是存储在类路径(classpath)上的文件或目录,
比如Java类文件、属性文件、文本文件等。
getFile() 会获取表示请求资源的URL对象。该方法会调用该URL对象的 getFile()方法,该方法返回表示URL对象的文件路径的String
XmlParserUtils.parse() 工具类里 是一个用于解析 XML 文件的方法,它使用了 XML 解析器来解析 XML 文件
六. 三层架构
第一层: controller 调用 service 直接调用 IOC容器去 @Autowired
第二层: service 调用 dao 直接调用 IOC容器去 @Autowired
第三层: dao 取出数据
#vim com/ssyy99/service/EmpService // 定义一个service 接口
public interface EmpService {
public List<Emp> listEmp();
}
#vim com/ssyy99/service/impl/EmpServiceA.java // 实现上面接口
public class EmpServiceA implements EmpService {
private EmpDao empDao = new EmpDaoA();
@Override
public List<Emp> listEmp() {
List<Emp> empList = empDao.listEmp();
empList.stream().forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("讲师");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就业指导");
}
});
return empList;
}
}
#vim com/ssyy99/dao/EmpDao.java // 接口
public interface EmpDao {
public List<Emp> listEmp();
}
#vim com/ssyy99/dao/impl/EmpDaoA.java
public class EmpDaoA implements EmpDao {
@Override
public List<Emp> listEmp() {
// 1. 加载并解析emp.xml 返回一个文件路径 用 String file 接收
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
System.out.println(file);
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
return empList;
}
}
#vim com/ssyy99/controller/EmpController.java
@RestController
public class EmpController {
private EmpService empService =new EmpServiceA();
@RequestMapping("/listEmp")
public Result list(){
List<Emp> empList = empService.listEmp();
return Result.success(empList);
}
}
七. 低耦合 高内聚
控制反转:
@Component 简称IOC 对象的创建控制权由程序自身转移到外部(容器),这中思想称为控制反转
IOC容器: 拥有创建对象的权利, 一个类把自己创建对象权利交给IOC. 使用 @Component
@Component /kəmˈpəʊnənt/ // 在类上使用, 将当前类交给IOC容器管理 称为IOC容器中的Bean
此注解包含三个 衍生注解 一般在 三层使用的时候 用下面的小弟. 在三层之外的类 还使用IOC容器 使用 主注解
@Component("daoA") 指定 Bean的名字 默认的名字为类名的首字母小写 一般不用指定。 下面三个小弟也同样可指定
在 Console 控制台 旁边的 Actuator --> Beans --> 查看左右的 Bean 白色的是自定义的 点开有名字
@Controller 标注在控制器类上 就是标注在 controller 上 @RestController 还包含 Controller 所有也不用标注
@Service 标注在业务类上 就是标注在 service 上 用的最多
@Repository 标注在数据访问类上 就是标注在 dao上 由于与mybatis整合用的少
注: @SpringBootApplication 在启动类上的标注
@SpringBootApplication 是一个用于标记和启动 Spring Boot 应用程序的主类的注解,包含了以下三个注解
@SpringBootConfiguration:表示这是一个 Spring Boot 的配置类
@EnableAutoConfiguration:表示启用 Spring Boot 的自动配置机制
@ComponentScan:表示对当前包及其子包进行组件扫描,自动发现和注册 bean
@ComponentScan 在程序启动时 自动扫描 主类所在包下的所有的类的bean 如果 声明 bean的类在主类包外的话会报错
可以使用 在主类上 从新定义 扫描的 范围: @ComponentScan({"dao","com.ssyy99"})
依赖注入:
@Autowired 简称DI 容器为应用程序提供运行时,所依赖的资源,称为依赖注入
一个接口由多个 类去实现, 另一个类中 需要此接口的 对象 通过 依赖注入
1. @Autowired // 在类里面使用, 只有一个实现接口时候使用
2. @Primary , @Component // 在类上使用, 声明此类是 实现接口的主类, 有多个实现的时候 使用这个实现类
3. @Qualifier("empServiceA") , @Autowired // 在类里面使用, 指明 使用哪个类对象
4. @Resource(name = "empServiceA") // 在类里面使用,单独使用 @Resource 使用哪个类对象
Bean对象: IOC容器中管理的对象叫Bean
1. 将service层及dao层的实现类交给IOC管理
@Component
2. 为Controller及service注入运行时 依赖的对象
@Autowired 在 类里面写 调用 IOC容器 得到 对象
手动获取 某个类的 Bean对象
@SpringBootTest
class SpringbootStudentServerApplicationTests {
@Autowired ApplicationContext applicationContext; // IOC容器对象
@Test
void contextLoads() {
// 根据bean 的名获取bean 默认情况下 bean的名字 是 类名的首字母小写
// 使用getBean 得到的 Bean是 object类型的 需要强制转换
DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
System.out.println(bean1);
// 根据bean的类型获取
DeptController bean2 = applicationContext.getBean(DeptController.class);
System.out.println(bean2);
// 根据bean的名称 和 类型 获取
DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
System.out.println(bean3);
}
}
bean作用域 spring 支持五种作用域, 后三种在web环境才生效
@Scope(prototype) 类的作用域 在实体类上交给IOC托管的类上加此注解
singleton 容器内同 名称 的 bean 只有一个实例 (单列) 默认 只有一个对象
prototype 每次使用该bean 时会创建新的实例(非单例) 都会创建新的对象
request 每个请求范围内会创建新的实例(web环境中,了解)
session 每个会话范围内会创建新的实例(web环境中,了解)
application 每个应用范围内会创建新的实例(web环境中,了解)
@Lazy 在类上使用, 延迟初始化bean 延迟到第一次使用的时候初始化 没有此注解 在IOC容器启动时候 初始化bean
第三方bean对象 管理 使用第三方插件的时候(不是自己创建的类) 要使用其中的对象 不
能直接使用@Component等注解 交给IOC管理 要单独定义之后才能使用
实例: 管理 dom4j 插件的 对象
#vim pom.xml
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
#vim com/ssyy99/config/CommonConfig.java // 创建第三方bean对象 管理类
package com.ssyy99.config;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
@Configurable //配置类 注解允许你将 Spring 管理的对象保存到数据库,并且可以从数据库加载对象
public class CommonConfig {
@Bean // 将当前方法的返回值对象交给IOC容器管理,成为IOC容器的bean对象
// 通过@Bean注解的name/value属性指定bean名称,如果未指定,默认是方法名
public SAXReader saxReader(){ // 如果在声明第三方bean时候需要依赖其他bean对象 可以直接在
return new SAXReader(); // 方法参数上直接声明这个类型的参数
} // public SAXReader saxReader(DeptService deptservice) SAXReader(deptservice)
}
至此可以直接在要使用的类上 @Autowired 注入对象了
八. Servle
连接 Servlet 实现方式
第一种: 实现Servlet接口 实现所有的抽象方法.
第二种: 继承GenericServlet 抽象类 ,必须重写service方法,其他方法可选择重写.使开发Servlet变简单.但是这种方式和HTTP协议无关.
第三种: 继承HttpServlet抽象类,需要重写doGet和doPost方法.该方法表示请求和响应都需要和HTTP协议相关.
第一种实现方法:
# vim /WEB-INF/web.xml // 在此文件中声明 servlet 才能访问servlet
<web-app>
<servlet>
<servlet-name>studentServlet</servlet-name>
<servlet-class>com.teo.servlet.StudentServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>studentServlet</servlet-name>
<url-pattern>/studentServlet</url-pattern>
</servlet-mapping>
</web-app>
注: 在</web-app>标签上面添加
<!-- servlet声明-->
<servlet>
<!-- 声明 servlet 填写 类的名字-->
<servlet-name>studentServlet</servlet-name>
<!-- 通过上面的name 找到此路径的servlet类 ... 包名 + 类名-->
<servlet-class>com.teo.servlet.StudentServlet</servlet-class>
</servlet>
<!--servlet映射-->
<servlet-mapping>
<!-- 此名字和 声明中的 name 保持一致-->
<servlet-name>studentServlet</servlet-name>
<!-- 浏览器地址栏中访问servlet的 路径-->
<url-pattern>/studentServlet</url-pattern>
</servlet-mapping>
</web-app>
# vim /src/com/teo/servlet/StudentServlet
package com.teo.servlet;
import javax.servlet.*;
import java.io.IOException;
public class StudentServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("这是我的第一个Servlet入门案例.... 学习使我快乐 ... ");
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
}
第二种实现方法: // /WEB-INF/web.xml 同上
# vim /src/com/teo/servlet/StudentServlet
package com.teo.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class StudentServlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("学习使我快乐... 我是doget方法");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
九. Mybatis 操作数据库
❶. Mybatis 入门程序
springboot 创建 Mybatis 项目
Name: springboot-mybatis-quicksstart
Location: D:\teo\springboot-mybatis-quicksstart
Language: java
Type: Maven
Group: com.ssyy99
Artifact: springboot-mybatis-quicksstart
Package name: com.ssyy99
Spring Boot 2.7.15
SQL MyBatis Framework 勾选
MySQL Driver 勾选
# vim resources/application.properties
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.10.11/mybatis
spring.datasource.username=root
spring.datasource.password=123456
#配置mybatis的日志, 指定输出到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#开启mybatis的驼峰命名自动映射开关 a_column ------> aCloumn
mybatis.configuration.map-underscore-to-camel-case=true
# vim com/ssyy99/mapper/UserMapper.java // 接口
@Mapper // 在运行时,框架会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理
public interface UserMapper {
// 查询用户全部的用户信息
@Select("select * from tb_user")
public List<User> list();
}
@Mapper
public interface EmpMapper {
@Delete("delete from emp where id = #{id}")
public int delete(Integer id);
}
注:
@Mapper // 用在接口上, 在运行时,框架会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理
@Select("select * from tb_user") 在方法上, 是它用于标记一个方法为一个 SQL 查询语句。
@Select 注解可以让 MyBatis 框架自动执行括号内的 SQL 语句,
并将结果映射到返回值类型或参数类型中. 如果调用该方法 就执行该查询语句,返回的结果
上面的sql语句不能被idea检测语法是否正确,可在sql语句中右键选择Show Context Actions
--> Inject language or reference --> Mysql, 此时就可以sql语句可以被识别,但是不能识别表名会 标红
可以在 右侧工具栏 --> Data Source --> MySQL 中设置数据库信息 即可
jdbc:mysql://192.168.10.11/mybatis 注意这个 url
如不想使用此功能 可 右键 Show Context Actions --> Uninjece language or reference
#{id} 占位符 会把下面方法的参数 传递到此占位符 如果接口方法形参只有一个普通类型参数, #{...}里面属性可随便写 最好与形参一样
#vim com/ssyy99/SpringbootMybatisQuicksstartApplicationTests.java
@SpringBootTest
class SpringbootMybatisQuicksstartApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void testListUser(){
List<User> userList = userMapper.list();
userList.stream().forEach(user -> {
System.out.println(user);
});
}
}
注:
@SpringBootTest 在类上,标识测试类, 将测试类作为应用程序的入口点,并创建一个完整的 Spring 上下文环境。
@Test 在方法上, 它用于标识一个测试方法,使其可以被 JUnit 执行和运行。
❷. 数据库连接池 springboot默认为 Hikari追光者连接池 Druid德鲁伊连接池是阿里巴巴的。 如果要改变 在pom.xml 下添加如下代码即可
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
❸. Mybatis 动态sql 对于查询条件不是固定死的 是动态的
XML映射文件定义动态sql
XML文件的约束: https://mybatis.net.cn/getting-started.html --> 探究已映射的 SQL 语句 --> 复制xml文件代码
1. 同包同名 需要在resources 下创建一个包. 就是要使用sql的类所在的包的全名 并且 创建一个xml配置文件 名字与该类的文件名相同
resources/com/ssyy99/mapper/EmpMapper.xml
2. XML约束文件中 namespace属性 与接口(使用sql的类)的全类名匹配
<mapper namespace="com/ssyy99/mapper/EmpMapper.java">
3. sql语句的 id 要与方法名(使用sql的类的方法)保持一致
sql语句中 resultType 返回类型保持一致(resultType 是单条记录所封装的类型,如果是实体类,直接拷贝实体类的全路径)
<select id="page" resultType="com.ssyy99.pojo.Emp">
Mybatis 的 XML文件的格式
vim resources/com/ssyy99/mapper/EmpMapper.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">
<mapper namespace="com.ssyy99.mapper.EmpMapper">
<select id="page" resultType="com.ssyy99.pojo.Emp">
select *
from emp
<where>
<if test="name != null and name != ''">
name like concat('%',#{name},'%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc
limit #{start},#{pageSize};
</select>
</mapper>
注: <where>标签的作用 1. 自动根据里面的条件判断是否要生成 where 子句
2. 自动去掉第一个条件多余的and 或者是or
<delete id="delete">
delete from emp where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
<foreac> 标签 用于遍历集合
collection 属性是要遍历的集合 写遍历集合的名字即可
item 属性是集合中每一个元素的名字
separator 属性是元素之间的分隔符 {1,2,3} 这里是使用逗号分隔符
open 和 close 属性分别是整个集合的开头和结尾。
这里的例子会生成一个括号内由逗号分隔的 id 列表,例如 (1,2,3)。
❹. Mybatis 操作数据库流程
①. 查询
请求路径:/emps @GetMapping("/emps")
请求方式:GET
接口描述:该接口用于员工列表数据的条件分页查询
需要查询:总数据数 和 emp对象
接收: 键值对 使用 参数接收
返回: jsion 返回一个 封装类
@RequestParam(name = "name", required = false) // 1. 名字不一致时使用, 2. 需要默认值的使用 , 将请求参数绑定到 控制器方法的 参数上
@DateTimeFormat(pattern = "yyyy-MM-dd") // 设置日期格式
@Slf4j // 日志记录
@RestController
Ⅰ. 创建一个 方法 返回值 是 Result 参数是 六个参数 2个有默认值 2个时间
调用 server 中的 查询 方法 返回值是 封装类 (创建一个实体类 一个 int值 一个 list<emp> 集合)
返回 这个封装类
@GetMapping("/emps")
@Autowired
Ⅱ. 查询 总条数 调用 dao类 的结果存到 int 封装在对象里
查询 所有数据 调用 dao类 的结果存到 list 集合 与 上面的一同封装
封装 调用 构造方法
返回 封装好的 实体类
Ⅲ. 查询 直接查询总数 有条件查询 使用 动态sql语句
②. 删除
请求路径/emps/{ids}
路径参数 @PathVariable 有多个参数 使用 集合来接收到
@DeleteMapping("/emps/{ids}")
@PathVariable
使用 动态sql语句 删除条数不一定是几条
接收: @PathVariable 接收路径类型 并 储存到 List数组中
返回:不用返回
③. 新增
请求路径 @PostMapping("/emps")
接收: @RequestBody 接收jsion类型 并 封装到 实体类中
返回: 不用返回
2. 设置两个默认值
3. 插入数据 insert into
④. 文件 图片 视频 音频 上传 本地存储
前端页面上传文件的三要素:
表单项 type = "file"
表单提交方式 post
表单的enctype 属性 multipart/form-data
服务端接收文件 会存到临时文件.请求响应完会自动删除. 要把文件拷贝出来
使用 MultipartFle 类型来接收
文件上传示例一: 使用原始文件名
@PostMapping("/upload")
public Result upload(MultipartFile image) throws Exception {
String originalFilename = image.getOriginalFilename(); // 获取文件的 原始名
image.transferTo(new File("D:\\image\\"+originalFilename)); // 此方法 保存文件
return Result.success();
}
文件上传示例二: 使用uuid作为新的文件名
@PostMapping("/upload")
public Result upload(MultipartFile image) throws Exception {
String originalFilename = image.getOriginalFilename();
int index = originalFilename.lastIndexOf("."); // 字符串 查找最后一个点 . 的位置
String extname = originalFilename.substring(index); // 字符截取 从点的 位置开始截 来获得文件扩展名
String newFileName = UUID.randomUUID().toString() + extname; // 随机获取一个 uuid 拼接上 一个 后缀 为新的名字
image.transferTo(new File("D:\\image\\"+newFileName));
return Result.success();
}
vim application.properties
spring.servlet.multipart.max-file-size=110MB // 设置上传单个上传文件大小 最大是110MB 默认为 1MB
spring.servlet.multipart.max-request-size=310MB // 设置上传总文件大小 最大是310MB 多个文件 可用 集合来接收
⑤. 阿里云 OSS 存储 // 没学 暂时用不上p148-p150 配置文件中 自动注入 阿里云相关的代码 也没学 p155
SDK 工具包
bucket 存储空间
AccessKey 秘钥
❺. <pageHelper> 分页查询 插件 没有学 以后用的可以补充 P142
十. SpringBoot中 的配置文件 名字必须是 application 只是扩展名不同
❶. application.properties
server.port=8080 // key=value
server.address=127.0.0.1
❷. application.yml
server:
port: 8080 // 有缩进 port 和 address 是平级的
address: 127.0.0.1
❸. application.yaml
yml配置文件
1. 大小写敏感
2. 数值前边必须有空格, 作为分隔符
3. 使用缩进表示层级关系, 缩进时,不允许使用Tab键,是能用空格,(idea中会自动将Tab转换为空格)
4. 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
5. #表示注释,从这个字符一直到行尾,都会被解析器忽略
6. 如果是框架用到的是有提示的 自定义的配置没有提示 如配置 阿里云OSS
application.yml
server:
port: 8080
#定义 对象/Map集合
user:
name: Tom
age: 20
address: beijing
# 定义 数组/List集合/Set集合
hobby:
- java
- c
- game
- sport
application.yml // 从application.properties配置文件改用 yml配置文件
# 数据库连接
spring:
datasource:
url: jdbc:mysql://192.168.10.11/hahadb
username: root
password: 123456
# 文件上传配置
servlet:
multipart:
max-file-size: 110MB
max-request-size: 310MB
# mybatis配置
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl
map-underscore-to-camel-case: true
注: 三种配置文件优先级最高的是 application.properties > application.yml > application.yaml
十一. JWT令牌 / 登录 / 登录效验 / 会话跟踪方案
cookie 不允许跨域
移动端app无法使用cookie
不安全 用户可以自己禁用cookie
cookie不能跨域
session
服务器集群环境无法直接使用session
cookie的缺点
JWT令牌
支持pc 移动
解决集群环境下认证问题
跨越 协议 ip/域名 端口 有任何一个不同 就是 跨越
JWT令牌 生成
https://jwt.io/ JWT令牌官网 可以解析令牌
https://c.runoob.com/front-end/693/ Base64 编码的 解码
vim pom.xml // 加入工具类的引用
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency> // 没有会报错 程序在运行时找不到javax.xml.bind.DatatypeConverter类
<groupId>jakarta.xml.bind</groupId> // JDK 11或更高版本 JDK版本过高,JAXB被移出了JDK的默认类路径
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.3</version>
</dependency>
vim com.ssyy99.utils // 生成JWT令牌 类 之后使用 调用此类即可
package com.ssyy99.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtils {
private static String signKey = "itheima"; // 签名的秘钥 秘钥可以随便写 这里是 itheima
private static Long expire = 43200000L; // 指定过期时间为 12小时
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map<String, Object> claims){
String jwt = Jwts.builder() // builder()方法 是生成 jwt 令牌
.addClaims(claims) // 自定义内容 载荷 ,这里是 在参数上加了一个Map集合来存储自定义部分 如 id name
.signWith(SignatureAlgorithm.HS256, signKey) // 指定jwt算法 .hs256是算法, 和秘钥 上面有定义
.setExpiration(new Date(System.currentTimeMillis() + expire)) // 设置有效期 需要一个date对象
.compact(); // 拿到一个字符串返回值 // ↑这里表示当前时间毫秒值 + 3600 *1000 表示1小时
return jwt;
/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser() // parser方法是解析令牌
.setSigningKey(signKey) // 设置签名秘钥 是上面定义的
.parseClaimsJws(jwt) // 添加 解析的 令牌
.getBody(); // 拿到 自定义的内容 Map 集合
return claims;
}
}
十二. 过滤器 filter
过滤器 连接所有请求 根据 JWT令牌 是否放行通过 学的不是很深入
vim com/ssyy99/filter/LoginCheckFilter.java
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
// @Override
// public void init(FilterConfig filterConfig) throws ServletException {
//// Filter.super.init(filterConfig);
// System.out.println("init 方法执行了");
// }
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
System.out.println("拦截到了请求");
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
// 强制转换的目的是为了让req变量能够调用HttpServletRequest接口中定义的方法,
// 而不仅仅是ServletRequest接口中定义的方法。这样可以更方便地处理HTTP请求。
String url = req.getRequestURI().toString(); // 获取请求的url
log.info("请求的url: {}", url);
if (url.contains("login")) { //String 的方法是用于判断一个字符串中是否包含另一个字符串或字符序列的方法
log.info("登录操作: 直接放行");
filterChain.doFilter(servletRequest,servletResponse);
return; // 放行之后 不需要在往后执行 跳出方法
}
String jwt = req.getHeader("token"); // 可以拿到 请求头里面的信息 // ↓要没有值或者null才执行if
if (!StringUtils.hasLength(jwt)) { // spring中提供的方法 判断的 字符串是否为空 有值返回true 前面加个非
log.info("请求头token为空,返回未登录的信息"); // ↓在之前的配置有注解会自动转换成jison 这个需要手动转jison
Result error = Result.error("NOT_LOGIN"); // Result 这个是对象类 不是jison
String notLogin = JSONObject.toJSONString(error); // 阿里巴巴的工具栏类 可以把对象转成 jison格式
resp.getWriter().write(notLogin); // 把 notLogin 返回给浏览器
return;
}
try {
JwtUtils.parseJWT(jwt); // 效验jwt 令牌 如果有异常 证明令牌解析失败
} catch (Exception e) {
e.printStackTrace(); // 捕获并打印异常信息
log.info("解析令牌失败,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
log.info("令牌合法,放行");
filterChain.doFilter(servletRequest,servletResponse); // 放行
}
// @Override
// public void destroy() {
//// Filter.super.destroy();
// System.out.println("destroy 方法 执行了");
// }
}
注: init 初始化方法 只调用一次 和 destroy销毁方法 只调用一次 都有默认的实现 可以不用实现 这里 三个方法都实现了
doFilter() 方法 每一次拦截到请求都会调用一次
javax.servlet.Filter 这个包下的filter
@ServletComponentScan // 需要在启动类上加上注解 开启对 servlet组件的支持
@WebFilter(urlPatterns = "/*") // 过滤器拦截类 /* 代表所有的请求
filterChain及后面两个参数 是 方法参数传递过来的
过滤器执行顺序 先进 doFilter 执行 放行前的代码 通过chain调用dofilter方法放行再执行要访问页面代码之后回来执行放行后的代码
拦截路径: /* 拦截所有 /login 拦截具体目录 只有访问/login才会拦截 /emps/* 访问 emps下的所有资源都会拦截
多个过滤器形成 过滤器链 优先级是 按照过滤器的字母顺序
拦截器 interceptor 没学 168 169 170
全局异常处理器 # vim com/ssyy99/exception/GlobalExceptionHandler.java 用到在补充
十二. SpringBoot 事务
注解: @Transactional // 只有 RuntimeException才回滚 运行异常
@Transactional(rollbackFor = Exception.class) // 所有异常都 回滚
propagation = required // 当一个事物去调用另一个事务方法:此注解的 参数 默认值 需要事务,有则加入,无则创建新事务
// 就是两个事物会合并一个事物 只要有代码抛出异常 就会回滚
requires_new // 需要新事物 无论有无 总是创建新事物 新创建事务 不受其他的事务影响
supports // 支持事务 有则加入 无则在无事务状态中运行
not_supported // 不支持事务, 在无事务状态下运行,如果当前存在事务,则挂起当前事务
mandatory // 必须有事务 否则抛出异常
mever // 必须没有事务 否则抛出异常
业务(service)层的 方法上 类上 接口上 都可以 一般加在 方法上
将当前方法交给spring 进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
十三. AOP 面向特定方法编程
应用场景: 记录操作日志 权限控制 事务管理
优势: 代码无侵入 减少重复代码 提高开发效率 维护方便
案例1: 统计各个业务层方法执行耗时
# vim pom.xml
<dependency> // aop的依赖
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
# vim com/ssyy99/aop/TimeAspect.java
@Slf4j
@Component // 把此类交给 aop容器 处理
@Aspect // 当前类是 apo类
public class TimeAspect {
@Around("execution(* com.ssyy99.service.*.*(..))") // 切入点表达式. 这个方法作用于 包下的所有的类 下的 所有方法
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 1.记录开始时间
long begin = System.currentTimeMillis(); // 获取当前时间的 毫秒值
// 2. 调用原始方法
Object result = joinPoint.proceed(); // 运行原始的方法 会出现异常 抛出即可 使用object 接收返回值
// 3. 记录结束时间,计算方法执行时间
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature() + "方法执行耗时: {}ms", end-begin); // 拿到原始方法的签名...
return result; //把原始的方法返回值 返回回去
}
}
切入点表达式:
1. execution 主要根据方法的返回值 包名 类名 方法名 方法参数等信息来匹配,语法为: // ↓用 参数类型的全名
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?) // 方法参数要 java.lang.Integer
其中带?的表示 前面可以省略的部分: 访问修饰符 包名.类名. 异常 这三部分可以省略
访问修饰符: 可省略 如 public protected
包名.类名: 可省略
throws 异常: 可省略 注意是方法上声明抛出的异常,不是实际抛出的异常
@Pointcut("execution(* com.ssyy99.service.impl.DeptServiceImpl.*(..))")
@Pointcut("execution(* com.ssyy99.service.*.*(..))") // 这个方法作用于 包下的所有的类 下的 所有方法
@Pointcut("execution(void com.ssyy99.service.DeptService.delete(java.lang.Integer))")
// 基于一个接口的表达式 java.lang.Interger 是参数类型的全名
@Pointcut("execution(void com.ssyy99.service.DeptService.*(java.lang.Integer))")
// 通配类下的所有方法 但是 返回值是void 参数类型是Integer
@Pointcut("execution(void com.ssyy99.service.DeptService.*(*))") // 有一个任意类型的参数 方法返回值是 void
@Pointcut("execution(* com.ssyy99.service.DeptService.*(*))") // 有一个任意类型的参数 任意返回值
@Pointcut("execution(* com.ssyy99.service.DeptService.*(..))") // 任意个类型的参数 包括0个
@Pointcut("execution(* *(..))") // 当前环境下 所有的方法 基本用不上
@Pointcut("execution(* *(..)) || execution(* *(..))") // 匹配两个表达式 或者的关系
* : 单个独立的任意符号, 可以通配任意返回值 报名 类名 方法名 任意类型的一个参数 也可以通配包 类 方法名的一部分
execution(* com.*.service.*.update*{*})
.. : 多个连续的任意符号, 可以通配任意层级的包 或任意类型 任意个数的参数
execution(* com.itheima..DeptService.*(..))
2. @annotation 切入点表达式 自定义注解法 用于匹配标识有特定注解的方法
vim com/ssyy99/aop/Mylog.java // 自定义 注解
@Retention(RetentionPolicy.RUNTIME) // 指定什么时候生效 runtime 是运行时生效
@Target(ElementType.METHOD) // 指定注解可以用在哪些地方 method 是 方法上生效
public @interface Mylog { // 定义注解 Mylog
}
@Pointcut("@annotation(com.ssyy99.aop.Mylog)") // 要匹配 方法上有 Mylog注解的方法 在对应方法上加 @Mylog即可
通知类型:
@Around: 环绕通知,此注解标注的通知方法在目标方法前 后 都被执行
@Before: 前置通知,此注解标注的通知方法在目标方法前被执行
@After: 后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
@AfterReturning: 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
@AfterThrowing: 异常后通知,此注解标注的通知方法发生异常后执行
切面类的执行顺序: 按类名排序 通知前方法: 字母靠前先执行... 通知后方法: 字母靠前后执行
@Order(2) // 在类上 控制 切面类的执行 顺序 数字越小 前置通知先执行 后置通知后执行
# vim MyAspect1
@Slf4j
@Component // 把此类交给 aop容器 处理
//@Aspect // 当前类是 apo类 这里注释了 没有开启 这部分代码不会执行
public class MyAspect1 {
@Pointcut("execution(* com.ssyy99.service.impl.DeptServiceImpl.*(..))") // @pointcut 抽取 切入点表达式
public void pt(){} // 声明一个空方法 名字随便定义 无参方法 没有方法体 // ↑其他地方 可以调用 pt() 方法即可
@Before("pt()") // 前置通知 调用pt()方法 使用上面的 且入点表达式
public void before(){
log.info("before ...");
}
@Around("pt()") // 环绕通知
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before ...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed(); // 运行原始的方法 会出现异常 抛出即可 使用object 接收返回值
log.info("around after ...");
return result;
}
@After("pt()") // 后置通知
public void after(){
log.info("after ...");
}
@AfterReturning("pt()") // 返回后通知
public void afterReturning(){
log.info("afterReturning ...");
}
@AfterThrowing("pt()") // 异常后通知
public void afterThrowing(){
log.info("afterThrowing ...");
}
}
连接点: 可以被AOP控制的方法 就是连接点
在 spring 中用 JoinPoint抽象了连接点 用它可以获得方法执行时的相关信息 如目标类名 方法名 方法参数等
对于 @Around通知 获取连接点信息只能使用 ProceedingJoinPoint
对于其他四种通知 获取连接点信息只能使用 JoinPoint 它是 ProceedingJoinPoint的父类型
vim com/ssyy99/aop/MyAspect8.java
//切面类
@Slf4j
//@Aspect
@Component
public class MyAspect8 {
@Pointcut("execution(* com.ssyy99.service.DeptService.*(..))")
private void pt(){}
@Before("pt()")
public void before(JoinPoint joinPoint){ // 这个JoinPoint 有两个包 要选org.aspectj.lang
log.info("MyAspect8 ... before ...");
}
@Around("pt()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("MyAspect8 around before ...");
//1. 获取 目标对象的类名 .
String className = joinPoint.getTarget().getClass().getName();
log.info("目标对象的类名:{}", className);
//2. 获取 目标方法的方法名 .
String methodName = joinPoint.getSignature().getName();
log.info("目标方法的方法名: {}",methodName);
//3. 获取 目标方法运行时传入的参数 .
Object[] args = joinPoint.getArgs();
log.info("目标方法运行时传入的参数: {}", Arrays.toString(args));
//4. 放行 目标方法执行 .
Object result = joinPoint.proceed();
//5. 获取 目标方法运行的返回值 .
log.info("目标方法运行的返回值: {}",result);
log.info("MyAspect8 around after ...");
return result;
}
}
AOP综合案例 将案例中的增 删 改 相关 接口的操作日志 记录到数据库表中 // P182 之后在看
十四. java 项目的打包 // 需要在xml中引入spring-boot-maven-plugin插件 基于官网骨架创建的项目,会自动添加
打包: 右侧Maven工具栏 --> 声明周期 --> 双击 package --> target 目录下会有 打包好的jar包
运行jar包
# java -jar springboot-student-server-0.0.1-SNAPSHOT.jar // java -jar 包名
# java -jar springboot-student-server-0.0.1-SNAPSHOT.jar --server.port=9000 // 在命令行参数上设置 端口号为9000