Search code examples
javamysqldatanucleusjdoql

How can I use DataNucleus JDOQL API to Query Object within Collection?


I'm trying to figure out how to query an object within a collection of persistent objects using an RDBMS and DataNucleus. I've got a User, an ObjectAttribute, and an ObjectAttributeDefinition.

The User may have more than one ObjectAttribute, and each ObjectAttribute has a single ObjectAttributeDefinition. The idea being that a few definitions can define a whole bunch of ObjectAttributes.

I want to check for the existence of an ObjectAttribute whose value field aligns with one that is specified, and whose ObjectAttributeDefinition contains field attributeName whose value is set to an attributeName that I've specified.

In other words:

for (ObjectAttribute attribute : userAttributeCollection)
{
    if(attribute.value == myValue && attribute.definition.attributeName == myName)
        return success;
}

I have the following three classes:

User

@Component
@PersistenceCapable
public class User implements Serializable
{
    @Persistent(table="USER_ATTRIBUTES")
    @Join(column="USER_ID")
    @Element(column = "ATTRIBUTE_ID")
    private List<ObjectAttribute> userAttributeCollection;

    ///... Constructors, getters and setters, Primary Key definition below
}

ObjectAttribute

@Component
@PersistenceCapable
public class ObjectAttribute implements Serializable
{
    @Persistent(table="ATTRIBUTE_DEFINITION_JOIN")
    @Join(column="ATTRIBUTE_ID")
    @Element(column="DEFINITION_ID")
    private ObjectAttributeDefinition definition;
    private String value;

    //...

    @Override
    public boolean equals(Object obj)
    {
        ObjectAttribute testObject = (ObjectAttribute) obj;
        if (this.value.equals(testObject.getValue()) && this.definition.getAttributeName().equals(testObject.getDefinition().getAttributeName())) {
            return true;
        }
        else {
            return false;
        }
    }
}

ObjectAttributeDefinition

public class ObjectAttributeDefinition implements Serializable
{
    public enum ATTRIBUTE_NAMES
    {
        //Google Search Page Attribute (https://cloud.google.com/appengine/docs/java/search/)
        SEARCH_PAGE("SEARCH_PAGE");

        private final String text;

        ATTRIBUTE_NAMES(String type)
        {
            this.text = type;
        }

        @Override
        public String toString()
        {
            return text;
        }
    }

    private ATTRIBUTE_NAMES attributeName;

    //...

    @Override
    public boolean equals(Object obj)
    {
        ObjectAttributeDefinition definition = (ObjectAttributeDefinition) obj;
        return this.attributeName.equals(definition.attributeName);
    }

}

Further Explanation

I have a DAO class and list of methods defined for accessing these persistent objects. The overall infrastructure appears to be set up correctly, as persisting objects and retrieving them again seems to be working properly.

My way of ensuring uniqueness with an object attribute will be to query an ObjectAttribute for its value and an ObjectAttributeDefinition for its name.

What I've Tried

Basic Query

        Query query = pm.newQuery(User.class, "userAttributeCollection.contains(att) & att.value == :val & att.definition.attributeName == :def");
        query.declareVariables("ObjectAttribute att");
        query.declareImports("import com.us.orm.general.models.ObjectAttributeDefinition; import com.us.orm.general.models.ObjectAttribute");

        query.setResultClass(User.class);

        rval =  (List<User>) query.execute(myAtt.getValue(), myAtt.getDefinition().getAttributeName());

Basic Query Error

Query has reference to member "attributeName" of class "com.us.orm.general.models.ObjectAttribute" yet this doesnt exist!

As the JDO Specification seems to imply, everything should be very Java-like. However, for some reason I can't seem to access objects any lower than the first-tier.

So I tried to hack the comparison.

Using equals() Override within ObjectAttribute

        Query query = pm.newQuery(User.class, "userAttributeCollection.contains(att) & att == :val");
        query.declareVariables("ObjectAttribute att");
        query.declareImports("import com.us.orm.general.models.ObjectAttributeDefinition; import com.us.orm.general.models.ObjectAttribute");

        query.setResultClass(User.class);

        rval =  (List<User>) query.execute(myAtt);

Using equals() Override within ObjectAttribute Result

I receive zero results back. Looking at the actual SQL generated for this object, I get the following:

SELECT DISTINCT 'com.us.orm.user.models.User' AS NUCLEUS_TYPE,`A0`.`CREATED`,`A0`.`EMAIL`,`A0`.`FIRSTNAME`,`A0`.`ISACTIVE`,`A0`.`LASTMODIFIED`,`A0`.`LASTNAME`,`A0`.`PASSWORD`,`A0`.`PROFILEBANNERBUCKET`,`A0`.`PROFILEBANNERFILENAME`,`A0`.`PROFILEIMAGEBUCKET`,`A0`.`PROFILEIMAGEFILENAME`,`A0`.`USERID`,`A0`.`USERNAME` FROM `USER` `A0` INNER JOIN `USER_ATTRIBUTES` `B0` ON `A0`.`USERID` = `B0`.`USER_ID` INNER JOIN `OBJECTATTRIBUTE` `C0` ON `B0`.`ATTRIBUTE_ID` = `C0`.`ATTRIBUTEID` WHERE `C0`.`ATTRIBUTEID` = null

The ATTRIBUTEID checking for a null reference will never return results. So this is on the side of my code's interface with DataNucleus, but I'm again at a loss.

Maybe I can override equals() on both the ObjectAttribute and ObjectAttributeDefinition..

Equals Override on both the ObjectAttribute and ObjectAttributeDefinition

    Query query = pm.newQuery(User.class, "userAttributeCollection.contains(att) & att.value == :val & att.definition == :def");
    query.declareVariables("ObjectAttribute att");
    query.declareImports("import com.up.orm.general.models.ObjectAttributeDefinition; import com.up.orm.general.models.ObjectAttribute");

    query.setResultClass(User.class);

    rval =  (List<User>) query.execute(myAtt.getValue(), myAtt.getDefinition());

Equals Override on both the ObjectAttribute and ObjectAttributeDefinition Result

rval returns null, no generated database query is made, and no exception is thrown.

Only Using Value Comparison

        Query query = pm.newQuery(User.class, "userAttributeCollection.contains(att) & att.value == :val");
        query.declareVariables("ObjectAttribute att");
        query.declareImports("import com.up.orm.general.models.ObjectAttributeDefinition; import com.up.orm.general.models.ObjectAttribute");

        query.setResultClass(User.class);

        rval =  (List<User>) query.execute(myAtt.getValue());

Only Using Value Comparison Result

This returns the expected values, but without using the definition comparison, which is only half of the goal.

Is there any way to query the definition and its fields at the second level of depth?


Solution

  • I've figured out the solution. I may have missed this in the documentation, but I wasn't aware that declared variables actually perform a "cross join" in SQL. This is a really important aspect to the solving the problem.

    The query is as follows:

        Query query = pm.newQuery(User.class, "userAttributeCollection.contains(att) & att.value == :val & definition.attributeName == :name");
        query.declareVariables("ObjectAttribute att; ObjectAttributeDefinition definition");
        query.declareImports("import com.us.orm.general.models.ObjectAttributeDefinition; import com.us.orm.general.models.ObjectAttribute");
    
        query.setResultClass(User.class);
    
        rval =  (List<User>) query.execute(myAtt.getValue(), name.toString());
    

    I was missing the "cross joined" variable piece to the filter (in the form of ObjectAttributeDefinition). Now that it's there, the query is pulling the expected results.