场景:
实际项目中某个页面如果连续点击更改或新增按钮时,后台代码调用saveAndFlush()方法后 有可能会出现数据库中被插入重复数据的现象。
分析:
经过分析后发现,当saveAndFlush方法开始操作数据库但未完成的时候,第二个请求也调用了saveAndFlush方法的时候,由于项目配置了数据库连接池,所以系统会重新建立一个数据库连接来完成操作,所以会偶尔出现相同数据被保存到数据库产生脏数据的现象。
解决:
原因知道了,解决就很简单了,90%的解决办法都是在前端用js来进行控制,请求发出后立即设按钮不可用,结果返回后再将按钮设置为可用,这样做可行,但是我个人比较懒不喜欢每个按钮都去写一遍。
我的解决方法:
用全局拦截器拦截所有请求,用注解标注需要被拦截的方法,有注解的进行延时处理,没有的就全部放行,代码如下:
1、创建一个注解类(默认延时1秒)
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Component public @interface Delay { int time() default 1000; }
2、创建拦截器
package com.lhzs.springdemo.interceptor; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class DelayInterceptor implements HandlerInterceptor { private long lastTime = 0; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { //开始进入请求地址拦截 HandlerMethod hm = (HandlerMethod) o; Delay delay = hm.getMethodAnnotation(Delay.class); if (delay != null) { return startDelay(delay.time()); } return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { //处理请求完成后视图渲染之前的处理操作 } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { //视图渲染之后的操作 } private boolean startDelay(int time) { long currentTime = System.currentTimeMillis(); if (currentTime - lastTime > time) { lastTime = currentTime; return true; } return false; } }
3、在需要进行延时处理的方法上用@Delay注解进行标注
@Delay(time = 2000) @RequestMapping(value = "dic/getDicAll") @ResponseBody public List<DicModel> getDicAll() { return ds.findAll(); }
4、springboot配置拦截器
/** * 配置自定义拦截器 */ @Override public void addInterceptors(InterceptorRegistry registry) { //注册自定义拦截器,添加拦截路径和排除拦截路径 registry.addInterceptor(new DelayInterceptor()) .addPathPatterns("/**"); }
点击按钮访问 dic/getDicAll 接口后2秒之内不响应请求。大功告成!^__^