Context:
I used queryDSL in API controller which binds query to the database get. Currently, I have two tables with OneToOne relationship, and we can call them Table A and Table B. If there are 3 rows in A and 2 rows in B, when I get list A with some conditions, and the queryDSL will generate query SQL like A CROSS JOIN B WHERE A.id=B.a_id, but it will miss one item in A. Thus, I am going to implement custom repository to support change join type when generating the SQL statement. The following is some parts of my code:
(Table A is named LabelTask and Table B is named AuditTask)
and generated sql segment is
from
label_task labeltask0_ cross
join
audit_task audittask1_
where
labeltask0_.id=audittask1_.label_task
Is there something wrong with my code or is there another good solution for this situation?
JoinDescriptor.java
public class JoinDescriptor {
public final EntityPath path;
public final JoinType type;
private JoinDescriptor(EntityPath path, JoinType type) {
this.path = path;
this.type = type;
}
public static JoinDescriptor innerJoin(EntityPath path) {
return new JoinDescriptor(path, JoinType.INNERJOIN);
}
public static JoinDescriptor join(EntityPath path) {
return new JoinDescriptor(path, JoinType.JOIN);
}
public static JoinDescriptor leftJoin(EntityPath path) {
return new JoinDescriptor(path, JoinType.LEFTJOIN);
}
public static JoinDescriptor rightJoin(EntityPath path) {
return new JoinDescriptor(path, JoinType.RIGHTJOIN);
}
public static JoinDescriptor fullJoin(EntityPath path) {
return new JoinDescriptor(path, JoinType.FULLJOIN);
}
}
JoinFetchCapableQueryDslJpaRepositoryFactoryBean.java
public class JoinFetchCapableQueryDslJpaRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
extends JpaRepositoryFactoryBean<R, T, I> {
public JoinFetchCapableQueryDslJpaRepositoryFactoryBean(Class<? extends R> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new JoinFetchCapableQueryDslJpaRepositoryFactory(entityManager);
}
private static class JoinFetchCapableQueryDslJpaRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {
private EntityManager entityManager;
public JoinFetchCapableQueryDslJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
protected Object getTargetRepository(RepositoryMetadata metadata) {
return new JoinFetchCapableRepositoryImpl<>(getEntityInformation(metadata.getDomainType()), entityManager);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return JoinFetchCapableRepository.class;
}
}
}
JoinFetchCapableRepository.java
#NoRepositoryBean
public interface JoinFetchCapableRepository<T, ID extends Serializable> extends
JpaRepository<T, ID>,
QuerydslPredicateExecutor<T> {
Page<T> findAll(Predicate predicate,
Pageable pageable,
JoinDescriptor... joinDescriptors);
}
JoinFetchCapableRepositoryImpl.java
public class JoinFetchCapableRepositoryImpl <T, ID extends Serializable>
extends QuerydslJpaRepository<T, ID>
implements JoinFetchCapableRepository<T, ID> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
public JoinFetchCapableRepositoryImpl(JpaEntityInformation<T, ID> entityInformation,
EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public JoinFetchCapableRepositoryImpl(JpaEntityInformation<T, ID> entityInformation,
EntityManager entityManager,
EntityPathResolver resolver) {
super(entityInformation, entityManager, resolver);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public Page<T> findAll(Predicate predicate, Pageable pageable, JoinDescriptor... joinDescriptors) {
JPQLQuery countQuery = createQuery(predicate);
JPQLQuery query = querydsl.applyPagination(pageable, createFetchQuery(predicate, joinDescriptors));
Long total = countQuery.fetchCount();
List<T> content = total > pageable.getOffset()
? query.fetch()
: Collections.emptyList();
return new PageImpl<>(content, pageable, total);
}
private JPQLQuery createFetchQuery(Predicate predicate, JoinDescriptor... joinDescriptors) {
JPQLQuery query = querydsl.createQuery(path);
for(JoinDescriptor joinDescriptor: joinDescriptors)
join(joinDescriptor, query);
return (JPQLQuery) query.where(predicate);
}
private JPQLQuery join(JoinDescriptor joinDescriptor, JPQLQuery query) {
switch(joinDescriptor.type) {
case DEFAULT:
throw new IllegalArgumentException("cross join not supported");
case INNERJOIN:
query.innerJoin(joinDescriptor.path);
break;
case JOIN:
query.join(joinDescriptor.path);
break;
case LEFTJOIN:
query.leftJoin(joinDescriptor.path);
break;
case RIGHTJOIN:
query.rightJoin(joinDescriptor.path);
break;
case FULLJOIN:
query.join(joinDescriptor.path);
break;
}
return query.fetchAll();
}
}
JpaConfig.java
#Configuration
#EnableJpaRepositories(
basePackages = "com.some.company.service.repository",
repositoryFactoryBeanClass =JoinFetchCapableQueryDslJpaRepositoryFactoryBean.class)
public class JpaConfig {}
LabelTaskRepository
#Repository
public interface LabelTaskRepository extends
JoinFetchCapableRepository<LabelTask, String>,
QuerydslBinderCustomizer<QLabelTask> {
#Override
default void customize(QuerydslBindings bindings, QLabelTask qLabelTask){
this.bindQueryByTaskType(bindings, qLabelTask);
this.bindQueryByCreatedDateRange(bindings, qLabelTask);
// TODO: should remove this when task could be able to assign
bindings.excluding(qLabelTask.status);
}
...
}
Result:
when I launch the spring application, It will return the following error message:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'auditTaskController' defined in file [/.../some/company/service/controllers/AuditTaskController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'auditTaskService' defined in file [/.../some/company/service/AuditTaskService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'auditTaskRepository': Invocation of init method failed; nested exception is java.lang.IllegalStateException: No suitable constructor found on interface some.company.utils.JoinFetchCapableRepository to match the given arguments: [class org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation, class com.sun.proxy.$Proxy182]. Make sure you implement a constructor taking these
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:733)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:198)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1266)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1123)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:548)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:386)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:127)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
No suitable constructor found on interface
some.company.utils.JoinFetchCapableRepository to match the given
arguments: [class
org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation,
class com.sun.proxy.$Proxy182].
Based on the exception message, JoinFetchCapableRepositoryImpl needs a constructor which receives two parameters: JpaMetamodelEntityInformation, $Proxy182.
I added a constructor like this:
public JoinFetchCapableRepositoryImpl(
JpaMetamodelEntityInformation<T, ID> entityInformation,
EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
After this, It works for me and is able to change join type for query dsl
Related
I'm trying to extend QuerydslJpaPredicateExecutor to modify a query method:
public class SliceableRepositoryImpl<T>
extends QuerydslJpaPredicateExecutor<T>
implements SliceableRepository<T> {
public SliceableRepositoryImpl(JpaEntityInformation<T, ?> entityInformation,
EntityManager entityManager,
EntityPathResolver resolver,
CrudMethodMetadata metadata) {
super(entityInformation, entityManager, resolver, metadata);
}
#Override
public Page<T> findAll(Predicate predicate, Pageable pageable) {
//TODO modify
return super.findAll(predicate, pageable);
}
}
public interface MysRepository extends
SliceableRepository<MyEntity>,
JpaRepository<MyEntity, Long> {
}
public interface SliceableRepository<T> {
Page<T> findAll(Predicate predicate, Pageable pageable);
}
Result:
Parameter 0 of constructor in SliceableRepositoryImpl required a bean of type 'org.springframework.data.jpa.repository.support.JpaEntityInformation' that could not be found.
Question: how can I actually extend QuerydslJpaPredicateExecutor and let spring autowire the constructor parameters?
I needed some custom QueryDSL enabled query methods and followed this SO answer.
That worked great, but after upgrading to Spring Boot 2.1 (which upgrades Spring Data), I've found that QuerydslJpaRepository has been deprecated.
Simply replacing it with QuerydslJpaPredicateExecutor - which the documentation tells me to use - leads to an error:
Caused by: java.lang.IllegalArgumentException: Object of class
[...ProjectingQueryDslJpaRepositoryImpl] must be an instance of
interface
org.springframework.data.jpa.repository.support.JpaRepositoryImplementation
...but implementing JpaRepositoryImplementation would mean that I have to implement all the standard CRUD methods, which I obviously don't want.
So if I remove the repositoryBaseClass config from #EnableJpaRepositories, to treat this just like a repository fragment with implementation, it will try to instantiate the fragment, even though it is marked with #NoRepositoryBean, giving me the error:
Caused by: java.lang.IllegalArgumentException: Failed to create query
for method public abstract java.util.Optional
ProjectingQueryDslJpaRepository.findOneProjectedBy(com.querydsl.core.types.Expression,com.querydsl.core.types.Predicate)!
At least 1 parameter(s) provided but only 0 parameter(s) present in
query.
...
Caused by: java.lang.IllegalArgumentException: At least 1 parameter(s)
provided but only 0 parameter(s) present in query.
Abriged version of source:
#Configuration
#EnableJpaRepositories(basePackageClasses = Application.class, repositoryBaseClass = ProjectingQueryDslJpaRepositoryImpl.class)
#EnableTransactionManagement
#EnableJpaAuditing
#RequiredArgsConstructor(onConstructor = #__({#Autowired}))
public class DatabaseConfig {}
_
#NoRepositoryBean
public interface ProjectingQueryDslJpaRepository<T> extends QuerydslBinderCustomizer<EntityPath<T>>, QuerydslPredicateExecutor<T> {
#NonNull
<P> Page<P> findPageProjectedBy(#NonNull Expression<P> factoryExpression, Predicate predicate,
#NonNull Pageable pageable);
#NonNull
<P> Optional<P> findOneProjectedBy(#NonNull Expression<P> factoryExpression, #NonNull Predicate predicate);
#Override
default void customize(#NonNull QuerydslBindings bindings, #NonNull EntityPath<T> root){
bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
}
}
_
public class ProjectingQueryDslJpaRepositoryImpl<T, ID extends Serializable> extends QuerydslJpaRepository<T, ID>
implements ProjectingQueryDslJpaRepository<T> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final Querydsl querydsl;
public ProjectingQueryDslJpaRepositoryImpl(#NonNull JpaEntityInformation<T, ID> entityInformation, #NonNull EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public ProjectingQueryDslJpaRepositoryImpl(#NonNull JpaEntityInformation<T, ID> entityInformation, #NonNull EntityManager entityManager,
#NonNull EntityPathResolver resolver) {
super(entityInformation, entityManager, resolver);
this.path = resolver.createPath(entityInformation.getJavaType());
PathBuilder<T> builder = new PathBuilder<>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public <P> Page<P> findPageProjectedBy(#NonNull Expression<P> factoryExpression, Predicate predicate,
#NonNull Pageable pageable) {
final JPQLQuery<?> countQuery = createCountQuery(predicate);
JPQLQuery<P> query = querydsl.applyPagination(pageable, createQuery(predicate).select(factoryExpression));
return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
}
#Override
public <P> Optional<P> findOneProjectedBy(#NonNull Expression<P> factoryExpression, #NonNull Predicate predicate) {
try {
return Optional.ofNullable(createQuery(predicate).select(factoryExpression).from(path).fetchOne());
} catch (NonUniqueResultException ex) {
throw new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex);
}
}
}
With Spring Boot 2.1.1 the following solution may help you. The key is to extend JpaRepositoryFactory and override the method getRepositoryFragments(RepositoryMetadata metadata). In this method you can provide base (or more specific fragment) implementations for any custom repository which should be taken for every extending repository.
Let me show you an example:
QueryableReadRepository:
#NoRepositoryBean
public interface QueryableReadRepository<T> extends Repository<T, String> {
List<T> findAll(Predicate predicate);
List<T> findAll(Sort sort);
List<T> findAll(Predicate predicate, Sort sort);
List<T> findAll(OrderSpecifier<?>... orders);
List<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);
Page<T> findAll(Pageable page);
Page<T> findAll(Predicate predicate, Pageable page);
Optional<T> findOne(Predicate predicate);
boolean exists(Predicate predicate);
}
The following interface combines different repositories.
DataRepository:
#NoRepositoryBean
public interface DataRepository<T>
extends CrudRepository<T, String>, QueryableReadRepository<T> {
}
Now your specific domain repos can extend from DataRepository:
#Repository
public interface UserRepository extends DataRepository<UserEntity> {
}
QueryableReadRepositoryImpl:
#Transactional
public class QueryableReadRepositoryImpl<T> extends QuerydslJpaPredicateExecutor<T>
implements QueryableReadRepository<T> {
private static final EntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
public QueryableReadRepositoryImpl(JpaEntityInformation<T, ?> entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager, resolver, null);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public Optional<T> findOne(Predicate predicate) {
return super.findOne(predicate);
}
#Override
public List<T> findAll(OrderSpecifier<?>... orders) {
return super.findAll(orders);
}
#Override
public List<T> findAll(Predicate predicate, Sort sort) {
return executeSorted(createQuery(predicate).select(path), sort);
}
#Override
public Page<T> findAll(Predicate predicate, Pageable pageable) {
return super.findAll(predicate, pageable);
}
#Override
public List<T> findAll(Predicate predicate) {
return super.findAll(predicate);
}
public List<T> findAll(Sort sort) {
return executeSorted(createQuery().select(path), sort);
}
#Override
public Page<T> findAll(Pageable pageable) {
final JPQLQuery<?> countQuery = createCountQuery();
JPQLQuery<T> query = querydsl.applyPagination(pageable, createQuery().select(path));
return PageableExecutionUtils.getPage(
query.distinct().fetch(),
pageable,
countQuery::fetchCount);
}
private List<T> executeSorted(JPQLQuery<T> query, Sort sort) {
return querydsl.applySorting(sort, query).distinct().fetch();
}
}
CustomRepositoryFactoryBean:
public class CustomRepositoryFactoryBean<T extends Repository<S, I>, S, I>
extends JpaRepositoryFactoryBean<T, S, I> {
public CustomRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomRepositoryFactory(entityManager);
}
CustomRepositoryFactory:
public class CustomRepositoryFactory extends JpaRepositoryFactory {
private final EntityManager entityManager;
public CustomRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
#Override
protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
RepositoryFragments fragments = super.getRepositoryFragments(metadata);
if (QueryableReadRepository.class.isAssignableFrom(
metadata.getRepositoryInterface())) {
JpaEntityInformation<?, Serializable> entityInformation =
getEntityInformation(metadata.getDomainType());
Object queryableFragment = getTargetRepositoryViaReflection(
QueryableReadRepositoryImpl.class, entityInformation, entityManager);
fragments = fragments.append(RepositoryFragment.implemented(queryableFragment));
}
return fragments;
}
Main class:
#EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
public class App {
}
This has the advantage that you provide only one (fragment) implementation for a custom repo. The base repository implementation is still Spring's default implementation. The example provided a new repo but you can probably also just override the default implementation of QuerydslPredicateExecutor in CustomRepositoryFactory
In Spring Data JPA 2.1.6 the constructor of QuerydslJpaPredicateExecutor has changed.
I present here an alternative approach using a wrapper to https://stackoverflow.com/a/53960209/3351474. This makes the solution independent from the internals of Spring Data JPA. Three classes have to be implemented.
As an example I take here a customized Querydsl implementation using always the creationDate of an entity as sort criteria if nothing is passed. I assume in this example that this column exists in some #MappedSuperClass for all entities. Use generated static metadata in real life instead the hard coded string "creationDate".
As first the wrapped delegating all CustomQuerydslJpaRepositoryIml delegating all methods to the QuerydslJpaPredicateExecutor:
/**
* Customized Querydsl JPA repository to apply custom filtering and sorting logic.
*
*/
public class CustomQuerydslJpaRepositoryIml<T> implements QuerydslPredicateExecutor<T> {
private final QuerydslJpaPredicateExecutor querydslPredicateExecutor;
public CustomQuerydslJpaRepositoryIml(QuerydslJpaPredicateExecutor querydslPredicateExecutor) {
this.querydslPredicateExecutor = querydslPredicateExecutor;
}
private Sort applyDefaultOrder(Sort sort) {
if (sort.isUnsorted()) {
return Sort.by("creationDate").ascending();
}
return sort;
}
private Pageable applyDefaultOrder(Pageable pageable) {
if (pageable.getSort().isUnsorted()) {
Sort defaultSort = Sort.by(AuditableEntity_.CREATION_DATE).ascending();
pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), defaultSort);
}
return pageable;
}
#Override
public Optional<T> findOne(Predicate predicate) {
return querydslPredicateExecutor.findOne(predicate);
}
#Override
public List<T> findAll(Predicate predicate) {
return querydslPredicateExecutor.findAll(predicate);
}
#Override
public List<T> findAll(Predicate predicate, Sort sort) {
return querydslPredicateExecutor.findAll(predicate, applyDefaultOrder(sort));
}
#Override
public List<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
return querydslPredicateExecutor.findAll(predicate, orders);
}
#Override
public List<T> findAll(OrderSpecifier<?>... orders) {
return querydslPredicateExecutor.findAll(orders);
}
#Override
public Page<T> findAll(Predicate predicate, Pageable pageable) {
return querydslPredicateExecutor.findAll(predicate, applyDefaultOrder(pageable));
}
#Override
public long count(Predicate predicate) {
return querydslPredicateExecutor.count(predicate);
}
#Override
public boolean exists(Predicate predicate) {
return querydslPredicateExecutor.exists(predicate);
}
}
Next the CustomJpaRepositoryFactory doing the magic and providing the Querydsl wrapper class instead of the default one. The default one is passed as parameter and wrapped.
/**
* Custom JpaRepositoryFactory allowing to support a custom QuerydslJpaRepository.
*
*/
public class CustomJpaRepositoryFactory extends JpaRepositoryFactory {
/**
* Creates a new {#link JpaRepositoryFactory}.
*
* #param entityManager must not be {#literal null}
*/
public CustomJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
}
#Override
protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
final RepositoryComposition.RepositoryFragments[] modifiedFragments = {RepositoryComposition.RepositoryFragments.empty()};
RepositoryComposition.RepositoryFragments fragments = super.getRepositoryFragments(metadata);
// because QuerydslJpaPredicateExecutor is using som internal classes only a wrapper can be used.
fragments.stream().forEach(
f -> {
if (f.getImplementation().isPresent() &&
QuerydslJpaPredicateExecutor.class.isAssignableFrom(f.getImplementation().get().getClass())) {
modifiedFragments[0] = modifiedFragments[0].append(RepositoryFragment.implemented(
new CustomQuerydslJpaRepositoryIml((QuerydslJpaPredicateExecutor) f.getImplementation().get())));
} else {
modifiedFragments[0].append(f);
}
}
);
return modifiedFragments[0];
}
}
Finally the CustomJpaRepositoryFactoryBean. This must be registered with the Spring Boot application, to make Spring aware where to get the repository implementations from, e.g. with:
#SpringBootApplication
#EnableJpaRepositories(basePackages = "your.package",
repositoryFactoryBeanClass = CustomJpaRepositoryFactoryBean.class)
...
Here now the class:
public class CustomJpaRepositoryFactoryBean<T extends Repository<S, I>, S, I> extends JpaRepositoryFactoryBean<T, S, I> {
/**
* Creates a new {#link JpaRepositoryFactoryBean} for the given repository interface.
*
* #param repositoryInterface must not be {#literal null}.
*/
public CustomJpaRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomJpaRepositoryFactory(entityManager);
}
}
This test case has a cleaner version of running queries using querydsl
https://github.com/spring-projects/spring-data-jpa/blob/master/src/test/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutorUnitTests.java
JpaEntityInformation<User, Integer> information = new JpaMetamodelEntityInformation<>(User.class,
em.getMetamodel());
SimpleJpaRepository<User, Integer> repository = new SimpleJpaRepository<>(information, em);
dave = repository.save(new User("Dave", "Matthews", "dave#matthews.com"));
carter = repository.save(new User("Carter", "Beauford", "carter#beauford.com"));
oliver = repository.save(new User("Oliver", "matthews", "oliver#matthews.com"));
adminRole = em.merge(new Role("admin"));
this.predicateExecutor = new QuerydslJpaPredicateExecutor<>(information, em, SimpleEntityPathResolver.INSTANCE, null);
BooleanExpression isCalledDave = user.firstname.eq("Dave");
BooleanExpression isBeauford = user.lastname.eq("Beauford");
List<User> result = predicateExecutor.findAll(isCalledDave.or(isBeauford));
assertThat(result).containsExactlyInAnyOrder(carter, dave);
I am trying to execute group by query with Spring Data JPA and Query DSL.
But, I am getting following exception:-
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'japanWHTDaoImpl':
Unsatisfied dependency expressed through field 'wht21940000DataRepo';
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'japanWHT21940000DataRepository':
Invocation of init method failed; nested exception is java.util.NoSuchElementException
I tried to write custom repository implementation and giving below my interfaces and impl classes:
Custom interface:
public interface JapanWHT21940000DataRepositoryCustom {
List<WHT21940000Royalties> findLocalCcyAmtsByRemarks();
}
Custom Impl class:
#Repository
#Transactional
public class JapanWHT21940000DataRepositoryCustomImpl extends QueryDslRepositorySupport implements JapanWHT21940000DataRepositoryCustom {
public JapanWHT21940000DataRepositoryCustomImpl(Class<?> domainClass) {
super(domainClass);
// TODO Auto-generated constructor stub
}
#PersistenceContext
private EntityManager entityManager;
#Override
public List<WHT21940000Royalties> findLocalCcyAmtsByRemarks() {
QWHT21940000Data wht21940000Data = QWHT21940000Data.wHT21940000Data;
JPAQuery<WHT21940000Royalties> query = new JPAQuery<WHT21940000Royalties>(entityManager);
query.from(wht21940000Data).groupBy(wht21940000Data.remarks).select(wht21940000Data.remarks, wht21940000Data.localCcyAmt.sum());
return null;
}
}
Spring data JPA interface:
public interface JapanWHT21940000DataRepository
extends JpaRepository<WHT21940000Data, Long>,
QueryDslPredicateExecutor<WHT21940000Data>,
JapanWHT21940000DataRepositoryCustom {
}
and in DAO class:
#Repository
#Transactional("japanWhtTransactionManager")
public class JapanWHTDaoImpl implements JapanWHTDao {
#Autowired
JapanWHT21940000DataRepository wht21940000DataRepo;
// more code to follow...
EDIT: Or is there a simpler and better way to do group by query in Spring data JPA + Query DSL than what I am trying?
I think that the real issue is executing custom querydsl queries from JpaRepositories, though I have to suggest using a custom extended base repository class, as following.
First, comes the extended base repository class.
#NoRepositoryBean
public interface ExtendedQueryDslJpaRepository<T, ID extends Serializable> extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
<T1> Page<T1> findAll(JPQLQuery jpqlQuery, Pageable pageable);
}
And related implementation.
public class ExtendedQueryDslJpaRepositoryImpl<T, ID extends Serializable>
extends QueryDslJpaRepository<T, ID> implements ExtendedQueryDslJpaRepository<T, ID> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
private EntityManager entityManager;
public ExtendedQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public ExtendedQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager, EntityPathResolver
resolver) {
super(entityInformation, entityManager);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder(this.path.getType(), this.path.getMetadata());
this.querydsl = new Querydsl(entityManager, this.builder);
this.entityManager = entityManager;
}
#Override
public <T1> Page<T1> findAll(JPQLQuery jpqlQuery, Pageable pageable) {
final JPQLQuery<?> countQuery = jpqlQuery;
JPQLQuery<T1> query = querydsl.applyPagination(pageable, jpqlQuery);
return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
}
}
The above classes could be placed in a configuration package.
Then, we define ExtendedQueryDslJpaRepositoryImpl as default class from which JpaRepository classes should extend, as following:
#Configuration
#EnableJpaRepositories(basePackageClasses = Application.class, repositoryBaseClass = ExtendedQueryDslJpaRepositoryImpl.class)
public class JpaConfig {
#PersistenceContext
private EntityManager entityManager;
#Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
The next step is to define a repository for an Entity of the application, eg. CustomEntity.
public interface CustomRepository extends ExtendedQueryDslJpaRepository<CustomEntity, Long>, CustomRepositorySupport {
}
Next we define interface CustomRepositorySupport for the custom methods definition.
public interface CustomRepositorySupport {
JPQLQuery<CustomListDto> createCustomIndexQuery();
}
And finally the custom repository implementation.
#Repository
public class CustomRepositoryImpl implements CustomRepositorySupport {
private JPAQueryFactory queryFactory;
#Autowired
public CustomRepositoryImpl(JPAQueryFactory queryFactory) {
this.queryFactory = queryFactory;
}
#Override
public JPQLQuery<CustomListDto> createCustomIndexQuery() {
QCustomEntity qCustomEntity = QCustomEntity.customEntity;
BooleanBuilder predicate = new BooleanBuilder();
// Create predicate as desired
// predicate.and(...);
// Create projection of fields
/* FactoryExpression<CustomListDto> factoryExpression = Projections.bean(CustomListDto.class,
qCustomEntity.fieldA,
qCustomEntity.fieldB,
qCustomEntity.fieldC,
qCustomEntity.fieldD,
qCustomEntity.fieldE,
qCustomEntity.fieldF); */
return queryFactory.from(qCustomEntity).select(factoryExpression).where(predicate);
}
}
And the final step is to actually call the method from a Service class as following.
public interface CustomFinder {
Page<CustomListDto> findIndex(Pageable pageable);
}
-
#Service
public class CustomFinderImpl implements CustomFinder {
private CustomRepository customRepository;
#Autowired
public CustomFinderImpl(CustomRepository customRepository) {
this.customRepository = customRepository;
}
#Override
public Page<CustomListDto> findIndex(Pageable pageable) {
JPQLQuery<CustomListDto> query = customRepository.createCustomIndexQuery();
return customRepository.findAll(query, pageable);
}
}
The same way we implemented public <T1> List<T1> findAll(JPQLQuery query, FactoryExpression<T1> factoryExpression), we can simply implement required desirable methods as public <T1> List<T1> findAll(JPQLQuery query, FactoryExpression<T1> factoryExpression), <T1> List<T1> findList(JPQLQuery query, FactoryExpression<T1> factoryExpression) etc.
This way, we add capabilities to all our repositories, in order to take advantage of all types of querydsl queries, not only group by or projections.
It might seem a little over-complicated, but if you choose to implement it, and see how it works in action, you would stick to it. It is being used successfully in real apps, providing access to projection queries (without loading full entities where they are not needed), paging queries, exists subqueries etc.
Finally, using a kind of central base repository class is a kind of reusable code which is written only once.
Hope that helps.
Since I got no answer to my previous question I tried to tweak the example given in the Spring documentation for customizing repositories. There ist a Method getRepository(Class repositoryInterface) which looks like It ist the right place to map my repository Overrides:
public class MyRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
extends JpaRepositoryFactoryBean<R, T, I> {
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new MyRepositoryFactory<>(entityManager);
}
private static class MyRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {
private EntityManager entityManager;
#Resource
private Map<Class<?>, Class<?>> overrideRepositories;
public MyRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
//Test
overrideRepositories = new HashMap<>();
overrideRepositories.put(CustomerRepository.class, Customer2Repository.class);
}
protected Object getTargetRepository(RepositoryMetadata metadata) {
return super.getTargetRepository(metadata);
// return new MyRepositoryImpl<T, I>((Class<T>)
// metadata.getDomainClass(), entityManager);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// The RepositoryMetadata can be safely ignored, it is used by the
// JpaRepositoryFactory
// to check for QueryDslJpaRepository's which is out of scope.
return JpaRepository.class;
}
#SuppressWarnings("unchecked")
#Override
public <E> E getRepository(Class<E> repositoryInterface, Object customImplementation) {
if (overrideRepositories != null) {
Class<?> override = overrideRepositories.get(repositoryInterface);
if (override != null) {
repositoryInterface = (Class<E>) override;
}
}
return super.getRepository(repositoryInterface, customImplementation);
}
}
}
I configured it like this: #EnableJpaRepositories(repositoryFactoryBeanClass=MyRepositoryFactoryBean.class)
Normally you would autowire the repositories themselves which doesn't work because there are two Interfaces with the same Type and I don't know how to tell Spring which one to use.
If I autowire the factory instead, I can call getRepository each time I need a specific one. But how do I get this factory? Does Spring Data JPA somehow expose this as a bean? I can't find anything on google concerning this. Or is this approach entirely wrong?
You can use the ApplicationContext instance to get your MyRepositoryFactoryBean bean class. All you have to do is implement the ApplicationContextAware interface in order to get access to the ApplicationContext instance.
public class myClass implements ApplicationContextAware{
private static ApplicationContext ac;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ac = applicationContext;
}
}
Now you can use ac.getBean("MyRepositoryFactoryBean") to get the factory directly from the ApplicationContext. Once you have that bean you can call getRepository on it.
I'm having problems with my project
Exception in thread "main" org.jboss.weld.exceptions.DeploymentException: WELD-001408: Unsatisfied dependencies for type JpaDAO with qualifiers #Default
at injection point [BackedAnnotatedField] #Inject private teste.view.Principal.dao
JpaDAO(Just for test, not fully implemented) :
package teste.cdihibernate;
import java.util.List;
import javax.persistence.EntityManager;
public class JpaDAO<T> implements DAO<T>
{
private EntityManager em;
private final Class<T> classe;
public JpaDAO(Class<T> classe, EntityManager em)
{
this.classe = classe;
this.em = em;
}
#Override
public void save(T entity)
{
em.persist(entity);
}
#Override
public void update(T entity)
{
}
#Override
public void remove(T entity)
{
em.remove(entity);
}
#Override
public T getById(Class<T> classe, Long pk)
{
return em.find(classe, pk);
}
#Override
public List<T> getAll(Class<T> classe)
{
List<T> resultList = (List<T>) em.createQuery("select e from " + classe.getSimpleName() + " e").getResultList();
return resultList;
}
#Override
public T getByRestriction(Class<T> classe, String attribute, String filter)
{
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
My DAOFactory:
public class DAOFactory
{
#Inject private EntityManager em;
#SuppressWarnings({ "rawtypes", "unchecked" })
#Produces
#Dependent
public JpaDAO createJpaDAO(InjectionPoint point) throws ClassNotFoundException
{
ParameterizedType type = (ParameterizedType) point.getType();
Class classe = (Class) type.getActualTypeArguments()[0];
return new JpaDAO(classe, em);
}
}
And my Principal.java:
#Inject private JpaDAO<Veiculo> dao;
What am I doing wrong?
The producer method's return type (JPaDAO) is not assignable to the required type for injection (JpaDAO<Veiculo>). I believe you need to add a type variable to your producer method.