首页
壁纸
统计
友链
Search
1
Vue2详细笔记
37 阅读
2
Nestjs概述-中文
19 阅读
3
ExpressAPI
17 阅读
4
答辩
14 阅读
5
JavaScript企业数据处理实用指南
13 阅读
Nodejs
Vue
Java
Msql
登录
Search
Wasnl
累计撰写
36
篇文章
累计收到
1
条评论
首页
栏目
Nodejs
Vue
Java
Msql
页面
壁纸
统计
友链
搜索到
36
篇与
的结果
2026-06-10
Spring MVC 知识地图
🌐 Spring MVC 知识地图一、核心架构与组件Spring MVC 核心 ├── DispatcherServlet(前端控制器) ├── HandlerMapping(处理器映射) │ ├── RequestMappingHandlerMapping (注解映射,最常用) │ ├── BeanNameUrlHandlerMapping │ └── SimpleUrlHandlerMapping ├── HandlerAdapter(处理器适配器) │ ├── RequestMappingHandlerAdapter │ ├── HttpRequestHandlerAdapter │ └── SimpleControllerHandlerAdapter ├── Handler / Controller(处理器) ├── ModelAndView(模型与视图封装) ├── ViewResolver(视图解析器) │ ├── InternalResourceViewResolver(JSP) │ ├── BeanNameViewResolver │ ├── XmlViewResolver │ ├── ContentNegotiatingViewResolver │ └── …… └── View(视图) ├── JSP / JSTL ├── Thymeleaf ├── FreeMarker └── ……二、请求处理完整流程客户端请求 → DispatcherServlet 接收 ├── 1. 通过 HandlerMapping 查找 Handler(返回 HandlerExecutionChain,包含拦截器) ├── 2. 通过 HandlerAdapter 执行 Handler(Controller) ├── 3. Handler 执行业务逻辑,返回 ModelAndView ├── 4. 通过 ViewResolver 解析 ModelAndView 得到 View 对象 └── 5. View 渲染,将模型数据填充到视图,生成响应三、控制器开发Controller 开发 ├── 注解 │ ├── @Controller / @RestController │ ├── @RequestMapping │ │ ├── @GetMapping │ │ ├── @PostMapping │ │ ├── @PutMapping │ │ ├── @DeleteMapping │ │ └── @PatchMapping │ └── @CrossOrigin(跨域) ├── 请求参数绑定 │ ├── @RequestParam │ ├── @PathVariable(路径变量) │ ├── @RequestHeader │ ├── @CookieValue │ ├── @MatrixVariable │ ├── @RequestBody(JSON/XML 等) │ ├── @ModelAttribute(表单/查询参数绑定到对象) │ ├── HttpServletRequest / HttpServletResponse │ └── MultipartFile(文件上传) ├── 返回类型 │ ├── String(视图名,配合 Model) │ ├── ModelAndView │ ├── void(直接操作 HttpServletResponse) │ ├── @ResponseBody / ResponseEntity(JSON/XML 响应) │ └── View、Model、Map 等 └── 异步处理 ├── Callable<T> ├── DeferredResult<T> └── ResponseBodyEmitter / SseEmitter / StreamingResponseBody四、数据绑定、类型转换与校验数据绑定与校验 ├── 类型转换 │ ├── PropertyEditor │ ├── Converter / ConverterFactory │ ├── Formatter(日期/数字等格式化) │ └── @InitBinder(自定义绑定规则,注册编辑器/校验器) ├── 数据校验 │ ├── JSR-380(Bean Validation):@Valid / @Validated │ ├── BindingResult / Errors(校验结果) │ └── 自定义校验注解 └── WebDataBinder └── 控制绑定行为(允许/禁止字段等)五、RESTful Web 服务REST 支持 ├── @RestController = @Controller + @ResponseBody ├── HTTP 方法映射注解 ├── 内容协商 ContentNegotiationManager ├── HttpMessageConverter 消息转换器 │ ├── MappingJackson2HttpMessageConverter(JSON) │ ├── Jaxb2RootElementHttpMessageConverter(XML) │ ├── StringHttpMessageConverter │ └── FormHttpMessageConverter 等 ├── 请求/响应体处理 │ ├── @RequestBody │ └── @ResponseBody / ResponseEntity / HttpEntity ├── 跨域 @CrossOrigin / 全局 CORS 配置 └── HATEOAS 支持(Spring HATEOAS)六、视图解析与渲染视图技术 ├── JSP + JSTL │ └── InternalResourceViewResolver 配置 ├── Thymeleaf │ └── SpringTemplateEngine + ThymeleafViewResolver ├── FreeMarker │ └── FreeMarkerViewResolver ├── 重定向与转发 │ ├── "redirect:/path"(RedirectView) │ └── "forward:/path"(InternalResourceView) ├── 视图解析链(多个 ViewResolver 顺序解析) └── ContentNegotiatingViewResolver(根据请求类型选择视图)七、拦截器与过滤器拦截器 HandlerInterceptor(Spring MVC 层面) ├── 方法 │ ├── preHandle(控制器执行前) │ ├── postHandle(控制器执行后,视图渲染前) │ └── afterCompletion(视图渲染后) ├── 配置方式 │ ├── XML 配置 <mvc:interceptors> │ └── Java 配置实现 WebMvcConfigurer.addInterceptors() └── 与 Servlet Filter 的区别 ├── Filter 在 Servlet 容器层面,不依赖 Spring └── Interceptor 可访问 Handler 对象、ModelAndView,更贴合 MVC八、异常处理异常处理体系 ├── @ExceptionHandler(方法级,仅处理当前 Controller) ├── @ControllerAdvice / @RestControllerAdvice(全局异常处理) │ └── + @ExceptionHandler、@InitBinder、@ModelAttribute ├── HandlerExceptionResolver 接口 │ ├── SimpleMappingExceptionResolver(映射异常到错误视图) │ ├── DefaultHandlerExceptionResolver(处理标准 Spring 异常) │ └── ResponseStatusExceptionResolver ├── @ResponseStatus(标注异常,返回对应 HTTP 状态码) └── 自定义异常 + 全局处理九、文件上传与下载文件操作 ├── 上传 │ ├── MultipartResolver │ │ ├── CommonsMultipartResolver(需 Apache Commons FileUpload) │ │ └── StandardServletMultipartResolver(Servlet 3.0+ 标准) │ └── @RequestParam("file") MultipartFile ├── 下载 │ ├── ResponseEntity<byte[]> 配合 InputStreamResource │ ├── StreamingResponseBody(流式大文件下载) │ └── 设置 Content-Disposition 响应头 └── 多文件上传(MultipartFile[] 或 List<MultipartFile>)十、国际化与主题i18n & 主题 ├── MessageSource(国际化消息源) │ ├── ResourceBundleMessageSource │ └── ReloadableResourceBundleMessageSource(可热加载) ├── LocaleResolver(区域解析器) │ ├── AcceptHeaderLocaleResolver │ ├── SessionLocaleResolver │ ├── CookieLocaleResolver │ └── FixedLocaleResolver ├── LocaleChangeInterceptor(切换语言拦截器) ├── 主题 Theme │ ├── ThemeResolver │ └── ThemeSource(ResourceBundleThemeSource)十一、配置方式配置方式演进 ├── XML 时代 │ ├── web.xml 配置 DispatcherServlet │ ├── [name]-servlet.xml 配置 │ ├── <mvc:annotation-driven/> │ ├── <mvc:resources>(静态资源处理) │ └── <mvc:view-controller>(无逻辑视图跳转) ├── Servlet 3.0+ 纯 Java 配置 │ ├── WebApplicationInitializer / AbstractAnnotationConfigDispatcherServletInitializer │ ├── @Configuration + @EnableWebMvc │ ├── 实现 WebMvcConfigurer 接口进行定制 │ └── 静态资源 addResourceHandlers() └── Spring Boot 自动配置 ├── spring-boot-starter-web ├── 自动配置 DispatcherServlet、HttpMessageConverters、ViewResolver 等 ├── application.properties / yml 微调 └── 使用 @Configuration + WebMvcConfigurer 扩展十二、测试支持测试 ├── MockMvc(独立测试控制器,不启动完整服务器) │ ├── mockMvc.perform(get("/api")) │ ├── .andExpect(status().isOk()) │ └── .andExpect(jsonPath("$.name").value("test")) ├── @WebMvcTest(Spring Boot 切片测试,仅加载 MVC 层) ├── TestRestTemplate / WebTestClient(集成测试) └── Mock 服务依赖(@MockBean)十三、高级与周边高级特性 ├── CORS 全局配置(WebMvcConfigurer.addCorsMappings) ├── HTTP 缓存控制(CacheControl, ETag, Last-Modified) ├── Servlet 异步支持 / 响应式桥梁 ├── WebSocket 集成(@MessageMapping, SimpMessagingTemplate) ├── Swagger / OpenAPI 3 文档生成 ├── 多版本 API 管理 └── 自定义 HandlerMethodArgumentResolver / HandlerMethodReturnValueHandler如果需要某个分支更详细的逐层展开,或想看 Spring MVC 与 Spring Boot 整合的实例脑图,可以告诉我。
2026年06月10日
3 阅读
0 评论
0 点赞
2026-06-10
Spring MVC 请求生命周期
Spring MVC 请求生命周期SpringBoot 2.7 + Java8 企业级开发一、整体架构图 —— 一次请求的完整流水线HTTP Request -> http://你的项目/getUser?id=1 │ ▼ ┌─────────────────────┐ │ Tomcat │ 内嵌 Servlet 容器,最早接收请求 │ Servlet Container │ └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Filter 过滤器链 │ Servlet 级别的“门卫”,工作在 Spring MVC 之前 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ DispatcherServlet │ Spring MVC 的“总控”,分发请求的核心 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ HandlerInterceptor │ Spring 自己的拦截器,更懂 Controller │ preHandle │ 请求到达 Controller 前执行 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Controller │ 你写的接口,@RestController / @GetMapping └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Service │ 业务逻辑层 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ DAO/JdbcTemplate │ 数据访问层,执行 SQL └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ MySQL │ 数据库 └─────────────────────┘ │ ▼ Controller返回结果 返回 Java 对象 │ ▼ Interceptor.postHandle 所有拦截器的 postHandle │ ▼ ViewResolver / JSON序列化 前后端分离时转成 JSON 字符串 │ ▼ Interceptor.afterCompletion 资源清理、日志记录 │ ▼ Response → 浏览器二、企业项目真实执行顺序(必背)虚假的顺序:Tomcat -> Interceptor -> Controller实际严格顺序:Tomcat -> Filter -> DispatcherServlet -> Interceptor -> Controller -> Service -> DAO -> MySQL为什么是这个顺序?Filter 是 Servlet 规范 定义的,工作在容器级别,比 Spring 更底层;Interceptor 是 Spring MVC 内部 的,只有在 DispatcherServlet 接收请求之后才生效。也就是说:Filter 可以拦截所有请求(包括静态资源),而 Interceptor 只能拦截进入 Spring MVC 的请求。三、Filter 和 Interceptor 区别 —— 两个不同级别的“保安”1. Filter(Servlet 级别)定义方式:实现 javax.servlet.Filter 接口执行时机:Tomcat → Filter → Spring MVC特点:工作在容器层,不依赖 Spring,拿不到 Controller、方法等 Spring 上下文信息。典型用途:JWT 登录校验(直接解析 token,失败直接返回 401)XSS 过滤(清洗 <script> 等危险字符)请求日志(记录每个请求的耗时、URL)跨域处理(CorsFilter 统一设置响应头)2. Interceptor(Spring MVC 级别)定义方式:实现 org.springframework.web.servlet.HandlerInterceptor 接口执行时机:DispatcherServlet → Interceptor → Controller特点:能获取到 Handler(Controller 方法)、参数、返回结果等,是 Spring 生态内的拦截手段。典型用途:用户信息注入(从 token 解析用户信息,放入 UserContext)权限校验(admin / user 角色判断)操作日志(记录“谁在何时做了什么”)API 访问统计(PV / UV 计数)面试标准答案Filter 先执行,Interceptor 后执行。 Filter 是 Servlet 规范,Intercetpor 是 Spring MVC 专属。四、DispatcherServlet 到底干了什么 —— 核心方法 doDispatch()设计模式:前端控制器模式(Front Controller Pattern)所有请求都会先被 DispatcherServlet 接管,它自己不干活,只负责指挥。源码简化流程:doDispatch() { // 1. 根据请求找到 Handler(Controller 方法)并包装成 HandlerExecutionChain HandlerExecutionChain chain = getHandler(request); // 2. 执行所有拦截器的 preHandle chain.applyPreHandle(request, response); // 3. 真正调用 Controller 方法 HandlerAdapter adapter = getHandlerAdapter(handler); ModelAndView mv = adapter.handle(request, response, handler); // 4. 执行所有拦截器的 postHandle chain.applyPostHandle(request, response, mv); // 5. 处理返回结果(JSON 序列化、视图渲染等) processDispatchResult(request, response, chain, mv, exception); // 6. 触发拦截器的 afterCompletion(总是执行) chain.triggerAfterCompletion(request, response, exception); }五、HandlerExecutionChain —— Controller + 拦截器的组合体请求到达 DispatcherServlet 后,getHandler() 方法会将你的 Controller 方法和所有适用的拦截器打包成一个执行链。结构示意:HandlerExecutionChain ├── Handler(你的 Controller 方法) ├── Interceptor A ├── Interceptor B └── Interceptor C执行顺序(链式调用,具有栈特性):进入时:A.pre → B.pre → C.pre → Controller返回时:C.post → B.post → A.post最终完成:C.after → B.after → A.after(即使前面出异常也会执行)六、@RequestScope 的底层原理 —— 代理对象,而非每次都 new很多人以为 @RequestScope 每次请求都会创建一个全新的 Bean,实际上 Spring 注入的是一个代理对象。特别提醒:代理对象通过当前线程的 ThreadLocal 拿到绑定的请求上下文,从而找到属于当前请求的那个真实 @RequestScope 实例。如果在 Controller 里用 @Async 等开了新线程,原请求的 ThreadLocal 会丢失,导致代理找不到请求上下文而直接报错。@Autowired private UserContext userContext;实际注入的是 UserContext$$SpringProxy 代理类。该代理对象内部并不持有真实数据,当真正调用其方法时,它会从当前请求的 RequestAttributes 中动态获取属于当前请求的真实实例。流程:当前线程 → RequestAttributes → 真实的 UserContext好处:即便 Controller 是单例(Singleton),也能安全地使用 @RequestScope 的 Bean,因为每次调用都会通过代理去获取当前请求对应的真实对象。七、ThreadLocal 与 @RequestScope —— 如何安全地存放用户信息在企业项目中,经常需要在任何地方(Service、DAO)获取当前登录用户,常用两种方案。方案实现方式优点缺点最佳实践ThreadLocalUserHolder.set(user) / UserHolder.get()极快,线程隔离必须手动 remove(),否则线程池复用时会内存泄漏或数据串人在拦截器的 afterCompletion 中清理@RequestScope注解在 Spring 管理的 Bean 上Spring 自动管理生命周期,请求结束自动销毁,无泄漏风险性能比 ThreadLocal 多一点点反射/代理开销优先推荐,简单安全推荐:存放用户上下文信息时,优先选择 @RequestScope。只有在对性能要求极高、并发极大的场景下,才考虑 ThreadLocal,并确保在拦截器的 afterCompletion 中执行 remove()。八、Controller 参数解析过程 —— 为什么参数能自动绑定你写的 public Result get(Long id),Spring 能自动从请求里拿到 id,靠的是 HandlerMethodArgumentResolver 接口。该解析器会遍历判断当前参数是否能被自己解析,若能则从 request 中取值并绑定。常见注解与对应解析器:注解解析器@RequestParamRequestParamMethodArgumentResolver@PathVariablePathVariableMethodArgumentResolver@RequestBodyRequestResponseBodyMethodProcessor@RequestHeaderRequestHeaderMethodArgumentResolver例如:@RequestHeader("token") String token 背后就是 RequestHeaderMethodArgumentResolver 在干活。你也可以实现该接口并注册,来支持自定义的参数注入(比如自动注入当前登录用户对象)。九、JSON 如何变成 Java 对象 —— HttpMessageConverter前端发送 JSON:POST { "name":"Tom" }到后端自动变成 UserDTO 对象,核心组件是 HttpMessageConverter。默认使用的实现是 MappingJackson2HttpMessageConverter,底层依赖 Jackson。请求方向:请求体 JSON → Jackson 反序列化 → Java 对象 → Controller 参数响应方向:Controller 返回的 Java 对象 → Jackson 序列化 → JSON 字符串 → HTTP 响应因此 Jackson 是 Spring Boot 中处理 JSON 的事实标准,排查问题时经常要关注它。十、统一异常处理 —— 别再到处 try-catch在企业项目中,应使用 @RestControllerAdvice 进行全局异常拦截,避免在每个 Controller 方法中写重复的 try-catch。@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public Result handleException(Exception e) { log.error("系统异常", e); return Result.fail(e.getMessage()); } @ExceptionHandler(BusinessException.class) public Result handleBusinessException(BusinessException e) { return Result.fail(e.getCode(), e.getMessage()); } }执行流程:Controller 抛出异常 ↓ HandlerExceptionResolver 捕获 ↓ 找到匹配的 @ExceptionHandler 方法 ↓ 返回统一的错误响应这样 Controller 只需关注正常业务逻辑,所有异常都会流入全局处理器,返回规范的接口格式。十一、完整认证流程 —— 不要只相信 Token真正的安全认证绝不能只解析 token,而必须结合 Redis 等存储做二次校验。标准流程:1. 请求携带 JWT token 2. Interceptor 解析 token 得到 userId 3. 去 Redis 查询该 userId 对应的完整用户信息(含权限、状态) 4. 如果 Redis 中不存在或 token 已失效 → 直接返回 401 5. 将用户信息放入 UserContext(@RequestScope 管理的 Bean) 6. Controller 直接从 UserContext 获取用户信息,无需再解析 token为什么要加 Redis?Token 可能被伪造(弱签名或密钥泄露)用户可能已被管理员禁用,但 token 还未过期用户执行了“退出登录”,token 理应立刻失效总结:Token 只负责提供身份标识(userId),真正的用户资料和权限应从 Redis(或数据库)获取,并在请求上下文中传递。十二、Spring MVC 八大核心组件(高级理解)DispatcherServlet 只是总控,真正复杂的工作由这些组件完成:组件作用重要性HandlerMapping根据请求 URL 找到对应的 Handler(Controller 方法)★★★HandlerAdapter真正执行 Handler,适配不同类型的处理器★★★HandlerExceptionResolver处理异常,分发给 @ExceptionHandler 或 @ControllerAdvice★★★ViewResolver解析视图名,找到具体视图(前后端分离时用得少)★MultipartResolver处理文件上传请求★LocaleResolver国际化,解析客户端的语言/地区★ThemeResolver主题切换(老旧项目中常见)★FlashMapManager用于重定向时保存临时数据★其中最核心的三个:HandlerMapping、HandlerAdapter、HandlerExceptionResolver,理解它们才能真正把握 Spring MVC 的执行原理。总结 —— 一条线全部串起来完整请求处理链路:Tomcat → Filter(登录校验、XSS、日志、跨域) → DispatcherServlet → HandlerMapping(找到 Controller 方法) → HandlerInterceptor.preHandle(权限、用户注入) → HandlerAdapter(执行方法) → Controller → Service → DAO / JdbcTemplate → MySQL → (若有异常)HandlerExceptionResolver → @RestControllerAdvice 统一处理 → HttpMessageConverter(Java 对象 ↔ JSON) → HandlerInterceptor.postHandle / afterCompletion → 返回 Response日常企业开发(SpringBoot 2.7 + JdbcTemplate + MySQL + Redis + JWT)中最核心的10个知识点:Filter — 容器层拦截,适合安全校验、跨域、日志Interceptor — Spring MVC 拦截,适合用户注入、权限、统计@RequestScope — 请求作用域 Bean,底层用代理实现ThreadLocal — 线程隔离的上下文存储,需手动清理HandlerMethodArgumentResolver — 参数解析器,实现自动注入HttpMessageConverter — JSON / 对象互转,底层 Jackson@RestControllerAdvice — 全局异常处理,不要到处 try-catchDispatcherServlet — 前端总控制器,调度所有请求HandlerExecutionChain — 拦截器与 Controller 的打包执行链Spring MVC 九大组件 — 理解项目架构离不开这些支撑组件
2026年06月10日
9 阅读
0 评论
1 点赞
2026-06-08
java
1. 策略模式 ⭐⭐⭐⭐⭐消灭 if-else 的核心武器,基于 Spring 容器自动选择策略。步骤:定义策略接口public interface Strategy { String getType(); void execute(); }实现多个策略并用 @Component 注册@Component public class StrategyA implements Strategy { @Override public String getType() { return "A"; } @Override public void execute() { /* ... */ } } @Component public class StrategyB implements Strategy { @Override public String getType() { return "B"; } @Override public void execute() { /* ... */ } }创建策略选择器@Component public class StrategySelector { private final Map<String, Strategy> strategyMap = new HashMap<>(); // 4. Spring自动注入所有Strategy实现到Map(详见文档末尾“注入条件”) public StrategySelector(Map<String, Strategy> strategyMap) { // 遍历注入的Map,按业务key重组 strategyMap.values().forEach(s -> this.strategyMap.put(s.getType(), s)); } // 5. 选择策略 public Strategy getStrategy(String type) { Strategy strategy = strategyMap.get(type); if (strategy == null) { throw new IllegalArgumentException("No strategy for: " + type); } return strategy; } }2. 工厂模式 ⭐⭐⭐⭐⭐创建对象的“万能工厂”,通过 Spring 容器自动装配产品族。步骤:定义产品接口public interface Product { String getType(); void doSomething(); }多个产品实现并注册@Component public class ProductA implements Product { @Override public String getType() { return "A"; } @Override public void doSomething() { /*...*/ } } @Component public class ProductB implements Product { @Override public String getType() { return "B"; } @Override public void doSomething() { /*...*/ } }创建工厂类(Spring自动注入所有产品)@Component public class ProductFactory { private final Map<String, Product> productMap; // 构造器注入:Spring会将所有Product的实现注入到Map中(key=bean名称,默认类名首字母小写) public ProductFactory(Map<String, Product> productMap) { this.productMap = productMap; } public Product create(String type) { Product product = productMap.get(type); // 或自定义getType()映射 if (product == null) throw new IllegalArgumentException("Unknown product type"); return product; } }与策略模式的区别:工厂侧重于创建/获取对象,策略侧重行为互换,但 Spring 集成写法相似。3. 责任链模式 ⭐⭐⭐⭐⭐多个处理器依次尝试,直到有人处理。步骤:定义请求上下文public class RequestContext { private boolean approved = false; private String rejectReason; private String approvedBy; // getters & setters... }编写 Handler 接口public interface Handler { boolean handle(RequestContext context); // true=处理完毕,不再传递 }具体处理器(@Component + @Order 控制顺序)@Component @Order(1) public class ManagerHandler implements Handler { public boolean handle(RequestContext context) { // 逻辑:通过则设置approved=true,返回true;否则返回false return true; } } @Component @Order(2) public class DirectorHandler implements Handler { /* ... */ }组装责任链(@PostConstruct 确保只排序一次)@Component public class HandlerChain { private final List<Handler> handlers; public HandlerChain(List<Handler> handlers) { this.handlers = handlers; } @PostConstruct public void init() { AnnotationAwareOrderComparator.sort(handlers); } public void process(RequestContext context) { for (Handler handler : handlers) { if (handler.handle(context)) break; } } }使用@Autowired HandlerChain chain; // ... RequestContext ctx = new RequestContext(); chain.process(ctx);4. 模板方法模式 ⭐⭐⭐⭐⭐父类定义骨架,子类填写细节。步骤:抽象模板类public abstract class AbstractExportService { public final void export() { prepareData(); writeFile(); cleanUp(); } protected abstract void prepareData(); protected abstract void writeFile(); protected void cleanUp() { /* 钩子:默认空实现 */ } }具体子类@Component public class ExcelExportService extends AbstractExportService { @Override protected void prepareData() { /*...*/ } @Override protected void writeFile() { /*...*/ } }Controller中使用(注入所有子类)@RestController public class ExportController { private final Map<String, AbstractExportService> exportServiceMap; // 注入时 key 为 bean 名称(类名首字母小写,或用@Service("excel")指定) public ExportController(Map<String, AbstractExportService> exportServiceMap) { this.exportServiceMap = exportServiceMap; } @GetMapping("/export/{type}") public void doExport(@PathVariable String type) { exportServiceMap.get(type).export(); } }5. 代理模式 ⭐⭐⭐⭐⭐控制对象访问,增强功能(Spring AOP 的基础)。Spring 推荐方式:声明式:@Transactional、@Cacheable 等本质是动态代理。手动示例:统计方法耗时。@Component public class SimpleService implements IService { @Override public void work() { /* ... */ } } @Component public class ServiceProxy implements IService { private final IService target; public ServiceProxy(@Qualifier("simpleService") IService target) { this.target = target; } @Override public void work() { long start = System.currentTimeMillis(); target.work(); System.out.println("cost: " + (System.currentTimeMillis() - start)); } }更常用的是直接使用 Spring AOP(@Aspect + @Around)实现动态代理。6. 状态模式 ⭐⭐⭐⭐⭐将状态逻辑拆分为独立的状态对象,避免大量 if-else。步骤(订单状态机):状态接口public interface OrderState { void pay(OrderContext ctx); void ship(OrderContext ctx); }具体状态@Component public class PendingPaymentState implements OrderState { public void pay(OrderContext ctx) { // 支付成功后切换状态 ctx.setState(ctx.getPaidState()); } public void ship(OrderContext ctx) { throw new UnsupportedOperationException(); } } // PaidState, ShippedState... 同理上下文持有状态@Component public class OrderContext { private OrderState current; // 注入所有状态 @Autowired private PendingPaymentState pendingPaymentState; @Autowired private PaidState paidState; //... getters/setters,初始化为 pendingPaymentState }Spring 中可使用状态枚举配合 Map<OrderStatus, OrderState> 自动注入。7. 观察者模式 ⭐⭐⭐⭐一对多通知,Spring 事件机制天然支持。步骤:定义事件public class OrderCreatedEvent extends ApplicationEvent { private final Long orderId; public OrderCreatedEvent(Object source, Long orderId) { super(source); this.orderId = orderId; } // getter }发布事件@Component public class OrderService { @Autowired private ApplicationEventPublisher publisher; public void createOrder() { // 业务... publisher.publishEvent(new OrderCreatedEvent(this, orderId)); } }监听事件(可使用 @EventListener 或 @TransactionalEventListener)@Component public class SendSmsListener { @EventListener public void handleOrderCreated(OrderCreatedEvent event) { // 发短信 } }8. 门面模式 ⭐⭐⭐⭐为复杂子系统提供统一入口,Service 层常见模式。示例: 下单门面聚合库存、支付、物流三个服务。@Component public class OrderFacade { @Autowired private InventoryService inventoryService; @Autowired private PaymentService paymentService; @Autowired private LogisticsService logisticsService; public void placeOrder(OrderDTO dto) { inventoryService.reduceStock(dto.getSku()); paymentService.charge(dto.getAmount()); logisticsService.scheduleDelivery(dto.getOrderId()); } }9. 装饰器模式 ⭐⭐⭐⭐动态附加责任,比继承更灵活。示例: 对数据读写组件增加缓存装饰器。public interface DataService { String read(); } @Component("basicDataService") public class BasicDataService implements DataService { /*...*/ } @Component public class CacheDecorator implements DataService { private final DataService delegate; public CacheDecorator(@Qualifier("basicDataService") DataService delegate) { this.delegate = delegate; } @Override public String read() { // 先查缓存,没有则调用delegate并缓存 return delegate.read(); } }10. 适配器模式 ⭐⭐⭐⭐兼容两个不匹配的接口,常用于整合外部系统。// 目标接口 public interface Logger { void log(String msg); } // 被适配的第三方类 public class ExternalLogLib { public void writeLog(String text) { /*...*/ } } // 适配器 @Component public class ExternalLogAdapter implements Logger { @Autowired private ExternalLogLib externalLogLib; @Override public void log(String msg) { externalLogLib.writeLog(msg); } }11. DDD(领域驱动设计) ⭐⭐⭐⭐⭐⭐聚焦核心业务复杂度,分层架构与战略设计。核心步骤(简化分层):用户接口层:Controller,接收请求并调用应用服务。应用层:@Service 应用服务,编排领域对象,不包含业务逻辑。领域层:实体(Entity)、值对象(Value Object)、聚合根(Aggregate Root)、领域服务(Domain Service)、仓储接口(Repository Interface)。基础设施层:仓储实现(@Repository),将聚合持久化到数据库。关键原则:领域层无任何 Spring 注解,纯 POJO。通过构造器注入将仓储接口注入领域服务或应用服务。聚合内通过根实体确保一致性。12. Event Driven(事件驱动架构) ⭐⭐⭐⭐⭐⭐通过事件解耦服务,提升伸缩性和最终一致性。Spring 实现:本地事件:ApplicationEventPublisher + @EventListener(见观察者模式)。分布式事件:集成 Kafka / RabbitMQ。// 发布领域事件到消息队列 @Autowired private KafkaTemplate<String, DomainEvent> kafkaTemplate; public void publish(DomainEvent event) { kafkaTemplate.send("topic-name", event); } // 消费 @KafkaListener(topics = "topic-name") public void handle(DomainEvent event) { /*...*/ }推荐配合 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 保证事务提交后再发消息。13. Saga ⭐⭐⭐⭐⭐⭐分布式事务的最终一致性方案,通过协调正向/补偿操作。编排型 Saga(借助事件驱动):每个微服务监听前序事件,执行本地事务并发布新事件。若失败,发布补偿事件,上游监听并回滚。// 服务A发布订单创建事件,服务B监听并扣库存 // 若扣库存失败,服务B发布库存不足事件,服务A监听后进行补偿(取消订单)实现时可配合 Spring Cloud Stream / Axon Framework。14. CQRS(命令查询职责分离) ⭐⭐⭐⭐⭐⭐读写模型分离,最大化性能与扩展性。简单实现:命令侧:应用服务接收 Command,调用领域逻辑,写入主库。查询侧:@Service 直接从读库或缓存查询 DTO。// 命令 @PostMapping public void createOrder(@RequestBody CreateOrderCmd cmd) { commandService.handle(cmd); } // 查询(独立Controller或同一模块内分离) @GetMapping("/orders/{id}") public OrderView queryOrder(@PathVariable Long id) { return queryService.findById(id); }高级玩法:事件溯源,将所有状态变更存储为事件流,查询视图由事件投影构建。SpringBoot 2.7 注入条件(集合/Map注入)核心行为: Spring 会自动将所有实现了某个接口或父类的 Bean 注入到 List 或 Map 中。注入 List: @Autowired List<SomeInterface> list; 会按 Order 排序包含所有实现类的 Bean。注入 Map: @Autowired Map<String, SomeInterface> map; key 为 Bean 的名称(默认类名首字母小写,若用 @Service("myName") 则用指定名称),value 为实例。没有实现类时: Spring 默认会注入一个空集合(EmptyList/EmptyMap),不会报错。若希望强制存在才注入,可使用 @Autowired(required = true),但注入集合时 required=true 仍允许空集合,除非你将它用在非集合字段上。推荐方式: 构造器注入,显式声明 final 字段,避免 @Autowired 注解(Spring 4.3+ 单构造器自动注入)。@Component public class StrategySelector { private final Map<String, Strategy> strategyMap = new HashMap<>(); // 构造器注入:Spring 自动将所有的 Strategy 实现类注入 Map public StrategySelector(Map<String, Strategy> allStrategies) { allStrategies.values().forEach(s -> strategyMap.put(s.getType(), s)); } }在 Spring Boot 2.7 (Spring Framework 5.3.x) 下此行为完全稳定,完美支持策略/工厂等模式的自动装配。
2026年06月08日
3 阅读
0 评论
0 点赞
2026-06-03
反射 + AOP + 注解
反射 + AOP + 注解 讲解目录注解(Annotation)什么是注解元注解与自定义注解如何在反射中读取注解反射(Reflection)反射的核心类:Class、Method、Field动态创建对象、调用方法、访问字段反射与注解的结合AOP(面向切面编程)AOP 的核心概念Spring AOP 的实现原理(代理)通知类型与切入点表达式如何在切面中使用反射和注解三剑合璧实战案例案例1:自定义 @Log 日志记录案例2:自定义 @RequirePermission 权限控制总结与关系图谱一、注解(Annotation)1.1 什么是注解?注解是 Java 5 引入的一种元数据,它本身不影响代码逻辑,但可以被编译、运行时工具或框架读取,从而赋予代码额外的语义或行为。常见注解如 @Override、@Deprecated、@Autowired 等。1.2 元注解定义注解时需要用到的注解,共有四个:元注解作用@Target指定注解能用在哪里(类、方法、字段等)@Retention指定注解的存活周期(源码、字节码、运行时)@Documented是否包含在 JavaDoc 中@Inherited是否允许子类继承父类的注解@Retention 的取值:RetentionPolicy.SOURCE:只在源码中存在,编译时丢弃(如 @Override)RetentionPolicy.CLASS:在字节码中存在,但运行时不可见(默认)RetentionPolicy.RUNTIME:运行时可通过反射读取(自定义注解必须选这个)1.3 自定义一个注解import java.lang.annotation.*; @Target(ElementType.METHOD) // 只能用在方法上 @Retention(RetentionPolicy.RUNTIME) // 运行时保留 public @interface Loggable { String value() default ""; // 注解属性 }使用:@Loggable("删除用户") public void deleteUser(Long id) { ... }1.4 在反射中读取注解无论是类、方法还是字段,反射 API 都提供了读取注解的方法:getAnnotation(Class<T> annotationClass) —— 获取指定类型的注解getAnnotations() —— 获取所有注解(含继承)getDeclaredAnnotations() —— 获取直接声明的注解(不含继承)isAnnotationPresent(Class<?> annotationClass) —— 判断是否存在某注解示例:读取方法上的 @LoggableMethod method = ...; // 通过反射拿到方法对象 Loggable log = method.getAnnotation(Loggable.class); if (log != null) { System.out.println("描述:" + log.value()); }二、反射(Reflection)2.1 反射的核心类类作用获取方式Class类的元信息对象.getClass()、类名.class、Class.forName("全类名")Method方法信息Class 对象的 getMethod(...)、getDeclaredMethods() 等Field字段信息Class 对象的 getField(...)、getDeclaredFields() 等Constructor构造器信息Class 对象的 getConstructor(...)、getDeclaredConstructors() 等2.2 获取 Class 对象的三种方式// 1. 通过类名.class Class<String> clz1 = String.class; // 2. 通过实例的 getClass() String s = "hello"; Class<?> clz2 = s.getClass(); // 3. 通过完全限定名字符串 Class<?> clz3 = Class.forName("java.lang.String");2.3 获取 Method 对象方法说明getMethod(name, paramTypes...)获取指定名称和参数的 public 方法(含继承)getMethods()获取所有 public 方法(含继承)getDeclaredMethod(name, paramTypes...)获取本类中声明的方法(不含继承,可访问私有)getDeclaredMethods()获取本类中声明的所有方法(不含继承)示例:获取 deleteUser(Long) 方法Method method = UserService.class.getMethod("deleteUser", Long.class);2.4 动态调用方法UserService target = new UserService(); Method method = UserService.class.getMethod("deleteUser", Long.class); Object result = method.invoke(target, 123L); // 相当于 target.deleteUser(123L)如果方法是私有的,需要先 setAccessible(true)。2.5 反射与注解的结合反射的 Method、Field、Class 等都实现了 AnnotatedElement 接口,因此可以直接读取注解。Method method = ...; if (method.isAnnotationPresent(Loggable.class)) { Loggable log = method.getAnnotation(Loggable.class); System.out.println("需要记录日志:" + log.value()); }应用场景:框架(Spring、Hibernate)通过反射扫描注解,完成依赖注入、ORM 映射等。自定义工具类,根据注解动态生成 SQL、校验参数等。三、AOP(面向切面编程)3.1 AOP 核心概念切面(Aspect):封装横切逻辑的模块(比如日志、权限)。连接点(JoinPoint):程序执行过程中的一个点(方法调用、字段访问等),Spring AOP 只支持方法连接点。切入点(Pointcut):匹配连接点的表达式,决定哪些方法会被增强。通知(Advice):在切入点处执行的动作(具体逻辑)。目标对象(Target):被代理的原始对象。代理(Proxy):包裹目标对象,加入切面逻辑后生成的对象。3.2 Spring AOP 的实现Spring AOP 基于动态代理:如果目标类实现了接口,默认使用 JDK 动态代理(java.lang.reflect.Proxy)。如果没有接口,则使用 CGLIB 生成子类作为代理。从 Spring Boot 2.0 开始,默认使用 CGLIB 代理(即使有接口也会用 CGLIB)。3.3 通知类型与切入点表达式通知类型注解说明@Before目标方法执行前@AfterReturning目标方法正常返回后@AfterThrowing目标方法抛出异常后@After目标方法结束后(类似 finally)@Around包围目标方法,可控制是否执行常用切入点表达式表达式含义execution(* com.example.service.*.*(..))匹配 service 包下的所有方法@annotation(com.example.Loggable)匹配带有 @Loggable 注解的方法execution(public * *(..))匹配所有 public 方法within(com.example.service.*)匹配 service 包下的所有类的所有方法3.4 切面中如何使用反射和注解在 @Around 通知中,通过 ProceedingJoinPoint 可以获取方法签名(MethodSignature),然后通过反射拿到 Method 对象,进而读取注解。@Around("@annotation(loggable)") public Object log(ProceedingJoinPoint pjp, Loggable loggable) throws Throwable { // 获得方法签名 MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); // 反射 Method 对象 // 可以读取更多注解 AnotherAnnotation anno = method.getAnnotation(AnotherAnnotation.class); // 执行原方法 return pjp.proceed(); }为什么要 MethodSignature? 因为 pjp.getSignature() 返回的是通用的 Signature 接口,只有强转成 MethodSignature 才能获取反射 Method 以及参数名、返回类型等详细信息。3.5 JoinPoint 信息树(速查)JoinPoint (ProceedingJoinPoint 继承它) ├─ getTarget() → 目标对象(被代理的原始 Bean) ├─ getThis() → 当前代理对象 ├─ getArgs() → 方法参数值数组 ├─ getSignature() → Signature (实际是 MethodSignature) │ ├─ getName() → 方法名 │ ├─ getDeclaringTypeName() → 声明该方法的类全名 │ └─ (MethodSignature 特有) │ ├─ getMethod() → 反射 Method ★ │ │ └─ getAnnotation(..) → 获取注解 │ ├─ getParameterNames() → 参数名数组(需编译配置) │ ├─ getReturnType() → 返回类型 │ └─ getParameterTypes() → 参数类型数组 └─ getStaticPart() → 同 getSignature()四、三剑合璧实战案例4.1 案例 1:@Log 注解实现方法日志记录需求:在方法执行时自动打印入参、出参和耗时。步骤:自定义 @Log 注解编写切面 LogAspect,使用 @Around + @annotation在切面中通过 MethodSignature 获取方法名,通过 getArgs() 获取参数值// 注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { } // 切面 @Aspect @Component public class LogAspect { @Around("@annotation(com.example.Log)") public Object around(ProceedingJoinPoint pjp) throws Throwable { MethodSignature sig = (MethodSignature) pjp.getSignature(); String method = sig.getDeclaringTypeName() + "." + sig.getName(); Object[] args = pjp.getArgs(); System.out.println(">>> 调用 " + method + ",参数:" + Arrays.toString(args)); long start = System.currentTimeMillis(); Object result = pjp.proceed(); long time = System.currentTimeMillis() - start; System.out.println("<<< 返回:" + result + " 耗时:" + time + "ms"); return result; } } // 业务使用 @Service public class UserService { @Log public String getUser(Long id) { return "User#" + id; } }4.2 案例 2:@RequirePermission 权限控制需求:标记方法需要某权限,没有则拒绝访问。实现思路:定义注解 @RequirePermission("user:delete")在拦截器中把用户权限存入 ThreadLocal切面中读取注解所需的权限值,与用户权限对比,不满足抛异常// 注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequirePermission { String value(); } // 权限上下文 public class UserContext { private static final ThreadLocal<List<String>> PERMISSIONS = new ThreadLocal<>(); public static void set(List<String> p) { PERMISSIONS.set(p); } public static List<String> get() { return PERMISSIONS.get() != null ? PERMISSIONS.get() : Collections.emptyList(); } public static void clear() { PERMISSIONS.remove(); } } // 切面 @Aspect @Component public class PermissionAspect { @Around("@annotation(RequirePermission)") public Object check(ProceedingJoinPoint pjp) throws Throwable { MethodSignature sig = (MethodSignature) pjp.getSignature(); Method method = sig.getMethod(); RequirePermission permission = method.getAnnotation(RequirePermission.class); String required = permission.value(); if (!UserContext.get().contains(required)) { throw new RuntimeException("无权限:" + required); } return pjp.proceed(); } } // 拦截器(简化) public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, ...) { String user = request.getHeader("X-User"); List<String> permissions = loadPermissions(user); // 从数据库查 UserContext.set(permissions); return true; } @Override public void afterCompletion(...) { UserContext.clear(); } }五、总结与关系图谱三者关系:注解 是标记,贴在代码上描述元数据。反射 是读取这些标记和操作代码结构(类、方法)的能力。AOP 是利用反射机制,在运行时动态地将横切逻辑织入到带有特定注解(或符合表达式)的方法周围。常见协作模式:自定义一个注解(如 @Log)在 AOP 切面中,通过切入点表达式 @annotation(Log) 拦截所有标注了该注解的方法在切面通知方法里,利用 JoinPoint/ProceedingJoinPoint 获取方法签名(MethodSignature)通过 MethodSignature.getMethod() 拿到反射 Method 对象,再用 getAnnotation() 读取注解的具体值,实现差异化逻辑学习路径建议:先熟练 反射(Class、Method、注解读取)理解 代理模式(JDK 动态代理、CGLIB)掌握 Spring AOP 的基本用法(切入点表达式、通知类型)最后将三者结合,实现自定义注解驱动切面当你遇到如下代码时,大脑能快速解析:MethodSignature sig = (MethodSignature) joinPoint.getSignature(); Method method = sig.getMethod(); MyAnnotation ann = method.getAnnotation(MyAnnotation.class);它正在用 AOP 的连接点 获取 反射的 Method,然后 读取自定义注解,从而执行相应逻辑——这。
2026年06月03日
6 阅读
0 评论
0 点赞
2026-06-03
JdbcTemplate
Spring JDBC + JdbcTemplate 学习笔记(方法驱动格式)本笔记聚焦于 JdbcTemplate 中常用的数据库操作方法。每个方法均按以下结构介绍:方法:方法签名/名称作用:该方法解决什么问题参数:主要参数的含义和类型方法案例:代码示例(可直接运行)实例说明:对案例的详细解释,包括注意事项前置环境:Spring 6 + HikariCP + HSQLDB,配置了 DataSource 和 JdbcTemplate Bean。第一章:准备工作与环境配置1.1 Maven 依赖<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.7.1</version> </dependency>1.2 Spring 配置类(AppConfig)@Configuration @ComponentScan @PropertySource("jdbc.properties") public class AppConfig { @Value("${jdbc.url}") String jdbcUrl; @Value("${jdbc.username}") String jdbcUsername; @Value("${jdbc.password}") String jdbcPassword; @Bean DataSource dataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl(jdbcUrl); config.setUsername(jdbcUsername); config.setPassword(jdbcPassword); return new HikariDataSource(config); } @Bean JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } }1.3 数据库初始化(自动建表)@Component public class DatabaseInitializer { @Autowired JdbcTemplate jdbcTemplate; @PostConstruct void init() { jdbcTemplate.update("CREATE TABLE IF NOT EXISTS users (" + "id BIGINT IDENTITY PRIMARY KEY, " + "email VARCHAR(100) NOT NULL UNIQUE, " + "password VARCHAR(100), " + "name VARCHAR(100))"); } }第二章:核心方法详解(按格式组织)方法一:queryForObject() – 查询单行记录并映射为对象作用:执行查询 SQL,期望返回一条记录,并将结果集的第一行通过 RowMapper 转换成 Java 对象。若查询结果为空或大于一行则抛出异常。参数:String sql – SQL 语句(可使用 ? 占位符)RowMapper<T> rowMapper – 将 ResultSet 当前行映射为 T 对象的函数式接口Object... args – 可变参数,按顺序替换 SQL 中的 ?方法案例:根据 email 查询用户public User getUserByEmail(String email) { String sql = "SELECT id, email, password, name FROM users WHERE email = ?"; return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ), email ); }实例说明:RowMapper 的 lambda 表达式接收 ResultSet 和当前行号(通常忽略行号),手动构建 User 对象。如果数据库中不存在该 email,会抛出 EmptyResultDataAccessException,需要捕获处理或改为返回 null。也适用于聚合查询,例如统计总数:public long countUsers() { String sql = "SELECT COUNT(*) FROM users"; return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> rs.getLong(1)); }方法二:query() – 查询多行记录,返回 List作用:执行查询 SQL,将结果集中的每一行都通过 RowMapper 转换为对象,最后返回 List<T>。结果集为空时返回空列表(不抛异常)。参数:String sql – SQL 语句RowMapper<T> rowMapper – 行映射器Object... args – 参数列表方法案例:分页查询用户public List<User> getUsersByPage(int pageIndex, int pageSize) { String sql = "SELECT * FROM users LIMIT ? OFFSET ?"; int offset = (pageIndex - 1) * pageSize; return jdbcTemplate.query(sql, (rs, rowNum) -> new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ), pageSize, offset ); }实例说明:使用 BeanPropertyRowMapper 可自动映射(要求列名与属性名一致或下划线转驼峰):public List<User> getAllUsers() { return jdbcTemplate.query("SELECT * FROM users", new BeanPropertyRowMapper<>(User.class)); }BeanPropertyRowMapper 适合字段较多的简单映射;复杂映射仍需手动 RowMapper。方法三:update() – 执行插入、更新、删除作用:执行 DML 语句(INSERT/UPDATE/DELETE),返回受影响的行数。参数:String sql – DML 语句Object... args – 参数列表返回值:int – 受影响的行数方法案例 1:更新用户名称public int updateUserName(Long id, String newName) { String sql = "UPDATE users SET name = ? WHERE id = ?"; return jdbcTemplate.update(sql, newName, id); }实例说明:调用方可根据返回值判断是否更新成功(例如返回值 == 0 表示用户不存在)。方法案例 2:删除用户public boolean deleteUser(Long id) { String sql = "DELETE FROM users WHERE id = ?"; int rows = jdbcTemplate.update(sql, id); return rows > 0; }方法案例 3:插入用户(不返回主键)public void addUserWithoutKey(String name, String email) { String sql = "INSERT INTO users (name, email) VALUES (?, ?)"; jdbcTemplate.update(sql, name, email); }方法四:update() + KeyHolder – 插入并返回自增主键作用:执行插入语句后,获取数据库自动生成的主键(如 IDENTITY 或 AUTO_INCREMENT)。参数:PreparedStatementCreator psc – 用于创建 PreparedStatement,必须指定 Statement.RETURN_GENERATED_KEYSKeyHolder keyHolder – 用于接收生成的主键方法案例:public User addUserAndGetId(String name, String email, String password) { String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"; KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, name); ps.setString(2, email); ps.setString(3, password); return ps; }, keyHolder); Long newId = keyHolder.getKey().longValue(); return new User(newId, email, password, name); }实例说明:PreparedStatementCreator 是一个函数式接口,需手动设置参数并返回带 RETURN_GENERATED_KEYS 的 PreparedStatement。执行后通过 keyHolder.getKey() 获取主键,通常为 Number 类型,需转换为 Long 或 Integer。适用于大多数关系型数据库(MySQL、HSQLDB、PostgreSQL、SQL Server 等)。方法五:batchUpdate() – 批量操作作用:一次性执行多条相同结构的 SQL(通常是批量插入或批量更新),性能远高于循环单条执行。参数:String sql – 带占位符的 SQL 模板List<Object[]> batchArgs – 每个元素是一个 Object[],代表一次执行对应的参数返回值:int[] – 数组长度等于批处理次数,每个元素为对应操作影响的行数方法案例:批量插入多个用户public int[] batchAddUsers(List<User> users) { String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"; List<Object[]> batch = new ArrayList<>(); for (User u : users) { batch.add(new Object[]{u.getName(), u.getEmail(), u.getPassword()}); } return jdbcTemplate.batchUpdate(sql, batch); }实例说明:批量操作会减少 JDBC 与数据库之间的网络往返次数,显著提升吞吐量。注意:某些数据库(如 HSQLDB)对单次批处理的大小有限制,可分批提交。方法六:execute() – 执行任意 SQL(DDL 或存储过程)作用:执行 DDL(如 CREATE TABLE)或任何不希望返回结果集的 SQL,也可以用于执行存储过程。参数:String sql – 要执行的 SQL 字符串返回值:无(或者可重载为 execute(Callback) 形式返回自定义结果)方法案例:创建日志表public void createLogTable() { jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS sys_log (" + "id INT PRIMARY KEY, " + "message VARCHAR(255), " + "create_time TIMESTAMP)"); }实例说明:execute(String sql) 不关心返回结果,适合做数据库初始化、清空表、重建索引等管理操作。方法七:execute(ConnectionCallback) – 最底层扩展作用:获取原生 JDBC Connection 对象,执行任意复杂数据库操作(如设置超时、调用存储过程、使用原生 API 等)。参数:ConnectionCallback<T> action – 回调接口,接收 Connection 并返回 T返回值:由回调决定的 T方法案例:使用原生 PreparedStatement 和手动控制事务public User getUserWithNativeConnection(Long id) { return jdbcTemplate.execute((Connection conn) -> { try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) { ps.setLong(1, id); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { return new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ); } return null; } } }); }实例说明:JdbcTemplate 会自动释放 Connection,但回调内创建的 PreparedStatement 和 ResultSet 必须由开发者手动释放(建议使用 try-with-resources)。一般情况下优先使用更高级的 query()、update() 等方法,只有非常规需求时才使用 ConnectionCallback。方法八:execute(PreparedStatementCallback) – 灵活处理 PreparedStatement作用:由 JdbcTemplate 创建 PreparedStatement,回调中可自行执行查询、更新并处理结果集。参数:String sql – SQL 模板PreparedStatementCallback<T> action – 回调接收 PreparedStatement,返回 T方法案例:判断用户是否存在public boolean existsUserByName(String name) { return jdbcTemplate.execute("SELECT 1 FROM users WHERE name = ?", (PreparedStatement ps) -> { ps.setString(1, name); try (ResultSet rs = ps.executeQuery()) { return rs.next(); // 有记录返回 true } }); }实例说明:此方法比 ConnectionCallback 更高层,无需创建 PreparedStatement,但依然需要手动执行查询和处理 ResultSet。第三章:RowMapper 详解(两个常用实现)3.1 手动 RowMapper(Lambda)作用:将 ResultSet 当前行的数据提取并组装成业务对象。格式:(ResultSet rs, int rowNum) -> { ... }案例:已在 queryForObject 中展示。3.2 BeanPropertyRowMapper作用:自动将结果集的列名(支持下划线转驼峰)映射到 JavaBean 的同名属性(需有 setter)。参数:BeanPropertyRowMapper.newInstance(Class<T>) 或构造函数。案例:List<User> users = jdbcTemplate.query("SELECT id, email, name FROM users", BeanPropertyRowMapper.newInstance(User.class));实例说明:要求数据库列名 email 对应 Java 属性 email;若列名为 user_name,则对应 userName。非常便捷,适合大多数查询场景。第四章:声明式事务管理核心注解:@Transactional作用:将方法(或类中的所有方法)标记为事务性方法。Spring 会在方法执行前开启事务,执行成功则提交,抛出未捕获异常则回滚。参数(常用):propagation – 传播行为(默认 REQUIRED)isolation – 隔离级别(默认数据库默认级别)rollbackFor – 指定哪些异常触发回滚(默认 RuntimeException 和 Error)方法案例:银行转账(两个更新操作必须在同一事务)@Service public class AccountService { @Autowired JdbcTemplate jdbcTemplate; @Transactional public void transfer(Long fromId, Long toId, BigDecimal amount) { jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId); // 模拟异常:如果转账金额过大,回滚两个更新 if (amount.compareTo(BigDecimal.valueOf(10000)) > 0) { throw new RuntimeException("转账金额超过限额"); } jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId); } }实例说明:需要先在配置类上添加 @EnableTransactionManagement 并注册 DataSourceTransactionManager。默认情况下,只有 RuntimeException 会触发回滚;若希望检查型异常也回滚,需设置 @Transactional(rollbackFor = Exception.class)。事务边界默认基于 AOP 代理,内部方法调用(this.method())不会触发事务。第五章:实战示例 – RESTful API 完整流程5.1 User 实体public class User { private Long id; private String name; private String email; private String password; // getter/setter/构造方法 省略 }5.2 UserService(使用上述方法)@Service public class UserService { @Autowired JdbcTemplate jdbcTemplate; public User addUser(String name, String email, String pwd) { KeyHolder kh = new GeneratedKeyHolder(); jdbcTemplate.update(conn -> { PreparedStatement ps = conn.prepareStatement( "INSERT INTO users (name, email, password) VALUES (?,?,?)", Statement.RETURN_GENERATED_KEYS); ps.setString(1, name); ps.setString(2, email); ps.setString(3, pwd); return ps; }, kh); return new User(kh.getKey().longValue(), email, pwd, name); } public User getUser(Long id) { try { return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?", BeanPropertyRowMapper.newInstance(User.class), id); } catch (EmptyResultDataAccessException e) { return null; } } public List<User> listUsers() { return jdbcTemplate.query("SELECT * FROM users", BeanPropertyRowMapper.newInstance(User.class)); } public int updateUser(Long id, String newName) { return jdbcTemplate.update("UPDATE users SET name = ? WHERE id = ?", newName, id); } public int deleteUser(Long id) { return jdbcTemplate.update("DELETE FROM users WHERE id = ?", id); } }5.3 UserController@RestController @RequestMapping("/api/users") public class UserController { @Autowired UserService userService; @PostMapping public User add(@RequestParam String name, @RequestParam String email, @RequestParam String pwd) { return userService.addUser(name, email, pwd); } @GetMapping("/{id}") public User get(@PathVariable Long id) { return userService.getUser(id); } @GetMapping public List<User> list() { return userService.listUsers(); } @PutMapping("/{id}") public String update(@PathVariable Long id, @RequestParam String name) { int rows = userService.updateUser(id, name); return rows > 0 ? "success" : "user not found"; } @DeleteMapping("/{id}") public String delete(@PathVariable Long id) { int rows = userService.deleteUser(id); return rows > 0 ? "deleted" : "not exist"; } }5.4 测试命令# 新增 curl -X POST "http://localhost:8080/api/users?name=张三&email=zs@test.com&pwd=123" # 查询单个 curl "http://localhost:8080/api/users/1" # 查询全部 curl "http://localhost:8080/api/users" # 更新 curl -X PUT "http://localhost:8080/api/users/1?name=张三丰" # 删除 curl -X DELETE "http://localhost:8080/api/users/1"第六章:常见问题与注意事项queryForObject 空结果抛异常:需捕获 EmptyResultDataAccessException 或改用 query 方法加判断。BeanPropertyRowMapper 要求属性有 setter:否则映射会忽略该列。批量插入时数据库限制:例如 HSQLDB 默认批量最大 100 条,可分批调用 batchUpdate。事务失效场景:同一个类内非 @Transactional 方法调用 @Transactional 方法(无代理)方法被 private 修饰异常被 try-catch 吞掉且没有重新抛出使用 KeyHolder 时:必须通过 Statement.RETURN_GENERATED_KEYS 创建 PreparedStatement。附录:JdbcTemplate 方法速查表方法名作用适用场景queryForObject单行,返回对象根据主键查询、聚合函数query多行,返回 List列表查询、分页update单条 DML插入/更新/删除update + KeyHolder插入并返回主键获取自增 IDbatchUpdate批量 DML批量导入数据execute(String)DDL/存储过程建表、清空表execute(ConnectionCallback)原生 JDBC 操作特殊需求、存储过程---。
2026年06月03日
2 阅读
0 评论
0 点赞
1
2
...
8