一、部门管理
1、准备工作
(1)开发规范
【1】开发模式:需求分析-接口设计-前后端并行开发-测试-联调
【2】Restful风格
基础Rest风格开发的功能接口也叫RestFul风格接口
特点:url定位资源,http动词描述操作(get查询 post新增 put修改 delete)

Apifox
一款集成了Api文档、Api调试、Api Mock、Api测试的一体化协作平台
作用:接口文档管理、接口请求测试、Mock服务
2、查询部门

(1)实现思路
- Controller层,负责接收前端发起的请求,并调用service查询部门数据,然后响应结果。
- Service层,负责调用Mapper接口方法,查询所有部门数据。
- Mapper层,执行查询所有部门数据的操作
(2)限制请求方式
@RequestMapping(value="/depts",method=RequestMethod.GET)
企业开发中多用下面的方式:
@GetMapping("/depts")
@DeleteMapping
@PutMapping
@PostMapping
//@RequestMapping(value = "/depts",method = RequestMethod.GET) @GetMapping("/depts")//企业开发中主要用这个 public Result list() { List<Dept> deptList = deptService.findAll(); return Result.success(deptList); }
(3)数据封装

实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装。如果实体类属性名和数据库表查询返回的字段名不一致,不能自动封装。
- 手动结果映射
@Results({@Result(column = "create_time", property = "createTime"),
@Result(column = "update_time", property = "updateTime")})
@Select("select id, name, create_time, update_time from dept")
public List<Dept> findAll();
- 起别名
@Select("select id, name, create_time createTime, update_time updateTime from dept")
public List<Dept> findAll();
- 开启驼峰命名(推荐)
mybatis:
configuration:
map-underscore-to-camel-case: true
(4)前后端联调
反向代理是一种网络架构,通过代理服务器为后端的服务器做代理,客户端的请求直接请求代理服务器,然后转给后端的服务器(安全、灵活、负载均衡)

前端请求地址是http://localhost:90/api/depts

- location:用于定义匹配特定uri请求的规则。
- ^~ /api/:表示精确匹配,即只匹配以/api/开头的路径。
- rewrite:该指令用于重写匹配到的uri路径。
- proxy_pass:该指令用于代理转发,它将匹配到的请求转发给位于后端的指令服务器。
3、删除部门

(1)简单参数接收
方式一: 原始的HttpServletRequest对象获取请求参数。不推荐,代码繁琐
//方式一: 原始的HttpServletRequest对象获取请求参数。不推荐,代码繁琐 @DeleteMapping("/depts") public Result delete(HttpServletRequest request) { String idStr=request.getParameter("id"); Integer id=Integer.parseInt(idStr); System.out.println("部门id":+id); deptService.delete(id); return Result.success(); }
方式二:通过spring提供的@RequestParam注解,将请求参数绑定给方法形参
//方式二:通过spring提供的@RequestParam注解,将请求参数绑定给方法形参 //注意事项:一旦声明了@RequestParam,该参数在请求时必须传递(默认required为true),不传递报错。 //如果参数可选,可以将required属性设置为false @DeleteMapping("/depts") public Result delete(@RequestParam(value = "id",required = false) Integer deptId) { deptService.delete(deptId); System.out.println("部门id:"+deptId); return Result.success(); }
方式三:如果请求参数名和形参变量名相同,可以省略@RequestParam(推荐)
//方式三:如果请求参数名和形参变量名相同,可以省略@RequestParam @DeleteMapping("/depts") public Result delete(Integer id) { deptService.deleteById(id); System.out.println("部门id:"+id); return Result.success(); }
4、新增

(1)json参数接收
- JSON格式的参数,通常会使用一个实体对象进行接收 。
- 规则:JSON数据的键名与方法形参对象的属性名相同,并需要使用
@RequestBody注解标识。
@RequestBody 将json格式的请求参数封装到一个对象中
//新增部门 @PostMapping("/depts") public Result add(@RequestBody Dept dept) { System.out.println("新增部门:" + dept); deptService.add(dept); return Result.success(); }
5、修改
(1)查询回显
/depts/{id}
路径参数
@PathVariable获取路径参数.@PathVariable注解的value属性是可以省略的。

/** * 根据Id查询部门 */ //如果路径参数名与controller方法形参名称一致,@PathVariable注解的value属性是可以省略的。 @GetMapping("/depts/{id}") public Result getInfo(@PathVariable Integer id) { System.out.println("获取的id:" + id); Dept dept = deptService.getById(id); return Result.success(dept); }
(2)修改数据
/** * 修改部门 */ @PutMapping("/depts") public Result update(@RequestBody Dept dept) { deptService.update(dept); return Result.success(); }
完整的请求路径:类上的@RequestMapping 的value属性+方法上的@RequestMapping的value属性
@RequestMapping注解可以加在类上(可选)和方法上
package com.ithema.controller; import com.ithema.pojo.Dept; import com.ithema.pojo.Result; import com.ithema.service.DeptService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController//表示当前类是请求处理类 @RequestMapping("/depts") public class DeptController { @Autowired private DeptService deptService; //@RequestMapping(value = "/depts",method = RequestMethod.GET) /** * 查询所有部门 */ @GetMapping//企业开发中主要用这个 public Result list() { List<Dept> deptList = deptService.findAll(); return Result.success(deptList); } //方式三:如果请求参数名和形参变量名相同,可以省略@RequestParam /** * 根据id删除部门 */ ///depts?id=1 @DeleteMapping public Result delete(Integer id) { deptService.deleteById(id); System.out.println("部门id:" + id); return Result.success(); } /** * 新增部门 */ //JSON格式的参数,用一个实体对象进行接收, //JSON数据的键名与方法形参对象的属性名相同,并需要使用@RequestBody注解标识。 @PostMapping public Result add(@RequestBody Dept dept) { System.out.println("新增部门:" + dept); deptService.add(dept); return Result.success(); } /** * 根据Id查询部门 */ //如果路径参数名与controller方法形参名称一致,@PathVariable注解的value属性是可以省略的。 @GetMapping("/{id}") public Result getInfo(@PathVariable Integer id) { System.out.println("获取的id:" + id); Dept dept = deptService.getById(id); return Result.success(dept); } /** * 修改部门 */ @PutMapping public Result update(@RequestBody Dept dept) { deptService.update(dept); return Result.success(); } }
6、日志技术

- JUL:这是JavaSE平台提供的官方日志框架,也被称为JUL。配置相对简单,但不够灵活,性能较差。
- Log4j:一个流行的日志框架,提供了灵活的配置选项,支持多种输出目标。
- Logback:基于Log4j升级而来,提供了更多的功能和配置选项,性能由于Log4j。
- Slf4j:(Simple Logging Facade for Java)简单日志门面,提供了一套日志操作的标准接口及抽象类,允许应用程序使用不同的底层日志框架。
Logback入门
logback.xml (资料中已经提供,拷贝进来,放在 src/main/resources 目录下; 或者直接AI生成)<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%logger显示日志记录器的名称, %msg表示日志消息,%n表示换行符 --> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern> </encoder> </appender> <!-- 系统文件输出 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- 日志文件输出的文件名, %i表示序号 --> <FileNamePattern>D:/tlias-%d{yyyy-MM-dd}-%i.log</FileNamePattern> <!-- 最多保留的历史日志文件数量 --> <MaxHistory>30</MaxHistory> <!-- 最大文件大小,超过这个大小会触发滚动到新文件,默认为 10MB --> <maxFileSize>10MB</maxFileSize> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d 表示日期,%thread 表示线程名,%-5level表示级别从左显示5个字符宽度,%msg表示日志消息,%n表示换行符 --> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern> </encoder> </appender> <!-- 日志输出级别 --> <root level="info"> <appender-ref ref="STDOUT" /> <appender-ref ref="FILE" /> </root> </configuration>
public class LogTest { //定义日志记录对象 private static final Logger log = LoggerFactory.getLogger(LogTest.class); @Test public void testLog(){ log.debug("开始计算..."); int sum = 0; int[] nums = {1, 5, 3, 2, 1, 4, 5, 4, 6, 7, 4, 34, 2, 23}; for (int i = 0; i < nums.length; i++) { sum += nums[i]; } log.info("计算结果为: "+sum); log.debug("结束计算..."); } }
(2)Logback配置文件
配置文件名:logback.xml
该配置文件是对logback日志框架输出的日志进行控制,可以来配置输出的格式、位置以及开关等
常用的两种输出日志的位置:控制台、系统文件

开启日志(ALL),关闭日志(OFF)
<!-- 日志输出级别 -->
<root level="ALL">
<!--输出到控制台-->
<appender-ref ref="STDOUT" />
<!--输出到文件-->
<appender-ref ref="FILE" />
</root>

(3)Logback日志级别

(4)@Slf4j以及lombook日志的占位符log.info("xxx{}",id)
可以在配置文件logback.xml中,灵活的控制输出那些类型的日志。(大于等于配置的日志级别的日志才会输出)
<!-- 日志输出级别 --> <root level="info"> <!--输出到控制台--> <appender-ref ref="STDOUT" /> <!--输出到文件--> <appender-ref ref="FILE" /> </root>
@Slf4j//塞肋fourj 相当于
private static final Logger log= LoggerFactory.getLogger(当前类.class);
package com.ithema.controller; import com.ithema.pojo.Dept; import com.ithema.pojo.Result; import com.ithema.service.DeptService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; //塞肋fourj @Slf4j @RestController//表示当前类是请求处理类 @RequestMapping("/depts") public class DeptController { //private static final Logger log= LoggerFactory.getLogger(DeptController.class); @Autowired private DeptService deptService; @DeleteMapping public Result delete(Integer id) { deptService.deleteById(id); //System.out.println("部门id:" + id); log.info("部门d:{}",id);//拼接字符串,{}占位符。几个变量几个占位符 return Result.success(); } }
二、多表关联(用户管理)
一对一
一对多
多对多
1、多表关系
(1)一对多
【1】外键约束
fk开头:fk_emp_dept_id
-- 创建表时指定 create table 表名( 字段名 数据类型, …… [constraint] [外键名称] foreign key (外键字段名) references 主表(字段名)); -- 建表后,添加外键 alter table 表名 add constraint 外键名 foreign key(外键名) references 主表(字段名);
create table user_info( id int primary key auto_increment comment 'id', name varchar(20) comment '名字', dept_id int comment '部门id', constraint fk_user_dept foreign key(dept_id) references dept(id) )
在视图修改:

【2】现在企业大多数弃用物理外键,采用逻辑外键

(2)一对一
在任意一方加入外键,关联另一方的主键,并且设置外键作为唯一的(unique)
例如用户与身份证信息的关系
(3)多对多
例如 学生和课程
建立 第三张中间表,中间表至少包含两个外键,分别关联两方主键
2、多表查询
(1)连接查询
【1】内连接
隐式内连接(推荐)
select * from user ,dept where user.dept_id=dept.id
显示内连接
select * from user [inner] join dept on user.dept_id=dept.id
【2】外连接
左右连接
(2)子查询
子查询的外部语句可以是insert、update、delete、select的任何一个。最常见的是select。
分类:
标量子查询:子查询返回的结果是单个值
列子查询:子查询返回的是一列
行子查询:子查询返回的是一行
-- 行子查询 -- 查询与 "李忠" 的薪资 及 职位都相同的员工信息 ; select *from emp where salary = (select salary from emp where name = '李忠') and job = (select job from emp where name = '李忠');
-- 优化 select * from emp where (salary,job)=(5000,5); select * from emp where (salary,job)=(select salary,job from emp where name = '李忠');
表子查询:子查询返回的结果是多行多列
-- 获取每个部门中薪资最高的员工信息 select * from emp where (dept_id,salary)in (select dept_id, max(salary) as salary from emp group by dept_id); select * from emp e,(select dept_id, max(emp.salary) max_sal from emp group by dept_id) as t where e.dept_id=t.dept_id and e.salary=t.max_sal
-- 5. 查询工资 低于本部门平均工资的员工信息 。 -- 5.1查询每个部门的平均薪资 select avg(salary) as avg_sal ,emp.dept_id from emp group by dept_id ; -- 5.2查询工资,低于平均工资 select emp.* from emp ,(select avg(salary) as avg_sal ,emp.dept_id from emp group by dept_id ) t where emp.dept_id=t.dept_id and salary<t.avg_sal
3、分页查询
(1)原始方式
- 前端传递参数:page:当前页码,pageSize 每页展示记录数。Controller 的方法设置默认值 @RequestParam(defaultValue = "1")
- 后端返回结果:total:总记录数,rows:结果列表
package com.ithema.controller; import com.ithema.pojo.PageResult; import com.ithema.pojo.Result; import com.ithema.service.EmpService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * 员工管理控制器 */ @RestController @Slf4j @RequestMapping("/emps") public class EmpController { @Autowired private EmpService empService; @GetMapping public Result page(@RequestParam(defaultValue = "1") Integer page, @RequestParam( defaultValue = "10")Integer pageSize)//@RequestParam(value = "start") { log.info("分页查询:{},{}", page, pageSize); PageResult pageResult = empService.page(page, pageSize); return Result.success(pageResult); } }
package com.ithema.service.impl; import com.ithema.mapper.EmpMapper; import com.ithema.pojo.Emp; import com.ithema.pojo.PageResult; import com.ithema.service.EmpService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class EmpServiceImpl implements EmpService { @Autowired private EmpMapper empMapper; /** * 分页查询员工数据 * * @param page 页码 * @param pageSize 每页记录数 */ @Override public PageResult page(Integer page, Integer pageSize) { Long total = empMapper.count(); Integer start = (page - 1) * pageSize; List<Emp> list = empMapper.list(start, pageSize); return new PageResult( total, list); } }
package com.ithema.mapper; import com.ithema.pojo.Emp; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; /** * 员工管理 */ @Mapper public interface EmpMapper { @Select("") List<Emp> findAll(); /** * 查询总记录数 */ @Select("select count(*) from emp left join dept d on d.id = emp.dept_id") long count(); /** * 分页查询员工数据 */ @Select("select emp.* ,d.name as deptName from emp left join dept d on d.id = emp.dept_id order by emp.update_time desc limit #{start},#{pageSize}") List<Emp> list(Integer start, Integer pageSize); }
(2)PageHelper分页插件
PageHelper是第三方提供的在Mybatis框架汇中用来实现分页的插件,用来简化分页操作,提高开发效率
【1】使用步骤

<!--分页插件PageHelper--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.7</version> </dependency>
【2】PageHelper实现机制
PageHelper会拦截Sql的执行,改造sql语句。最终生成两条sql:
select count(0) from emp ……-- 把order by去掉
select ……from ……limit ?,?
最后将执行的sql语句结果封装到Page<T>中
注意事项:定义的sql语句结尾不能加分号
PageHelper仅仅能对紧跟在其后面的第一个查询语句进行分页处理
package com.ithema.mapper; import com.ithema.pojo.Emp; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; /** * 员工管理 */ @Mapper public interface EmpMapper { @Select("select emp.* ,d.name as deptName from emp left join dept d on d.id = emp.dept_id order by emp.update_time desc") List<Emp> list(); }
package com.ithema.service.impl; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.ithema.mapper.EmpMapper; import com.ithema.pojo.Emp; import com.ithema.pojo.PageResult; import com.ithema.service.EmpService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class EmpServiceImpl implements EmpService { @Autowired private EmpMapper empMapper; /** * 分页查询员工数据 * * @param page 页码 * @param pageSize 每页记录数 * 注意事项: * 1、定义的sal语句结尾不能加分号 * 2、PageHelper仅仅能对紧跟在其后的第一个查询语句进行分页处理 */ @Override public PageResult page(Integer page, Integer pageSize) { //1、设置分页参数PageHepler PageHelper.startPage(page, pageSize); //2、执行查询 List<Emp> list = empMapper.list(); //3、解析查询结果并封装 Page<Emp> p = (Page<Emp>) list; return new PageResult<Emp>(p.getTotal(), p.getResult()); } }
4、条件分页查询
(1)DateTimeFormat
日期类型的参数在前端传递的时候可能多样,通过注解@DateTimeFormat("yyyy-MM-dd")来指定前端传递的日期格式
(2)contact
用于sql语句拼接。#{}不能出现在引号之内。所以 like concat('%', #{name}, '%')
(3)动态sql
随着用户的输入或外部条件的变化而变化的SQL语句,我们称之为动态SQL
<if>判断条件是否成立,如果条件为true,则拼接sql
<where>根据查询条件,来生成where关键字,并会自动去除条件前多余的and或者or;若where之后的条件都不成立,则不会生成where
<?xml version="1.0" encoding="UTF-8" ?><!--Mybatis中文网-入门-探究映射sql-粘贴--> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ithema.mapper.EmpMapper"> <select id="list" resultType="com.ithema.pojo.Emp"> select emp.* ,d.name as deptName from emp left join dept d on d.id = emp.dept_id <where> <if test="gender != null"> emp.gender = #{gender} </if> <if test="name != null and name!=''"> and emp.name like concat('%',#{name},'%') </if> <if test="start!=null and end!=null"> and emp.entry_date between #{start} and #{end} </if> </where> order by emp.update_time desc </select> </mapper>
package com.ithema.pojo; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDate; @Data public class EmpQueryParam { private Integer page=1;//当前页码 private Integer pageSize=10;//每页展示记录数 private String name;// 姓名 private Integer gender;// 性别 @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate start;//入职开始日期 @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate end;//入职结束日期 }
package com.ithema.controller; import com.ithema.pojo.EmpQueryParam; import com.ithema.pojo.PageResult; import com.ithema.pojo.Result; import com.ithema.service.EmpService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * 员工管理控制器 */ @RestController @Slf4j @RequestMapping("/emps") public class EmpController { @Autowired private EmpService empService; @GetMapping public Result page(EmpQueryParam param) { log.info("分页查询:{}", param); PageResult pageResult = empService.page(param); return Result.success(pageResult); } }
package com.ithema.service.impl; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.ithema.mapper.EmpMapper; import com.ithema.pojo.Emp; import com.ithema.pojo.EmpQueryParam; import com.ithema.pojo.PageResult; import com.ithema.service.EmpService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class EmpServiceImpl implements EmpService { @Autowired private EmpMapper empMapper; /** * 分页查询员工数据 * * @param page 页码 * @param pageSize 每页记录数 * 注意事项: * 1、定义的sal语句结尾不能加分号 * 2、PageHelper仅仅能对紧跟在其后的第一个查询语句进行分页处理 */ @Override public PageResult page(EmpQueryParam param) { PageHelper.startPage(param.getPage(), param.getPageSize()); List<Emp> list = empMapper.list(param); Page<Emp> p = (Page<Emp>) list; return new PageResult<Emp>(p.getTotal(), p.getResult()); } }
package com.ithema.mapper; import com.ithema.pojo.Emp; import com.ithema.pojo.EmpQueryParam; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; /** * 员工管理 */ @Mapper public interface EmpMapper { /** * 分页查询员工数据 */ List<Emp> page( EmpQueryParam param); }
5、新增员工
(1)插入数据之后,获取主键值:主键返回@Options(useGeneratedKeys = true, keyProperty = "id")
useGeneratedKeys :使用Mybaits中生成的主键,
keyProperty :生成的主键赋值给哪个属性
(2)<foreach>动态SQL标签
- 作用:遍历集合/数组
-
属性:
collection:集合名称
item集合中的元素
separator:每次遍历还是用的分分隔符(可选)
open:遍历开开始前拼接的片段(可选)
close:遍历结束后拼接的片段(可选)


package com.ithema.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @Data public class Emp { private Integer id; //ID,主键 private String username; //用户名 private String password; //密码 private String name; //姓名 private Integer gender; //性别, 1:男, 2:女 private String phone; //手机号 private Integer job; //职位, 1:班主任,2:讲师,3:学工主管,4:教研主管,5:咨询师 private Integer salary; //薪资 private String image; //头像 private LocalDate entryDate; //入职日期 private Integer deptId; //关联的部门ID private LocalDateTime createTime; //创建时间 private LocalDateTime updateTime; //修改时间 private String deptName;//部门名称 private List<EmpExpr> exprList;//工作经历 }
/** * 新增员工 */ @PostMapping public Result save(@RequestBody Emp emp) { log.info("新增员工:{}", emp); empService.save(emp); return Result.success(); }
package com.ithema.service.impl; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.ithema.mapper.EmpExprMapper; import com.ithema.mapper.EmpMapper; import com.ithema.pojo.Emp; import com.ithema.pojo.EmpQueryParam; import com.ithema.pojo.PageResult; import com.ithema.service.EmpService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.time.LocalDateTime; import java.util.List; @Service public class EmpServiceImpl implements EmpService { @Autowired private EmpMapper empMapper; @Autowired private EmpExprMapper empExprMapper; @Override public PageResult page(EmpQueryParam param) { PageHelper.startPage(param.getPage(), param.getPageSize()); List<Emp> list = empMapper.list(param); Page<Emp> p = (Page<Emp>) list; return new PageResult<Emp>(p.getTotal(), p.getResult()); } @Override public void save( Emp emp ) { //1、保存员工的基本信息 emp.setCreateTime( LocalDateTime.now()); emp.setUpdateTime( LocalDateTime.now()); empMapper.insert(emp);//使用Mybatis生成主键功能,emp中的id属性会自动赋值 //2、保存员工的工作经历 if(!CollectionUtils.isEmpty(emp.getExprList())) { emp.getExprList().forEach(expr -> { expr.setEmpId(emp.getId()); }); empExprMapper.insertBatch(emp.getExprList()); } } }
package com.ithema.mapper; import com.ithema.pojo.Emp; import com.ithema.pojo.EmpQueryParam; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Options; import org.apache.ibatis.annotations.Select; import java.util.List; /** * 员工管理 */ @Mapper public interface EmpMapper { List<Emp> list(EmpQueryParam param); //使用Mybaits中生成的主键,当前获取的主键往对象的哪个属性中来封装 @Options(useGeneratedKeys = true, keyProperty = "id") @Insert("insert into emp(username,name,gender,phone,image,job,entry_date,dept_id,create_time,update_time) values(#{username},#{name},#{gender},#{phone},#{image},#{job},#{entryDate},#{deptId},#{createTime},#{updateTime})") void insert(Emp emp); }
package com.ithema.mapper; import com.ithema.pojo.EmpExpr; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface EmpExprMapper { void insertBatch(List<EmpExpr> exprList); }
<?xml version="1.0" encoding="UTF-8" ?><!--Mybatis中文网-入门-探究映射sql-粘贴--> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ithema.mapper.EmpMapper"> <select id="list" resultType="com.ithema.pojo.Emp"> select emp.* ,d.name as deptName from emp left join dept d on d.id = emp.dept_id <where> <if test="gender != null"> emp.gender = #{gender} </if> <if test="name != null and name!=''"> and emp.name like concat('%',#{name},'%') </if> <if test="start!=null and end!=null"> and emp.entry_date between #{start} and #{end} </if> </where> order by emp.update_time desc </select> </mapper>
<?xml version="1.0" encoding="UTF-8" ?><!--Mybatis中文网-入门-探究映射sql-粘贴--> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ithema.mapper.EmpExprMapper"> <insert id="insertBatch"> insert into emp_expr(emp_id,company,begin,end,job) values <foreach collection="exprList" item="expr" separator=","> (#{expr.empId},#{expr.company},#{expr.begin},#{expr.end},#{expr.job}) </foreach> </insert> </mapper>
或者

(3)判断集合是否为空 CollectionUtils.isEmpty(emp.getExprList())
三、事务管理
1、介绍&操作
(1)要么同时成功,要么同时失败
注意:默认MySQL的事务是自动提交的,也就是说,当执行一条DML语句,MySQL会立即隐式的提交事务
(2)操作步骤
【1】开始事务start transanction/begin
【2】提交事务commit
【3】回滚事务rollback
2、Spring事务管理
(1)控制事务@Transactional [trænˈzækʃən(ə)l]
作用:方法运行前,给开启事务,运行完毕后根据运行的结果来提交或者回滚事务
位置:方法上(方法中多次进行数据库的操作),类上,接口上(所有的实现类中的方法都进行事务操作)
(2)事务进阶
【1】rollbackfor(用于控制出现何种远异常类型,回滚事务)
- 在Spring的事务管理中,默认只有运行时异常 RuntimeException才会回滚。
- 如果还需要回滚指定类型的异常,可以通过rollbackFor属性来指定。
@Override @Transactional(rollbackFor = {Exception.class}) public void save(Emp emp) { //1、保存员工的基本信息 emp.setCreateTime(LocalDateTime.now()); emp.setUpdateTime(LocalDateTime.now()); empMapper.insert(emp);//使用Mybatis生成主键功能,emp中的id属性会自动赋值 //2、保存员工的工作经历 if (!CollectionUtils.isEmpty(emp.getExprList())) { emp.getExprList().forEach(expr -> { expr.setEmpId(emp.getId()); }); empExprMapper.insertBatch(emp.getExprList()); } }
在application.yml中配置Mybatis日志
spring: application: name: tlias-web-management #mysql连接配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/tlias username: root password: root mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true #开启驼峰命名映射开关 #spring事务管理日志 logging: level: org.springframework.jdbc.support.JdbcTransactionManager: debug
增加扩展grep console

运行后,控制台输出如下:

【2】propagation 事务传播行为:当一个事务B方法被另一个事务方法A调用时,这个事务B方法用哪个该如何进行事务控制

主要用前两个。REQUIRED有事务就加入,没有就创建(大部分场景);REQUIRED_NEW(希望两个方法在独立的事务中运行,互不影响)
例如:新增员工时无论成功还是失败都记录操作日志
插入日志不会随着save事务回滚。
3、四大特性(ACID)
(1)原子性(Atomicity):事务是不可分割的最小单元,要么全部成功,要么全部失败。
(2)一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
(3)隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
(4)持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
四、文件上传
1、简介

2、本地存储
(1)MultipartFile 常见方法
【1】String getOriginalFilename(); //获取原始文件名
【2】void transferTo(File dest); //将接收的文件转存到磁盘文件中
long getSize(); //获取文件的大小,单位:字节
byte[] getBytes(); //获取文件内容的字节数组
InputStream getInputStream(); //获取接收到的文件内容的输入流
package com.ithema.controller; 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; @RestController public class UploadController { private static final String UPLOAD_DIR = "D:/upload/"; @PostMapping("/upload") public String upload(MultipartFile file) throws IOException { String originalFilename = file.getOriginalFilename(); String newName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); File targetFile = new File(UPLOAD_DIR + newName); // 如果目标目录不存在,则创建它 if (!targetFile.getParentFile().exists()) { targetFile.getParentFile().mkdirs(); } file.transferTo(targetFile); return "上传成功"; } }
(2)文件大小受限制
【1】在SpringBoot中,文件上传时默认单个文件最大为1M
在application.yml中配置:输入multipart,再选


3、阿里云OSS
阿里云对象存储OSS(Object Storage Service)是一款海量、安全、低成本、高可靠的云存储服。使用OSS可以通过网络随时存储和调用包括文本、图片、音频视频等在内的各种软件。
第三方服务的通用思路:
准备工作--参照官方SDK编写入门程序--集成使用
SDK: Software Development Kit。软件开发工具包,包括辅助软件开发的依赖(jar包)、代码实例等,都可以叫做SDK。
(1)准备
Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间。
(2)入门
阿里云的OSS的SDK
https://help.aliyun.com/zh/oss/user-guide/oss-sdk-quick-start?spm=a2c4g.11186623.0.0.7d426a752P5s5F#0cf90ff8b28eg
(3)集成
package com.ithema.controller; import com.aliyuncs.exceptions.ClientException; import com.ithema.pojo.Result; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; @RestController public class UploadController { private static final String UPLOAD_DIR = "D:/upload/"; private static final Logger log = LoggerFactory.getLogger(UploadController.class); private AliyunOSSOperator aliyunOSSOperator; // region 本地存储 /*@PostMapping("/upload") public String upload(MultipartFile file) throws IOException { String originalFilename = file.getOriginalFilename(); String newName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); File targetFile = new File(UPLOAD_DIR + newName); // 如果目标目录不存在,则创建它 if (!targetFile.getParentFile().exists()) { targetFile.getParentFile().mkdirs(); } file.transferTo(targetFile); return "上传成功"; }*/ // endregion @PostMapping("/upload") public Result upload(MultipartFile file) throws IOException, ClientException { log.info("文件上传:{}", file); String url = aliyunOSSOperator.uploadFile(file.getBytes(), file.getOriginalFilename()); return Result.success(url); } }
4、功能优化-注入外部配置文件
(1)@Value("${key}")一个属性一个属性的注入
将灵活变化的参数,配置在配置文件中,通过@Value注解来注入外部配置的属性。
适用于属性较少,不复用

(2)@ConfigurationPorperties批量将多个属性注入到bean对象中
适用于:属性较多,考虑复用。

package com.ithema.pojo; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties(prefix = "aliyun.oss") public class AliyunOSSProperties { private String endpoint; private String bucketName; private String region; }
package com.ithema.utils; import com.aliyun.oss.ClientBuilderConfiguration; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.common.auth.CredentialsProviderFactory; import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider; import com.aliyun.oss.common.comm.SignVersion; import com.aliyuncs.exceptions.ClientException; import com.ithema.pojo.AliyunOSSProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.UUID; @Component public class AliyunOSSOperator { //方式一:通过@Value一个属性一个属性的注入 // @Value("${aliyun.oss.endpoint}") // private String endpoint; // @Value("${aliyun.oss.bucketName}") // private String bucketName; // @Value("${aliyun.oss.region}") // private String region; //方式二:通过ConfigurationProperties @Autowired private AliyunOSSProperties aliyunOSSProperties; public String uploadFile(byte[] content, String originalFilename) throws ClientException { String endpoint = aliyunOSSProperties.getEndpoint(); String bucketName = aliyunOSSProperties.getBucketName(); String region = aliyunOSSProperties.getRegion(); // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。 EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider(); // 填写Object完整路径,例如202406/1.png。Object完整路径中不能包含Bucket名称。 //获取当前系统日期的字符串,格式为 yyyy/MM String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM")); //生成一个新的不重复的文件名 String newFileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf(".")); String objectName = dir + "/" + newFileName; // 创建OSSClient实例。 ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration(); clientBuilderConfiguration.setSignatureVersion(SignVersion.V4); OSS ossClient = OSSClientBuilder.create() .endpoint(endpoint) .credentialsProvider(credentialsProvider) .clientConfiguration(clientBuilderConfiguration) .region(region) .build(); try { ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content)); } finally { ossClient.shutdown(); } return endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + objectName; } }
package com.ithema.controller; import com.aliyuncs.exceptions.ClientException; import com.ithema.pojo.Result; import com.ithema.utils.AliyunOSSOperator; 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.IOException; @Slf4j @RestController public class UploadController { //private static final Logger log = LoggerFactory.getLogger(UploadController.class); @Autowired private AliyunOSSOperator aliyunOSSOperator; // region 本地存储 /*@PostMapping("/upload") public String upload(MultipartFile file) throws IOException { String originalFilename = file.getOriginalFilename(); String newName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); File targetFile = new File(UPLOAD_DIR + newName); // 如果目标目录不存在,则创建它 if (!targetFile.getParentFile().exists()) { targetFile.getParentFile().mkdirs(); } file.transferTo(targetFile); return "上传成功"; }*/ // endregion @PostMapping("/upload") public Result upload(MultipartFile file) throws IOException, ClientException { log.info("文件上传:{}", file); String url = aliyunOSSOperator.uploadFile(file.getBytes(), file.getOriginalFilename()); return Result.success(url); } }
五、删除员工 修改与员工。全局异常处理,员工统计
1、删除员工
DELETE /emp?id=1,2,3

推荐第二种方式,方便后续的操作
2、修改
(1)查询回显
Mybatis中封装查询结果resultType和resultMap
如果查询返回的字段名与实体的属性名可以直接对象上,用resultType
如果查询返回的字段名与实体的属性名对应不上,或者实体属性比较复杂,可以通过resultMap手动封装
<mapper namespace="com.ithema.mapper.EmpMapper"> <!--自定义结果集--> <resultMap id="empResultType" type="com.ithema.pojo.Emp"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> <result column="name" property="name"/> <result column="gender" property="gender"/> <result column="phone" property="phone"/> <result column="job" property="job"/> <result column="salary" property="salary"/> <result column="image" property="image"/> <result column="salary" property="salary"/> <result column="image" property="image"/> <result column="entry_date" property="entryDate"/> <result column="dept_id" property="deptId"/> <result column="create_time" property="createTime"/> <result column="update_time" property="updateTime"/> <result column="dept_name" property="deptName"/> <!--封装exprList--> <collection property="exprList" ofType="com.ithema.pojo.EmpExpr"> <id column="ee_id" property="id"/> <result column="emp_id" property="empId"/> <result column="begin" property="begin"/> <result column="end" property="end"/> <result column="company" property="company"/> <result column="job" property="job"/> </collection> </resultMap> <select id="getById" resultMap="empResultType"> select p.*, t.name as dept_name, ee.id as ee_id, ee.emp_id,ee.job, ee.begin, ee.end, ee.company from emp p left join emp_expr ee on p.id = ee.emp_id left join dept t on t.id = p.dept_id where p.id = #{id} </select> </mapper>
package com.ithema.controller; import com.ithema.pojo.Emp; import com.ithema.pojo.EmpQueryParam; import com.ithema.pojo.PageResult; import com.ithema.pojo.Result; import com.ithema.service.EmpService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 员工管理控制器 */ @RestController @Slf4j @RequestMapping("/emps") public class EmpController { @Autowired private EmpService empService; /** * 根据id查询员工 */ @GetMapping("/{id}") public Result getInfo(@PathVariable Integer id) { log.info("查询员工:{}", id); Emp emp = empService.getInfo(id); return Result.success(emp); } }
//EmpServiceImpl @Override public Emp getInfo(Integer id) { return empMapper.getById(id); }
(2)修改数据
<set> 标签:自动生成set关键字,自动去除更新字段后多余的逗号
<update id="updateById"> UPDATE emp <set> <!-- 只有当参数不为 null 时,才拼接该字段 --> <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="phone != null and phone!=''"> phone = #{phone}, </if> <if test="job != null"> job = #{job}, </if> <if test="salary != null"> salary = #{salary}, </if> <if test="image != null and image!=''"> image = #{image}, </if> <if test="entryDate != null"> entry_date = #{entryDate}, </if> <if test="deptId != null"> dept_id = #{deptId}, </if> <if test="updateTime!=null"> update_time = #{updateTime}, </if> </set> WHERE id = #{id} </update>
@PutMapping public Result update(@RequestBody Emp emp) { log.info("修改员工:{}", emp); empService.update(emp); return Result.success(); }
//EmpServiceImpl @Override @Transactional(rollbackFor = Exception.class) public void update(Emp emp) { emp.setUpdateTime(LocalDateTime.now()); empMapper.updateById(emp); empExprMapper.deleteByEmpIds(Arrays.asList(emp.getId())); if (!emp.getExprList().isEmpty()) { emp.getExprList().forEach(expr -> { expr.setEmpId(emp.getId()); }); empExprMapper.insertBatch(emp.getExprList()); } }
3、全局异常处理
(1)@RestControllerAdvice 表示当前类为全局异常处理器
@RestControllerAdvice = @ControllerAdvice + @ResponseBody 处理异常的方法返回值会转换为json后再响应给前端
(2)@ExceptionHandler 指定可以捕获哪种类型的异常进行处理
package com.ithema.exception; import com.ithema.pojo.Result; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DuplicateKeyException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.multipart.MultipartException; //全局异常处理类 @RestControllerAdvice @Slf4j public class GlobalExceptionHandler { //捕获异常 @ExceptionHandler public Result handleException(Exception e) { log.error("出错啦,请联系管理员",e); return Result.error("服务器异常"); } @ExceptionHandler//(value = DuplicateKeyException.class) public Result handleDuplicateKeyException(DuplicateKeyException e) { log.error("出错啦,请联系管理员",e); int i=e.getMessage().indexOf("Duplicate entry"); String mesage=e.getMessage().substring(i); String[] arr=mesage.split(" "); return Result.error(arr[2]+"已存在"); } }
4、员工统计
(1)case 流程控制函数
-
语法一:case
when cond1 then res1
when cond2 then res2
else res end ;
-
语法二(仅适用于等值匹配):case expr when val1 then res1
when val2 then res2
else res end ;
-- 统计每种职位对应的人数 select count(1) num, case job when 1 then '班主任' when 2 then '讲师' when 3 then '学工主管' when 4 then '教研主管' when 5 then '咨询师' else '其他' end pose from emp group by job order by num; -- 方法二 select (case when job = 1 then '班主任' when job = 2 then '讲师' when job = 3 then '学工主管' when job = 4 then '教研主管' when job = 5 then '咨询师' else '其他' end) as pos from emp group by job
(2)if、ifnull流程控制函数
if(expr,value1,value2) 如果expr表达式成立取value1,否则取value2
ifnull(expr,value) 如果expr不为null,取自身,否则取value
select if(gender = 1, '男性员工', '女性员工') name, count(*) value from emp group by gender
(3)柱状图
返回结果
{ "code": 1, "msg": "success", "data": { "jobList": ["教研主管","学工主管","其他","班主任","咨询师","讲师"], "dataList": [1,1,2,6,8,13] } }
具体代码:
package com.ithema.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor public class JobOption { private List jobList;//职位列表 private List dataList;//数据列表 }
/** * 统计员工职位人数 */ @RequestMapping("/empJobData") public Result getJobData() { log.info("员工职位数据统计"); JobOption empJobData = reportService.getEmpJobData(); return Result.success(empJobData); }
@Override public JobOption getEmpJobData() { List<Map<String, Object>> list = empMapper.countEmpJobData(); List jobList = list.stream().map(map -> map.get("pos")).toList(); List dataList = list.stream().map(map -> map.get("num")).toList(); return new JobOption(jobList, dataList); }
(4)饼状图
返回结果
{ "code": 1, "msg": "success", "data": [ {"name": "男性员工","value": 5}, {"name": "女性员工","value": 6} ] }
具体代码
/** * 统计员工性别人数 */ @RequestMapping("/empGenderData") public Result getGenderData() { log.info("员工性别数据统计"); List<Map<String, Object>> empGenderData = reportService.getEmpGenderData(); return Result.success(empGenderData); }
@Override public List<Map<String, Object>> getEmpGenderData() { return empMapper.getEmpGenderData(); }
<!-- 统计员工职位人数--> <select id="countEmpJobData" resultType="java.util.Map"> select (case when job = 1 then '班主任' when job = 2 then '讲师' when job = 3 then '学工主管' when job = 4 then '教研主管' when job = 5 then '咨询师' else '其他' end) as pos,count(1) as num from emp group by job order by num </select> <!-- 统计员工性别人数--> <select id="getEmpGenderData" resultType="java.util.Map"> select if(gender = 1, '男性员工', '女性员工') name,count(*) value from emp group by gender </select>
浙公网安备 33010602011771号