Question

We can select specific columns by writing custom @Query methods in our Repository Interface. However, I don't want to write so many methods for different properties.

I tried this, but it returns the entire object all the time.

public class MySpecifications {

    public static Specification<MyInfo> propertiesWithId(final String[] properties, final Object id, final String idProperty)
    {

        return new Specification<MyInfo>() {

            @Override
            public Predicate toPredicate(Root<MyInfo> root,
                    CriteriaQuery<?> query, CriteriaBuilder cb) {

                query = cb.createTupleQuery(); //tried cb.createQuery(MyInfo.class); as well

                List<Selection<? extends Object>> selectionList = new ArrayList<Selection<? extends Object>>();

                for (String property : properties) {

                    Selection<? extends Object> selection = root.get(property);

                    selectionList.add(selection);
                }

                return query.multiselect(selectionList).where(cb.equal(root.get(idProperty), id)).getRestriction();
            }

        };
    }
}

used as:

MyInfo findOne(Specification(properties,idValue, idProperty));

Is this the correct way? Where is the mistake?

No correct solution

OTHER TIPS

The current spring data jpa specification executor is limited to criteria in the where clause, so you can't change the selected columns, it's implicitely limited to full entities only (take a look at JpaSpecificationExecutor interface documentation). You'll have to go with a custom repository implementation, or move to named queries-

Spring Data JPA and Querydsl to fetch subset of columns using bean/constructor projection

Correct me if this is wrong.

Usually Speicification is in a XxxSpecifications class and has static method. And they are usually used in the following 2 ways:

public class SwVecSpecifications {

    // Method 1. 
    public static Specification<WhiteVecEntity> conditions(String groupId, String appId, String typeId) {
        return (Specification<WhiteVecEntity>) (root, query, cb) -> {
            Predicate p3 = cb.equal(root.get("groupId"), groupId);
            Predicate p2 = cb.equal(root.get("appId"), appId);
            Predicate condition = cb.and(p2,p3);
            if (typeId != null && !typeId.isEmpty()) {
                Predicate p1 = cb.equal(root.get("typeId"), typeId);
                condition = cb.and(condition, p1);
            }
            return query.select(root.get("id")).where(condition).getRestriction();
        };
    }

    // Method 2.
    static void findByConditions(EntityManager em) {
        String groupId = "";
        String typeId = "";

        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<SuspectVecEntity> query = cb.createQuery(SuspectVecEntity.class);
        Root<SuspectVecEntity> root = query.from(SuspectVecEntity.class);

        Predicate p1 = cb.equal(root.get("groupId"), groupId);
        Predicate p2 = cb.equal(root.get("appId"), appId);
        Predicate condition = cb.and(p2,p3);
        if (typeId != null && !typeId.isEmpty()) {
            Predicate p1 = cb.equal(root.get("typeId"), typeId);
            condition = cb.and(condition, p1);
        }
        CriteriaQuery<SuspectVecEntity> cq = query.select(root.get("id")).where(condition);
        List<SuspectVecEntity> resultList = em.createQuery(cq).getResultList(); // resultList has Entity that only contains id
    }
}

Repository for Method 1:

public interface SuspectVecRepository extends JpaRepository<SuspectVecEntity, Long>, JpaSpecificationExecutor<SuspectVecEntity>  {
     List<SuspectVecEntity> findAll(Specification specs);
}

Service:

@Autowired
EntityManager em;
void foo() {
     // Method 1:
     List<SuspectVecEntity> r1 = findAll(SwVecSpecifications.conditions());
     // Method 2:
     List<SuspectVecEntity> r2 = SwVecSpecifications.findByConditions(em); 
}

The difference between this 2 methods is:

Method 1, DB query returns the whole column.

The reason is that repository findAll takes a Specification. That Specification is returned as restriction, and therefore, it queries all columns in Entity. It has a select to specify columns, which is in selection not in restriction. See more in class QueryStructure.

Method 2, DB query returns only the select columns.

It looks like return a Entity, this is because it is a chained generic type call, and that generic type is Entity.

I tried this, but it returns the entire object all the time.

This method returns single entity matching given specification. Please check here

According to my understanding this is the correct way. U can access the properties of the entity as normal (Eg. MyInfo.getIdProperty())

Specifications are abstractions over where clauses. Due to the design of the JPA criteria API you can all kinds of stuff in Specification but the behaviour of any side effect apart from declaring a where clause is undefined.

If you want to control the select list you can either use query derivation with projections and the very limited query support or construct complete custom queries in a custom method.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top