春季如何实现基于JpaRepository的自定义存储库?

时间:2018-12-05 18:03:19

标签: java jpa spring-data querydsl one-to-one

上下文: 我在API控制器中使用了queryDSL,它将查询绑定到数据库get。当前,我有两个具有OneToOne关系的表,我们可以将它们称为表A和表B。如果A中有3行,B中有2行,当我在有条件的情况下获得列表A时,queryDSL将生成查询SQL像 A C.JOIN B WHERE A.id = B.a_id ,但是它将遗漏A中的一项。因此,在生成SQL语句时,我将实现自定义存储库以支持更改联接类型。以下是我的代码的某些部分: (表A命名为LabelTask​​,表B命名为AuditTask)

生成的sql段是

    from
    label_task labeltask0_ cross 
join
    audit_task audittask1_ 
where
    labeltask0_.id=audittask1_.label_task 

我的代码是否有问题,或者针对这种情况还有其他好的解决方案?

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 {}

LabelTask​​Repository

@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);
    }
    ...
}

结果: 当我启动spring应用程序时,它将返回以下错误消息:

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)

1 个答案:

答案 0 :(得分:0)

  

在接口上找不到合适的构造函数   some.company.utils.JoinFetchCapableRepository匹配给定   参数:[类   org.springframework.data.jpa.repository.support。 JpaMetamodelEntityInformation ,   类com.sun.proxy。$ Proxy182]。

根据异常消息,JoinFetchCapableRepositoryImpl需要一个接收两个参数的构造函数:JpaMetamodelEntityInformation,$ Proxy182。

我添加了一个像这样的构造函数:

       public JoinFetchCapableRepositoryImpl(
                JpaMetamodelEntityInformation<T, ID> entityInformation,
                EntityManager entityManager) {

           this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
       }

此后,它对我有用,并且能够更改查询dsl的联接类型