javahibernatejpanestedcriteria-api

jpa 2 criteria hibernate 5.2 nested joins


I'm trying to add to my crud services the possibility to specify what nested relationship I need so I don't have to read everything from the database.

Take for example I have those entities

Company.java

private List<Department> departments;
private SalaryCode salaryCode;

Department.java

private List<Employee> employees;
private Company company;
private SalaryCode salaryCode;

Employee.java

private Department department;
private SalaryCode salaryCode

And my Criteria query for now is this :

Session session = sessionFactory.openSession();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = builder.createQuery(clazz);
Root<T> root = criteriaQuery.from(clazz);

//nestedRelationships is a varargs passed as parameters
for(String nestedRelationship : nestedRelationships) {
    root.fetch(nestedRelationship, JoinType.LEFT);
}

List<T> result = session.createQuery(criteriaQuery.select(root)).list();

The thing is if I specify "department" as nestedRelationship and querying for Employee entity it works well but when I try to specify "department.salaryCode" it doesn't work saying " Unable to locate Attribute with the the given name ". Of course I'm fetching "department" first and then "department.salaryCode".

Is it supported? If yes how does it work and if it's not supported what can I do?


Solution

  • I found a solution by making an algorithm using the Root element

    protected void fetch(Root<T> root, String... joins) {
        //Sort the joins so they are like this :
        //A
        //A.F
        //B.E
        //B.E.D
        //B.G
        Arrays.sort(joins);
    
        Map<String, Fetch> flattenFetches = new HashMap<>();
    
        for (String join : joins) {
            try {
                if (join.contains(".")) {
                    String[] subrelations = join.split("\\.");
                    Fetch lastRelation = null;
                    int i;
    
                    for (i = subrelations.length - 1; i >= 0; i--) {
                        String subJoin = String.join(".", Arrays.copyOf(subrelations, i));
    
                        if (flattenFetches.containsKey(subJoin)) {
                            lastRelation = flattenFetches.get(subJoin);
                            break;
                        }
                    }
    
                    if (lastRelation == null) {
                        lastRelation = root.fetch(subrelations[0], JoinType.LEFT);
                        flattenFetches.put(subrelations[0], lastRelation);
                        i = 1;
                    }
    
                    for (; i < subrelations.length; i++) {
                        String relation = subrelations[i];
                        String path = String.join(".", Arrays.copyOf(subrelations, i + 1));
    
                        if (i == subrelations.length - 1) {
                            Fetch fetch = lastRelation.fetch(relation, JoinType.LEFT);
                            flattenFetches.put(path, fetch);
                        } else {
                            lastRelation = lastRelation.fetch(relation, JoinType.LEFT);
                            flattenFetches.put(path, lastRelation);
                        }
                    }
                } else {
                    Fetch fetch = root.fetch(join, JoinType.LEFT);
                    flattenFetches.put(join, fetch);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    and to use it I just have to do for example :

    employeeController.getAll("punches", "currentSchedule.shifts", "defaultDepartment.currentSchedule.shifts",
                "defaultDepartment.company.currentSchedule.shifts", "bankExtras")
    

    I would like to comment the algorithm but I do not have time and it's pretty easy to understand