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(&apos;'+ row.socialSecurity +'&apos;);">' + 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 感谢您的评论。是的,我现在正按计划进行这项工作,尽管无论出于何种原因,我的应用程序似乎都想要使用这些变量的旧(我认为)版本,即使我正在为数据表&lt;version&gt;1.10.19&lt;/version&gt; 拉入 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&lt;Person&gt;,还需要返回具有某些参数的格式良好的 JSON 对象,Datatable 使用这些参数来决定在屏幕上显示多少页,以及数据 List&lt;Person&gt; 本身。

@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&lt;Person&gt;,而且还有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

还有 ma​​pTableBean 方法,它通过实际调用数据库来获取记录,从而将它们拉到一起。

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(&apos;'+ row.socialSecurity +'&apos;);">' + 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. @Ruhshan 我一直在想 'enter' 会给我一个换行符,但它一直在制作新的 cmets。对不起。无论如何....然后我创建了一个具体类,该类扩展了该抽象类以用于我想与我的数据表一起使用的任何东西。所以,在这个例子中,我制作了一个“PersonDatabase”或必须满足搜索方法的东西。所以无论如何,我会查看传入的排序名称,然后根据实际 DB2 数据库中的列名创建一个 asc 或 desc 的“order by 子句”。我所有的 DB2 调用都设置为使用 MyBatis,因此我将该字符串设置为 order by 子句..... @Ruhshan 然后从传入的数值中找出我需要从哪一行开始以及我需要在哪一行结束,并将其作为标准添加到我的数据库调用中(我有一个自定义的 MyBatis 映射器来处理这些值)。然后我使用 MyBatis 使用自定义 SQL 语句调用数据库,该语句选择所有结果,按条件排序,然后仅返回这些结果中的 #s x 到 y。然后,该“搜索”方法的其余部分只是映射并从数据库返回这些结果。 @Ruhshan 至于自定义 SQL 是如何在 mybatis 中编写的——我以前经常这样做。类似于 SELECT * FROM (SELECT FAKE.*, ROW_NUMBER() OVER ([insert order by clause here]) AS RANK FROM ([insert normal select statement for ALL records here]) AS FAKE) WHERE RANK BETWEEN [insert first number此处] 和 [在此处插入第二个数字]

以上是关于Spring 中具有服务器端处理的 JQuery DataTables(使用 Java 和 Thymeleaf)的主要内容,如果未能解决你的问题,请参考以下文章

jQuery中数据表插件的服务器端处理失败

无法通过服务器端处理显示 jQuery Datatable 的数据

Jquery 插件,如 dataable,但根据需要具有 ajax 分页和服务器端搜索

如何在没有 Ajax 的 JQuery DataTables 中使用服务器端处理

使用 jquery 数据表进行服务器端处理,包括分页、过滤和搜索

JQuery DataTable 标头未与 ScrollY 和服务器端处理对齐