Using conditional operator in a spring mongo [dupl

2020-05-06 11:02发布

问题:

Following in my Mongo query

{$project:{
       id:"$_id",
       login:"$login",
       firstName: "$firstName",
       lastName:"$lastName",
       email:"$email",
       deactivateFlag:"$deactivateFlag",
       lastActivity:"$lastActivity",
       company :"$organization.name",
       RoleName :"$organization.roles.roleName",
       isMatchingRoles: { $eq: [ "$organization.roles.orgRoleId","$userOrgMap.roleId" ] }
      }
    },
    { $match: {isMatchingRoles:true},

This works perfectly fine. Particularly the last $match works perfectly to reduce the duplicates. But when I tried to convert the above code to Spring data equivalent, I am facing the following problem.

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(ArrayList.java:653) at java.util.ArrayList.get(ArrayList.java:429) at java.util.Collections$UnmodifiableList.get(Collections.java:‌​1309)

Following is my Spring Data code. I printed the raw output and happen to get boolean value for isMatchingRoles which is part of my bean class which is mapped during aggregation. But when the Boolean values is getting mapped using getMappedResult , I am getting this issue.

aggregation = newAggregation(
                        match(getUsersCriteria(searchTxt)), 
                        unwind("userOrgMap"),
                        lookup("organizations", "userOrgMap.orgId", "_id", "organization"),
                        unwind("organization"),
                        unwind("organization.roles"),
    project("userId", "login", "firstName", "lastName", "email", "deactivateFlag", "lastActivity")
                                .and("organization.name").as("companyName")
                                .and("organization.roles.roleName").as("roleName")
                                .and(when(where("organization.roles.orgRoleId").is("userOrgMap.roleId")).then(true).otherwise(false)).as("isMatchingRoles"),
                            match(Criteria.where("isMatchingRoles").is(true))

    AggregationResults<UserDTO> groupResults groupResults = mongoOperation.aggregate(aggregation, UserDTO.class, UserDTO.class);
                    System.out.println(" Raw result "+groupResults.getMappedResults().get(0));
                    // The above result is getting data properly for isMatchingRoles

                    List<UserDTO> result = groupResults.getMappedResults();
                      // I am getting the exception in the above code

                    System.out.println(" actual mapped Reesult "+result.size());

Following is my input class

@Document(collection = "users")
public class UserDTO {



    @Id
    private String userId;
    private String login;
    private String firstName;
    private String lastName;
    private String lastActivity;
    private String email;
    private boolean deactivateFlag;
    private String companyName;
    private String roleName;
    private boolean isMatchingRoles;



    public UserDTO(String userId, String login, String firstName, String lastName, String lastActivity, String email,
            boolean deactivateFlag, String companyName, String roleName, boolean isMatchingRoles) {
        super();
        this.userId = userId;
        this.login = login;
        this.firstName = firstName;
        this.lastName = lastName;
        this.lastActivity = lastActivity;
        this.email = email;
        this.deactivateFlag = deactivateFlag;
        this.companyName = companyName;
        this.roleName = roleName;
        this.isMatchingRoles = isMatchingRoles;
    }


    public String getUserId() {
        return userId;
    }


    public void setUserId(String userId) {
        this.userId = userId;
    }


    public String getLogin() {
        return login;
    }


    public void setLogin(String login) {
        this.login = login;
    }


    public String getFirstName() {
        return firstName;
    }


    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }


    public String getLastName() {
        return lastName;
    }


    public void setLastName(String lastName) {
        this.lastName = lastName;
    }


    public String getLastActivity() {
        return lastActivity;
    }


    public void setLastActivity(String lastActivity) {
        this.lastActivity = lastActivity;
    }


    public String getEmail() {
        return email;
    }


    public void setEmail(String email) {
        this.email = email;
    }


    public boolean isDeactivateFlag() {
        return deactivateFlag;
    }


    public void setDeactivateFlag(boolean deactivateFlag) {
        this.deactivateFlag = deactivateFlag;
    }


    public String getCompanyName() {
        return companyName;
    }


    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }


    public String getRoleName() {
        return roleName;
    }


    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }


    public boolean isMatchingRoles() {
        return isMatchingRoles;
    }


    public void setMatchingRoles(boolean isMatchingRoles) {
        this.isMatchingRoles = isMatchingRoles;
    }



}

Following is my sample collection

user collection


{
    "_id" : "123",
    "login" : "abc@abc.com",
    "firstName" : "xxx",
    "lastName" : "yyy",
    "email" : "abc@abc.com",
    "deactivateFlag" : false,
    "userOrgMap" : [
        {
            "orgId" : "999",
            "roleId" : "888"
        }
    ]
}


Organization collection


{
    "_id" : "999",
    "name" : "orgName",
    "roles" : [
        {
            "orgRoleId" : "888",
            "roleName" : "Standard User"
        },
        {
            "orgRoleId" : "777",
            "roleName" : "Company Administrator"
        }
    ]
}

回答1:

You are using the (CriteriaDefinition) api (not sure why is this exposed in projection, may be because aggregation operators does share some of its functions with query operators ) to generate the conditional expression, which should be used for regular query expressions( used in $match stage and regular find queries).

The criteria definition api generates the following query.

"isMatchingRoles": {
    "$cond": {
        "if": {
            "$eq": ["$organization.roles.orgRoleId", "userOrgMap.roleId"]
        },
        "then": true,
        "else": false
    }
} 

Notice the second argument to the $eq is string value rather than field reference.

You can do it in following two ways.

Using ConditonalOperator api.

.and(when(ComparisonOperators.Eq.valueOf("organization.roles.orgRoleId").equalTo("userOrgMap.roleId")).then(true).otherwise(false)).as("isMatchingRoles")

Use AggregationExpression to generate the conditional expression.

and(new AggregationExpression() {
            @Override
            public DBObject toDbObject(AggregationOperationContext context) {
                return new BasicDBObject("$cond",
                        Arrays.<Object>asList(
                                new BasicDBObject("$eq", 
                                        Arrays.asList("$organization.roles.orgRoleId", "$userOrgMap.roleId")),
                                true,
                                false));
            }
 }).as("isMatchingRoles");