目录
SpringBoot-Mybatis实战案例
现在限制同时指定某个请求方式,可以使用注解@GetMapping和@PostMapping等。
准备条件:封装 Emp类、Dept类、Result类,数据库建表等。
1.部门查询
controller层:
@Slf4j
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
// private static Logger log = LoggerFactory.getLogger(DeptController.class);
@GetMapping(value = "/depts")
public Result getDeptList(){
log.info("查询所有部门信息");
List<Dept> deptList = deptService.getDeptList();
return Result.success(deptList);
}
}
service层:
public interface DeptService {
List<Dept> getDeptList();
}
service层实现类:
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Override
public List<Dept> getDeptList() {
return deptMapper.getDeptList();
}
}
mapper层:
@Mapper
public interface DeptMapper {
@Select("select * from dept")
List<Dept> getDeptList();
}
实现前后端联调测试:
2.删除部门
controller层:
@DeleteMapping(value = "/depts/{id}")
public Result deleteDept(@PathVariable Integer id){
log.info("根据id删除部门:{}",id);
deptService.deleteDept(id);
return Result.success();
service层:
/*根据id删除部门信息*/
void deleteDept(Integer id);
service层实现类:
@Override
public void deleteDept(Integer id) {
deptMapper.deleteDept(id);
}
mapper层:
@Delete("delete from dept where id=#{id}")
void deleteDept(Integer id);
实现前后端联调测试:
3.新增部门
controller层:
@Insert("insert into dept(name,create_time,update_time) values(#{name},#{createTime},#{updateTime})")
void addDept(Dept dept);
service层:
/*添加部门信息*/
void addDept(Dept dept);
service层实现类:
@Override
public void addDept(Dept dept) {
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
deptMapper.addDept(dept);
}
mapper层:
@Override
public void addDept(Dept dept) {
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
deptMapper.addDept(dept);
}
实现前后端联调测试:
4.修改部门
controller层:
///修改部门
@GetMapping("/{id}")
public Result getByID(@PathVariable Integer id) {
log.info("获取部门ID:{}",id);
Dept dept = deptService.getByID(id);
return Result.success(dept);
}
@PutMapping
public Result updateDept(@RequestBody Dept dept){
log.info("修改部门:{}",dept);
deptService.updateDept(dept);
return Result.success();
}
service层:
void updateDept(Dept dept);
Dept getByID(Integer id);
service层实现类:
@Override
public void updateDept(Dept dept) {
dept.setUpdateTime(LocalDateTime.now());
deptMapper.updateDept(dept);
}
@Override
public Dept getByID(Integer id) {
Dept dept = deptMapper.getByID(id);
return dept;
}
mapper层:
@Select("select * from dept where id = #{id}")
Dept getByID(Integer id);
@Update("update dept set name = #{name},update_time = #{updateTime} where id = #{id}")
void updateDept(Dept dept);
实现前后端联调测试:
5.分页查询
封装PageBean类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean {
private Long total;
private List rows;
}
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) {
log.info("分页查询,当前页码{},每页条数{}", page, pageSize);
PageBean pageBean = empService.page(page, pageSize);
return Result.success(pageBean);
}
}
service层:
public interface EmpService {
PageBean page(Integer page, Integer pageSize);
}
service层实现类:
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> rows = empMapper.page(start, pageSize);
//3.将分页查询结果封装到PageBean对象中
PageBean pageBean = new PageBean(count, rows);
return pageBean;
}
}
mapper层:
@Mapper
public interface EmpMapper {
@Select("select count(*) from emp")
public Long count();
@Select("select * from emp limit #{start},#{pageSize}")
public List<Emp> page(Integer start, Integer pageSize);
}
实现前后端联调测试:
使用PageHelper插件
在pom.xml配置文件夹中,加入依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
6.条件分页查询
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);
PageBean pageBean = empService.page(page, pageSize,name,gender,begin,end);
return Result.success(pageBean);
}
}
service层:
public interface EmpService {
PageBean page(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end);
}
service层实现类:
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@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.page(name, gender, begin, end);
Page<Emp> p = (Page<Emp>) empList;
//3.将分页查询结果封装到PageBean对象中
PageBean pageBean = new PageBean(p.getTotal(), p.getResult());
return pageBean;
}
}
mapper层:
public interface EmpMapper {
//注意方法名要和xml文件<select>便签中id值相同
public List<Emp> page(String name, Short gender, LocalDate begin, LocalDate end);
}
XML映射文件动态SQL:
<?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.faw.mapper.EmpMapper">
<select id="page" resultType="com.faw.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>
动态SQL查询前后端联调:
7.删除员工
controller层:
@DeleteMapping("/{ids}")
public Result delete(@PathVariable List<Integer> ids) {
log.info("批量删除:{}", ids);
empService.delete(ids);
return Result.success();
}
service层:
void delete(List<Integer> ids);
service层实现类:
@Override
public void delete(List<Integer> ids) {
empMapper.delete(ids);
}
mapper层:
void delete(List<Integer> ids);
XML映射文件动态SQL:
<!--批量删除-->
<delete id="delete">
delete from emp where id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
动态SQL批量删除前后端联调:
8.新增员工
controller层:
@PostMapping
public Result save(@RequestBody Emp emp) {
log.info("新增员工:{}", emp);
empService.save(emp);
return Result.success();
}
service层:
void save(Emp emp);
service层实现类:
@Override
public void save(Emp emp) {
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
empMapper.insert(emp);
}
mapper层:
//新增员工
@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);
前后端联调:
9.文件上传 (本地存储)
服务端接收:
必须保证表单的name和方法形参一致。 名称不一致就要加上注解@RequestParam进行参数绑定,里面所指定的value属性为前端所传的name参数名。
使用APIFox测试上传文件:
代码:
@RestController
@Slf4j
public class UploadController {
@RequestMapping(value = "/upload")
public Result upload(String username, Integer age, MultipartFile image) {
log.info("{},{},{}", username, age, image);
//将文件存储在本地E:\images目录下
//1.拿到原始文件名
String originalFilename = image.getOriginalFilename();
//2.获取原始文件名最一个点的位置
int index = originalFilename.lastIndexOf(".");
//3.截取文件名
String ext = originalFilename.substring(index);
//4.拼接新的文件名,使用UUID
String newFileName = UUID.randomUUID().toString() + ext;
//5.日志输出新的文件名
log.info("新文件名:{}", newFileName);
//6.将文件存储到本地
try {
image.transferTo(new java.io.File("E:\\images\\" + newFileName));
} catch (Exception e) {
e.printStackTrace();
}
return Result.success();
}
}
测试已经将图片保存到本地,文件名使用UUID形式:
配置文件上传大小配置,在mybatis核心配置文件 application.properties中加入
#配置上传文件的配置项
#单个文件最大上传大小
spring.servlet.multipart.max-file-size=10MB
#所有文件最大上传大小
spring.servlet.multipart.max-request-size=10MB
10.修改员工
(1)查询回显
controller层:
@GetMapping("/{id}")
public Result selectById(@PathVariable Integer id) {
log.info("修改员工id为:{}", id);
Emp emp = empService.selectById(id);
return Result.success(emp);
}
service层:
Emp selectById(Integer id);
service层实现类:
@Override
public Emp selectById(Integer id) {
return empMapper.selectById(id);
}
mapper层:
//查询回显
@Select("select * from emp where id = #{id}")
Emp selectById(Integer id);
前后端联调测试:
(2)修改员工
controller层:
@PutMapping
public Result update(@RequestBody Emp emp) {
log.info("修改员工:{}", emp);
empService.update(emp);
return Result.success();
}
service层:
void update(Emp emp);
service层实现类:
@Override
public void update(Emp emp) {
emp.setUpdateTime(LocalDateTime.now());
empMapper.update(emp); }
mapper层:
//更新员工
void update(Emp emp);
mapper映射文件动态SQL:
<!--修改员工信息-->
<update id="update">
update emp
<set>
<if test="username!=null and username!=''">
username=#{username},
</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>
前后端联调:
11.配置文件
(1)参数配置化
(2)yml配置文件
#数据库连接信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/tlias
username: root
password: 123456
servlet:
multipart:
max-request-size: 10MB
max-file-size: 100MB
#mybatis配置
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
camel-case-to-lower-case-mapper: true
(3)@ConfigurationProperties
12.登录校验
(1)登录
controller层:
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);
return e != null ? Result.success() : Result.error("用户名或密码错误");
}
}
service层:
Emp login(Emp emp);
service层实现类:
@Override
public Emp login(Emp emp) {
return empMapper.getByUsernameAndPassword(emp);
}
mapper层:
//根据用户名和密码查询员工
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);
(2)会话技术
(3)JWT令牌
全称:JSON Web Token (IWT)
定义了一种简洁的、自包含的格式,用于在通信双方以ison数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的,。
组成:
第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256","type":"JWT"}
第二部分:Pavload(有效载荷),携带一些自定义信息、默认信息等。例如:!"id":"1""username":"Tom"
第三部分:signature(签名),防止Token被篡改、确保安全性。将header、pavload,并加入指定秘钥,通过指定签名算法计算而来。
JWT工具类:
package com.faw.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtils {
// 与测试类一致的密钥(Base64编码后的"fawvw-iot")
private static final String SECRET_KEY = "ZmF3dnctaW90";
// 12小时过期时间(毫秒)
private static final long EXPIRATION = 12 * 3600 * 1000;
/**
* 生成JWT令牌
* @param claims 载荷声明
* @return JWT令牌字符串
*/
public static String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
/**
* 解析JWT令牌
* @param token JWT令牌
* @return 解析后的声明体
*/
public static Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
}
LoginController:
package com.faw.controller;
import com.faw.pojo.Emp;
import com.faw.pojo.Result;
import com.faw.service.EmpService;
import com.faw.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){
//登陆成功,生成JWT
Map<String, Object> claims = new HashMap<>();
claims.put("id",e.getId());
claims.put("username",e.getUsername());
String jwt = JwtUtils.generateToken(claims);
return Result.success(jwt);
}
//登录失败
return Result.error("用户名或密码错误");
}
}
(4)过滤器Filter
(5)拦截器Intetor
拦截路径:
定义拦截器:
package com.faw.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.faw.pojo.Result;
import com.faw.utils.JwtUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override//目标资源方法运行前运行,返回true放行,返回false拦截
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取url
String url = request.getRequestURI();
log.info("拦截的url是{}",url);
//2.判断是否是登录操作
if(url.contains("login")){
//放行
return true;
}
//3.获取令牌
String jwt = request.getHeader("token");
//4.判断令牌是否为空,如果为空,拦截,否则放行
if(jwt==null || jwt.equals("")){
//拦截
log.info("token不存在,返回错误信息");
Result error = Result.error("NOT_LOGIN");
//返回未登录的错误信息
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(error);
// 设置响应类型并写入响应流
response.setContentType("application/json");
response.getWriter().write(json);
return false;
}
//5.解析token,如果解析失败,拦截,否则放行
try {
JwtUtils.parseToken(jwt);
}catch (Exception e){
//拦截
log.info("解析令牌失败,返回未登录的错误信息");
Result error = Result.error("NOT_LOGIN");
//返回未登录的错误信息
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(error);
// 设置响应类型并写入响应流
response.setContentType("application/json");
response.getWriter().write(json);
return false;
}
//6.放行
log.info("合法放行");
return true;
}
@Override//目标资源方法运行后运行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override//视图渲染完成后运行,最后运行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
注册拦截器:
package com.faw.config;
import com.faw.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;
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
(6)异常处理
定义全局异常处理器类,类上增加@RestControllerAdvice 注解,方法上增加@ExceptionHandler注解。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result ex(Exception ex) {
ex.printStackTrace();
return Result.error("对不起操作失败,请联系管理员");
}
}