Ming Sun
Ming Sun

Reputation: 324

How to implement custom repository based on JpaRepository in spring?

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)

Upvotes: 0

Views: 2889

Answers (1)

Ming Sun
Ming Sun

Reputation: 324

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

Upvotes: 1

Related Questions