Java Java学习 - JavaWeb Wiretender 2025-07-03 2025-07-03 JavaWeb 事务进阶-propagation
事务传播行为:指的就是当一个事务办法被另外一个实物办法调用时,这个事务方法应该如何进行事务控制
REQUIRED
: 默认值- 需要事务,有则加入,无则创建新事务
REQUIRES_NEW
: 需要新事务,无论有无,总是创建新事务
1 2 3 4 5 6 @Transactional(propagation = Propagation.REQUIRES_NEW) @Override public void insertLog (EmpLog empLog) { empLogMapper.insert(empLog); }
文件上传 简介
将我们的文件上传到服务器,供其他用户浏览或下载的过程
比如发微博和发朋友圈
前端代码示例
后端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.itheima.controller;import com.itheima.pojo.Result;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;import java.io.File;import java.io.IOException;import java.util.UUID;@Slf4j @RestController public class UploadController { @PostMapping("/upload") public Result upload (String name, Integer age, MultipartFile file) throws IOException { log.info("接收参数: {} {} {}" , name, age, file); String originalFilename = file.getOriginalFilename(); String extension = originalFilename.substring(originalFilename.lastIndexOf("." )); String newFileName = UUID.randomUUID().toString() + extension; file.transferTo(new File ("E:/BaiduNetdiskDownload/images/" + originalFilename)); return Result.success(); } }
全局异常处理器 我们该怎么样定义全局异常处理器?
定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解 ==@RestControllerAdvice==,加上这个注解就代表我们定义了一个全局异常处理器。
在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler 注解当中的 value 属性来指定我们要捕获的是哪一类型的异常。
1 2 3 4 5 6 7 8 9 10 11 12 @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler public Result ex (Exception e) { e.printStackTrace(); return Result.error("对不起,操作失败,请联系管理员" ); } }
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
处理异常的方法返回值会转换为 json 后再响应给前端
实现职位统计的函数(==SQL 语句重点==) 1). 定义封装结果对象 JobOption
在 com.itheima.pojo
包中定义实体类 JobOption
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.itheima.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; }
1). 定义 ReportController,并添加方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Slf4j @RequestMapping("/report") @RestController public class ReportController { @Autowired private ReportService reportService; @GetMapping("/empJobData") public Result getEmpJobData () { log.info("统计各个职位的员工人数" ); JobOption jobOption = reportService.getEmpJobData(); return Result.success(jobOption); } }
2). 定义 ReportService 接口,并添加接口方法。
1 2 3 4 5 6 7 public interface ReportService { JobOption getEmpJobData () ; }
3). 定义 ReportServiceImpl 实现类,并实现方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Service public class ReportServiceImpl implements ReportService { @Autowired private EmpMapper empMapper; @Override public JobOption getEmpJobData () { List<Map<String,Object>> list = empMapper.countEmpJobData(); List<Object> jobList = list.stream().map(dataMap -> dataMap.get("pos" )).toList(); List<Object> dataList = list.stream().map(dataMap -> dataMap.get("total" )).toList(); return new JobOption (jobList, dataList); } }
4). 定义 EmpMapper 接口
统计的是员工的信息,所以需要操作的是员工表。 所以代码我们就写在 EmpMapper
接口中即可。
1 2 3 4 5 @MapKey("pos") List<Map<String,Object>> countEmpJobData () ;
如果查询的记录往 Map 中封装,可以通过@MapKey 注解指定返回的 map 中的唯一标识是那个字段。【也可以不指定】
5). 定义 EmpMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 <select id ="countEmpJobData" resultType ="java.util.Map" > select (case job when 1 then '班主任' when 2 then '讲师' when 3 then '学工主管' when 4 then '教研主管' when 5 then '咨询师' else '其他' end) pos, count(*) total from emp group by job order by total </select >
case 流程控制函数:
语法一:case when cond1 then res1 [ when cond2 then res2 ] else res end ;
含义:如果 cond1 成立, 取 res1。 如果 cond2 成立,取 res2。 如果前面的条件都不成立,则取 res。
语法二(仅适用于等值匹配):case expr when val1 then res1 [ when val2 then res2 ] else res end ;
含义:如果 expr 的值为 val1 , 取 res1。 如果 expr 的值为 val2 ,取 res2。 如果前面的条件都不成立,则取 res。
重点剖析代码 1 2 3 4 5 6 List<Map<String,Object>> list = empMapper.countEmpJobData(); List<Object> jobList = list.stream().map(dataMap -> dataMap.get("pos" )).toList(); List<Object> dataList = list.stream().map(dataMap -> dataMap.get("total" )).toList(); return new JobOption (jobList, dataList);
按行来解释(配合自己的理解):
第一行
1 List<Map<String,Object>> list = empMapper.countEmpJobData();
这句就是调用 empMapper 的接口,去数据库查各个职位的员工人数。返回的是一个 List,这个 List 的“每一项”其实是一个 Map。
为什么是 Map?因为我们用的 Mybatis 查询,每查出一行(比如“班主任 5 人”),就会用一个 Map 封装它,这个 Map 里 key 就是每个字段名(比方说“pos”和“total”),value 就是字段的值,非常灵活实用。
对于 Map 的理解
这里的 Map,一般是 Map <String, Object> 类型,也就是说,key 一定是 String,例如“pos”和“total”。
value 是 Object,理论上可以装任何类型,但我们这里,实际用到的通常是 String(比如“班主任”)和 Integer(比如“5”)。
@MapKey(“pos”) 主要是在结果用 Map 返回时,为 Map 的 key 指定数据库的某个字段。本文这里用的是 List ,不用指定 key,直接这样用即可。
直观感受:最终的 list 的内容长这样——
1 2 3 4 5 [ {"pos" : "班主任" , "total" : 5 }, {"pos" : "讲师" , "total" : 7 }, {"pos" : "学工主管" , "total" : 2 } ]
每一个 Map,代表一类职位及其人数。
“pos” 对应职位名称(String),而 “total” 对应该职位统计人数(Integer)。
提取所有的职位名
1 2 List<Object> jobList = list.stream().map(dataMap -> dataMap.get("pos" )).toList();
这一行用 Java 的 stream 流,遍历整个 list,把每个 Map 里的 “pos” 拿出来(也就是所有职位的名字),最后组成一个 List。
结果就是:[“班主任”, “讲师”, “学工主管”]
提取所有的人数数据
1 2 List<Object> dataList = list.stream().map(dataMap -> dataMap.get("total" )).toList();
这行做的事情和上面类似,只不过提取的是每个 Map 里的 “total” 字段(也就是人数),最后也是组成一个 List。
结果就是:[5, 7, 2]
最后,进行封装,方便后续传递使用
1 return new JobOption (jobList, dataList);
用 JobOption 这个对象,把职位名列表和数据列表都打包起来,后续 Controller 返回的时候会直接返回这个对象,结构简单明了,方便前端做展示。
总结一下
list 就像是一叠一叠的小表,每个 Map 记录了一个职位和人数,用 List 把它们串联起来。
再进一步处理,把所有的职位名称和人数各自提取成 List,专门让前端展示的时候用。
这样一来,数据传递和处理非常灵活,结构清晰,前后端都很友好。
过滤器(Filter)
概念:Fileter 是 Javaweb 的三大组件之一(Servlet, Filter,Listener )
可以把对后端的所有请求拦截下来,然后实现一些特殊的功能
过滤器一般完成一些通用的操作,比如:登录校验,统一编码处理,敏感字符处理
快速入门
实现令牌校验过滤器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package com.itheima.filter;import com.itheima.util.JwtUtils;import jakarta.servlet.*;import jakarta.servlet.annotation.WebFilter;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import lombok.extern.slf4j.Slf4j;import java.io.IOException;@WebFilter(urlPatterns = "/*") @Slf4j public class TokenFilter implements Filter { @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String requestURL = request.getRequestURI(); if (requestURL.contains("/login" )) { log.info("登录请求,放行" ); filterChain.doFilter(request, response); return ; } String token = request.getHeader("token" ); if (token == null || token.isEmpty()) { log.info("令牌为空,响应401" ); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return ; } try { JwtUtils.parseJWT(token); } catch (Exception e) { log.info("令牌为空,响应401" ); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return ; } log.info("令牌合法,放行" ); filterChain.doFilter(request, response); return ; } }
拦截器(Interceptor)
快速入门
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.itheima.interceptor;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import lombok.extern.slf4j.Slf4j;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;@Slf4j public class DemoInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("preHandle..." ); 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); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.itheima.config;import com.itheima.interceptor.DemoInterceptor;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 DemoInterceptor demoInterceptor; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(demoInterceptor).addPathPatterns("/**" ); } }
拦截路径
AOP 切面编程
连接点:JoinPoint, 可以被 AOP 控制的方法
通知:Advice,指那些重复的逻辑
切入点:PointCut,切入点,匹配连接点的条件,通知仅会在切入点方法执行时被应用
切面:Aspect,描述通知与切入点的对应关系
目标对象:Target, 通知所应用的对象
通知类型:
通知顺序
切入点表达式
execution 1 execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throw 异常?)
annotation注解来进行匹配 1 2 3 4 5 6 7 8 9 10 11 12 package com.itheima.anno;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 package com.itheima.aop;import com.itheima.mapper.OperateLogMapper;import com.itheima.pojo.OperateLog;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import java.lang.reflect.Method;import java.time.LocalDateTime;import java.util.Arrays;import java.util.stream.Collectors;@Aspect @Component @Slf4j public class OperateLogAspect { @Autowired private OperateLogMapper operateLogMapper; @Pointcut("@annotation(com.itheima.anno.Log)") public void logPointCut () {} @Around("logPointCut()") public Object doAround (ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); String className = joinPoint.getTarget().getClass().getName(); String methodName = method.getName(); Object[] args = joinPoint.getArgs(); String params = Arrays.stream(args) .map(Object::toString) .collect(Collectors.joining(", " )); Object result = null ; try { result = joinPoint.proceed(); } catch (Exception e) { throw e; } finally { long costTime = System.currentTimeMillis() - startTime; OperateLog operateLog = new OperateLog (); operateLog.setOperateEmpId(getCurrentUserId()); operateLog.setOperateTime(LocalDateTime.now()); operateLog.setClassName(className); operateLog.setMethodName(methodName); operateLog.setMethodParams(params); operateLog.setReturnValue(result != null ? result.toString() : "null" ); operateLog.setCostTime(costTime); log.info("记录操作日志:{}" , operateLog); try { operateLogMapper.insert(operateLog); } catch (Exception ex) { log.error("插入操作日志失败" , ex); } } return result; } private Integer getCurrentUserId () { return 1 ; } }
完善获取ID方法 ThreadLocal
总结