欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

LayUI+Springboot多图上传及并发插入数据库数据出现的主键id重复问题解决

程序员文章站 2022-03-03 20:02:01
...

LayUI提供了非常方便的文件上传控制

 ,uploadListIns = upload.render({
                    elem: '#testList'   //选择文件按钮的id
                    ,url: '../../../file/uploadCarousel'
                    ,accept: 'images'
                    ,multiple: true
                    ,auto: false
                    ,bindAction: '#testListAction'     //开始上传按钮的id
                    ,choose: function(obj){  
                      var files = this.files = obj.pushFile(); //将每次选择的文件追加到文件队列
                      //读取本地文件
                      obj.preview(function(index, file, result){
                        //console.log(index); //得到文件索引
                        //console.log(file); //得到文件对象
                        //console.log(result); //得到文件base64编码,比如图片
                        var tr = $(['<tr id="upload-'+ index +'">'
                          ,'<td><img src='+result+' style="width:60px;height:60px"></td>'
                          ,'<td>'+ (file.size/1014).toFixed(1) +'kb</td>'
                          ,'<td>等待上传</td>'
                          ,'<td>'
                            ,'<button class="layui-btn layui-btn-mini layui-btn-danger demo-delete">删除</button>'
                          ,'</td>'
                        ,'</tr>'].join(''));//join() 方法用于把数组中的所有元素转换一个字符串。


                        //删除
                        tr.find('.demo-delete').on('click', function(){
                          delete files[index]; //删除对应的文件
                          tr.remove();
                          uploadListIns.config.elem.next()[0].value = ''; //清空 input file 值,以免删除后出现同名文件不可选
                        });
                       
                        demoListView.append(tr);
                      });
                    }
                    ,done: function(res, index, upload){
                      //res(服务端响应信息)、index(当前文件的索引)、upload(重新上传的方法,一般在文件上传失败后使用)
                    if(res.status == "success"){
                        var tr = demoListView.find('tr#upload-'+ index), tds = tr.children();
                        tds.eq(2).html('<span style="color: #5FB878;">上传成功</span>');
                        tds.eq(3).html(''); //清空操作
                        var str = '<input type="hidden" name="photo[]" value='+res.image+'>';
                    $('#demoList').append(str);
                        return delete this.files[index]; //删除文件队列已经上传成功的文件
                       }
                      this.error(index, upload);
                    }
                    ,error: function(index, upload){
                      var tr = demoListView.find('tr#upload-'+ index)
                      ,tds = tr.children();
                      tds.eq(2).html('<span style="color: #FF5722;">上传失败</span>');
                      tds.eq(3).find('.demo-reload').removeClass('layui-hide'); //显示重传
                    }
                  });
                });

直接使用upload.render方法
mutiple指明可多图上传

后端代码

@RequestMapping(value = "/uploadCarousel",method = RequestMethod.POST)
    public Map<String, String> uploadCarousel(MultipartFile file, HttpServletRequest request, HttpServletResponse response) throws IllegalStateException, IOException{
        Map<String, String> map = new HashMap<>();
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");//设置日期格式  HH:mm:ss
        String date = df.format(new Date());// new Date()为获取当前系统时间,也可使用当前时间戳
        String path = request.getSession().getServletContext().getRealPath("/upload/carousel");
        File filepath = new File(path);
        if (!filepath.exists()) {
            filepath.mkdirs();
        }
        String originalFilename = file.getOriginalFilename();
        String fileName = getRandomFileName() + originalFilename;
        File dir = new File(path, fileName);
        //文件上传
        try {
            file.transferTo(dir);
            map.put("status", "success");
            map.put("src", contextPath+"/upload/carousel/" + fileName);
            //轮播图文件上传后在数据库内添加该记录
            Carousel carousel = new Carousel();
            carousel.setImage(map.get("src"));
            Map<String,Object>  returnMap = new HashMap<>();
            returnMap = carouselService.insert(carousel);
            //如果操作不成功就删除文件
            if(returnMap.get("message") == null){}
            else{
                if(dir.isFile()){
                    dir.delete();
                    map.put("status", "error");
                    map.put("message", "上传失败!");
                }
                else{
                    map.put("status", "error");
                    map.put("message", "上传失败!");
                }
            }
        } catch (IOException e) {
            map.put("status", "error");
            map.put("message", "上传失败!");
        }

        return map;

    }

事实上这个后端接口多图单图上传都可以调用,因为多图的话应该只是多创建几个线程同时上传而已,但是多图上传时就会出现上传失败的问题,其原因就在于多图上传应该是调用了好几次这个接口,但是相互之间的间隔又很多,就导致并发场景下出现数据库写数据主键重复的问题,这也是很常见的并发场景下的问题。
解决方法:
由于我们的项目是跑在单服务器下,因此可以使用java的synchronized (this)同步锁上锁,当进入这段代码操作的时候,其他线程无法操作,进入阻塞状态,因为是单服务器,所以这段代码只有一个,也就不存在分布式相关的问题。
实测解决了多图上传出现上传失败的问题。

需要注意的点
synchronized不要写在service里,因为service里事物控制只有在事物完成之后才会进行提交,这期间同步锁锁住还是可能会出现并发访问的问题,因此应该在controller里写该方法上锁。
贴上上锁的代码:

    @RequestMapping(value = "/insert",method = RequestMethod.POST)
    private Map<String,Object> insert(Carousel carousel){
        Map<String, Object> modelMap = new HashMap<String, Object>();
        Map<String, Object> returnMap = new HashMap<String, Object>();
        synchronized (this) {      //同步锁 单服务器下保证只能一个线程访问 结束之后再释放
            returnMap = carouselService.insert(carousel);
            if (returnMap.get("message") == null) {
                modelMap.put("status", "success");
            } else {
                modelMap.put("status", "error");
                modelMap.put("message", returnMap.get("message"));
            }
        }
        return modelMap;
    }