Maven
Apache Maven 是一个项目管理和构建工具,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建。
Maven作用
依赖管理
统一的项目结构
项目构建
仓库
安装Maven
<mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url><mirrorOf>central</mirrorOf>
</mirror>
测试:
mvn -v
配置Maven环境
创建Maven项目
坐标
导入Maven项目
依赖配置
仓库网址:https://mvnrepository.com/
依赖传递
显示关系图
A的依赖关系图
B的依赖关系图
C的依赖关系图
排除依赖
依赖范围
生命周期
在同一套生命周期中,当运行后面的阶段时,前面的阶段都会运行,上图三套
后端Web开发
SpringBoot
入门
入门代码
package com.itheima.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class HelloController {@RequestMapping("/Hello")public String hello() {System.out.println("Hello!!!");return "Hello world!!!";}
}
启动
HTTP协议
请求协议
相应协议
状态码大类
状态码分类 | 说明 |
---|---|
1xx | 响应中——临时状态码,表示请求已经接受,告诉客户端应该继续请求或者如果它已经完成则忽略它 |
2xx | 成功——表示请求已经被成功接收,处理已完成 |
3xx | 重定向——重定向到其它地方:它让客户端再发起一个请求以完成整个处理。 |
4xx | 客户端错误——处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等 |
5xx | 服务器端错误——处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等 |
常见的响应状态码
状态码 | 英文描述 | 解释 |
---|---|---|
200 | OK |
客户端请求成功,即处理成功,这是我们最想看到的状态码 |
302 | Found |
指示所请求的资源已移动到由Location 响应头给定的 URL,浏览器会自动重新访问到这个页面 |
304 | Not Modified |
告诉客户端,你请求的资源至上次取得后,服务端并未更改,你直接用你本地缓存吧。隐式重定向 |
400 | Bad Request |
客户端请求有语法错误,不能被服务器所理解 |
403 | Forbidden |
服务器收到请求,但是拒绝提供服务,比如:没有权限访问相关资源 |
404 | Not Found |
请求资源不存在,一般是URL输入有误,或者网站资源被删除了 |
405 | Method Not Allowed |
请求方式有误,比如应该用GET请求方式的资源,用了POST |
428 | Precondition Required |
服务器要求有条件的请求,告诉客户端要想访问该资源,必须携带特定的请求头 |
429 | Too Many Requests |
指示用户在给定时间内发送了太多请求(“限速”),配合 Retry-After(多长时间后可以请求)响应头一起使用 |
431 | Request Header Fields Too Large |
请求头太大,服务器不愿意处理请求,因为它的头部字段太大。请求可以在减少请求头域的大小后重新提交。 |
500 | Internal Server Error |
服务器发生不可预期的错误。服务器出异常了,赶紧看日志去吧 |
503 | Service Unavailable |
服务器尚未准备好处理请求,服务器刚刚启动,还未初始化好 |
状态码大全:https://cloud.tencent.com/developer/chapter/13553
协议解析
Tomcat
基本使用
配置端口号:
部署:将文件移动到webapps文件夹下即可
https://docs.spring.io/spring-boot/docs/2.7.4/reference/htmlsingle/#using.build-systems.starters
请求响应
请求
postman
简单参数
如果方法形参名称与请求参数名称不匹配,可以使用 @RequestParam 完成映射
原始方法(不要求掌握)
//原始方法
@RestController
public class HelloController {@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方式
@RestController
public class HelloController {
// Springboot方式@RequestMapping("/simpleParam")public String simpleParam(String name, int age) {System.out.println(name + ":" + age);return "OK";}
}
GET请求
POST请求
请求参数名与方法变量名不同,不能传递
@RequestMapping("/simpleParam")public String simpleParam(String userName, int age) {System.out.println(userName + ":" + age);return "OK";}
强制传递:注释@RequestParam(name/value="请求参数名", require = true(默认,必须传递name))
@RequestMapping("/simpleParam")public String simpleParam(@RequestParam(name = "name") String userName, int age) {System.out.println(userName + ":" + age);return "OK";}
不传递会报错,因为默认required=true,除非required=false
required=false,不传递name:
实体参数
要求:请求参数名与形参对象属性名相同
简单实体参数
实体类
public class User {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}
}
Springboot方法
@RestController
public class HelloController {
// 实体参数@RequestMapping("/simplePojo")public String simplePojo(User user) {System.out.println(user);return "OK";}
}
复杂实体参数
实体类:address实体类不要封装有参构造函数,否则报错
public class Address {private String province;private String city;// 不要写有参构造函数public String getProvince() {return province;}public void setProvince(String province) {this.province = province;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}@Overridepublic String toString() {return "Address{" +"province='" + province + '\'' +", city='" + city + '\'' +'}';}
}
public class User {private String name;private int age;private Address address;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", address=" + address +'}';}
}
Springboot方法
@RestController
public class HelloController {
// 复杂实体方法@RequestMapping("/complexPojo")public String complexPojo(User user) {System.out.println(user);return "OK";}
}
数组集合参数
@RestController
public class HelloController {
// 数组集合参数@RequestMapping("/arrayParam")public String arrayParam(String[] hobby) {System.out.println(Arrays.toString(hobby));return "OK";}
}
数组参数
集合参数
请求参数名与形参集合名称相同且请求参数为多个,@RequestParam 绑定参数关系
注意:一定要加注解
@RestController
public class HelloController {
// 集合参数@RequestMapping("/listParam")public String listParam(@RequestParam List<String> hobby) {System.out.println(hobby);return "OK";}
}
日期时间参数
使用 @DateTimeFormat 注解完成日期参数格式转换
@RestController
public class HelloController {
// 日期时间参数@RequestMapping("/dateParam")
// 方法形参:加注解指定日期格式,声明一个日期时间对象updateTimepublic String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime) {System.out.println(updateTime);return "OK";}
}
JSON参数
JSON数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数,需要使用 @RequestBody 标识
注意:实体类不要写有参构造器
@RestController
public class HelloController {@RequestMapping("/jsonParam")public String jsonParam(@RequestBody User user) {System.out.println(user);return "OK";}
}
// Json参数如下
{"name": "itheima", "age": 22, "address": {"province": "安徽", "city": "阜阳"}
}
路径参数
如果方法形参名称与请求参数名称不匹配,可以使用 @RequestParam 完成映射
@RestController
public class HelloController {
// 路径参数-单个参数@RequestMapping("/path/{id}")public String pathParam(@PathVariable Integer id) {System.out.println(id);return "OK";}
}
多个参数
// 多个参数
@RequestMapping("/path/{id}/{name}")
public String pathParam(@PathVariable Integer id, @PathVariable String name) {System.out.println(id + ":" + name);return "OK";
}
总结-参数
响应
@RestController
public class RequestController {
// 响应-字符串@RequestMapping("/Hello")public String hello() {System.out.println("Hello!!!");return "Hello world!!!";}
// 响应-对象@RequestMapping("/getAddr")public Address getAddr() {Address addr = new Address();addr.setProvince("广东");addr.setCity("深圳");return addr;}
// 响应-集合@RequestMapping("/listAddr")public List<Address> 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 list;}
}
响应数据形式不一,不便管理,难以维护
统一响应结果
Result类
/*** 统一响应结果封装类*/
public class Result {private Integer code ;//1 成功 , 0 失败private String msg; //提示信息private Object data; //数据 datapublic Result() {}public Result(Integer code, String msg, Object data) {this.code = code;this.msg = msg;this.data = data;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static Result success(Object data){return new Result(1, "success", data);}public static Result success(){return new Result(1, "success", null);}public static Result error(String msg){return new Result(0, msg, null);}@Overridepublic String toString() {return "Result{" +"code=" + code +", msg='" + msg + '\'' +", data=" + data +'}';}
}
@RestController
public class ResponseController {
// 统一响应结果// 响应-字符串@RequestMapping("/Hello")public Result hello() {System.out.println("Hello!!!");return Result.success("Hello world!!!");}// 响应-对象@RequestMapping("/getAddr")public Result getAddr() {Address addr = new Address();addr.setProvince("广东");addr.setCity("深圳");System.out.println(addr);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);System.out.println(addr1);System.out.println(addr2);System.out.println(list);return Result.success(list);}
}
响应-案例
Springboot项目的静态资源(html,css,js等前端资源)默认存放目录为:classpath:/static 、 classpath:/public、 classpath:/resources
empController
package com.itheima.controller;import com.itheima.pojo.Emp;
import com.itheima.pojo.Result;
import com.itheima.utils.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
public class empController {@RequestMapping("/listEmp")public Result listEmp() {
// 动态获取获取静态文件路径,路径中不含中文String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
// 实在不行就拿绝对路径
// String file = "C:\\Users\\bfs\\Desktop\\JavaWeb\\study\\JavaWeb_project\\springboot-web-quickstart\\src\\main\\resources\\emp.xml";
// 加载emp.xml,并解析数据List<Emp> empList = XmlParserUtils.parse(file, Emp.class);// 处理gender、job字段empList.stream().forEach(emp -> {String gender = emp.getGender(); // gender: 1 男 2 女if("1".equals(gender)) {emp.setGender("男");} else if("2".equals(gender)) {emp.setGender("女");}String job = emp.getJob(); // job: 1:讲师 2:班主任 3:就业指导if("1".equals(job)) {emp.setJob("讲师");} else if("2".equals(job)) {emp.setJob("班主任");} else if("3".equals(job)) {emp.setJob("就业指导");}});// 返回结果return Result.success(empList);}
}
接口调试
前端
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>员工信息</title>
</head><link rel="stylesheet" href="element-ui/index.css">
<script src="./js/vue.js"></script>
<script src="./element-ui/index.js"></script>
<script src="./js/axios-0.18.0.js"></script><body><h1 align="center">员工信息列表展示</h1><div id="app"><el-table :data="tableData" style="width: 100%" stripe border ><el-table-column prop="name" label="姓名" align="center" min-width="20%"></el-table-column><el-table-column prop="age" label="年龄" align="center" min-width="20%"></el-table-column><el-table-column label="图像" align="center" min-width="20%"><template slot-scope="scope"><el-image :src="scope.row.image" style="width: 80px; height: 50px;"></el-image></template></el-table-column><el-table-column prop="gender" label="性别" align="center" min-width="20%"></el-table-column><el-table-column prop="job" label="职位" align="center" min-width="20%"></el-table-column></el-table></div>
</body><style>.el-table .warning-row {background: oldlace;}.el-table .success-row {background: #f0f9eb;}
</style><script>new Vue({el: "#app",data() {return {tableData: []}},mounted(){axios.get('/listEmp').then(res=>{if(res.data.code){this.tableData = res.data.data;}});},methods: {}});
</script>
</html>
渲染效果
分层解耦
三层结构
文件结构
dao接口
package com.itheima.dao;import com.itheima.pojo.Emp;import java.util.List;public interface EmpDao {List<Emp> listEmp();
}
dao接口的实现类
package com.itheima.dao.impl;import com.itheima.dao.EmpDao;
import com.itheima.pojo.Emp;
import com.itheima.utils.XmlParserUtils;import java.util.List;public class EmpDaoA implements EmpDao {@Overridepublic List<Emp> listEmp() {
// 动态获取获取静态文件路径,路径中不含中文String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
// 实在不行就拿绝对路径
// String file = "C:\\Users\\bfs\\Desktop\\JavaWeb\\study\\JavaWeb_project\\springboot-web-quickstart\\src\\main\\resources\\emp.xml";
// 加载emp.xml,并解析数据List<Emp> empList = XmlParserUtils.parse(file, Emp.class);return empList;}
}
service接口
package com.itheima.service;import com.itheima.pojo.Emp;import java.util.List;public interface EmpService {List<Emp> listEmp();
}
service接口实现类
package com.itheima.service.impl;import com.itheima.dao.EmpDao;
import com.itheima.dao.impl.EmpDaoA;
import com.itheima.pojo.Emp;
import com.itheima.service.EmpService;import java.util.List;public class EmpServiceA implements EmpService {private EmpDao empDao = new EmpDaoA();@Overridepublic List<Emp> listEmp() {List<Emp> empList = empDao.listEmp();// 处理gender、job字段empList.stream().forEach(emp -> {String gender = emp.getGender(); // gender: 1 男 2 女if("1".equals(gender)) {emp.setGender("男");} else if("2".equals(gender)) {emp.setGender("女");}String job = emp.getJob(); // job: 1:讲师 2:班主任 3:就业指导if("1".equals(job)) {emp.setJob("讲师");} else if("2".equals(job)) {emp.setJob("班主任");} else if("3".equals(job)) {emp.setJob("就业指导");}});return empList;}
}
controller
package com.itheima.controller;import com.itheima.pojo.Emp;
import com.itheima.pojo.Result;
import com.itheima.service.EmpService;
import com.itheima.service.impl.EmpServiceA;
import com.itheima.utils.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
public class empController {private EmpService empService = new EmpServiceA();@RequestMapping("/listEmpA")public Result listEmpA() {List<Emp> list = empService.listEmp();return Result.success(list);}
}
存在的问题:耦合
分层解耦
- 内聚:软件中各个功能模块内部的功能联系。
- 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。
- 软件设计原则:高内聚,低耦合
控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。
依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
Bean对象:IOC容器中创建、管理的对象,称之为bean。
IOC
Bean的声明
要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一
声明bean的时候,可以通过value属性指定bean的名字,如果没有指定,默认为类名首字母小写。
使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。
Bean组件扫描
DI
Mybatis
Mybatis入门
Mybatis简介
官网:https://mybatis.org/mybatis-3/zh/index.html
入门程序
查询user表中数据
mapper接口
package com.itheima.mapper;import com.itheima.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;//运行时,会自动生成该接口的实现类对象(动态代理,代理对象),并且将该对象交给IOC容器管理
@Mapper
public interface UserMapper {// 查询sql语句@Select("select * from user")public List<User> list();}
实体User类
package com.itheima.pojo;public class User {private Integer id;private String name;private Short age;private Short gender;private String phone;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;}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;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", gender=" + gender +", phone='" + phone + '\'' +'}';}
}
数据库配置信息
application.properties文件
#驱动类名称
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=1234
测试类
package 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;//Springboot整合单元测试的注解
@SpringBootTest
class SpringbootMybatisQuickstartApplicationTests {@Testvoid contextLoads() {}// 依赖注入@Autowiredprivate UserMapper userMapper;@Testpublic void testListUser() {List<User> userList = userMapper.list();userList.stream().forEach(user -> {System.out.println(user);});}}
运行结果
JDBC介绍(不需要掌握)
数据库连接池
德鲁伊:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
lombok
lombok是一个实用的Java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化java开发、提高效率。
Mybatis基础操作
删除
package com.itheima.mapper;import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;//Mapper接口
@Mapper
public interface EmpMapper {// 删除员工@Delete("delete from emp where id = #{id}")
// void delete(Integer id);
// 返回删除几条数据int delete(Integer id);
}
package com.itheima;import com.itheima.mapper.EmpMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;//测试类
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
// 防止标红 required@Autowired(required = false)private EmpMapper empMapper;@Testvoid contextLoads() {empMapper.delete(17);}
}
SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
增添
实体类
package 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;private String username;private String password;private String name;private Short gender;private String image;private Short job;private LocalDate entrydate;private Integer deptId;private LocalDateTime createTime;private LocalDateTime updateTime;
}
接口
package com.itheima.mapper;import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;//Mapper接口
@Mapper
public interface EmpMapper {// 添加员工@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);}
测试接口
package com.itheima;import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.time.LocalDate;
import java.time.LocalDateTime;//测试类
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
// 防止标红 required@Autowired(required = false)private EmpMapper empMapper;@Testvoid testAdd() {
// 创建一个实体类empEmp emp = new Emp();emp.setUsername("Tom3");emp.setName("汤姆3");emp.setImage("1.jpg");emp.setGender((short)1);emp.setJob((short)1);emp.setEntrydate(LocalDate.of(2000,1,1));emp.setCreateTime(LocalDateTime.now());emp.setUpdateTime(LocalDateTime.now());emp.setDeptId(1);empMapper.insert(emp);}}
主键返回
描述:在数据添加成功后,需要获取插入数据库数据的主键。如:添加套餐数据时,还需要维护套餐菜品关系表数据。
更新
Mapper接口
package com.itheima.mapper;import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;//Mapper接口
//运行时,会自动生成该接口的实现类对象(动态代理,代理对象),并且将该对象交给IOC容器管理
@Mapper
public interface EmpMapper {
// 修改员工@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}")void update(Emp emp);
}
测试类
package com.itheima;import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.time.LocalDate;
import java.time.LocalDateTime;//测试类
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
// 依赖注入
// 防止标红 required@Autowired(required = false)private EmpMapper empMapper;@Testvoid testUpdate() {Emp emp = new Emp();emp.setId(22);emp.setUsername("更新操作");emp.setName("汤姆666");emp.setImage("1.jpg");emp.setGender((short)1);emp.setJob((short)1);emp.setEntrydate(LocalDate.of(2000,1,1));emp.setUpdateTime(LocalDateTime.now());emp.setDeptId(2);empMapper.update(emp);}
}
查询
Mapper接口
package com.itheima.mapper;import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;//Mapper接口
//运行时,会自动生成该接口的实现类对象(动态代理,代理对象),并且将该对象交给IOC容器管理
@Mapper
public interface EmpMapper {
// 查询员工@Select("select * from emp where id = #{id}")Emp selectById(Integer id);
}
测试类
package com.itheima;import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.time.LocalDate;
import java.time.LocalDateTime;//测试类
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
// 依赖注入
// 防止标红 required@Autowired(required = false)private EmpMapper empMapper;@Testvoid testSelect() {Emp emp = empMapper.selectById(22);System.out.println(emp);}
}
运行结果在下面
数据封装
问题:数据库中字段名与实体类中属性名不一致
解决方案
- 方案一:起别名
@Select("select id, username, password, name, gender, image, job, entrydate, " +"dept_id deptId, create_time createTime, update_time updateTime from emp where id = #{id}")
Emp selectById(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}")
Emp selectById(Integer id);
- 方案三:开启驼峰命名
#开启驼峰命名用于主句封装,前提是数据库字段明明严格采用下划线命名并且实体类属性名采用驼峰明明
mybatis.configuration.map-underscore-to-camel-case=true
然后直接select
传递多个参数
Mapper接口,多个参数要写@Param注解
package com.itheima.mapper;import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;import java.time.LocalDate;
import java.util.List;//Mapper接口
//运行时,会自动生成该接口的实现类对象(动态代理,代理对象),并且将该对象交给IOC容器管理
@Mapper
public interface EmpMapper {
// 单个参数查询
// 用$代表字串拼接
// @Select("select * from emp where name like '%${name}%'" +
// " order by update_time desc ")
// List<Emp> selectByInfo(String name);// 多参数要@Param注释@Select("select * from emp where name like concat('%', #{name}, '%') and gender = #{gender} and " +"entrydate between #{begin} and #{end} order by update_time desc ")
// List<Emp> selectByInfo(String name, Short gender, LocalDate begin , LocalDate end);List<Emp> selectByInfo(@Param("name")String name, @Param("gender")Short gender, @Param("begin")LocalDate begin , @Param("end")LocalDate end);// 或者多个参数封装到对象里@Select("select * from emp where name like concat('%', #{name}, '%') and gender = #{gender} and entrydate >= #{entrydate} order by update_time desc")List<Emp> selectMulti(Emp emp);
}
package com.itheima;import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;//测试类
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
// 依赖注入
// 防止标红 required@Autowired(required = false)private EmpMapper empMapper;@Testvoid testSelectByInfo() {
// 测试查找单个参数,带有模糊匹配
// List<Emp> list = empMapper.selectByInfo((short) 1, LocalDate.of(2010, 1, 1), LocalDate.of(2020, 1, 1));
// List<Emp> empList = empMapper.selectByInfo("张");
// 测试查找多个参数List<Emp> empList = empMapper.selectByInfo("张", (short) 1, LocalDate.of(2010, 1, 1), LocalDate.of(2020, 1, 1));System.out.println(empList);System.out.println(empList.size());}// 测试查找多个参数@Testvoid testSelectMulti() {Emp emp = new Emp();emp.setName("张");emp.setGender((short) 1);emp.setEntrydate(LocalDate.of(2010, 1, 1));List<Emp> list = empMapper.selectMulti(emp);System.out.println(list);}
}
可能存在的问题,Mapper传参要写@Param注解,否则传参失败报错
XML映射文件
三点规范:
-
XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下(同包同名)。
-
XML映射文件的namespace属性为Mapper接口全限定名一致。
-
XML映射文件中sql语句的id与Mapper 接口中的方法名一致,并保持返回类型一致。
XML文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">
<!-- resultType:单条记录所封装的类型的全限定类名即Emp类--><select id="selectByInfo" resultType="com.itheima.pojo.Emp">select * from emp where name like concat('%', #{name}, '%') and gender = #{gender} and entrydate between #{begin} and #{end} order by update_time desc</select></mapper>
Mapper中的XML映射
package com.itheima.mapper;import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.*;import java.time.LocalDate;
import java.util.List;//Mapper接口
//运行时,会自动生成该接口的实现类对象(动态代理,代理对象),并且将该对象交给IOC容器管理
@Mapper
public interface EmpMapper {
// xml映射List<Emp> selectByInfo(@Param("name")String name, @Param("gender")Short gender, @Param("begin")LocalDate begin , @Param("end")LocalDate end);
}
package com.itheima;import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;//测试类
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
// 依赖注入
// 防止标红 required@Autowired(required = false)private EmpMapper empMapper;@Testvoid testSelectByInfo() {
// 测试查找单个参数,带有模糊匹配
// List<Emp> list = empMapper.selectByInfo((short) 1, LocalDate.of(2010, 1, 1), LocalDate.of(2020, 1, 1));
// List<Emp> empList = empMapper.selectByInfo("张");
// 测试查找多个参数List<Emp> empList = empMapper.selectByInfo("张", (short) 1, LocalDate.of(2010, 1, 1), LocalDate.of(2020, 1, 1));System.out.println(empList);System.out.println(empList.size());}
}
动态SQL
<if>
和 <where>
where标签可以自动删除语句开头的and
注意SQL语句中的and在每个if开头不可省略
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper"><!-- 动态查询--><!-- resultType:单条记录所封装的类型的全限定类名即Emp类--><select id="selectByInfo" resultType="com.itheima.pojo.Emp">select * from emp<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>
</mapper>
mapper
package com.itheima.mapper;import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.*;import java.time.LocalDate;
import java.util.List;//Mapper接口
//运行时,会自动生成该接口的实现类对象(动态代理,代理对象),并且将该对象交给IOC容器管理
@Mapper
public interface EmpMapper {
// xml映射
// 动态查询List<Emp> selectByInfo(@Param("name")String name, @Param("gender")Short gender, @Param("begin")LocalDate begin , @Param("end")LocalDate end);
}
test测试类
package com.itheima;import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;//测试类
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
// 依赖注入
// 防止标红 required@Autowired(required = false)private EmpMapper empMapper;@Testvoid testSelectByInfo() {// 动态SQL
// List<Emp> empList = empMapper.selectByInfo("张", (short) 1, null, null);
// List<Emp> empList = empMapper.selectByInfo("张", null, null, null);List<Emp> empList = empMapper.selectByInfo(null, null, null, null);System.out.println(empList);}
}
<set>
自动删除语句最后的逗号
动态更新xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper"><!-- 动态更新操作--><update id="updateDynamic">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>
</mapper>
mapper
package com.itheima.mapper;import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.*;import java.time.LocalDate;
import java.util.List;//Mapper接口
//运行时,会自动生成该接口的实现类对象(动态代理,代理对象),并且将该对象交给IOC容器管理
@Mapper
public interface EmpMapper {
// 动态修改void updateDynamic(Emp emp);
}
测试类
package com.itheima;import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;//测试类
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
// 依赖注入
// 防止标红 required@Autowired(required = false)private EmpMapper empMapper;// 案例:动态更新@Testvoid testUpdateDynamic() {
// 封装的对象属性是根据每一条SQL语句set,不灵活,如果缺少了某条会直接赋值nullEmp emp = new Emp();emp.setId(26);emp.setUsername("动态更新");emp.setName("动态更新666");
// emp.setImage("1.jpg");emp.setGender((short)2);emp.setJob((short)1);
// emp.setEntrydate(LocalDate.of(2000,1,1));emp.setUpdateTime(LocalDateTime.now());emp.setDeptId(2);// empMapper.update(emp);
// 动态更新empMapper.updateDynamic(emp);}
}
<foreach>
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper"><!-- 批量删除-->
<!--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>
mapper
package com.itheima.mapper;import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.*;import java.time.LocalDate;
import java.util.List;//Mapper接口
//运行时,会自动生成该接口的实现类对象(动态代理,代理对象),并且将该对象交给IOC容器管理
@Mapper
public interface EmpMapper {
// xml映射
// 批量删除,返回删除几条数据int deleteByIds(@Param("ids") List<Integer> ids);
}
test测试类
package com.itheima;import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;//测试类
@SpringBootTest
class SpringbootMybatisCrudApplicationTests {
// 依赖注入
// 防止标红 required@Autowired(required = false)private EmpMapper empMapper;@Testvoid testDeleteByIds() {
// 工具类Arrays的asList方法创建集合List<Integer> ids = Arrays.asList(13, 14, 15);int cnt = empMapper.deleteByIds(ids);System.out.println(cnt);}
}
<sql>
和<include>
-
:定义可重用的 SQL 片段。 -
:通过属性refid,指定包含的sql片段。
以动态查询为例
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper"><!-- sql片段--><sql id="commonSelect">select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp</sql><!-- 动态查询--><select id="selectByInfo" resultType="com.itheima.pojo.Emp"><!-- 引入sql片段--><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>
</mapper>
登录认证
JWT令牌
组成
依赖
<!-- JWT令牌--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
jwt令牌生成和校验:
package com.itheima;import com.itheima.controller.UploadController;
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;@SpringBootTest
class TliasApplicationTests {// 测试生成jwt令牌@Testpublic void testJWT() {
// Map容器存储自定义内容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)) // 设置时间为一小时以后.compact();System.out.println(jwt);}// 解析jwt令牌@Testpublic void testParseJWT() {Claims claims = Jwts.parser().setSigningKey("itheima").parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTcyMjEwNDgyMH0.U6oovJ5AJz7IpTjnMwQxuSxauHtwzGtPkm6ZRMH2hoo").getBody();System.out.println(claims);}
}
过滤器Filter
package com.itheima.filter;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;//配置filter
//拦截所有请求
@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {// 初始化方法,只调用一次@Overridepublic void init(FilterConfig filterConfig) throws ServletException {
// Filter.super.init(filterConfig);System.out.println("init初始化方法被执行了");}// 每次拦截请求之后都会调用,会调用多次@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("拦截到了请求,Demo放行前逻辑");// 放行filterChain.doFilter(servletRequest, servletResponse);System.out.println("拦截到了请求,Demo放行后逻辑");}// 销毁方法,只调用一次@Overridepublic void destroy() {
// Filter.super.destroy();System.out.println("destroy销毁方法被执行了");}
}
package com.itheima.filter;import com.alibaba.fastjson.JSONObject;
import com.itheima.pojo.Result;
import com.itheima.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Slf4j
//@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 获取请求urlHttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;// 判断请求中是否包含login,包含就放行String url = request.getRequestURL().toString();log.info("请求的URL:{}", url);// 获取请求头中的令牌tokenif(url.contains("login")) {log.info("登录操作,放行");filterChain.doFilter(servletRequest, servletResponse);return;}// 获取请求头中的令牌String jwt = request.getHeader("token");
// 调试,查看令牌log.info("jwt:{}", jwt);
// 判断令牌是否存在,如果不存在,返回错误信息
// 字符串是否有长度if(!StringUtils.hasLength(jwt)) {log.info("请求头token为空,返回未登录的信息");Result error = Result.error("NOT_LOGIN");// 手动转换,对象-->JSON---使用阿里fastJSONString notLogin = JSONObject.toJSONString(error);response.getWriter().write(notLogin);return;}// 解析token,如果解析失败返回错误结果(未登录);try {JwtUtils.parseJWT(jwt);} catch (Exception e) { // 出现异常,jwt令牌解析失败
// throw new RuntimeException(e);e.printStackTrace();log.info("解析令牌失败,返回未登录的信息");Result error = Result.error("NOT_LOGIN");// 手动转换,对象-->JSON---使用阿里fastJSONString notLogin = JSONObject.toJSONString(error);response.getWriter().write(notLogin);return;}// 放行log.info("令牌合法,放行");filterChain.doFilter(servletRequest, servletResponse);}
}
拦截器
package 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 {
// 目标资源方法执行前执行,返回true代表放行,返回false代表不放请@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle...");// // 获取请求url
// HttpServletRequest request = (HttpServletRequest) servletRequest;
// HttpServletResponse response = (HttpServletResponse) servletResponse;// 判断请求中是否包含login,包含就放行String url = request.getRequestURL().toString();log.info("请求的URL:{}", url);// 获取请求头中的令牌tokenif(url.contains("login")) {log.info("登录操作,放行");
// filterChain.doFilter(servletRequest, servletResponse);return true;}// 获取请求头中的令牌String jwt = request.getHeader("token");
// 调试,查看令牌log.info("jwt:{}", jwt);
// 判断令牌是否存在,如果不存在,返回错误信息
// 字符串是否有长度if(!StringUtils.hasLength(jwt)) {log.info("请求头token为空,返回未登录的信息");Result error = Result.error("NOT_LOGIN");// 手动转换,对象-->JSON---使用阿里fastJSONString notLogin = JSONObject.toJSONString(error);response.getWriter().write(notLogin);
// 不放行return false;}// 解析token,如果解析失败返回错误结果(未登录);try {JwtUtils.parseJWT(jwt);} catch (Exception e) { // 出现异常,jwt令牌解析失败
// throw new RuntimeException(e);e.printStackTrace();log.info("解析令牌失败,返回未登录的信息");Result error = Result.error("NOT_LOGIN");// 手动转换,对象-->JSON---使用阿里fastJSONString notLogin = JSONObject.toJSONString(error);response.getWriter().write(notLogin);return true;}// 放行log.info("令牌合法,放行");filterChain.doFilter(servletRequest, servletResponse);
// return HandlerInterceptor.super.preHandle(request, response, handler);return true;
// return false;}// 目标方法执行后执行@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle...");
// HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}// 视图渲染玩不后执行,最后执行@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion...");
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}
区别
package 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 {
// 目标资源方法执行前执行,返回true代表放行,返回false代表不放请@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle...");// // 获取请求url
// HttpServletRequest request = (HttpServletRequest) servletRequest;
// HttpServletResponse response = (HttpServletResponse) servletResponse;// 判断请求中是否包含login,包含就放行String url = request.getRequestURL().toString();log.info("请求的URL:{}", url);// 获取请求头中的令牌tokenif(url.contains("login")) {log.info("登录操作,放行");
// filterChain.doFilter(servletRequest, servletResponse);return true;}// 获取请求头中的令牌String jwt = request.getHeader("token");
// 调试,查看令牌log.info("jwt:{}", jwt);
// 判断令牌是否存在,如果不存在,返回错误信息
// 字符串是否有长度if(!StringUtils.hasLength(jwt)) {log.info("请求头token为空,返回未登录的信息");Result error = Result.error("NOT_LOGIN");// 手动转换,对象-->JSON---使用阿里fastJSONString notLogin = JSONObject.toJSONString(error);response.getWriter().write(notLogin);
// 不放行return false;}// 解析token,如果解析失败返回错误结果(未登录);try {JwtUtils.parseJWT(jwt);} catch (Exception e) { // 出现异常,jwt令牌解析失败
// throw new RuntimeException(e);e.printStackTrace();log.info("解析令牌失败,返回未登录的信息");Result error = Result.error("NOT_LOGIN");// 手动转换,对象-->JSON---使用阿里fastJSONString notLogin = JSONObject.toJSONString(error);response.getWriter().write(notLogin);return true;}// 放行log.info("令牌合法,放行");
// filterChain.doFilter(servletRequest, servletResponse);
// return HandlerInterceptor.super.preHandle(request, response, handler);return true;
// return false;}// 目标方法执行后执行@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle...");
// HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}// 视图渲染玩不后执行,最后执行@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion...");
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}
全局异常处理器
package 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 e) {
// 输出堆栈信息e.printStackTrace();return Result.error("对不起,操作失败,请联系管理员");}
}
事务管理
@Transactional注解
配置信息
事务传播
package com.itheima.service.impl;import com.itheima.mapper.DeptLogMapper;
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.apache.ibatis.annotations.Mapper;
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 {@Autowiredprivate DeptMapper deptMapper;@Autowiredprivate EmpMapper empMapper;@Autowiredprivate DeptLogService deptLogService;@Overridepublic List<Dept> list() {return deptMapper.list();}//事务管理,处理任何异常,默认只处理运行时异常@Transactional(rollbackFor = Exception.class)
// @Transactional
// 删除部门以及部门下的员工@Overridepublic void delete(Integer id) throws Exception {try {deptMapper.deleteById(id);
// 用于测试事务
// int i = 1 / 0;// 用于测试事务
// if(true){
// throw new Exception("出错啦...");
// }empMapper.deleteByDeptId(id);} finally {DeptLog deptLog = new DeptLog();deptLog.setCreateTime(LocalDateTime.now());deptLog.setDescription("执行了解散部门的操作,此次解散的是" + id + "号部门");deptLogService.insert(deptLog);}}@Overridepublic void add(Dept dept) {
// 补全时间信息dept.setCreateTime(LocalDateTime.now());dept.setUpdateTime(LocalDateTime.now());deptMapper.addDept(dept);}
}
AOP
- AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。
依赖
快速入门:记录原始方法运行时间
package 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.*.*(..))") // 切入点表达式public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {// 记录时间long begin = System.currentTimeMillis();// 调用原始方法Object proceed = joinPoint.proceed();// 记录时间long end = System.currentTimeMillis();// 打印日志:得到原始方法方法签名 + 运行耗时log.info(joinPoint.getSignature() + "方法耗时{}s", (end - begin) / 1e3);// 返回原始方法返回值return proceed;}
}
应用场景
优点
核心概念
- 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
- 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
- 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
- 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
- 目标对象:Target,通知所应用的对象
通知类型
- @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
- @Before:前置通知,此注解标注的通知方法在目标方法前被执行
- @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
- @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
- @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行
@Pointcut
- 该注解的作用是将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可。
- private:仅能在当前切面类中引用该表达式
- public:在其他外部的切面类中也可以引用该表达式
通知顺序
1.不同切面类中,默认按照切面类的类名字母排序:
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名靠前的后执行
2.用 @Order(数字) 加在切面类上来控制顺序
- 目标方法前的通知方法:数字小的先执行
- 目标方法后的通知方法:数字小的后执行
切入点表达式
- 切入点表达式:描述切入点方法的一种表达式
- 作用:主要用来决定项目中的哪些方法需要加入通知
- 常见形式:
- execution(……):根据方法的签名来匹配
- @annotation(……) :根据注解匹配
execution
格式
- 根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。
- 书写建议
- 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是 find 开头,更新类方法都是 update开头。
- 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性。
- 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用 ..,使用 * 匹配单个包。
@annotation
- 用自定义注解提前标识切入点,能够灵活的标注切入点
- @annotation注解中写标识的全类名
package 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 的父类型
package com.itheima.aop;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;import java.util.Arrays;@Slf4j
@Aspect
@Component
public class Aspect1 {@Pointcut("execution(* com.itheima.service.DeptService.*(..))")private void pc() {}@Around("pc()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("before");// 获取对象类名String className = joinPoint.getTarget().getClass().getName();log.info("对象类名:{}", className);// 获取方法名String methodName = joinPoint.getSignature().getName();log.info("目标的方法名:{}", methodName);// 获取目标方法运行时传入的参数Object[] args = joinPoint.getArgs();log.info("方法传入的参数:{}", Arrays.toString(args));// 放行 目标方法执行Object proceed = joinPoint.proceed();// 获取目标方法运行的返回值log.info("目标方法运行的返回值:{}", proceed);log.info("after");return proceed;}
}