Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA spring data specification on joins

I have some already created org.springframework.data.jpa.domain.Specifications. Now I am creating a query in which I would like to use the specification on a table that I join to. But in order to use a Specification I need a Root, but joining gives me a Join object.

Is there a way of converting from a Join object to a Root? Or is there something analogous to Specification, but for Joins?

like image 532
freafrea Avatar asked Feb 13 '17 16:02

freafrea


2 Answers

You don't need Root object. Join object is instance of Path and Expression interfaces. See example with working with join from Specification:

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);

It will provide query like

SELECT e FROM Entity e LEFT JOIN e.joinedEntity j WITH j.someExpr=10 WHERE j.value = 20;
like image 81
Tarwirdur Turon Avatar answered Jan 04 '23 17:01

Tarwirdur Turon


Tarwirdur Turon's solution does not fit my need so I managed to turn a Join into a Root by creating a Root<T> implementation that delegates all methods to a Join<?,T> instance. (Join and Root being children interfaces of From) Although it works, it looks very dirty to me.

Tarwirdur Turon's solution doesn't work for me because I have an already built Specification<JoinedEntity> and I want to find all Entity for which the joinedEntity matches the specification without knowing what's 'inside' this specification.

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");
    }
}

Then use this class like follow :

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);

I really wonder why the Specification.toPredicatemethod takes a Root<X> as first argument instead of a From<Z,X>, this would ease all the thing ...

like image 44
Ghurdyl Avatar answered Jan 04 '23 16:01

Ghurdyl