问题场景
项目背景:CRM练习项目,通过复选框选择列表中的数据,用户点击删除按钮,弹出确认弹窗,用户点击确认后,前端分装数据到数组,发送ajax请求,将数据传递至Controller进行处理。
JDK:jdk21.0.2
IDE:intelliJ IDEA
数据库:MariaDB10
服务器:Tomcat10
构建工具:apache-maven-3.9.6
项目地址:https://gitcode.com/weixin_44803446/crm-project/overview
问题描述
- ajax发送数组数据,后台接收不到的问题
前端将选中的复选框的value值封装到数组中,在ajax中,使用data:JSON.stringify(Arry)处理并发送。前端部分JavaScript代码如下:
var checkedActivities = $("#listBody input[type='checkbox']:checked");
// 封装数据
var checkedActivitiesIdArray = [];checkedActivities.each(function () {checkedActivitiesIdArray.push(this.value);
});if(window.confirm(confirmMessage)){// 用户确认删除,发送ajax请求$.ajax({url:"deleteActivitiesByIdArray.do",type:"POST",contentType: 'application/json',data: JSON.stringify(checkedActivitiesIdArray),success:function (data) {if(data.code === "1"){// 刷新市场活动的列表$("#searchBtn").click();$("#deleteConfirmMessage").text("");}else{alert(data.message);}}});
}
后端Controller如下:
@RequestMapping("/workbench/activity/deleteActivitiesByIdArray.do")@ResponseBodypublic Object deleteActivitiesByIdArray(@RequestBody List<String> ids){System.out.println(ids);// 做了一些事情}
当这样写的时候,在运行后,一旦前端发送ajax请求,就会报IllegalStateException错误;
java.lang.IllegalStateException: No primary or single unique constructor found for interface java.util.Listat org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:265)at org.springframework.validation.DataBinder.createObject(DataBinder.java:924)at org.springframework.validation.DataBinder.construct(DataBinder.java:903)at org.springframework.web.bind.ServletRequestDataBinder.construct(ServletRequestDataBinder.java:116)at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.constructAttribute(ServletModelAttributeMethodProcessor.java:157)
- 使用模态框时,在前端进行删除操作的时候,有一个诡异事件,就是服务器第一次启动时,删除任务可以正常进行,但是第二次删除时,后台可以删除数据,但是前端收到的反馈确实删除失败。
前端页面代码如下:
<!-- 删除市场活动 -->
<div class="modal fade" id="deleteActivityConfirmModal" data-backdrop="static" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title" id="staticBackdropLabel">删除市场活动</h5><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button></div><div class="modal-body"><p id="confirmMessage"></p></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button><button type="button" class="btn btn-danger" id="deleteConfirmBtn">确认删除</button></div></div></div>
</div>
JavaScript代码如下:
// 给删除按钮绑定事件$("#deleteActivitiesBtn").on("click", function () {var checkedActivities;checkedActivities = $("#listBody input[type='checkbox']:checked");if (checkedActivities.length <= 0) {alert("不可以删除空气!")return;}const confirmMessage = "您确定要删除选中的 " + checkedActivities.length + " 条数据吗?";$("#confirmMessage").text(confirmMessage);$("#deleteActivityConfirmModal").modal("show");// 给删除确认按钮添加事件$("#deleteConfirmBtn").on("click", function () {// 定义一个数组,遍历选中的复选框,将其value值装入数组中var checkedActivitiesIdArray = [];checkedActivities.each(function () {checkedActivitiesIdArray.push(this.value);});console.log(checkedActivitiesIdArray);if (checkedActivities.length === checkedActivitiesIdArray.length) {// 用户确认删除,发送ajax请求$.ajax({url: "/crm-core/workbench/activity/deleteActivitiesByIdArray.do",type: "POST",contentType: 'application/json',data: JSON.stringify(checkedActivitiesIdArray),success: function (data) {console.log("ajax发送后的数组" + checkedActivitiesIdArray);if (data.code === "1") {checkedActivitiesIdArray = [];// 刷新市场活动的列表$("#searchBtn").click();$("#confirmMessage").text("");$("#deleteActivityConfirmModal").modal("hide");} else {alert(data.message);}}});} else {console.log("请求发送有问题!")}});});
原因分析
- ajax发送数组数据,后台接收不到的问题原因分析:
这个从报错信息中可以看出原因,大致意思就是说List是一个接口,Spring框架无法确定如何实例化 java.util.List 接口的实例对象,所以需要用有具体实现类的类作为参数;我在这里使用了ArrayList<String>来接收前端传递来的数组数据,经过亲自验证,使用String[]数组来接收参数也是可以的。 - 使用模态框时,点击一次发送两次请求的诡异问题原因分析:
查看日志:
# 第一次发送的删除请求
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3e15312a]
JDBC Connection [org.mariadb.jdbc.Connection@23c57da9] will be managed by Spring
==> Preparing: delete from tbl_activity where id in ( ? )
==> Parameters: 13e08e9d72ac4bd7abe4203abddb6a02(String)
<== Updates: 1
Creating a new SqlSession
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@27c6725c]
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1f9813e2]
JDBC Connection [org.mariadb.jdbc.Connection@3e9bbae4] will be managed by Spring
JDBC Connection [org.mariadb.jdbc.Connection@623508ea] will be managed by Spring
# 一次发了两个请求,其中一个还是上一次已经删除过了的数据
==> Preparing: delete from tbl_activity where id in ( ? )
==> Preparing: delete from tbl_activity where id in ( ? )
==> Parameters: 7ef3f93c34504922be02f3a9b1de4526(String)
==> Parameters: 13e08e9d72ac4bd7abe4203abddb6a02(String)
<== Updates: 0
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@27c6725c]
<== Updates: 1
查看Mybatis日志后发现,每次前端一旦点击删除确认按钮,就会在发送本次选中的删除的项目时,将前一次已经删过的数据又向后台发送了一遍,已经删除过了,所以后台肯定就会报错。由于前面使用window.confirm()时功能是正常的,所以可以断定后端代码是没有问题,问题就出在前端JavaScript中。
在查看了前面的JavaScript代码后,发现有以下问题:
将$(“#deleteConfirmBtn”).on(“click”, function () {});写在了$(“#deleteActivitiesBtn”).on(“click”, function () {});函数中,这就导致了在点击删除按钮时,内部再次为删除确认按钮绑定了点击事件。这意味着每次点击删除按钮时,都会为删除确认按钮重复添加一次事件监听器。如果多次点击删除按钮,点击删除确认按钮时会执行多次ajax请求。
解决方案:
- ajax发送数组数据,后台接收不到的问题解决方案:
避免使用List<>这种接口类型作为接收前端传递的数据,可以使用ArrayList<>集合或者String[]数组来接收前端传递的数组数据
@RequestMapping("/workbench/activity/deleteActivitiesByIdArray.do")@ResponseBodypublic Object deleteActivitiesByIdArray(@RequestBody ArrayList<String> ids){// 做了一些事情
}
@RequestMapping("/workbench/activity/deleteActivitiesByIdArray.do")@ResponseBodypublic Object deleteActivitiesByIdArray(@RequestBody String[] ids){// 做了一些事情
}
- 使用模态框时,点击一次发送两次请求的诡异问题解决方案
针对事件绑定类操作,避免嵌套,应该独立放置,否则就会导致重复绑定。调整后的JavaScript代码如下:
// 定义在外面
var checkedActivities;
var checkedActivitiesIdArray = [];
// 给删除按钮绑定事件
$("#deleteActivitiesBtn").on("click", function () {checkedActivities = $("#listBody input[type='checkbox']:checked");if (checkedActivities.length <= 0) {alert("不可以删除空气!")return;}const confirmMessage = "您确定要删除选中的 " + checkedActivities.length + " 条数据吗?";$("#confirmMessage").text(confirmMessage);$("#deleteActivityConfirmModal").modal("show");
});
// 给删除确认按钮添加事件
$("#deleteConfirmBtn").on("click", function () {// 定义一个数组,遍历选中的复选框,将其value值装入数组中checkedActivities.each(function () {checkedActivitiesIdArray.push(this.value);});console.log(checkedActivitiesIdArray);if (checkedActivities.length === checkedActivitiesIdArray.length) {// 用户确认删除,发送ajax请求$.ajax({url: "/crm-core/workbench/activity/deleteActivitiesByIdArray.do",type: "POST",contentType: 'application/json',data: JSON.stringify(checkedActivitiesIdArray),success: function (data) {console.log("ajax发送后的数组" + checkedActivitiesIdArray);if (data.code === "1") {checkedActivitiesIdArray = [];// 刷新市场活动的列表$("#searchBtn").click();$("#confirmMessage").text("");$("#deleteActivityConfirmModal").modal("hide");} else {alert(data.message);}}});} else {console.log("请求发送有问题!")}
});
});