Spring 中具有服务器端处理的 JQuery DataTables(使用 Java 和 Thymeleaf)
Posted
技术标签:
【中文标题】Spring 中具有服务器端处理的 JQuery DataTables(使用 Java 和 Thymeleaf)【英文标题】:JQuery DataTables with ServerSide Processing in Spring (with Java and Thymeleaf) 【发布时间】:2019-09-02 15:56:16 【问题描述】:我有一个使用 Java 和 Thymeleaf 的 Spring Boot 应用程序。在其中,我有一个带有 JQuery DataTable 的页面。该表中有数千行,因此虽然我目前只是将其全部放在页面上并让 JQuery 完全处理它,但我想切换到使用 ServerSide 处理来进行分页、排序等。
我设置为在后端执行此操作,在那里我可以将开始和结束行号以及动态排序信息传递给我的 SQL 查询。但是,我的问题是,当用户单击“下一页”按钮或排序按钮时,我无法准确弄清楚 DataTables 是如何尝试通过控制器通知我的。 我的控制器在哪里/如何获取此信息,以便我可以将其插入我的 SQL 查询并返回必要的信息?
所以,假设我有一个示例对象,比如“人”。
public class Person
private static String PERSON_IMAGE = "<img th:src=\"@/images/personimage.png\" alt=\"icon\"/>";
private static String PERSON_IMAGE_2 = "<img th:src=\"@/images/personimage2.png\" alt=\"icon\"/>";
private String name;
private String socialSecurity;
private String birthdate;
private String gender;
private String personImage;
public String getName()
return name;
public void setName(String name)
this.name = name;
public String getSocialSecurity()
return socialSecurity;
public void setSocialSecurity(String socialSecurity)
this.socialSecurity = socialSecurity;
public String getBirthdate()
return birthdate;
public void setBirthdate(String birthdate)
this.birthdate = birthdate;
public String getGender()
return gender;
public void setGender(String gender)
this.gender = gender;
public String getPersonImage()
if(null != birthdate)
return PERSON_IMAGE;
else
return PERSON_IMAGE_2;
所以,在我的 Thymeleaf HTML 页面上,我曾经有以下内容:(版本 1)
<table id="myDataTable" class="dataTable display compact">
<thead>
<tr>
<th>Name</th>
<th>Social Security</th>
<th>Birthdate</th>
<th>Gender</th>
</tr>
</thead>
<tbody>
<tr th:each="person : $peopleList">
<td th:attr="data-order=$person.name"><a th:href="|javascript:openPersonDetail('$person.socialSecurity');|"><span th:text="$person.name">name</span></a></td>
<td th:text="$person.socialSecurity">socialSecurity</td>
<td class="dt-body-center" th:text="$person.birthdate">birthdate</td>
<td class="dt-body-center" th:text="$person.gender">gender</td>
</tr>
</tbody>
</table>
我的页面controller很简单,只是传入了一个完整的人员列表,如下:(Version 1)
@GetMapping("/ApplicationName/MyDataTablePage")
public String myDataTablePage(Model model)
SearchCriteria searchCriteria = new SearchCriteria();
searchCriteria.setOrderByString("name");
searchCriteria.setUsePaging(false);
searchCriteria.setFIRST_NUMBER(null);
searchCriteria.setLAST_NUMBER(null);
model.addAttribute("peopleList", myMapperService.getPeople(searchCriteria)); //returns an ArrayList<Person>
return "/pages/MyDataTablePage";
最后,我的 Javascript 曾经有以下内容:(版本 1)
$(document).ready(function()
$('#myDataTable').DataTable(
"destroy" : true,
"scrollY" : 300,
"scrollCollapse" : true,
"paging" : true,
"autoWidth" : true,
"ordering" : true,
"searching" : false,
"order" : [ [ 0, 'asc' ] ],
"pageLength" : 20,
"lengthChange" : false,
"pagingType" : "full_numbers",
"dom" : '<"top"ip>rt<"bottom"fl><"clear">'
);
);
这可行,但我想切换 DataTable 以使用服务器端处理,因此第一步是(1)更改主页控制器以停止将人员列表附加到模型,(2)创建一个新的控制器,它将我的 ArrayList 作为 JSON 返回给 DataTable 自己调用,(3) 将主页的 html 更改为不再向 DataTable 提供任何 tbody 标记,以及 (4)更改创建表的 javascript 以自行放入数据。
================================================ ===============
这让我想到了以下元素的第 2 版。
我更改后的 Thymeleaf HTML 页面:(第 2 版)
<table id="myDataTable" class="dataTable display compact">
<thead>
<tr>
<th>Name</th>
<th>Social Security</th>
<th>Birthdate</th>
<th>Gender</th>
</tr>
</thead>
</table>
我更改的页面控制器:(版本2)
@GetMapping("/ApplicationName/MyDataTablePage")
public String myDataTablePage(Model model)
return "/pages/MyDataTablePage";
我的新数据表控制器:
@RequestMapping(path = "/ApplicationName/Data/Person", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public List<Person> getPersonData()
System.out.println("I was called!");
SearchCriteria searchCriteria = new SearchCriteria();
searchCriteria.setOrderByString("name");
searchCriteria.setUsePaging(false);
searchCriteria.setFIRST_NUMBER(null);
searchCriteria.setLAST_NUMBER(null);
return myMapperService.getPeople(searchCriteria); //returns an ArrayList<Person>
我更改的(更复杂的)Javascript:(第 2 版)
$(document).ready(function ()
$('#myDataTable').DataTable(
'destroy' : true,
'serverSide' : true,
'sAjaxSource': '/ApplicationName/Data/Person',
'sAjaxDataProp': '',
'order': [ [ 0, 'asc' ] ],
'columns':
[
'data': 'name',
'render': function(data, type, row, meta)
if(type === 'display')
data = '<a href="javascript:openPersonDetail(''+ row.socialSecurity +'');">' + data + '</a>'
return data;
,
'data': 'socialSecurity' ,
'data': 'birthdate' ,
'data': 'gender'
],
'scrollY' : 300,
'scrollCollapse' : true,
'paging' : true,
'autoWidth' : true,
'ordering' : true,
'searching' : false,
'pageLength' : 20,
'lengthChange' : false,
'pagingType' : 'full_numbers',
'dom' : '<"top"ip>rt<"bottom"fl><"clear">'
);
);
================================================ ===============
这也有效。特别是如果我删除“'serverSide':true”配置 - DataTable 的工作方式与我进行上述更改之前完全一样,但现在它调用我的新 Data JSON 控制器而不是使用模型属性。
问题:
但是,将“'serverSide' : true”配置添加到 DataTable 时,每次我转到表的新“页面”时,它都会调用我的“/Clinic/Detail/Data/Patient”控制器每一次。
但是,我需要 DataTables 来传递有关用户选择的页面或用户尝试排序的列的控制器信息。我还需要(动态地)传递表格的总行数(计数),以便 DataTables 可以为用户正确设置页码。
DataTables 是否向我的控制器发送了一些我可以获取的附加参数? @PathVariable("whatever") 之类的东西?
我觉得我离让这项工作如此接近,但我被困在这一点上。
非常感谢任何帮助!
【问题讨论】:
当您指定serverside: true
时,draw
请求中会包含其他参数,请参阅docs。如果您查看浏览器工具的网络面板,您将能够在数据表加载时看到这一点。这些参数是您的控制器需要使用的。
@markpsmith 感谢您的评论。是的,我现在正按计划进行这项工作,尽管无论出于何种原因,我的应用程序似乎都想要使用这些变量的旧(我认为)版本,即使我正在为数据表<version>1.10.19</version>
拉入 webjars。无论如何,一旦我让它完全工作,我计划发布我自己问题的答案。假设一切顺利...
@markpsmith 既然您似乎知道您在说什么,也许您可以帮我解决另一个问题?我一直在浏览有关 DataTables 的文档,但找不到它。有没有办法将自定义排序参数字符串添加到列列表中,类似于以下示例.... 'columns': [ 'data' : 'name', 'customSortableParamer' : 'FIRST_NAME' , 'data' : 'gender' ],并在用户排序的请求参数中将该值返回给我?
您能补充一下您使用的DataTable、Spring-Boot、Java、Thymeleaf等版本吗?
【参考方案1】:
我找到了 2 个解决方案:
-
看这个帖子:Spring Boot + Bootstrap + Thymeleaf Datatable
<script>
$('#example').DataTable(
"processing": true,
"serverSide": true,
"ajax":
"url": "/employees",
"type": "POST",
"dataType": "json",
"contentType": "application/json",
"data": function (d)
return JSON.stringify(d);
,
"columns": [
"data": "name", "width": "20%",
"data": "position","width": "20%",
"data": "office", "width": "20%",
"data": "start_date", "width": "20%",
"data": "salary", "width": "20%"
]
);
</script>
-
看这个github项目:spring-data-jpa-datatables
【讨论】:
【参考方案2】:我已经设法解决了我的问题。
我的表格HTML保持不变,如下:
<table id="myDataTable" class="dataTable display compact">
<thead>
<tr>
<th>Name</th>
<th>Social Security</th>
<th>Birthdate</th>
<th>Gender</th>
</tr>
</thead>
</table>
我的页面控制器保持不变,如下:
@GetMapping("/ApplicationName/MyDataTablePage")
public String myDataTablePage(Model model)
return "/pages/MyDataTablePage";
我的表格控制器,用于检索需要更改的数据。首先,它需要获取数据表正在发送的@RequestParam
,其中包括我需要显示的记录数、需要排序的记录以及正在排序的单元格所需的所有参数。其次,它不仅需要返回 List<Person>
,还需要返回具有某些参数的格式良好的 JSON 对象,Datatable 使用这些参数来决定在屏幕上显示多少页,以及数据 List<Person>
本身。
@RequestMapping(path = "/ApplicationName/Data/Person", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public TableBean getPersonData(@RequestParam Map<String, String> allRequestParams)
TableParameters tableParameters = myMapperService
.getTableParameters(allRequestParams);
return myMapperService.mapTableBean(tableParameters); //returns an TableBean which contains the ArrayList<Person>
为了让它工作,我需要创建一些新的类和方法。首先,我制作了一个 TableParameters bean,它包含我的数据表的所有参数:
public class TableParameters implements Serializable
private static final long serialVersionUID = 1L;
private Integer iDisplayStart;
private Integer iDisplayLength;
private Integer iColumns;
private String sSearch;
private Boolean bRegex;
private Integer iSortingCols;
private Integer sEcho;
private Integer sortedColumnNumber;
private String sortedColumnName;
private String sortedColumnDirection;
// ... getters and setters
然后我需要创建一个方法,将这些变量从请求参数中提取出来并将它们映射到新的 bean。这告诉我在我的 SQL 中使用什么,就如何排序,以及从哪些数字开始和返回。
private static String iDisplayLength = "iDisplayLength";
private static String iDisplayStart = "iDisplayStart";
private static String iColumns = "iColumns";
private static String sSearch = "sSearch";
private static String bRegex = "bRegex";
private static String iSortingCols = "iSortingCols";
private static String sEcho = "sEcho";
private static String iSortCol_0 = "iSortCol_0";
private static String sSortDir_0 = "sSortDir_0";
public TableParameters getTableParameters(Map<String, String> allRequestParams)
TableParameters finalBean = new TableParameters();
if (null != allRequestParams.get(bRegex))
finalBean.setbRegex(new Boolean(allRequestParams.get(bRegex)));
if (null != allRequestParams.get(iColumns))
finalBean.setiColumns(new Integer(allRequestParams.get(iColumns)));
if (null != allRequestParams.get(iDisplayLength))
finalBean.setiDisplayLength(new Integer(allRequestParams.get(iDisplayLength)));
if (null != allRequestParams.get(iDisplayStart))
finalBean.setiDisplayStart(new Integer(allRequestParams.get(iDisplayStart)));
if (null != allRequestParams.get(iSortingCols))
finalBean.setiSortingCols(new Integer(allRequestParams.get(iSortingCols)));
if (null != allRequestParams.get(sEcho))
try
finalBean.setsEcho(new Integer(allRequestParams.get(sEcho)));
catch (Exception e)
// ignore
if (null != allRequestParams.get(sSearch))
finalBean.setsSearch(allRequestParams.get(sSearch));
int numberOfColumnsSortedOn = 0;
if (allRequestParams.containsKey(iSortingCols) && null != allRequestParams.get(iSortingCols))
numberOfColumnsSortedOn = new Integer(allRequestParams.get("iSortingCols")).intValue();
if (numberOfColumnsSortedOn > 0)
if (null != allRequestParams.get(iSortCol_0))
finalBean.setSortedColumnNumber(new Integer(allRequestParams.get(iSortCol_0)));
if (null != allRequestParams.get(sSortDir_0))
finalBean.setSortedColumnDirection(allRequestParams.get(sSortDir_0));
String keyForName = "mDataProp_" + finalBean.getSortedColumnNumber();
if (null != allRequestParams.get(keyForName))
finalBean.setSortedColumnName(allRequestParams.get(keyForName).toUpperCase());
return finalBean;
同样,Return json object TableBean需要被创建,它不仅有一个List<Person>
,而且还有datatable用来向用户显示页码的必要计数信息。这是现在在表控制器方法中返回的内容,而不仅仅是基本列表。
public class TableBean implements Serializable
private static final long serialVersionUID = 1L;
private int iTotalRecords;
private int iTotalDisplayRecords;
private String sEcho;
private List data;
public TableBean(int iTotalRecords, int iTotalDisplayRecords, String sEcho, List data)
super();
this.iTotalRecords = iTotalRecords;
this.iTotalDisplayRecords = iTotalDisplayRecords;
this.sEcho = sEcho;
this.data = data;
// ... getters and setters
还有 mapTableBean 方法,它通过实际调用数据库来获取记录,从而将它们拉到一起。
public TableBean mapTableBean(TableParameters tableParameters)
int iTotalRecords = database.getCount();
int iTotalDisplayRecords = iTotalRecords;
if (null != tableParameters.getsEcho())
String sEcho = tableParameters.getsEcho().toString();
List data = database.search(tableParameters.getiDisplayStart(), tableParameters.getiDisplayLength(),
tableParameters.getSortedColumnName(),
tableParameters.getSortedColumnDirection().equalsIgnoreCase("asc"));
return new TableBean(iTotalRecords, iTotalDisplayRecords, sEcho, data);
最后,必须更改 Javascript,以便 DataTables 知道通过添加 'sAjaxDataProp': 'data',
配置来查看表本身中显示的 Bean 列表中标题为“数据”的变量。
$(document).ready(function ()
$('#myDataTable').DataTable(
'destroy' : true,
'serverSide' : true,
'sAjaxSource': '/ApplicationName/Data/Person',
'sAjaxDataProp': 'data',
'order': [ [ 0, 'asc' ] ],
'columns':
[
'data': 'name',
'render': function(data, type, row, meta)
if(type === 'display')
data = '<a href="javascript:openPersonDetail(''+ row.socialSecurity +'');">' + data + '</a>'
return data;
,
'data': 'socialSecurity' ,
'data': 'birthdate' ,
'data': 'gender'
],
'scrollY' : 300,
'scrollCollapse' : true,
'paging' : true,
'autoWidth' : true,
'ordering' : true,
'searching' : false,
'pageLength' : 20,
'lengthChange' : false,
'pagingType' : 'full_numbers',
'dom' : '<"top"ip>rt<"bottom"fl><"clear">'
);
);
瞧,现在服务器端处理功能正常。
【讨论】:
@Ruhshan 我实现这个已经很长时间了,但让我们看看我能记住什么。 @Ruhshan 我相信我创建了一个名为“数据库”的抽象类,上面有许多方法,包括一个“搜索”方法,它获取所有显示的传入值并返回一个 List以上是关于Spring 中具有服务器端处理的 JQuery DataTables(使用 Java 和 Thymeleaf)的主要内容,如果未能解决你的问题,请参考以下文章
无法通过服务器端处理显示 jQuery Datatable 的数据
Jquery 插件,如 dataable,但根据需要具有 ajax 分页和服务器端搜索
如何在没有 Ajax 的 JQuery DataTables 中使用服务器端处理