目录
[1.1 创建mybatis项目(使用spring项目整合式方法)](#1.1 创建mybatis项目(使用spring项目整合式方法))
[1.2 JDBC](#1.2 JDBC)
[1.3 数据库连接池](#1.3 数据库连接池)
[1.4 实用工具:Lombok](#1.4 实用工具:Lombok)
[2.1 准备工作](#2.1 准备工作)
[2.2 导入项目并实现操作](#2.2 导入项目并实现操作)
[2.3 具体操作方法(上方已经提前写好对应方法)](#2.3 具体操作方法(上方已经提前写好对应方法))
[2.3.1 基础操作-删除](#2.3.1 基础操作-删除)
[2.3.2 日志输出](#2.3.2 日志输出)
[2.3.3 基础操作-新增](#2.3.3 基础操作-新增)
[2.3.5 基础操作-查询](#2.3.5 基础操作-查询)
[3.1 动态sql](#3.1 动态sql)
[3.1.1 与](#3.1.1 与)
[3.1.2 与](#3.1.2 与)
[3.1.3 与](#3.1.3 与)
[3.1.4 与](#3.1.4 与)
[4.1 部门管理](#4.1 部门管理)
[4.2 前后端联调](#4.2 前后端联调)
[4.3 员工管理](#4.3 员工管理)
[4.3.1 文件的上传和剩下的增、改功能](#4.3.1 文件的上传和剩下的增、改功能)
[4.3.2 阿里云OOS](#4.3.2 阿里云OOS)
[5.1 登录功能](#5.1 登录功能)
[5.2 JWT令牌](#5.2 JWT令牌)
[5.3 过滤器Filter](#5.3 过滤器Filter)
[5.4 拦截器Interceptor](#5.4 拦截器Interceptor)
[9.1 配置原理](#9.1 配置原理)
[9.2 Bean管理](#9.2 Bean管理)
[9.3 SpringBoot 原理](#9.3 SpringBoot 原理)
前言
自用笔记复盘
一、MyBatis入门(MyBatis官网)
概述:
- MyBatis是一款优秀的 持久层 框架,用于简化JDBC的开发。
- MyBatis本是 Apache的一个开源项目iBatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
下方步骤鄙人不建议使用,srpingboot的工程和mybatis工程都建议使用maven项目创建,并在对应的项目里面添加所对应的依赖即可。不需要创建spring直接整合项目中勾勒选项
1.1 创建mybatis项目(使用spring项目整合式方法)
数据准备(前提新建一个为mybatis的数据库)
sqlcreate table user( id int unsigned primary key auto_increment comment 'ID', name varchar(100) comment '姓名', age tinyint unsigned comment '年龄', gender tinyint unsigned comment '性别, 1:男, 2:女', phone varchar(11) comment '手机号' ) comment '用户表'; insert into user(id, name, age, gender, phone) VALUES (null,'白眉鹰王',55,'1','18800000000'); insert into user(id, name, age, gender, phone) VALUES (null,'金毛狮王',45,'1','18800000001'); insert into user(id, name, age, gender, phone) VALUES (null,'青翼蝠王',38,'1','18800000002'); insert into user(id, name, age, gender, phone) VALUES (null,'紫衫龙王',42,'2','18800000003'); insert into user(id, name, age, gender, phone) VALUES (null,'光明左使',37,'1','18800000004'); insert into user(id, name, age, gender, phone) VALUES (null,'光明右使',48,'1','18800000005');
小提示:
尝试查询所有user对象
在pojo包下新建一个user.class对象
javapackage com.itheima.pojo; public class User { private Integer id; private String name; private Short age; private Short gender; private String phone; @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", gender=" + gender + ", phone='" + phone + '\'' + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Short getAge() { return age; } public void setAge(Short age) { this.age = age; } public Short getGender() { return gender; } public void setGender(Short gender) { this.gender = gender; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public User() { } public User(Integer id, String name, Short age, Short gender, String phone) { this.id = id; this.name = name; this.age = age; this.gender = gender; this.phone = phone; } }
在mapper包下新建一个UserMapper.interface
javapackage com.itheima.mapper; import com.itheima.pojo.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; @Mapper//在运行时,会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理 public interface UserMapper { //查询全部的用户信息 @Select("select * from user") List<User> findAll(); }
applicaton.proprties配置文件
javaspring.application.name=springboot-mybatis-quickstart #驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://localhost:3306/mybatis #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=root
SpringbootMybatisQuickstartApplicationTests.class
sqlpackage com.itheima; import com.itheima.mapper.UserMapper; import com.itheima.pojo.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 SpringbootMybatisQuickstartApplicationTests { @Autowired private UserMapper userMapper; @Test public void testUserMapper(){ List<User> userList = userMapper.findAll(); userList.forEach(u-> System.out.println(u)); } }
1.2 JDBC
JDBC:(Java DataBase Connectivity),就是使用Java语言操作关系型数据库的一套API。
本质
- sun公司官方定义的一套操作所有关系型数据库的规范,即接口
- 各个数据库厂商去实现这套接口,提供数据库驱动jar包
- 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
了解即可(JDBC封装数据转换成集合)
sql@Test public void testJdbc() throws Exception { //1. 注册驱动 Class.forName("com.mysql.cj.jdbc.Driver"); //2. 获取连接对象 String url = "jdbc:mysql://localhost:3306/mybatis"; String username = "root"; String password = "自己设置的mysql密码"; Connection connection = DriverManager.getConnection(url, username, password); //3. 获取执行SQL的对象Statement,执行SQL,返回结果 String sql = "select * from user"; Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql); //4. 封装结果数据 List<User> userList = new ArrayList<>(); while (resultSet.next()){ int id = resultSet.getInt("id"); String name = resultSet.getString("name"); short age = resultSet.getShort("age"); short gender = resultSet.getShort("gender"); String phone = resultSet.getString("phone"); User user = new User(id,name,age,gender,phone); userList.add(user); } //5. 释放资源 statement.close(); connection.close(); }
1.3 数据库连接池
- 数据库连接池是个容器,负责分配、管理数据库连接(Connection)
- 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
- 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
使用数据库连接池的优点:
- 资源重用
- 提升系统响应速度
- 避免数据库连接遗漏
标准接口:DataSource
- 官方(sun)提供的数据库连接池接口,由第三方组织实现此接口,
- 功能:获取连接=》Connection getConnection() throws SOLException;
常见使用:Druid(使用的比较多)、Hikari(springboot默认)
1.4 实用工具:Lombok
Lombok是一个实用的]ava类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、tostring等方法,并可以自动化生成日志变量,简化java开发、提高效率。
注解 作用 @Getter/@Setter 为所有的属性提供get/set方法 @ToString 会给类自动生成易阅读的 toString 方法 @EqualsAndHashCode 根据类所拥有的非静态字段自动重写 equals 方法和 hashCode 方法 @Data 提供了更综合的生成代码功能(@Getter+@Setter+@ToString+@EqualsAndHashCode) @NoArgsConstructor 为实体类生成无参的构造器方法 @AllArgsConstructor 为实体类生成除了static修饰的字段之外带有各参数的构造器方法。 同时,要注意添加lombok依赖
sql<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
注意事项
Lombok会在编译时,自动生成对应的java代码。我们使用lombok时,还需要安装一个lombok的插件(idea自带,除非idea版本特别老)。
二、MyBatis基础操作
2.1 准备工作
- 准备数据库表 emp
- 创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动、lombok)
- application.properties中引入数据库连接信息
- 创建对应的实体类 Emp(实体类属性采用驼峰命名)
- 准备Mapper接口 EmpMapper
sql-- 部门管理 create table dept( id int unsigned primary key auto_increment comment '主键ID', name varchar(10) not null unique comment '部门名称', create_time datetime not null comment '创建时间', update_time datetime not null comment '修改时间' ) comment '部门表'; insert into dept (id, name, create_time, update_time) values(1,'学工部',now(),now()),(2,'教研部',now(),now()),(3,'咨询部',now(),now()), (4,'就业部',now(),now()),(5,'人事部',now(),now()); -- 员工管理 create table emp ( id int unsigned primary key auto_increment comment 'ID', username varchar(20) not null unique comment '用户名', password varchar(32) default '123456' comment '密码', name varchar(10) not null comment '姓名', gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女', image varchar(300) comment '图像', job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师', entrydate date comment '入职时间', dept_id int unsigned comment '部门ID', create_time datetime not null comment '创建时间', update_time datetime not null comment '修改时间' ) comment '员工表'; INSERT INTO emp (id, username, password, name, gender, image, job, entrydate,dept_id, create_time, update_time) VALUES (1,'jinyong','123456','金庸',1,'1.jpg',4,'2000-01-01',2,now(),now()), (2,'zhangwuji','123456','张无忌',1,'2.jpg',2,'2015-01-01',2,now(),now()), (3,'yangxiao','123456','杨逍',1,'3.jpg',2,'2008-05-01',2,now(),now()), (4,'weiyixiao','123456','韦一笑',1,'4.jpg',2,'2007-01-01',2,now(),now()), (5,'changyuchun','123456','常遇春',1,'5.jpg',2,'2012-12-05',2,now(),now()), (6,'xiaozhao','123456','小昭',2,'6.jpg',3,'2013-09-05',1,now(),now()), (7,'jixiaofu','123456','纪晓芙',2,'7.jpg',1,'2005-08-01',1,now(),now()), (8,'zhouzhiruo','123456','周芷若',2,'8.jpg',1,'2014-11-09',1,now(),now()), (9,'dingminjun','123456','丁敏君',2,'9.jpg',1,'2011-03-11',1,now(),now()), (10,'zhaomin','123456','赵敏',2,'10.jpg',1,'2013-09-05',1,now(),now()), (11,'luzhangke','123456','鹿杖客',1,'11.jpg',5,'2007-02-01',3,now(),now()), (12,'hebiweng','123456','鹤笔翁',1,'12.jpg',5,'2008-08-18',3,now(),now()), (13,'fangdongbai','123456','方东白',1,'13.jpg',5,'2012-11-01',3,now(),now()), (14,'zhangsanfeng','123456','张三丰',1,'14.jpg',2,'2002-08-01',2,now(),now()), (15,'yulianzhou','123456','俞莲舟',1,'15.jpg',2,'2011-05-01',2,now(),now()), (16,'songyuanqiao','123456','宋远桥',1,'16.jpg',2,'2010-01-01',2,now(),now()), (17,'chenyouliang','123456','陈友谅',1,'17.jpg',NULL,'2015-03-21',NULL,now(),now());
2.2 导入项目并实现操作
跟上一个项目一样创建一个spring的项目
pom.xlm
XML<?xml version="1.0" encoding="UTF-8"?> <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.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.itheima</groupId> <artifactId>springboot-mybatis-crud</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
Emp.class
javapackage com.itheima.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDate; import java.time.LocalDateTime; @Data @NoArgsConstructor @AllArgsConstructor public class Emp { private Integer id; //ID private String username; //用户名 private String password; //密码 private String name; //姓名 private Short gender; //性别, 1 男, 2 女 private String image; //图像url private Short job; //职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师' private LocalDate entrydate; //入职日期 private Integer deptId; //部门ID private LocalDateTime createTime; //创建时间 private LocalDateTime updateTime; //修改时间 }
EmpMapper.interface
javapackage com.itheima.mapper; import com.itheima.pojo.Emp; import org.apache.ibatis.annotations.*; import java.time.LocalDate; import java.util.List; @Mapper public interface EmpMapper { //根据ID删除数据 @Delete("delete from emp where id = #{id}") public void delete(Integer id); //public int delete(Integer id); //新增员工 @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" + " values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})") public void insert(Emp emp); //更新员工 @Update("update emp set username = #{username}, name = #{name}, gender = #{gender}, image = #{image}," + " job = #{job}, entrydate = #{entrydate}, dept_id = #{deptId},update_time = #{updateTime} where id = #{id}") public void update(Emp emp); //方案三: 开启mybatis的驼峰命名自动映射开关 --- a_cloumn ------> aColumn //根据ID查询员工 @Select("select * from emp where id = #{id}") public Emp getById(Integer id); //方案一: 给字段起别名, 让别名与实体类属性一致 //@Select("select id, username, password, name, gender, image, job, entrydate, " + // "dept_id deptId, create_time createTime, update_time updateTime from emp where id = #{id}") //public Emp getById(Integer id); //方案二: 通过@Results, @Result注解手动映射封装 //@Results({ // @Result(column = "dept_id", property = "deptId"), // @Result(column = "create_time", property = "createTime"), // @Result(column = "update_time", property = "updateTime") //}) //@Select("select * from emp where id = #{id}") //public Emp getById(Integer id); //条件查询员工 //方式一 //@Select("select * from emp where name like '%${name}%' and gender = #{gender} and " + // "entrydate between #{begin} and #{end} order by update_time desc ") //public List<Emp> list(String name, Short gender, LocalDate begin , LocalDate end); //方式二 // @Select("select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and " + // "entrydate between #{begin} and #{end} order by update_time desc ") // public List<Emp> list(String name, Short gender, LocalDate begin , LocalDate end); //动态条件查询 public List<Emp> list(String name, Short gender, LocalDate begin , LocalDate end); //动态更新员工 public void update2(Emp emp); //批量删除员工 public void deleteByIds(List<Integer> ids); }
application.properties
java#驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://localhost:3306/mybatis #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=root #配置mybatis的日志, 指定输出到控制台 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #开启mybatis的驼峰命名自动映射开关 a_column ------> aCloumn mybatis.configuration.map-underscore-to-camel-case=true
EmpMaper.xml(注意,要在resource包下创建和java包下的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"> <mapper namespace="com.itheima.mapper.EmpMapper"> <sql id="commonSelect"> select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp </sql> <!-- 动态更新员工--> <update id="update2"> update emp <set> <if test="username != null">username = #{username},</if> <if test="name != null">name = #{name},</if> <if test="gender != null">gender = #{gender},</if> <if test="image != null">image = #{image},</if> <if test="job != null">job = #{job},</if> <if test="entrydate != null">entrydate = #{entrydate},</if> <if test="deptId != null">dept_id = #{deptId},</if> <if test="updateTime != null">update_time = #{updateTime}</if> </set> where id = #{id} </update> <!--resultType: 单条记录封装的类型--> <select id="list" resultType="com.itheima.pojo.Emp"> <include refid="commonSelect"/> <where> <if test="name != null"> 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 </select> <!--批量删除员工 (18,19,20)--> <!-- collection: 遍历的集合 item: 遍历出来的元素 separator: 分隔符 open: 遍历开始前拼接的SQL片段 close: 遍历结束后拼接的SQL片段 --> <delete id="deleteByIds"> delete from emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete> </mapper>
2.3 具体操作方法(上方已经提前写好对应方法)
2.3.1 基础操作-删除
注意事项
如果mapper接口方法形参只有一个普通类型的参数,#..,里面的属性名可以随便写,如:#i{id}、#{value}。2.3.2 日志输出
打开mybatis的日志并指定输出到控制台可以在application.properties中
优势
- 性能更高
- 更安全(防止SQL注入)【SQL注入是通过操作输入的数据来修改事先定义好的SOL语句,以达到执行代码对服务器进行攻击的方法】
2.3.3 基础操作-新增
主键返回
描述:在数据添加成功后,需要获取插入数据库数据的主键。如:添加套餐数据时,还需要维护套餐菜品关系表数据。
java//新增员工 @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)" + " values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})") public void insert(Emp emp);
2.3.4基础操作-更新
java//更新员工 @Update("update emp set username = #{username}, name = #{name}, gender = #{gender}, image = #{image}," + " job = #{job}, entrydate = #{entrydate}, dept_id = #{deptId},update_time = #{updateTime} where id = #{id}") public void update(Emp emp);
2.3.5 基础操作-查询
根据id查询
java//方案三: 开启mybatis的驼峰命名自动映射开关 --- a_cloumn ------> aColumn //根据ID查询员工 @Select("select * from emp where id = #{id}") public Emp getById(Integer id);
java#在application.properties #开启mybatis的驼峰命名自动映射开关 a_column ------> aCloumn mybatis.configuration.map-underscore-to-camel-case=true
数据封装
- 实体类属性名 和 数据库表查询返回的字段名一致,mybatis会自动封装
- 如果实体类属性名 和 数据库表查询返回的字段名不一致,不能自动封装
java//方案一: 给字段起别名, 让别名与实体类属性一致 //@Select("select id, username, password, name, gender, image, job, entrydate, " + // "dept_id deptId, create_time createTime, update_time updateTime from emp where id = #{id}") //public Emp getById(Integer id); //方案二: 通过@Results, @Result注解手动映射封装 //@Results({ // @Result(column = "dept_id", property = "deptId"), // @Result(column = "create_time", property = "createTime"), // @Result(column = "update_time", property = "updateTime") //}) //@Select("select * from emp where id = #{id}") //public Emp getById(Integer id);
根据条件查询
java//条件查询员工 //方式一 //@Select("select * from emp where name like '%${name}%' and gender = #{gender} and " + // "entrydate between #{begin} and #{end} order by update_time desc ") //public List<Emp> list(String name, Short gender, LocalDate begin , LocalDate end); //方式二 // @Select("select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and " + // "entrydate between #{begin} and #{end} order by update_time desc ") // public List<Emp> list(String name, Short gender, LocalDate begin , LocalDate end);
参数说明
三、XML映射文件
- XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)
- XML映射文件的namespace属性为Mapper接口全限定名一致。
- XML映射文件中sql语句的id与Mapper 接口中的方法名一致,并保持返回类型一致。
java/动态条件查询 public List<Emp> list(String name, Short gender, LocalDate begin , LocalDate end);
MybatisX 是一款基于 IDEA的快速开发Mybatis的插件,为效率而生。
使用Mybatis的注解,主要是来完成一些简单的增删改查功能。如果需要实现复杂的SOL功能,建议使用XML来配置映射语句。
3.1 动态sql
随着用户的输入或外部条件的变化而变化的SQL语句,我们称为动态SQL。
3.1.1 <if> 与 <where>
- if用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL
- where 元素只会在子元素有内容的情况下才插入where子句。而且会自动去除子句的开头的AND 或OR。
XML<!--resultType: 单条记录封装的类型--> <select id="list" resultType="com.itheima.pojo.Emp"> <include refid="commonSelect"/> <where> <if test="name != null"> 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 </select>
3.1.2 <update> 与 <set>
XML<!-- 动态更新员工--> <update id="update2"> update emp <set> <if test="username != null">username = #{username},</if> <if test="name != null">name = #{name},</if> <if test="gender != null">gender = #{gender},</if> <if test="image != null">image = #{image},</if> <if test="job != null">job = #{job},</if> <if test="entrydate != null">entrydate = #{entrydate},</if> <if test="deptId != null">dept_id = #{deptId},</if> <if test="updateTime != null">update_time = #{updateTime}</if> </set> where id = #{id} </update>
<set>:动态地在行首插入 SET关键字,并会删掉额外的逗号。(用在update语句中)
3.1.3 <delete> 与 <foreach>
XML<!--批量删除员工 (18,19,20)--> <!-- collection: 遍历的集合 item: 遍历出来的元素 separator: 分隔符 open: 遍历开始前拼接的SQL片段 close: 遍历结束后拼接的SQL片段 --> <delete id="deleteByIds"> delete from emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
- collection:集合名称
- item:集合遍历出来的元素/项
- separator:每一次遍历使用的分隔符
- open:遍历开始前拼接的片段
- close:遍历结束后拼接的片段
3.1.4 <sql> 与 <include>
XML<sql id="commonSelect"> select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp </sql>
例如上面的例子,就引用了sql使用include条件
XML<!--resultType: 单条记录封装的类型--> <select id="list" resultType="com.itheima.pojo.Emp"> <include refid="commonSelect"/> <where> <if test="name != null"> 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 </select>
四、练习案例
环境搭配:
新建一个springboot项目,跟之前的一样,使用相关依赖,在导入对应的数据,具体数据为:
数据准备
sql-- 部门管理 create table dept( id int unsigned primary key auto_increment comment '主键ID', name varchar(10) not null unique comment '部门名称', create_time datetime not null comment '创建时间', update_time datetime not null comment '修改时间' ) comment '部门表'; insert into dept (id, name, create_time, update_time) values(1,'学工部',now(),now()),(2,'教研部',now(),now()),(3,'咨询部',now(),now()), (4,'就业部',now(),now()),(5,'人事部',now(),now()); -- 员工管理(带约束) create table emp ( id int unsigned primary key auto_increment comment 'ID', username varchar(20) not null unique comment '用户名', password varchar(32) default '123456' comment '密码', name varchar(10) not null comment '姓名', gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女', image varchar(300) comment '图像', job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师', entrydate date comment '入职时间', dept_id int unsigned comment '部门ID', create_time datetime not null comment '创建时间', update_time datetime not null comment '修改时间' ) comment '员工表'; INSERT INTO emp (id, username, password, name, gender, image, job, entrydate,dept_id, create_time, update_time) VALUES (1,'jinyong','123456','金庸',1,'1.jpg',4,'2000-01-01',2,now(),now()), (2,'zhangwuji','123456','张无忌',1,'2.jpg',2,'2015-01-01',2,now(),now()), (3,'yangxiao','123456','杨逍',1,'3.jpg',2,'2008-05-01',2,now(),now()), (4,'weiyixiao','123456','韦一笑',1,'4.jpg',2,'2007-01-01',2,now(),now()), (5,'changyuchun','123456','常遇春',1,'5.jpg',2,'2012-12-05',2,now(),now()), (6,'xiaozhao','123456','小昭',2,'6.jpg',3,'2013-09-05',1,now(),now()), (7,'jixiaofu','123456','纪晓芙',2,'7.jpg',1,'2005-08-01',1,now(),now()), (8,'zhouzhiruo','123456','周芷若',2,'8.jpg',1,'2014-11-09',1,now(),now()), (9,'dingminjun','123456','丁敏君',2,'9.jpg',1,'2011-03-11',1,now(),now()), (10,'zhaomin','123456','赵敏',2,'10.jpg',1,'2013-09-05',1,now(),now()), (11,'luzhangke','123456','鹿杖客',1,'11.jpg',5,'2007-02-01',3,now(),now()), (12,'hebiweng','123456','鹤笔翁',1,'12.jpg',5,'2008-08-18',3,now(),now()), (13,'fangdongbai','123456','方东白',1,'13.jpg',5,'2012-11-01',3,now(),now()), (14,'zhangsanfeng','123456','张三丰',1,'14.jpg',2,'2002-08-01',2,now(),now()), (15,'yulianzhou','123456','俞莲舟',1,'15.jpg',2,'2011-05-01',2,now(),now()), (16,'songyuanqiao','123456','宋远桥',1,'16.jpg',2,'2007-01-01',2,now(),now()), (17,'chenyouliang','123456','陈友谅',1,'17.jpg',NULL,'2015-03-21',NULL,now(),now());
依赖:
XML<dependencies> <!--web起步依赖--> <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.2.2</version> </dependency> <!--mysql驱动--> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--springboot单元测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--PageHelper分页插件--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build>
application.properties
XMLspring.application.name=tlias-web-management #驱动类名称 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #数据库连接的url spring.datasource.url=jdbc:mysql://localhost:3306/tlias #连接数据库的用户名 spring.datasource.username=root #连接数据库的密码 spring.datasource.password=自己mysql的密码 #配置mybatis的日志, 指定输出到控制台 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #开启mybatis的驼峰命名自动映射开关 a_column ------> aCloumn mybatis.configuration.map-underscore-to-camel-case=true
Result.class(用来接收合适的数据,并返回给前端数据)
javapackage com.itheima.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class Result { private Integer code;//响应码,1 代表成功; 0 代表失败 private String msg; //响应信息 描述字符串 private Object data; //返回的数据 //增删改 成功响应 public static Result success(){ return new Result(1,"success",null); } //查询 成功响应 public static Result success(Object data){ return new Result(1,"success",data); } //失败响应 public static Result error(String msg){ return new Result(0,msg,null); } }
(上述条件需要注意的是,每个对应方类和接口,需要创建到合适的包中)
例如 :
开发规范-Restful
- REST(REpresentational State Transfer),表述性状态转换,它是一种软件架构风格
注意事项
- REST是风格,是约定方式,约定不是规定,可以打破。
- 描述模块的功能通常使用复数,也就是加s的格式来描述,表示此类资源,而非单个资源。如:users、emps、books
统一响应结果
4.1 部门管理
Dept.class
javapackage com.itheima.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDateTime; /** * 部门实体类 */ @Data @NoArgsConstructor @AllArgsConstructor public class Dept { private Integer id; //ID private String name; //部门名称 private LocalDateTime createTime; //创建时间 private LocalDateTime updateTime; //修改时间 }
DeptController.class
javapackage com.itheima.controller; import com.itheima.pojo.Dept; import com.itheima.pojo.Result; import com.itheima.service.DeptService; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 部门管理Controller */ @Slf4j @RequestMapping("/depts") @RestController public class DeptController { //private static Logger log = LoggerFactory.getLogger(DeptController.class); @Autowired private DeptService deptService; /** * 查询部门数据 * @return */ //@RequestMapping(value = "/depts",method = RequestMethod.GET) //指定请求方式为GET @GetMapping public Result list(){ log.info("查询全部部门数据"); //调用service查询部门数据 List<Dept> deptList = deptService.list(); return Result.success(deptList); } /** * 删除部门 * @return */ @DeleteMapping("/{id}") public Result delete(@PathVariable Integer id){ log.info("根据id删除部门:{}",id); //调用service删除部门 deptService.delete(id); return Result.success(); } /** * 新增部门 * @return */ @PostMapping public Result add(@RequestBody Dept dept){ log.info("新增部门: {}" , dept); //调用service新增部门 deptService.add(dept); return Result.success(); } /** * 查询某个部门的id */ @GetMapping("/{id}") public Result findById(@PathVariable(name = "id") Integer id) { log.info("根据id查询部门:{}", id); Dept dept = deptService.findById(id); return Result.success(dept); } /** * 修改部门 */ @PutMapping public Result update(@RequestBody Dept dept) { log.info("修改部门: {}", dept); deptService.update(dept); return Result.success(); } }
DeptService.interface
javapackage com.itheima.service; import com.itheima.pojo.Dept; import java.util.List; /** * 部门管理 */ public interface DeptService { /** * 查询全部部门数据 * @return */ List<Dept> list(); /** * 删除部门 * @param id */ void delete(Integer id); /** * 新增部门 * @param dept */ void add(Dept dept); /** * 查询某个部门的id */ Dept findById(Integer id); /** * 修改部门 */ void update(Dept dept); }
DeptServiceImpl.class
javapackage com.itheima.service.impl; import com.itheima.mapper.DeptMapper; import com.itheima.pojo.Dept; import com.itheima.service.DeptService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.List; @Service public class DeptServiceImpl implements DeptService { @Autowired private DeptMapper deptMapper; @Override public List<Dept> list() { return deptMapper.list(); } @Override public void delete(Integer id) { deptMapper.deleteById(id); } @Override public void add(Dept dept) { dept.setCreateTime(LocalDateTime.now()); dept.setUpdateTime(LocalDateTime.now()); deptMapper.insert(dept); } @Override public Dept findById(Integer id) { return deptMapper.findById(id); } @Override public void update(Dept dept) { dept.setCreateTime(LocalDateTime.now()); dept.setUpdateTime(LocalDateTime.now()); deptMapper.update(dept); } }
DeptMapper.interface
javapackage com.itheima.mapper; import com.itheima.pojo.Dept; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; /** * 部门管理 */ @Mapper public interface DeptMapper { /** * 查询全部部门 * @return */ @Select("select * from dept") List<Dept> list(); /** * 根据ID删除部门 * @param id */ @Delete("delete from dept where id = #{id}") void deleteById(Integer id); /** * 新增部门 * @param dept */ @Insert("insert into dept(name, create_time, update_time) values(#{name},#{createTime},#{updateTime})") void insert(Dept dept); /** * 查询部门id */ @Select("select * from dept where id=#{id};") Dept findById(Integer id); /** * 修改部门 */ @Update("update dept set name=#{name},create_time=#{createTime},update_time=#{updateTime} where id =#{id}") void update(Dept dept); }
4.2 前后端联调
使用nginx启动,具体启动方式在之前的文章中写过
4.3 员工管理
注意:需要涉及到分页,所以一般要进行封装一个实体类
PageBean.class
javapackage com.itheima.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; /** * 分页查询结果封装类 */ @Data @NoArgsConstructor @AllArgsConstructor public class PageBean { private Long total;//总记录数 private List rows;//数据列表 }
EmpController.class
javapackage com.itheima.controller; import com.itheima.pojo.PageBean; import com.itheima.pojo.Result; import com.itheima.service.EmpService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.*; import java.time.LocalDate; import java.util.List; /** * 员工管理Controller */ @Slf4j @RestController @RequestMapping("/emps") public class EmpController { @Autowired private EmpService empService; @GetMapping public Result page(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer pageSize, String name, Short gender, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin, @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){ log.info("分页查询, 参数: {},{},{},{},{},{}",page,pageSize,name,gender,begin,end); //调用service分页查询 PageBean pageBean = empService.page(page,pageSize,name,gender,begin,end); return Result.success(pageBean); } @DeleteMapping("/{ids}") public Result delete(@PathVariable List<Integer> ids){ log.info("批量删除操作, ids:{}",ids); empService.delete(ids); return Result.success(); } }
EmpService.interface
javapackage com.itheima.service; import com.itheima.pojo.PageBean; import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDate; import java.util.List; /** * 员工管理 */ public interface EmpService { /** * 分页查询 * @param page * @param pageSize * @return */ PageBean page(Integer page, Integer pageSize,String name, Short gender,LocalDate begin,LocalDate end); /** * 批量删除 * @param ids */ void delete(List<Integer> ids); }
EmpServiceImpl.class
javapackage com.itheima.service.impl; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.itheima.mapper.EmpMapper; import com.itheima.pojo.Emp; import com.itheima.pojo.PageBean; import com.itheima.service.EmpService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDate; import java.util.List; @Service public class EmpServiceImpl implements EmpService { @Autowired private EmpMapper empMapper; /*@Override public PageBean page(Integer page, Integer pageSize) { //1. 获取总记录数 Long count = empMapper.count(); //2. 获取分页查询结果列表 Integer start = (page - 1) * pageSize; List<Emp> empList = empMapper.page(start, pageSize); //3. 封装PageBean对象 PageBean pageBean = new PageBean(count, empList); return pageBean; }*/ @Override public PageBean page(Integer page, Integer pageSize,String name, Short gender,LocalDate begin,LocalDate end) { //1. 设置分页参数 PageHelper.startPage(page,pageSize); //2. 执行查询 List<Emp> empList = empMapper.list(name, gender, begin, end); Page<Emp> p = (Page<Emp>) empList; //3. 封装PageBean对象 PageBean pageBean = new PageBean(p.getTotal(), p.getResult()); return pageBean; } @Override public void delete(List<Integer> ids) { empMapper.delete(ids); } }
EmpMapper.interface
javapackage com.itheima.mapper; import com.itheima.pojo.Emp; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.time.LocalDate; import java.util.List; /** * 员工管理 */ @Mapper public interface EmpMapper { /** * 查询总记录数 * @return */ //@Select("select count(*) from emp") //public Long count(); /** * 分页查询,获取列表数据 * @param start * @param pageSize * @return */ //@Select("select * from emp limit #{start},#{pageSize}") //public List<Emp> page(Integer start, Integer pageSize); /** * 员工信息查询 * @return */ //@Select("select * from emp") public List<Emp> list(String name, Short gender,LocalDate begin,LocalDate end); /** * 批量删除 * @param ids */ void delete(List<Integer> ids); }
分页条件查询的条件
EmpMapper.xml
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.itheima.mapper.EmpMapper"> <!--批量删除员工 (1, 2, 3)--> <delete id="delete"> delete from emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete> <!--条件查询--> <select id="list" resultType="com.itheima.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 </select> </mapper>
4.3.1 文件的上传和剩下的增、改功能
Controller层
java@PostMapping public Result save(@RequestBody Emp emp){ log.info("新增员工, emp: {}",emp); empService.save(emp); return Result.success(); } @GetMapping("/{id}") public Result getById(@PathVariable Integer id){ log.info("根据ID查询员工信息, id: {}",id); Emp emp = empService.getById(id); return Result.success(emp); } @PutMapping public Result update(@RequestBody Emp emp){ log.info("更新员工信息 : {}", emp); empService.update(emp); return Result.success(); }
service层
java/** * 新增员工 * @param emp */ void save(Emp emp); /** * 根据ID查询员工 * @param id * @return */ Emp getById(Integer id); /** * 更新员工 * @param emp */ void update(Emp emp);
ServiceImpl实现方法层
java@Override public void save(Emp emp) { emp.setCreateTime(LocalDateTime.now()); emp.setUpdateTime(LocalDateTime.now()); empMapper.insert(emp); } @Override public Emp getById(Integer id) { return empMapper.getById(id); } @Override public void update(Emp emp) { emp.setUpdateTime(LocalDateTime.now()); empMapper.update(emp); }
mapper层
java/** * 新增员工 * @param emp */ @Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " + " values(#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})") void insert(Emp emp); /** * 根据ID查询员工 * @param id * @return */ @Select("select * from emp where id = #{id}") Emp getById(Integer id); /** * 更新员工 * @param emp */ void update(Emp emp);
EmpMapper.xml
XML<!--更新员工--> <update id="update"> update emp <set> <if test="username != null and username != ''"> username = #{username}, </if> <if test="password != null and password != ''"> password = #{password}, </if> <if test="name != null and name != ''"> name = #{name}, </if> <if test="gender != null"> gender = #{gender}, </if> <if test="image != null and image != ''"> image = #{image}, </if> <if test="job != null"> job = #{job}, </if> <if test="entrydate != null"> entrydate = #{entrydate}, </if> <if test="deptId != null"> dept_id = #{deptId}, </if> <if test="updateTime != null"> update_time = #{updateTime} </if> </set> where id = #{id} </update>
文件上传
- 文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程。
- 文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能。
本地存储
在服务端,接收到上传上来的文件之后,将文件存储在本地服务器磁盘中
添加UploadController.class
javapackage com.itheima.controller; import com.itheima.pojo.Result; import com.itheima.utils.AliOSSUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @Slf4j @RestController public class UploadController { @Autowired private AliOSSUtils aliOSSUtils; //本地存储文件 /*@PostMapping("/upload") public Result upload(String username , Integer age , MultipartFile image) throws Exception { log.info("文件上传: {}, {}, {}", username, age, image); //获取原始文件名 - 1.jpg 123.0.0.jpg String originalFilename = image.getOriginalFilename(); //构造唯一的文件名 (不能重复) - uuid(通用唯一识别码) de49685b-61c0-4b11-80fa-c71e95924018 int index = originalFilename.lastIndexOf("."); String extname = originalFilename.substring(index); String newFileName = UUID.randomUUID().toString() + extname; log.info("新的文件名: {}", newFileName); //将文件存储在服务器的磁盘目录中 E:\images image.transferTo(new File("E:\\images\\"+newFileName)); return Result.success(); }*/ @PostMapping("/upload") public Result upload(MultipartFile image) throws IOException { log.info("文件上传, 文件名: {}", image.getOriginalFilename()); //调用阿里云OSS工具类进行文件上传 String url = aliOSSUtils.upload(image); log.info("文件上传完成,文件访问的url: {}", url); return Result.success(url); } }
在application.properties添加相关配置
java#文件上传的配置 servlet: multipart: max-file-size: 10MB max-request-size: 100MB
4.3.2 阿里云OOS
阿里云对象存储OOS(Object storage service),是一款海量、安全、低成本、高可靠的云存储服务。使用0SS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。
SDK: Software DevelopmentKit 的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做SDK。
Bucket:存储空间是用户用于存储对象(0bject,就是文件)的容器,所有的对象都必须隶属于某个存储空间。
创建阿里云服务
(1)打开https://www.aliyun.com/ 申请阿里云账号并完成实名认证。
(2)充值【一般充1块钱足够】
(3)开通OSS
登录阿里云官网。 点击右上角的控制台。
将鼠标移至产品,找到并单击对象存储OSS,打开OSS产品详情页面。在OSS产品详情页中的单击立即开通。
开通服务后,在OSS产品详情页面单击管理控制台直接进入OSS管理控制台界面。您也可以单击位于官网首页右上方菜单栏的控制台,进入阿里云管理控制台首页,然后单击左侧的对象存储OSS菜单进入OSS管理控制台界面。
(4)创建存储空间
新建Bucket,命名自拟 ,读写权限为 公共读
获取AccessKeyId
切记,一定要保存好access key 后期在application文件中使用阿里云的功能需要填写自己的key!!!!!
例如此图片中的key:
导入阿里云依赖(注这是java9 以上的依赖)如果是9以下则根据官网的sdk文档查看
XML<!--阿里云OSS--> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.15.1</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency> <!-- no more than 2.3.3--> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.3.3</version> </dependency>
AliOSSProperties.java
javapackage com.itheima.utils; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties(prefix = "aliyun.oss") public class AliOSSProperties { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; }
oss集成
- 引入阿里云OSS上传文件工具类(由官方的示例代码改造而来)
- 上传图片接口开发
配置文件
参数配置化
AliOSSUtils.java
javapackage com.itheima.utils; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.util.UUID; /** * 阿里云 OSS 工具类 */ @Component public class AliOSSUtils { // @Value("${aliyun.oss.endpoint}") // private String endpoint ; // @Value("${aliyun.oss.accessKeyId}") // private String accessKeyId ; // @Value("${aliyun.oss.accessKeySecret}") // private String accessKeySecret ; // @Value("${aliyun.oss.bucketName}") // private String bucketName ; @Autowired private AliOSSProperties aliOSSProperties; /** * 实现上传图片到OSS */ public String upload(MultipartFile file) throws IOException { //获取阿里云OSS参数 String endpoint = aliOSSProperties.getEndpoint(); String accessKeyId = aliOSSProperties.getAccessKeyId(); String accessKeySecret = aliOSSProperties.getAccessKeySecret(); String bucketName = aliOSSProperties.getBucketName(); // 获取上传的文件的输入流 InputStream inputStream = file.getInputStream(); // 避免文件覆盖 String originalFilename = file.getOriginalFilename(); String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); //上传文件到 OSS OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); ossClient.putObject(bucketName, fileName, inputStream); //文件访问路径 String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName; // 关闭ossClient ossClient.shutdown(); return url;// 把上传到oss的路径返回 } }
yml文件
将properties文件替换成application.yml
XMLspring: #数据库连接信息 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/tlias username: root password: 自己的mysql密码 #文件上传的配置 servlet: multipart: max-file-size: 10MB max-request-size: 100MB #Mybatis配置 mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true #阿里云OSS aliyun: oss: endpoint: https://oss-cn-hangzhou.aliyuncs.com accessKeyId:自己的 accessKeyId accessKeySecret: 自己的accessKeySecret bucketName: 自己的buckName(桶名)
- 大小写敏感
- 数值前边必须有空格,作为分隔符
- 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能用空格(idea中会自动将Tab转换为空格)
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
表示注释,从这个字符一直到行尾,都会被解析器忽略
@ConfigurationProperties
五、登录与校验和认证
5.1 登录功能
- 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
- 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
会话跟踪方案:
- 客户端会话跟踪技术:Cookie
- 服务端会话跟踪技术:Session
- 令牌技术
跨域区分三个维度:协议、IP/域名、端口
以上两个方案是传统方案
目前用的比较广的是令牌技术
javapackage com.itheima.controller; import com.itheima.pojo.Result; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * Cookie、HttpSession演示 */ @Slf4j @RestController public class SessionController { //设置Cookie @GetMapping("/c1") public Result cookie1(HttpServletResponse response){ response.addCookie(new Cookie("login_username","itheima")); //设置Cookie/响应Cookie return Result.success(); } //获取Cookie @GetMapping("/c2") public Result cookie2(HttpServletRequest request){ Cookie[] cookies = request.getCookies(); for (Cookie cookie : cookies) { if(cookie.getName().equals("login_username")){ System.out.println("login_username: "+cookie.getValue()); //输出name为login_username的cookie } } return Result.success(); } @GetMapping("/s1") public Result session1(HttpSession session){ log.info("HttpSession-s1: {}", session.hashCode()); session.setAttribute("loginUser", "tom"); //往session中存储数据 return Result.success(); } @GetMapping("/s2") public Result session2(HttpServletRequest request){ HttpSession session = request.getSession(); log.info("HttpSession-s2: {}", session.hashCode()); Object loginUser = session.getAttribute("loginUser"); //从session中获取数据 log.info("loginUser: {}", loginUser); return Result.success(loginUser); } }
5.2 JWT令牌
定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
场景:登录认证。
- 登录成功后,生成令牌
- 后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,通过后,再处理
JWT的生成
javapackage com.itheima; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; //@SpringBootTest class TliasWebManagementApplicationTests { @Test public void testUuid(){ for (int i = 0; i < 1000; i++) { String uuid = UUID.randomUUID().toString(); System.out.println(uuid); } } /** * 生成JWT */ @Test public void testGenJwt(){ Map<String, Object> claims = new HashMap<>(); claims.put("id",1); claims.put("name","tom"); String jwt = Jwts.builder() .signWith(SignatureAlgorithm.HS256, "itheima")//签名算法 .setClaims(claims) //自定义内容(载荷) .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期为1h .compact(); System.out.println(jwt); } /** * 解析JWT */ @Test public void testParseJwt(){ Claims claims = Jwts.parser() .setSigningKey("itheima") .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTY3MDQ2NDU0N30.yPLRyiusrlrmWeC4-dhInjFuAghPkmiHSRHd_DTKi9E") .getBody(); System.out.println(claims); } }
注意事项
- JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。
- 如果JWT令牌解析校验时报错,则说明JWT令牌被篡改 或 失效了,令牌非法
登录后下发令牌
令牌生成:登录成功后,生成JWT令牌,并返回给前端
令牌校验:在请求到达服务端后,对令牌进行统一拦截、校验。
JWTUtils.class
javapackage com.itheima.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"; private static Long expire = 43200000L; /** * 生成JWT令牌 * @param claims JWT第二部分负载 payload 中存储的内容 * @return */ public static String generateJwt(Map<String, Object> claims){ String jwt = Jwts.builder() .addClaims(claims) .signWith(SignatureAlgorithm.HS256, signKey) .setExpiration(new Date(System.currentTimeMillis() + expire)) .compact(); return jwt; } /** * 解析JWT令牌 * @param jwt JWT令牌 * @return JWT第二部分负载 payload 中存储的内容 */ public static Claims parseJWT(String jwt){ Claims claims = Jwts.parser() .setSigningKey(signKey) .parseClaimsJws(jwt) .getBody(); return claims; } }
LoginController.class
javapackage com.itheima.controller; import com.itheima.pojo.Emp; import com.itheima.pojo.Result; import com.itheima.service.EmpService; import com.itheima.utils.JwtUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @Slf4j @RestController public class LoginController { @Autowired private EmpService empService; @PostMapping("/login") public Result login(@RequestBody Emp emp){ log.info("员工登录: {}", emp); Emp e = empService.login(emp); //登录成功,生成令牌,下发令牌 if (e != null){ Map<String, Object> claims = new HashMap<>(); claims.put("id", e.getId()); claims.put("name", e.getName()); claims.put("username", e.getUsername()); String jwt = JwtUtils.generateJwt(claims); //jwt包含了当前登录的员工信息 return Result.success(jwt); } //登录失败, 返回错误信息 return Result.error("用户名或密码错误"); } }
5.3 过滤器Filter
- 概念:Filter 过滤器,是JavaWeb 三大组件(Servlet、Filter、Listener)之一。
- 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
- 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
如何使用Filter?
- 定义Filter:定义一个类,实现 Filter 接口,并重写其所有方法。
- 配置Filter:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @Servletcomponentscan开启Servlet组件支持
DemoFilter.class
javapackage com.itheima.filter; import jakarta.servlet.*; import java.io.IOException; //@WebFilter(urlPatterns = "/*") public class DemoFilter implements Filter { @Override //初始化方法, 只调用一次 public void init(FilterConfig filterConfig) throws ServletException { System.out.println("init 初始化方法执行了"); } @Override //拦截到请求之后调用, 调用多次 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Demo 拦截到了请求...放行前逻辑"); //放行 chain.doFilter(request,response); System.out.println("Demo 拦截到了请求...放行后逻辑"); } @Override //销毁方法, 只调用一次 public void destroy() { System.out.println("destroy 销毁方法执行了"); } }
LoginCheckFliter.class
javapackage com.itheima.filter; import com.alibaba.fastjson.JSONObject; import com.itheima.pojo.Result; import com.itheima.utils.JwtUtils; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import java.io.IOException; @Slf4j //@WebFilter(urlPatterns = "/*") public class LoginCheckFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; //1.获取请求url。 String url = req.getRequestURL().toString(); log.info("请求的url: {}",url); //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。 if(url.contains("login")){ log.info("登录操作, 放行..."); chain.doFilter(request,response); return; } //3.获取请求头中的令牌(token)。 String jwt = req.getHeader("token"); //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。 if(!StringUtils.hasLength(jwt)){ log.info("请求头token为空,返回未登录的信息"); Result error = Result.error("NOT_LOGIN"); //手动转换 对象--json --------> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return; } //5.解析token,如果解析失败,返回错误结果(未登录)。 try { JwtUtils.parseJWT(jwt); } catch (Exception e) {//jwt解析失败 e.printStackTrace(); log.info("解析令牌失败, 返回未登录错误信息"); Result error = Result.error("NOT_LOGIN"); //手动转换 对象--json --------> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return; } //6.放行。 log.info("令牌合法, 放行"); chain.doFilter(request, response); } }
过滤器链
介绍:一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链。
顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序。
登录校验Filter
Filter-流程
- 获取请求url。
- 判断请求url中是否包含login,如果包含,说明是登录操作,放行。
- 获取请求头中的令牌(token)
- 判断令牌是否存在,如果不存在,返回错误结果(未登录)
- 解析token,如果解析失败,返回错误结果(未登录)
- 放行。
5.4 拦截器Interceptor
- 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行。
- 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码
LoginCheckInterceptor.class
注意:这里面都是实现了HandlerInterceptor接口, 重写的三个方法, 使用ctrl+o 或者 alt+insert 重写这三个方法就可以了 ,不需要手动敲入
javapackage com.itheima.interceptor; import com.alibaba.fastjson.JSONObject; import com.itheima.pojo.Result; import com.itheima.utils.JwtUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Slf4j @Component public class LoginCheckInterceptor implements HandlerInterceptor { @Override //目标资源方法运行前运行, 返回true: 放行, 放回false, 不放行 public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception { //1.获取请求url。 String url = req.getRequestURL().toString(); log.info("请求的url: {}",url); //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。 if(url.contains("login")){ log.info("登录操作, 放行..."); return true; } //3.获取请求头中的令牌(token)。 String jwt = req.getHeader("token"); //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。 if(!StringUtils.hasLength(jwt)){ log.info("请求头token为空,返回未登录的信息"); Result error = Result.error("NOT_LOGIN"); //手动转换 对象--json --------> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return false; } //5.解析token,如果解析失败,返回错误结果(未登录)。 try { JwtUtils.parseJWT(jwt); } catch (Exception e) {//jwt解析失败 e.printStackTrace(); log.info("解析令牌失败, 返回未登录错误信息"); Result error = Result.error("NOT_LOGIN"); //手动转换 对象--json --------> 阿里巴巴fastJSON String notLogin = JSONObject.toJSONString(error); resp.getWriter().write(notLogin); return false; } //6.放行。 log.info("令牌合法, 放行"); return true; } @Override //目标资源方法运行后运行 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle ..."); } @Override //视图渲染完毕后运行, 最后运行 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion..."); } }
创建一个配置类,注册配置拦截器
config包下的
WebConfig.class
javapackage com.itheima.config; import com.itheima.interceptor.LoginCheckInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration //配置类 public class WebConfig implements WebMvcConfigurer { @Autowired private LoginCheckInterceptor loginCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { //addPathPatterns : 需要拦截的路径 //excludePathPatterns :不需要拦截的路径 registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login"); } }
-- 拦截路径
拦截器可以根据需求,配置不同的拦截路径
拦截路径 含义 举例 /* 一级路径 能匹配/depts,/emps,/login,不能匹配/depts/1 /** 任意级路径 能匹配/depts,/depts/1,/depts/1/2 /depts/* /depts下的一级路径 能匹配/depts/1,不能匹配/depts/1/2,/depts /depts/** /depts下的任意路径 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1 -- 执行流程
二者区别(过滤器和拦截器)
- 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。
- 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。
六、异常处理
一般自己使用定义的全局异常处理器
例如:GlobalExceptionHandler.class
javapackage com.itheima.exception; import com.itheima.pojo.Result; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * 全局异常处理器 */ @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class)//捕获所有异常 public Result ex(Exception ex){ ex.printStackTrace(); return Result.error("对不起,操作失败,请联系管理员"); } }
七、事务管理
事务 是一组操作的集合,它是一个不可分割的工作单位,这些操作 要么同时成功,要么同时失败。
操作
- 开启事务(一组操作开始前,开启事务):starttransaction/begin;
- 提交事务(这组操作全部成功后,提交事务):commit;
- 回滚事务(中间任何一个操作出现异常,回滚事务):rollback;
- 注解:@Transactional
- 位置:业务(service)层的方法上、类上、接口上
- 作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现常,回滚事务
java#spring事务管理日志 logging: level: org.springframework.jdbc.support.JdbcTransactionManager: debug
-- 事务属性:回滚
rollbackFor
默认情况下,只有出现 RuntimeException:才回滚异常。rollbackFor属性用于控制出现何种异常类型,回滚事务。
-- 事务的传播传为:propagation
事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
事务传播行为是为了解决业务层方法之间互相调用的事务问题。
当事务方法被另一事务方法调用时,必须指定事务应该如何传播。
例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
Spring支持7个种事务传播行为的:
- 必须事务:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
- 必须新事务:创建一个新的事务,如果当前存在事务,则把当前事务挂起
- 强制事务:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
- 支持事务:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
- 不支持事务:以非事务方式运行,如果当前存在事务,则把当前事务挂起
- 强制无事务:以非事务方式运行,如果当前存在事务,则抛出异常
- 嵌套事务:如果当前存在事务,则创建一个当前事务的嵌套事务来运行;如果当前没有事务,则创建一个事务;嵌套事务是已存在事务的一个子事务,嵌套事务开始执行时,将取得一个保存点,如果这个嵌套事务失败,将回滚到此保存点;嵌套事务是外部事务的一部分,只有外部事务结束后它才会被提交
javapackage com.itheima.service.impl; import com.itheima.mapper.DeptMapper; import com.itheima.mapper.EmpMapper; import com.itheima.pojo.Dept; import com.itheima.pojo.DeptLog; import com.itheima.service.DeptLogService; import com.itheima.service.DeptService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; @Service public class DeptServiceImpl implements DeptService { @Autowired private DeptMapper deptMapper; @Autowired private EmpMapper empMapper; @Autowired private DeptLogService deptLogService; @Override public List<Dept> list() { return deptMapper.list(); } //@Transactional(rollbackFor = Exception.class) //spring事务管理 @Transactional @Override public void delete(Integer id) throws Exception { try { deptMapper.deleteById(id); //根据ID删除部门数据 int i = 1/0; //if(true){throw new Exception("出错啦...");} empMapper.deleteByDeptId(id); //根据部门ID删除该部门下的员工 } finally { DeptLog deptLog = new DeptLog(); deptLog.setCreateTime(LocalDateTime.now()); deptLog.setDescription("执行了解散部门的操作,此次解散的是"+id+"号部门"); deptLogService.insert(deptLog); } } @Override public void add(Dept dept) { dept.setCreateTime(LocalDateTime.now()); dept.setUpdateTime(LocalDateTime.now()); deptMapper.insert(dept); } }
八、AOP
AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程
添加依赖
XML<!--AOP--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
编写一个入门AOP程序
javapackage com.itheima.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Slf4j @Component //@Aspect //AOP类 public class TimeAspect { //@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))") //切入点表达式 @Around("com.itheima.aop.MyAspect1.pt()") public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable { //1. 记录开始时间 long begin = System.currentTimeMillis(); //2. 调用原始方法运行 Object result = joinPoint.proceed(); //3. 记录结束时间, 计算方法执行耗时 long end = System.currentTimeMillis(); log.info(joinPoint.getSignature()+"方法执行耗时: {}ms", end-begin); return result; } }
AOP核心概念
- 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
- 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
- 切入点:Pointcut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
- 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
- 目标对象:Target,通知所应用的对象
AOP通知类型
- @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
- @Before:前置通知,此注解标注的通知方法在目标方法前被执行
- @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行@AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
- @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
注意事项
- @Around环绕通知需要自己调用 ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
- @Around环绕通知方法的返回值,必须指定为0biect,来接收原始方法的返回值。
javapackage com.itheima.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Slf4j @Component //@Aspect public class MyAspect1 { @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))") public void pt(){} @Before("pt()") public void before(){ log.info("before ..."); } @Around("pt()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { log.info("around before ..."); //调用目标对象的原始方法执行 Object result = proceedingJoinPoint.proceed(); 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 ..."); } }
通知顺序
当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行。
执行顺序
不同切面类中,默认按照切面类的类名字母排序
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名靠前的后执行
用 @Order(数字) 加在切面类上来控制顺序
- 目标方法前的通知方法:数字小的先执行
- 目标方法后的通知方法:数宗小的后执行
--切入点表达式
切入点表达式:描述切入点方法的一种表达式
作用:主要用来决定项目中的哪些方法需要加入通知
常见形式:
execution(....):根据方法的签名来匹配
@annotation(....):根据注解匹配
javapackage com.itheima.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; //切面类 @Slf4j //@Aspect @Component public class MyAspect6 { //@Pointcut("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))") //@Pointcut("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))") //@Pointcut("execution(void delete(java.lang.Integer))") //包名.类名不建议省略 //@Pointcut("execution(void com.itheima.service.DeptService.delete(java.lang.Integer))") //@Pointcut("execution(void com.itheima.service.DeptService.*(java.lang.Integer))") //@Pointcut("execution(* com.*.service.DeptService.*(*))") //@Pointcut("execution(* com.itheima.service.*Service.delete*(*))") //@Pointcut("execution(* com.itheima.service.DeptService.*(..))") //@Pointcut("execution(* com..DeptService.*(..))") //@Pointcut("execution(* com..*.*(..))") //@Pointcut("execution(* *(..))") //慎用 @Pointcut("execution(* com.itheima.service.DeptService.list()) || " + "execution(* com.itheima.service.DeptService.delete(java.lang.Integer))") private void pt(){} @Before("pt()") public void before(){ log.info("MyAspect6 ... before ..."); } }
书写建议
- 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是 find 开头,更新类方法都是 update开头
- 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性。
- 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:忽名匹配尽量不使用...使用*匹配单个包
@annotation切入点表达式
javapackage com.itheima.aop; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyLog { }
--连接点
在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
- 对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint
- 对于其他四种通知,获取连接点信息只能使用 JoinPoint,它是 ProceedingJoinPoint 的父类型
javapackage com.itheima.aop; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import java.util.Arrays; //切面类 @Slf4j @Aspect @Component public class MyAspect8 { @Pointcut("execution(* com.itheima.service.DeptService.*(..))") private void pt(){} @Before("pt()") public void before(JoinPoint joinPoint){ 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; } }
九、SpringBoot综合原理
9.1 配置原理
注意事项
虽然springboot支持多种格式配置文件,但是在项目开发时,推荐统一使用一种格式的配置
(ym1是主流)
SpringBoot 除了支持配置文件属性配置,还支持|ava系统属性和命令行参数的方式进行属性配置
注意事项
Springboot项目进行打包时,需要引入插件 spring-boot-maven-plugin(基于官网骨架创建项目,会自动添加该插件)
所生成的java保存到target文件中--配置优先级
- application.yaml(忽略)
- application.yml
- application.properties
- java系统属性(-Dxxx=xxx)
- 命令行参数(--xxx=xxx)
9.2 Bean管理
默认情况下,Spring项目启动时,会把bean都创建好放在I0C容器中,如果想要主动获取这些bean,可以通过如下
方式:
- 根据name获取bean:Object getBean(String name)
- 根据类型获取bean:<T>T getBean(Class<T> requiredType)
- 根据name获取bean(带类型转换):<T>T getBean(String name,Class<T> requiredType)
javapackage com.itheima; import com.itheima.controller.DeptController; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; @SpringBootTest class SpringbootWebConfig2ApplicationTests { @Autowired private ApplicationContext applicationContext; //IOC容器对象 //获取bean对象 @Test public void testGetBean(){ //根据bean的名称获取 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的作用域 @Test public void testScope(){ for (int i = 0; i < 10; i++) { DeptController deptController = applicationContext.getBean(DeptController.class); System.out.println(deptController); } } @Autowired private SAXReader saxReader; //第三方bean的管理 @Test public void testThirdBean() throws Exception { //SAXReader saxReader = new SAXReader(); Document document = saxReader.read(this.getClass().getClassLoader().getResource("1.xml")); Element rootElement = document.getRootElement(); String name = rootElement.element("name").getText(); String age = rootElement.element("age").getText(); System.out.println(name + " : " + age); } @Test public void testGetBean2(){ Object saxReader = applicationContext.getBean("reader"); System.out.println(saxReader); } }
注意事项
- 上述所说的【Spring项目启动时,会把其中的bean都创建好】还会受到作用域及延迟初始化影响,这里主要针对于 默认的单例非延迟加载的bean而言。
-- Bean的作用域
|-------------|-------------------------------|
| 作用域 | 说明 |
| singleton | 容器内同 名称 的 bean 只有一个实例(单例)(默认) |
| prototype | 每次使用该 bean 时会创建新的实例(非单例) |
| request | 每个请求范围内会创建新的实例(web环境中,了解) |
| session | 每个会话范围内会创建新的实例(web环境中,了解) |
| application | 每个应用范围内会创建新的实例(web环境中,了解) |可以通过 @Scope 注解来进行配置作用域
一般bean的初始化是在项目启动时自动创建,可以加@Lazy注解来延缓初始化
注意事项
- 默认singleton的bean,在容器启动时被创建,可以使用@Lazv注解来延迟初始化(延迟到第一次使用时)
- prototype的bean,每一次使用该bean的时候都会创建一个新的实例。
- 实际开发当中,绝大部分的Bean是单例的,也就是说绝大部分Bean不需要配置scope属性。
--第三方的bean
@Bean
- 如果要管理的bean对象来自于第三方(不是自定义的),是无法用 @Component及衍生注解声明bean的,就需要用到 @Bean注解
- 若要管理的第三方bean对象,建议对这些bean进行集中分类配置,可以通过@Configuration 注解声明一个配置类。
javapackage com.itheima.config; import com.itheima.service.DeptService; import org.dom4j.io.SAXReader; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration //配置类 public class CommonConfig { //声明第三方bean @Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean //通过@Bean注解的name/value属性指定bean名称, 如果未指定, 默认是方法名 public SAXReader reader(DeptService deptService){ System.out.println(deptService); return new SAXReader(); } }
注意事项
- 通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名。
- 如果第三方bean需要依赖其它bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。
9.3 SpringBoot 原理
起步依赖
自动配置
- SpringBoot的自动配置就是当spring容器启动后,一些配置类、bean对象就自动存入到了I0C容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。
自动配置原理
方案一:@ComponentScan 组件扫描
方案二:@lmport 导入。使用@lmport导入的类会被Spring加载到I0C容器中,导入形式主要有以下几种:
- 导入 普通类
- 导入 配置类
- 导入 ImportSelector 接口实现类
- @EnableXxxx注解,封装@Import注解
该注解标识在SpringBoot工程引导类上,是SpringBoot中最最最重要的注解。该注解由三个部分组成:
- @SpringBootConfiguration:该注解与 @Configuration 注解作用相同,用来声明当前也是一个配置类。
- @ComponentScan:组件扫描,默认扫描当前引导类所在包及其子包。
- @EnableAutoConfiguration:SpringBoot实现自动化配置的核心注解
@ConditionalOnMissingBean 按条件注入所需要的注解
--@conditional
作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到SpringI0C容器中。
位置:方法、类
@Conditional本身是一个父注解,派生出大量的子注解:
- @ConditionalOnClass:判断环境中是否有对应字节码文件,才注册bean到IOC容器。
- @Conditional0nMissingBean:判断环境中没有对应的bean(类型 或名称),才注册bean到IOC容器
- @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。
--自定义starter
在实际开发中,经常会定义一些公共组件,提供给各个项目团队使用。而在SpringBoot的项目中,一般会将这些公共组件封装为SpringBoot 的 starter.
操作步骤
创建一个srtart模块,导入对应依赖
创建一个auto configure模块,在start中引用模块
XML<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-oss-spring-boot-autoconfigure</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
在 alivun-oss-spring-boot-autoconfiqure 模块中的定义自动配置功能,并定义自动配置文件 META-INF/spring/xxxx.imports
XML<!--阿里云OSS--> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.15.1</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency> <!-- no more than 2.3.3--> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.3.3</version> </dependency>
AliOSSAutoConfiguration
javapackage com.aliyun.oss; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableConfigurationProperties(AliOSSProperties.class) public class AliOSSAutoConfiguration { @Bean public AliOSSUtils aliOSSUtils(AliOSSProperties aliOSSProperties){ AliOSSUtils aliOSSUtils = new AliOSSUtils(); aliOSSUtils.setAliOSSProperties(aliOSSProperties); return aliOSSUtils; } }
AliOSSProperties
javapackage com.aliyun.oss; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "aliyun.oss") public class AliOSSProperties { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; public String getEndpoint() { return endpoint; } public void setEndpoint(String endpoint) { this.endpoint = endpoint; } public String getAccessKeyId() { return accessKeyId; } public void setAccessKeyId(String accessKeyId) { this.accessKeyId = accessKeyId; } public String getAccessKeySecret() { return accessKeySecret; } public void setAccessKeySecret(String accessKeySecret) { this.accessKeySecret = accessKeySecret; } public String getBucketName() { return bucketName; } public void setBucketName(String bucketName) { this.bucketName = bucketName; } }
AliOSSUtils
javapackage com.aliyun.oss; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; import java.util.UUID; /** * 阿里云 OSS 工具类 */ public class AliOSSUtils { // @Value("${aliyun.oss.endpoint}") // private String endpoint ; // @Value("${aliyun.oss.accessKeyId}") // private String accessKeyId ; // @Value("${aliyun.oss.accessKeySecret}") // private String accessKeySecret ; // @Value("${aliyun.oss.bucketName}") // private String bucketName ; private AliOSSProperties aliOSSProperties; public AliOSSProperties getAliOSSProperties() { return aliOSSProperties; } public void setAliOSSProperties(AliOSSProperties aliOSSProperties) { this.aliOSSProperties = aliOSSProperties; } /** * 实现上传图片到OSS */ public String upload(MultipartFile file) throws IOException { //获取阿里云OSS参数 String endpoint = aliOSSProperties.getEndpoint(); String accessKeyId = aliOSSProperties.getAccessKeyId(); String accessKeySecret = aliOSSProperties.getAccessKeySecret(); String bucketName = aliOSSProperties.getBucketName(); // 获取上传的文件的输入流 InputStream inputStream = file.getInputStream(); // 避免文件覆盖 String originalFilename = file.getOriginalFilename(); String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); //上传文件到 OSS OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); ossClient.putObject(bucketName, fileName, inputStream); //文件访问路径 String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName; // 关闭ossClient ossClient.shutdown(); return url;// 把上传到oss的路径返回 } }
在META-INF/spring/新建=》org.springframework.boot.autoconfigure.AutoConfiguration.imports(名称不能出错)
javacom.aliyun.oss.AliOSSAutoConfiguration
可以参照测试工程来进行测试
测试数据,用apipost或者postman测试就可以