Giter Club home page Giter Club logo

spring-boot-rest-api-helpers's Introduction

spring-boot-rest-api-helpers

Java >= 8 (thanks to davidegironi)

Inspired by built-in fake REST data provider react-admin (see documentation) that queries like that:

    GET /movies?filter={id: 1} //get movies by id = 1
    GET /movies?filter={id: [1,2]} // get movies by id = 1 or id = 2
    GET /actors?filter={movies: 1, firstName: John} = //actors played in movie with id = 1 and their first  name is John
    GET /actors?filter={birthYearGt: 1960}&sort=[id,DESC]&range=[0,100] // actors born later than 1960
    GET /actors?filter={q: %Keanu Re%} // full text search on all text fields
    GET /actors?sort=[firstName,DESC,birthDate,ASC] //sort by multiple fields in case of ties

More Inspiration was drawn from languages like FIQL/RSQL so recently more features were added along with in-memory integration tests, support for non-number primary keys, resulting in a total refactoring of the code and fix of a lot of bugs (there are still some edge cases).

Now it is possible to also do the following (after url-encode of the query part of the url):

    GET /movies?filter={idNot: 1} //get movies with id not equal to 1
    GET /actors?filter={movies: null} = //actors that have played in no movie
    GET /actors?filter={moviesNot: null} = //actors that have played to a movie
    GET /actors?filter={movies: [1,2]} = //actors played in either movie with id = 1, or movie with id = 2
    GET /actors?filter={moviesAnd: [1,2]} = //actors played in both movies with id = 1 and id = 2
    GET /actors?filter={moviesNot: [1,2]} = //actors played in neither movie with id = 1, nor movie with id = 2
    GET /actors?filter={name: Keanu Ree%} // full text search on specific fields just by the inclusion of one or two '%' in the value

    GET /actors?filter={movies: {name: Matrix}} = //actors that have played in movie with name Matrix
    GET /actors?filter={movies: {name: Matrix%}} = //actors that have played in movies with name starting with Matrix
    GET /movies?filter={actors: {firstName: Keanu, lastNameNot: Reves}} = //movies with actors that firstName is 'Keanu' but lastName is not 'Reves'

    GET /actors?filter=[{firstName: Keanu},{firstName: John}] = //actors with firstName  'Keanu' or 'John'
    GET /actors?filter={firstName: [Keanu, John]} = //equivalent to the above
    
    GET /documents?filter={uuid: f44010c9-4d3c-45b2-bb6b-6cac8572bb78} // get document with java.util.UUID equal to f44010c9-4d3c-45b2-bb6b-6cac8572bb78
    GET /libraries?filter={documents: {uuid: f44010c9-4d3c-45b2-bb6b-6cac8572bb78}} // get libraries that contain document with uuid equal to f44010c9-4d3c-45b2-bb6b-6cac8572bb78
    GET /libraries?filter={documents: f44010c9-4d3c-45b2-bb6b-6cac8572bb78} // same as above

    GET /actors?filter={birthDateGt: '1960-01-01'}&sort=[id,DESC]&range=[0,100] // actors born later than 1960-01-01
    GET /actors?filter={birthDateGt: '1960-01-01T00:00:00'}&sort=[id,DESC]&range=[0,100] // actors born later than 1960-01-01 00:00:00 (database timezone - UTC recommended)

The key names are not the ones on the database but the ones exposed by the REST API and are the names of the entity attribute names. Here movies is plural because an Actor has @ManyToMany annotation on List<Movie> movies attribute.

  • Keep in mind that key/value pairs that are in { } are combined by default with AND.
/actors?filter={firstName:'A',lastName:'B'} => firstName = A and lastName = B
  • Values or Objects that contain key/values in [] are combined by default with OR unless the key in front of the [] is ending with 'And'.
/actors?filter={movies: [1,2]} => actors having acted at movies with ids 1 OR 2 
/movies?filter={actors: [{firstName:'A'}, {lastName:'B'}] } => movies having actors with firstName = A OR lastName = B
/actors?filter={moviesAnd: [1,2]} => actors acted at movies with ids 1 AND 2 
/movies?filter={actorsAnd: [{firstName:'A'}, {lastName:'B'}] } => movies having actors with firstName = A AND lastName = B
  • Disabling distinct search can have some performance boost sometimes - Warning it will return duplicate entries
/actors?filter={movies: 1, firstName: John}
/actors?filter={movies: 1, firstName: John, allowDuplicates: true}

Important: Keep in mind that the object/array that is passed in filter needs to be url encoded for the request to work. E.g in Javascript someone would use encodeURIComponent() like that

let filterObj = {movies: [1,2]};
fetch('/actors?filter=' + encodeURIComponent(JSON.stringify(filterObj)));

The above functionality is possible via this simple setup:

@RestController
@RequestMapping("actors")
public class ActorController {

    @Autowired
    private ActorRepository repository;

    @Autowired
    private FilterService<Actor, Long> filterService;

    @GetMapping
    public Iterable<Actor> filterBy(
            @RequestParam(required = false, name = "filter") String filterStr,
            @RequestParam(required = false, name = "range") String rangeStr, 
            @RequestParam(required = false, name="sort") String sortStr) {

        QueryParamWrapper wrapper = QueryParamExtractor.extract(filterStr, rangeStr, sortStr);
        return filterService.filterBy(wrapper, repository, Arrays.asList("firstName", "lastName"));
    }
}

The main important parts include:

  • @ControllerAdvices that wrap Collections in objects {content: []) with paging and number of results information along with Status Codes based on Exceptions thrown and returns 404 in case of null returned from endpoints.
  • BaseRepository interface that needs to be extended by each of resource Repositories
  • CustomSpecifications does all the magic of Criteria API query generation so that filtering and sorting works along with FilterService that provides some helper methods to the Controller code and helps provide convert the String query params to FilterWrapper so that it can be injected behind the scenes.
  • ObjectMapperProvider that can be used by the Spring Boot Application in case serialization and deserialization need to work through fields instead of Getters and Setters
  • you need to create classes annotated with @ControllerAdvice and extend the appropriate classes under package springboot.rest.controllerAdvices if needed in your project

Installation

For now installation is done through jitpack:

Add this in your pom.xml repositories:

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
    ...
</repositories>

and add this as a dependency in your pom.xml dependencies:

    <dependency>
        <groupId>com.github.zifnab87</groupId>
        <artifactId>spring-boot-rest-api-helpers</artifactId>
        <version>edb1770</version> <!-- or latest short commit id -->
    </dependency>

Usage

  • Add springboot.rest package in the scanBasePackages at the top of your Spring Boot Application class
@SpringBootApplication(scanBasePackages = {"com.myproject", springbootrest});
  • configure application.properties to use snake-case or camelCase for properties in API
spring-boot-rest-api-helpers.use-snake-case = false
  • for each of the Rest API resources create a class XYZ that is annotated with @Entity
  • for each of the Rest API resources create an interface XYZRepository that extends BaseRepository<XYZ,KeyType>
  • for each of the Rest API resources create a class XYZController annotated with @RestController
  • for each of Value object annotate them with with com.nooul.apihelpers.springbootrest.annotations.ValueObject. See Sender with Mobile and MobileConverter in test helpers. They should behave like plain strings. No comparisons are supported with Gte/Lte/Gt/Lt yet

for more examples see/run the integration tests Note: three-level join tests are failing and are not implemented yet - Any help towards an implementation that allows any number of depth for queries would be greatly appreciated :D

Previous Versions

This repo used to be called react-admin-java-rest and it was used to provide the needed building blocks for building a real backend API like that can give responses to the above requests in conjunction with react-admin/admin-on-rest (used here together: https://github.com/zifnab87/admin-on-rest-demo-java-rest). Since the time of their first incarnation, it seemed obvious that those API helpers were useful outside of the react-admin REST API realm, so the name spring-boot-rest-api-helpers was given.

Fully working example (outdated)

For an example of how it can be used along admin-on-rest there is a fork of admin-on-rest-demo that is fully working and uses react-admin-java-rest

Fully Working Fork of admin-on-rest-demo: react-admin-demo-java-rest

Release Notes

  • 0.9.0 - Support for Instant fields on Entities for date and date time range comparisons similar to Timestamp querying
  • 0.10.0 - Support for Value Objects that can be used in search with q, exact match and search by null

spring-boot-rest-api-helpers's People

Contributors

billk97 avatar jazcarate avatar zifnab87 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

spring-boot-rest-api-helpers's Issues

Spring security issue

Hi
Thank for making this code available. It was a big help for me. there are some issue though.
Spring security is included because of one exception handler. if you import this api into a spring boot app without spring security you will have authorize first. and i ran into another problem with Swagger which not working anymore. I needed to download the source code and and comment out the line which uses spring security.
I tried to use simpleRestProvider, but this api is not compatible. i needed to do some modification and put the data into the root element of json response instead of wrap it into content, maybe i do something wrong, but i needed to do that to make it work.

Duplicated map keys on snake case

Hi,

The following function on FilterService is creating duplicated maps when converting snake case to camel case, and both the snake case and camel case remain in the map, causing it to fail:

    private HashMap<String, Object> convertToCamelCase(HashMap<String, Object> snakeCaseMap) {
        Set<String> keys = snakeCaseMap.keySet();
        HashMap<String, Object> camelCaseMap = new HashMap<>(snakeCaseMap);
        for (String key : keys) {
            Object val = snakeCaseMap.get(key);
            camelCaseMap.put(convertToCamelCase(key), val);
-->            camelCaseMap.remove(key);
        }
        return camelCaseMap;
    }

I included camelCaseMap.remove(key); to properly replace the key instead of adding a new one.

Filter by Instant field is not working in Jpa version 3.1.0

It seems that the Filter Service can not query with Instant type fields in JPA 3.1.0

We are using:
Spring Boot 3.1.0
Java Jdk version 17
Jpa version 3.1.0
Hibernate core: 6.2.2.Final

Request:

curl --location 'localhost:5003/api/sms-campaigns?filter=%7B%22scheduledAtGte%22%3A%222023-04-02T00%3A00%3A00Z%22%7D'

Entity:

public class SmsCampaign {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false, length = 11)
    private String originator;
    @CreatedDate
    @Column(nullable = false)
    private Instant createdAt;
    @Column(nullable = false)
    private Instant scheduledAt;
    private Instant startedAt;
    private Instant completedAt;
    @Column(nullable = false)
    private String alias;
    }

Result:

Method threw 'org.hibernate.QueryException' exception.

image

{
    "timestamp": "2023-07-12T11:38:10.340+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "trace": "org.springframework.dao.InvalidDataAccessResourceUsageException: Parameter 1 of function lower() has type STRING, but argument is of type java.time.Instant\n\tat org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:277)\n\tat org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:229)\n\tat org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)\n\tat org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)\n\tat org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)\n\tat org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)\n\tat org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:163)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)\n\tat org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)\n\tat org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)\n\tat jdk.proxy3/jdk.proxy3.$Proxy199.findAll(Unknown Source)\n\tat mpass.aegeanCampaignManager.apihelpers.FilterService.filterByHelper(FilterService.java:122)\n\tat mpass.aegeanCampaignManager.apihelpers.FilterService.filterBy(FilterService.java:63)\n\tat mpass.aegeanCampaignManager.usecases.smscampaign.FilterSmsCampaign.query(FilterSmsCampaign.java:46)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\n\tat org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)\n\tat org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391)\n\tat org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:702)\n\tat mpass.aegeanCampaignManager.usecases.smscampaign.FilterSmsCampaign$$SpringCGLIB$$0.query(<generated>)\n\tat mpass.aegeanCampaignManager.controllers.SmsCampaignController.filterBy(SmsCampaignController.java:88)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\n\tat org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)\n\tat org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.invoke(AuthorizationManagerBeforeMethodInterceptor.java:199)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)\n\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)\n\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:702)\n\tat mpass.aegeanCampaignManager.controllers.SmsCampaignController$$SpringCGLIB$$0.filterBy(<generated>)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207)\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152)\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081)\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)\n\tat jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:110)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:110)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:110)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat mpass.aegeanCampaignManager.auth.CronJobAuthenticationFilter.doFilterInternal(CronJobAuthenticationFilter.java:36)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat mpass.aegeanCampaignManager.auth.NoCacheFilter.doFilterInternal(NoCacheFilter.java:17)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat org.springframework.web.filter.ShallowEtagHeaderFilter.doFilterInternal(ShallowEtagHeaderFilter.java:103)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365)\n\tat org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126)\n\tat org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131)\n\tat org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat mpass.aegeanCampaignManager.auth.TokenAuthFilter.doFilter(TokenAuthFilter.java:65)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat mpass.aegeanCampaignManager.auth.JwtAuthorizationFilter.doFilterInternal(JwtAuthorizationFilter.java:38)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107)\n\tat org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)\n\tat org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:82)\n\tat org.springframework.security.web.context.SecurityContextHolderFilter.doFilter(SecurityContextHolderFilter.java:69)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat mpass.aegeanCampaignManager.auth.ExceptionHandlerFilter.doFilterInternal(ExceptionHandlerFilter.java:45)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)\n\tat org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)\n\tat org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)\n\tat org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:352)\n\tat org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:268)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174)\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149)\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166)\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894)\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)\n\tat org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\tat java.base/java.lang.Thread.run(Thread.java:833)\nCaused by: org.hibernate.QueryException: Parameter 1 of function lower() has type STRING, but argument is of type java.time.Instant\n\tat org.hibernate.query.sqm.produce.function.ArgumentTypesValidator.throwError(ArgumentTypesValidator.java:248)\n\tat org.hibernate.query.sqm.produce.function.ArgumentTypesValidator.checkType(ArgumentTypesValidator.java:203)\n\tat org.hibernate.query.sqm.produce.function.ArgumentTypesValidator.validate(ArgumentTypesValidator.java:97)\n\tat org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor.generateSqmExpression(AbstractSqmFunctionDescriptor.java:104)\n\tat org.hibernate.query.sqm.function.SqmFunctionDescriptor.generateSqmExpression(SqmFunctionDescriptor.java:117)\n\tat org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder.lower(SqmCriteriaNodeBuilder.java:1531)\n\tat org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder.lower(SqmCriteriaNodeBuilder.java:182)\n\tat mpass.aegeanCampaignManager.apihelpers.CustomSpecifications.createGtePredicate(CustomSpecifications.java:298)\n\tat mpass.aegeanCampaignManager.apihelpers.CustomSpecifications.handleAllCases(CustomSpecifications.java:129)\n\tat mpass.aegeanCampaignManager.apihelpers.CustomSpecifications.handleMap(CustomSpecifications.java:83)\n\tat mpass.aegeanCampaignManager.apihelpers.CustomSpecifications.customSpecificationBuilder(CustomSpecifications.java:45)\n\tat mpass.aegeanCampaignManager.apihelpers.FilterService.lambda$filterByHelper$aae0cab8$1(FilterService.java:131)\n\tat org.springframework.data.jpa.repository.support.SimpleJpaRepository.applySpecificationToCriteria(SimpleJpaRepository.java:834)\n\tat org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:768)\n\tat org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:729)\n\tat org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:472)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.base/java.lang.reflect.Method.invoke(Method.java:568)\n\tat org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:288)\n\tat org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:136)\n\tat org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:120)\n\tat org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516)\n\tat org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)\n\tat org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)\n\tat org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168)\n\tat org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)\n\tat org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:77)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)\n\tat org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)\n\tat org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391)\n\tat org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)\n\tat org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)\n\t... 159 more\n",
    "message": "Parameter 1 of function lower() has type STRING, but argument is of type java.time.Instant",
    "path": "/api/sms-campaigns"
}

Java 11 dependency error on 1.8 projects

First of all I want to thank you for this library.

We are currently using NodeJS with https://github.com/lalalilo/express-sequelize-crud to make the MySQL db talk with the RA interface . I'm experimenting with your library to make all consistent with the Java technology I'm using for the main API backend developed for a small app, which is Spring Boot of course.

Our Spring Boot is compiled with Java 1.8 (<java.version>1.8</java.version>), but you use 11. So we end up with an error that looks like this:

Application run failed
java.lang.UnsupportedClassVersionError: com/nooul/apihelpers/springbootrest/repositories/BaseRepository has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0

I'm recompiling your code with "<java.version>1.8</java.version>" and all seems to work. Is there a reason why you set it to 11? Do you know how to make it compliant with 8?
Also I've to delete the application-test.properties and the related dipendencies cause it makes any @SpringBootTest fails due to circular dependencies error on DataSourceInitializer.

Anyway I've forked your project and now it's working.

I'm building a Generic Controller and a DataProvider for RA which works with custom table Ids (like "users_id" instead of id), and Joins directly on entity. If anyone is interested i can share those two files.

This is my fork: https://github.com/telemedicacloud/spring-boot-rest-api-helpers

Performance problem with pagination.

The library seems to have a performance problem with large tables.
While the actual query is fast by it self, in order to serve pageable data it has to know the total amount of results.
To achieve that it performs the bellow query, which is extremely slow 36s in the below example.

select distinct count(distinct message0_.id) as col_0_0_ from message message0_ inner join application_user applicatio1_ on message0_.created_by_id=applicatio1_.id where applicatio1_.id=5

The problem seems to be in the distinct which is added to the query.
By simply removing the distinct as shown bellow the performance improvement was significant.
Total table rows: 2.300.000
time with distinct: 32sec
time without distinct: 0,9sec

select count(message0_.id) as col_0_0_ from message message0_ inner join application_user applicatio1_ on message0_.created_by_id=applicatio1_.id where applicatio1_.id=5

Sample image of the transactions as shown in our monitoring tool.
image

Update Doc to enable easy setup with React Admin

Hello!

I'm starting a project with Springboot and I'll need an admin. Having worked with React Admin before I'm happy this repo exists ๐Ÿ˜„

However when setting up the backend I am stuck while trying to write the controller and the doc don't seem up to date.

For instance the code example doesn't include the items listed in the list under it (ControllerAdvice and CustomSpecification). Also, there is a typo QueryParamExtracter => QueryParamExtractor

image

I'm trying to work through this by reading the source code, I'll happily make a PR to update the doc once I've successfully setup the admin ๐Ÿ™‚

(and I'll volunteer to submit other PRs too if necessary)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.