darrachequesne / spring-data-jpa-datatables

Spring Data JPA extension to work with the great jQuery plugin DataTables (https://datatables.net/)
Apache License 2.0
440 stars 174 forks source link

Extend instead of overwrite jpa repositories #113

Open cdalexndr opened 4 years ago

cdalexndr commented 4 years ago

Instead of prodividing separate custom repositories for data table usage(DataTablesRepository), an extension mechanism would be better, like querydsl does (example QuerydslPredicateExecutor).

This also eases configuration, as there will not be required to provide two @Configuration instances.

cdalexndr commented 4 years ago

More info: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations

darrachequesne commented 4 years ago

Hi @cdalexndr ! I'm not sure I understand fully, isn't that already the case? See https://github.com/darrachequesne/spring-data-jpa-datatables/blob/61ba124db6bd6c5370cd0861dff330d9a40de726/src/main/java/org/springframework/data/jpa/datatables/repository/DataTablesRepositoryImpl.java#L16-L17

I think the two configurations are only needed if you want that only some repositories receive the DataTablesRepositoryImpl implementation.

In any case, PR is welcome!

cdalexndr commented 4 years ago

Using this repository impl require overwriting the default repository factory bean to DataTablesRepositoryFactoryBean: @EnableJpaRepositories(repositoryFactoryBeanClass = DataTablesRepositoryFactoryBean.class)

Without this, the following repository:

interface SurveyRepository extends JpaRepository<Survey, Integer>, DataTablesRepository<Survey, Integer> {
...
}
results in error:

``` java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:123) at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244) at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.springTestContextPrepareTestInstance(AbstractTestNGSpringContextTests.java:145) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:134) at org.testng.internal.MethodInvocationHelper.invokeMethodConsideringTimeout(MethodInvocationHelper.java:63) at org.testng.internal.ConfigInvoker.invokeConfigurationMethod(ConfigInvoker.java:348) at org.testng.internal.ConfigInvoker.invokeConfigurations(ConfigInvoker.java:302) at org.testng.internal.TestMethodWorker.invokeBeforeClassMethods(TestMethodWorker.java:176) at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:122) at java.base/java.util.ArrayList.forEach(ArrayList.java:1540) at org.testng.TestRunner.privateRun(TestRunner.java:766) at org.testng.TestRunner.run(TestRunner.java:587) at org.testng.SuiteRunner.runTest(SuiteRunner.java:384) at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:378) at org.testng.SuiteRunner.privateRun(SuiteRunner.java:337) at org.testng.SuiteRunner.run(SuiteRunner.java:286) at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:53) at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:96) at org.testng.TestNG.runSuitesSequentially(TestNG.java:1187) at org.testng.TestNG.runSuitesLocally(TestNG.java:1109) at org.testng.TestNG.runSuites(TestNG.java:1039) at org.testng.TestNG.run(TestNG.java:1007) at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:73) at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:123) Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'surveyService': Unsatisfied dependency expressed through field 'surveyRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'surveyRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Failed to create query for method public abstract org.springframework.data.jpa.datatables.mapping.DataTablesOutput org.springframework.data.jpa.datatables.repository.DataTablesRepository.findAll(org.springframework.data.jpa.datatables.mapping.DataTablesInput,org.springframework.data.jpa.domain.Specification,org.springframework.data.jpa.domain.Specification,java.util.function.Function)! No property findAll found for type Survey! at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:639) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:116) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:397) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1429) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:125) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ... 30 common frames omitted Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'surveyRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Failed to create query for method public abstract org.springframework.data.jpa.datatables.mapping.DataTablesOutput org.springframework.data.jpa.datatables.repository.DataTablesRepository.findAll(org.springframework.data.jpa.datatables.mapping.DataTablesInput,org.springframework.data.jpa.domain.Specification,org.springframework.data.jpa.domain.Specification,java.util.function.Function)! No property findAll found for type Survey! at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1803) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1287) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1207) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:636) ... 48 common frames omitted Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract org.springframework.data.jpa.datatables.mapping.DataTablesOutput org.springframework.data.jpa.datatables.repository.DataTablesRepository.findAll(org.springframework.data.jpa.datatables.mapping.DataTablesInput,org.springframework.data.jpa.domain.Specification,org.springframework.data.jpa.domain.Specification,java.util.function.Function)! No property findAll found for type Survey! at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.(PartTreeJpaQuery.java:103) at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:106) at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:211) at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:79) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lookupQuery(RepositoryFactorySupport.java:574) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$mapMethodsToQuery$1(RepositoryFactorySupport.java:567) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133) at java.base/java.util.Collections$UnmodifiableCollection$1.forEachRemaining(Collections.java:1054) at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.mapMethodsToQuery(RepositoryFactorySupport.java:569) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$new$0(RepositoryFactorySupport.java:559) at java.base/java.util.Optional.map(Optional.java:265) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.(RepositoryFactorySupport.java:559) at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:332) at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:297) at org.springframework.data.util.Lazy.getNullable(Lazy.java:212) at org.springframework.data.util.Lazy.get(Lazy.java:94) at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:300) at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:121) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1862) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1799) ... 58 common frames omitted Caused by: org.springframework.data.mapping.PropertyReferenceException: No property findAll found for type Survey! at org.springframework.data.mapping.PropertyPath.(PropertyPath.java:94) at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:382) at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:358) at org.springframework.data.mapping.PropertyPath.lambda$from$0(PropertyPath.java:311) at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330) at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:293) at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:276) at org.springframework.data.repository.query.parser.Part.(Part.java:82) at org.springframework.data.repository.query.parser.PartTree$OrPart.lambda$new$0(PartTree.java:250) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177) at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) at org.springframework.data.repository.query.parser.PartTree$OrPart.(PartTree.java:251) at org.springframework.data.repository.query.parser.PartTree$Predicate.lambda$new$0(PartTree.java:380) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177) at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) at org.springframework.data.repository.query.parser.PartTree$Predicate.(PartTree.java:381) at org.springframework.data.repository.query.parser.PartTree.(PartTree.java:93) at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.(PartTreeJpaQuery.java:96) ... 84 common frames omitted ```

Using the DataTablesRepositoryFactoryBean you are overwriting the base repository class to DataTablesRepositoryImpl, class that overwrites the default one SimpleJpaRepository so the base functionality is kept. This method is restrictive as it doesn't allow to use other base repository class (some in house base repository with project specific imlementation) AND use datables repository, so this is a bad way of registering this repository implementation.

Workaround is to group datatables repositories in packages and use @EnableJpaRepositories(basePackages=...) to restrict the overwrite, but this has other issues:

  1. you must use two configurations, each with @EnableJpaRepositories. One for datatables repositories and another one for other repositories.
  2. If you have the datatables repositories spread out inside project, it is hard to configure properly.
  3. What happens if the two configuration's basePackages overlap?

Instead of overwriting the base repository class, you should provide a repository fragment that when it's used by a repository will automatically decorate that repository and provide datatables functionality. See https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations This method doesn't require overwriting the base repository class with @EnableJpaRepositories(repositoryFactoryBeanClass =...).