All Projects → darrachequesne → Spring Data Jpa Datatables

darrachequesne / Spring Data Jpa Datatables

Licence: apache-2.0
Spring Data JPA extension to work with the great jQuery plugin DataTables (http://datatables.net/)

Programming Languages

java
68154 projects - #9 most used programming language

Projects that are alternatives of or similar to Spring Data Jpa Datatables

spring-boot-jpa
A Spring Boot microservices reference application using Spring Data JPA
Stars: ✭ 25 (-92.45%)
Mutual labels:  spring-data
spring-data-requery
Spring Data with Requery
Stars: ✭ 43 (-87.01%)
Mutual labels:  spring-data
Laravel Livewire Tables
A dynamic table component for Laravel Livewire
Stars: ✭ 288 (-12.99%)
Mutual labels:  datatables
movie-db-java-on-azure
Sample movie database app built using Java on Azure
Stars: ✭ 28 (-91.54%)
Mutual labels:  spring-data
spring-data-aerospike
Spring Data Aerospike
Stars: ✭ 33 (-90.03%)
Mutual labels:  spring-data
BusinessInfrastructurePlatformGroupVersion
A java web project based on Spring Boot using MySQL, Spring MVC, Hibernate, Spring Data JPA, Query DSL, Lombok, Logback, etc.
Stars: ✭ 90 (-72.81%)
Mutual labels:  spring-data
flask-tables
Beautiful Interactive tables in your Flask templates.
Stars: ✭ 147 (-55.59%)
Mutual labels:  datatables
Angularjs Springmvc Sample Boot
A RESTful sample using Spring Boot, Spring MVC, Spring Data and Angular/Bootstrap.
Stars: ✭ 309 (-6.65%)
Mutual labels:  spring-data
laravel-datatables-fractal
Laravel DataTables Fractal Plugin.
Stars: ✭ 94 (-71.6%)
Mutual labels:  datatables
Swiftdatatables
A Swift Data Table package, display grid-like data sets in a nicely formatted table for iOS. Subclassing UICollectionView that allows ordering, and searching with extensible options.
Stars: ✭ 287 (-13.29%)
Mutual labels:  datatables
spring-boot-mongodb-example
Spring Boot Using Spring Data MongoDB Example
Stars: ✭ 36 (-89.12%)
Mutual labels:  spring-data
Datatable
Modular server side Datatable package for Laravel 5 for various client side table plugins
Stars: ✭ 52 (-84.29%)
Mutual labels:  datatables
Spring Petclinic Rest
REST version of the Spring Petclinic sample application
Stars: ✭ 257 (-22.36%)
Mutual labels:  spring-data
jquery-datatables
Jquery datatables ruby gems for assets pipeline
Stars: ✭ 73 (-77.95%)
Mutual labels:  datatables
Spring Boot Spring Security Jwt Authentication
Spring Boot + Security: Token Based Authentication example with JWT, Authorization, Spring Data & MySQL
Stars: ✭ 292 (-11.78%)
Mutual labels:  spring-data
spring-data-mongodb-encrypt
Lightweight library for simple & easy per-field encryption in mongodb+spring
Stars: ✭ 62 (-81.27%)
Mutual labels:  spring-data
inline-gdocs-viewer
📊 Feature-rich WordPress plugin integrating Google Spreadsheets, Google Query Language, Google Charts API, and jQuery DataTables using a shortcode.
Stars: ✭ 12 (-96.37%)
Mutual labels:  datatables
Spring Data Cassandra
Provides support to increase developer productivity in Java when using Apache Cassandra. Uses familiar Spring concepts such as a template classes for core API usage and lightweight repository style data access.
Stars: ✭ 315 (-4.83%)
Mutual labels:  spring-data
Atom
Java course materials
Stars: ✭ 293 (-11.48%)
Mutual labels:  spring-data
Django Datatable View
Server-side datatable representations for Django querysets for automatic rendering in templates
Stars: ✭ 282 (-14.8%)
Mutual labels:  datatables

Build Status Maven Central

spring-data-jpa-datatables

This project is an extension of the Spring Data JPA project to ease its use with jQuery plugin DataTables with server-side processing enabled.

This will allow you to handle the Ajax requests sent by DataTables for each draw of the information on the page (i.e. when paging, ordering, searching, etc.) from Spring @RestController.

For a MongoDB counterpart, please see spring-data-mongodb-datatables.

Example:

@RestController
public class UserRestController {

  @Autowired
  private UserRepository userRepository;

  @RequestMapping(value = "/data/users", method = RequestMethod.GET)
  public DataTablesOutput<User> getUsers(@Valid DataTablesInput input) {
    return userRepository.findAll(input);
  }
}

Example

Contents

Maven dependency

<dependency>
  <groupId>com.github.darrachequesne</groupId>
  <artifactId>spring-data-jpa-datatables</artifactId>
  <version>5.1.0</version>
</dependency>

Back to top.

Getting started

Please see the sample project for a complete example.

1. Enable the use of DataTablesRepository factory

With either

@Configuration
@EnableJpaRepositories(repositoryFactoryBeanClass = DataTablesRepositoryFactoryBean.class)
public class DataTablesConfiguration {}

or its XML counterpart

<jpa:repositories factory-class="org.springframework.data.jpa.datatables.repository.DataTablesRepositoryFactoryBean" />

You can restrict the scope of the factory with @EnableJpaRepositories(repositoryFactoryBeanClass = DataTablesRepositoryFactoryBean.class, basePackages = "my.package.for.datatables.repositories"). In that case, only the repositories in the given package will be instantiated as DataTablesRepositoryImpl on run.

@Configuration
@EnableJpaRepositories(basePackages = "my.default.package")
public class DefaultJpaConfiguration {}

@Configuration
@EnableJpaRepositories(repositoryFactoryBeanClass = DataTablesRepositoryFactoryBean.class, basePackages = "my.package.for.datatables.repositories")
public class DataTablesConfiguration {}

2. Create a new entity

@Entity
public class User {

  private Integer id;

  private String mail;

  @ManyToOne
  @JoinColumn(name = "id_address")
  private Address address;

}

3. Extend the DataTablesRepository interface

public interface UserRepository extends DataTablesRepository<User, Integer> {}

The DataTablesRepository interface extends both PagingAndSortingRepository and JpaSpecificationExecutor.

4. On the client-side, create a new DataTable object

$(document).ready(function() {
  var table = $('table#sample').DataTable({
    'ajax' : '/data/users',
    'serverSide' : true,
    columns : [{
      data : 'id'
    }, {
      data : 'mail'
    }, {
      data : 'address.town',
      render: function (data, type, row) {
        return data || '';
      }
    }]
  });
}

5. Fix the serialization / deserialization of the query parameters

By default, the parameters sent by the plugin cannot be deserialized by Spring MVC and will throw the following exception: InvalidPropertyException: Invalid property 'columns[0][data]' of bean class [org.springframework.data.jpa.datatables.mapping.DataTablesInput].

There are multiple solutions to this issue:

It overrides jQuery data serialization to allow Spring MVC to correctly map input parameters (by changing column[0][data] into column[0].data in request payload)

  • retrieve data with POST requests

Client-side:

$('table#sample').DataTable({
  'ajax': {
    'contentType': 'application/json',
    'url': '/data/users',
    'type': 'POST',
    'data': function(d) {
      return JSON.stringify(d);
    }
  }
})

Server-side:

@RequestMapping(value = "/data/users", method = RequestMethod.POST)
public DataTablesOutput<User> getUsers(@Valid @RequestBody DataTablesInput input) {
  return userRepository.findAll(input);
}
  • manually serialize the query parameters
function flatten(params) {
  params.columns.forEach(function (column, index) {
    params['columns[' + index + '].data'] = column.data;
    params['columns[' + index + '].name'] = column.name;
    params['columns[' + index + '].searchable'] = column.searchable;
    params['columns[' + index + '].orderable'] = column.orderable;
    params['columns[' + index + '].search.regex'] = column.search.regex;
    params['columns[' + index + '].search.value'] = column.search.value;
  });
  delete params.columns;

  params.order.forEach(function (order, index) {
    params['order[' + index + '].column'] = order.column;
    params['order[' + index + '].dir'] = order.dir;
  });
  delete params.order;

  params['search.regex'] = params.search.regex;
  params['search.value'] = params.search.value;
  delete params.search;

  return params;
}

$('table#sample').DataTable({
  'ajax': {
    'url': '/data/users',
    'type': 'GET',
    'data': flatten
  }
})

Back to top.

API

The repositories now expose the following methods:

DataTablesOutput<T> findAll(DataTablesInput input);
DataTablesOutput<R> findAll(DataTablesInput input, Function<T, R> converter);
DataTablesOutput<T> findAll(DataTablesInput input, Specification<T> additionalSpecification);

DataTablesOutput<T> findAll(DataTablesInput input, Specification<T> additionalSpecification,
    Specification<T> preFilteringSpecification);

DataTablesOutput<R> findAll(DataTablesInput input, Specification<T> additionalSpecification,
    Specification<T> preFilteringSpecification, Function<T, R> converter);

Note: since version 2.0, QueryDSL is also supported:

  • replace DataTablesRepositoryFactoryBean with QDataTablesRepositoryFactoryBean
  • replace DataTablesRepository with QDataTablesRepository

and your repositories will now expose:

DataTablesOutput<T> findAll(DataTablesInput input);
DataTablesOutput<R> findAll(DataTablesInput input, Function<T, R> converter);
DataTablesOutput<T> findAll(DataTablesInput input, Predicate additionalPredicate);

DataTablesOutput<T> findAll(DataTablesInput input, Predicate additionalPredicate,
    Predicate preFilteringPredicate);

DataTablesOutput<R> findAll(DataTablesInput input, Predicate additionalPredicate,
    Predicate preFilteringPredicate, Function<T, R> converter);

Your controllers should be able to handle the parameters sent by DataTables:

@RestController
public class UserRestController {

  @Autowired
  private UserRepository userRepository;

  @JsonView(DataTablesOutput.View.class)
  @RequestMapping(value = "/data/users", method = RequestMethod.GET)
  public DataTablesOutput<User> getUsers(@Valid DataTablesInput input) {
    return userRepository.findAll(input);
  }

  // or with some preprocessing
  @JsonView(DataTablesOutput.View.class)
  @RequestMapping(value = "/data/users", method = RequestMethod.GET)
  public DataTablesOutput<User> getUsers(@Valid DataTablesInput input) {
    ColumnParameter parameter0 = input.getColumns().get(0);
    Specification additionalSpecification = getAdditionalSpecification(parameter0.getSearch().getValue());
    parameter0.getSearch().setValue("");
    return userRepository.findAll(input, additionalSpecification);
  }

  // or with an additional filter allowing to 'hide' data from the client (the filter will be applied on both the count and the data queries, and may impact the recordsTotal in the output)
  @JsonView(DataTablesOutput.View.class)
  @RequestMapping(value = "/data/users", method = RequestMethod.GET)
  public DataTablesOutput<User> getUsers(@Valid DataTablesInput input) {
    return userRepository.findAll(input, null, removeHiddenEntitiesSpecification);
  }
}

The DataTablesInput class maps the fields sent by the client (listed there).

Spring documentation for Specification

How to

Apply filters

By default, the main search field is applied to all columns.

You can apply specific filter on a column with table.columns(<your column id>).search(<your filter>).draw(); (or table.columns(<your column name>:name)...) (see documentation).

Supported filters:

  • Strings (WHERE <column> LIKE %<input>%)
  • Booleans
  • Array of values (WHERE <column> IN (<input>) where input is something like 'PARAM1+PARAM2+PARAM4')
  • NULL values are also supported: 'PARAM1+PARAM3+NULL' becomes WHERE (<column> IN ('PARAM1', 'PARAM3') OR <column> IS NULL) (to actually search for 'NULL' string, please use \NULL)

Also supports paging and sorting.

Example:

{
  "draw": 1,
  "columns": [
    {
      "data": "id",
      "name": "",
      "searchable": true,
      "orderable": true,
      "search": {
        "value": "",
        "regex": false
      }
    },
    {
      "data": "firstName",
      "name": "",
      "searchable": true,
      "orderable": true,
      "search": {
        "value": "",
        "regex": false
      }
    },
    {
      "data": "lastName",
      "name": "",
      "searchable": true,
      "orderable": true,
      "search": {
        "value": "",
        "regex": false
      }
    }
  ],
  "order": [
    {
      "column": 0,
      "dir": "asc"
    }
  ],
  "start": 0,
  "length": 10,
  "search": {
    "value": "john",
    "regex": false
  }
}

is converted into the following SQL (through the Criteria API):

SELECT user0_.id AS id1_0_0_,
       user0_.first_name AS first_na3_0_0_,
       user0_.last_name AS last_nam4_0_0_
FROM users user0_
WHERE (user0_.id LIKE "%john%" OR user0_.first_name LIKE "%john%" OR user0_.last_name LIKE "%john%")
ORDER BY user0_.id ASC LIMIT 10

Note: the regex flag is currently ignored because JPQL only supports LIKE expressions (with % and _ tokens).

Yet you should be able to use the DBMS-specific regex operator with the CriteriaBuilder.function() method.

Example with H2 REGEXP_LIKE:

Column column = input.getColumn("my_column");
column.setSearchable(false); // so the default filter will not be applied
String regexValue = column.getSearch().getValue();
DataTablesOutput<...> output = repository.findAll(input, (root, query, builder) -> {
  Expression<String> regex = builder.function("REGEXP_LIKE", String.class, root.get("my_column"), builder.literal(regexValue));
  return builder.equal(regex, builder.literal(1));
});

Manage non-searchable fields

If you have a column that does not match an attribute on the server-side (for example, an 'Edit' button), you'll have to set the searchable and orderable attributes to false.

$(document).ready(function() {
  var table = $('table#sample').DataTable({
    'ajax' : '/data/users',
    'serverSide' : true,
    columns : [{
      data: 'id'
    }, {
      data: 'mail'
    }, {
      searchable: false,
      orderable: false
    }]
  });
}

Limit the exposed attributes of the entities

There are several ways to restrict the attributes of the entities on the server-side:

  • using DTO
@RestController
public class UserRestController {

  @Autowired
  private UserRepository userRepository;

  @RequestMapping(value = "/data/users", method = RequestMethod.GET)
  public DataTablesOutput<UserDTO> getUsers(@Valid DataTablesInput input) {
    return userRepository.findAll(input, toUserDTO);
  }
}
  • using @JsonView
@Entity
public class User {

  @JsonView(DataTablesOutput.View.class)
  private Integer id;

  // ignored
  private String mail;

}

@RestController
public class UserRestController {

  @Autowired
  private UserRepository userRepository;

  @JsonView(DataTablesOutput.View.class)
  @RequestMapping(value = "/data/users", method = RequestMethod.GET)
  public DataTablesOutput<User> getUsers(@Valid DataTablesInput input) {
    return userRepository.findAll(input);
  }
}

  • using @JsonIgnore
@Entity
public class User {

  private Integer id;

  @JsonIgnore
  private String mail;

}

Search on a rendered column

Let's say you have an User entity with two attributes, firstName and lastName.

To display the rendered column on the client-side:

$('table#sample').DataTable({
  ajax: '/data/users',
  serverSide: true,
  columns : [
    {
      data: 'fullName',
      render: (_, __, row) => `${row.firstName} ${row.lastName}`,
      searchable: false,
      orderable: false
    }
  ]
});

Both searchable and orderable option are necessary, because the User entity has nofullName attribute.

To filter on the server-side, you'll have to manually create the matching specification:

@RequestMapping(value = "/data/users", method = RequestMethod.GET)
public DataTablesOutput<User> list(@Valid DataTablesInput input) {
    String searchValue = escapeContent(input.getSearch().getValue());
    input.getSearch().setValue(""); // prevent search on other fields

    Specification<User> fullNameSpecification = (Specification<User>) (root, query, criteriaBuilder) -> {
        if (!hasText(searchValue)) {
            return null;
        }
        String[] parts = searchValue.split(" ");
        Expression<String> firstNameExpression = criteriaBuilder.lower(root.get("firstName"));
        Expression<String> lastNameExpression = criteriaBuilder.lower(root.get("lastName"));
        if (parts.length == 2 && hasText(parts[0]) && hasText(parts[1])) {
            return criteriaBuilder.or(
                    criteriaBuilder.and(
                            criteriaBuilder.equal(firstNameExpression, parts[0]),
                            criteriaBuilder.like(lastNameExpression, parts[1] + "%", '~')
                    ),
                    criteriaBuilder.and(
                            criteriaBuilder.equal(lastNameExpression, parts[0]),
                            criteriaBuilder.like(firstNameExpression, parts[1] + "%", '~')
                    )
            );
        } else {
            return criteriaBuilder.or(
                    criteriaBuilder.like(firstNameExpression, searchValue + "%", '~'),
                    criteriaBuilder.like(lastNameExpression, searchValue + "%", '~')
            );
        }
    };
    return userRepository.findAll(input, fullNameSpecification);
}

private String escapeContent(String content) {
    return content
            .replaceAll("~", "~~")
            .replaceAll("%", "~%")
            .replaceAll("_", "~_")
            .trim()
            .toLowerCase();
}

You can find a complete example here.

Back to top.

Use with the SearchPanes extension

Server-side:

@RestController
@RequiredArgsConstructor
public class UserRestController {
  private final UserRepository userRepository;

  @RequestMapping(value = "/data/users", method = RequestMethod.GET)
  public DataTablesOutput<User> getUsers(@Valid DataTablesInput input, @RequestParam Map<String, String> queryParams) {
    input.parseSearchPanesFromQueryParams(queryParams, Arrays.asList("position", "status"));
    return userRepository.findAll(input);
  }
}

Client-side:

$(document).ready(function() {
  var table = $('table#sample').DataTable({
    ajax : '/data/users',
    serverSide: true,
    dom: 'Pfrtip',
    columns : [{
      data : 'id'
    }, {
      data : 'mail'
    }, {
      data : 'position'
    }, {
      data : 'status'
    }]
  });
}

Regarding the deserialization issue detailed above, here is the compatibility matrix:

Solution Compatibility with the SearchPanes extension
jquery.spring-friendly.js YES
POST requests NO
flatten() method NO

Troubleshooting

  • Invalid property 'columns[0][data]' of bean class [org.springframework.data.jpa.datatables.mapping.DataTablesInput]

Please see here.

  • java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name ...

It seems you have a column with a data attribute that does not match the attribute of the @Entity on the server-side.

Please see here.

  • java.lang.NoClassDefFoundError: org/hibernate/jpa/criteria/path/AbstractPathImpl

The versions >= 5.0.0 of the library are not compatible with Spring 4 (Spring Boot 1.x), please use the previous versions.

Back to top.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].