【发布时间】:2019-05-07 08:54:59
【问题描述】:
上下文: 我在 API 控制器中使用 queryDSL 将查询绑定到数据库获取。目前,我有两个具有OneToOne关系的表,我们可以将它们称为表A和表B。如果A中有3行,B中有2行,当我得到带有某些条件的列表A时,queryDSL会生成查询SQL像A CROSS 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 {}
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);
}
...
}
结果: 当我启动 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)
【问题讨论】:
-
通常我将注入注释(扩展@Component的东西)设置到实现类而不是接口中。如果您通过构造函数使用注入,则该组件将使用其自动装配的依赖关系或在构造函数中定义的依赖关系进行实例化。
-
@uncleBounty 谢谢分享,是的,一般都是通过注解和注入来获取实例。但是对于 Jpa 中的自定义存储库,它需要使用这种方法来覆盖默认实现并通过工厂方法获取实例。
标签: java jpa spring-data querydsl one-to-one