JPA Spring Data规范中的关联查询

6

我已经有了一些已创建的org.springframework.data.jpa.domain.Specifications。现在我正在创建一个查询,在这个查询中,我想使用规范来操作加入的表格。但是为了使用Specification,我需要一个Root,但是加入操作会给我一个Join对象。

是否有一种方法可以将Join对象转换为Root?或者是否有类似于Specification的东西,但是用于Join操作?

2个回答

7
Tarwirdur Turon的解决方案不符合我的需求,因此我将一个Join转化为一个Root,通过创建一个Root<T>实现,将所有方法委托给一个Join<?,T>实例来实现。(Join和Root都是From接口的子接口) 虽然它能工作,但对我来说看起来很不干净。
Tarwirdur Turon的解决方案对我不起作用,因为我已经构建了一个Specification<JoinedEntity>,并且我想找到所有匹配该规范的Entity,而不知道这个规范中有什么内容。
public class JoinRoot<T> implements Root<T> {
    private final Join<?, T> join;
    public JoinRoot(Join<?, T> join) {
        this.join = join;
    }

    // implements all Root methods, delegating them to 'this.join' (#boilerplate),
    // cast when needed

    @Override
    public EntityType<T> getModel() {
        // this one is the only one that cannot be delegated, although it's not used in my use case
        throw new UnsupportedOperationException("getModel cannot be delegated to a JoinRoot");
    }
}

然后按照以下方式使用这个类:
Specification<JoinedEntity> joinedSpecs = ... 

Specification<Entity> specs = (root, query, builder) -> {
    // Convert Join into Root using above JoinRoot class
    Root<JoinedEntity> r = new JoinRoot<>(root.join(Entity_.joinedEntity));
    return joinedSpecs.toPredicate(r, query, builder);
}
Specification<Entity> where = Specifications.where(specs);

List<Entity> entities = entityRepository.findAll(where);

我真的很想知道为什么Specification.toPredicate方法将Root<X>作为第一个参数,而不是From<Z,X>,如果使用From<Z,X>就可以使一切更加容易…


我在想为什么不接受From实例到toPredicate中,所以我在spring-data-jpa上开了一个票:https://github.com/spring-projects/spring-data-jpa/issues/2761 - Baylej

7

您不需要Root对象。 Join对象是PathExpression接口的实例。请参见使用规范中处理连接的示例:

class JoinedSpecification extends Specification<JoinedEntity>() { 
    public Predicate pathPredicate(Path<JoinedEntity> joinedEntity, CriteriaQuery<?> query, CriteriaBuilder builder) {
        return builder.equal(joinedEnity.get(JoinedEntity_.value), 20L);
    }

    @Override
    public Predicate toPredicate(Root<JoinedEntity> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        return pathPredicate(root, query, builder);
    }
}

class MySpecification extends Specification<Entity>() {
    private static JoinedSpecification joinedSpecification = new JoinedSpecification();

    @Override
    public Predicate toPredicate(Root<Entity> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        Join<T, JoinedEntity> join = root.join(Entity_.joinedEntity, JoinType.LEFT);

        // Some join condition
        Path<Long> someExpr = join.get(JoinedEntity_.someExpr);
        Long someExprCriteria = 10L;
        join = join.on(builder.equal(someExpr, someExprCriteria));

        return joinedSpecification.pathPredicate(join, query, builder);
    }
}

@Autowired
JpaSpecififcationExecutor<Entity> service;

Specification<Entity> spec = new MySpecification();
serivce.findAll(spec);

它将提供类似于查询的功能。
SELECT e FROM Entity e LEFT JOIN e.joinedEntity j WITH j.someExpr=10 WHERE j.value = 20;

我理解,但我的问题是,如果我希望部分builder.equal(join.get(JoinedEntity_.value), 20L)包含在规范中,我该怎么做?在这种情况下,条件很简单,但在我的情况下可能会更复杂,我希望能够重用它。 - freafrea
@freafrea 我更新了答案。如果您在联接规范中不使用特定于根的条件(例如另一个联接),则可以将条件移动到另一个方法中,该方法使用 Path<X> 而不是 Root<X>,然后从 toPredicate(Root<X> ... 调用。这样代码就不会重复了。 - Tarwirdur Turon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接