Using query
Backed by Morphia project, PlayMorphia provides powerful query interface to make the "information at your finger tips".
30 seconds tutorial
Suppose you have a model class defined as:
@Entity public class User extends Models {
public String firstName;
public String lastName;
public String country;
public String department;
public int score;
}
A typical pattern of using PlayMorphia query interface:
List<User> users = User.q()
.filter("country", "China")
.filter("department", "IT").asList();
You could also achieve the same result with:
List<User> users = User.find("country,department", "China", "IT").asList();
Fields can also be separated by space:
List<User> users = User.find("country department", "China", "IT").asList();
or with the Play JPA style:
List<User> users = user.find("byCountryAndDepartment", "China", "IT").asList();
Build MorphiaQuery object
The simplest way to get an MorphiaQuery
object out from a Model
object is call the static q()
method upon the model class. Note you must call the method on your model class not the inherited play.modules.morphia.Model
:
MorphiaQuery q = User.q();
Specify query criteria
Once you have the MorphiaQuery
instance you could filter the return result set using the filter interface. For example, to find all users with score between 80 and 90, you use the “>” and “<” operators:
List<User> users = user.q().filter("score > ", 80).filter("score < 90").asList();
Note the filter()
call on a MorphiaQuery
instance adds certain constraints to the current query instance and return "this
" object. In other words, filter()
is a mutable method and not thread-safe.
Here is a list of operators you can used in PlayMorphia query:
operator | mongo op | description |
---|---|---|
= | $eq | field value equals to the supplied object 1 |
!=, <> | $ne | field value is not equal to the supplied object |
>, <, >=, <= | $gt, $lt, $gte, $lte | greater than, less than, greater than or equal to, less than or equal to |
in | $in | field value is in the supplied list 2 |
nin | $nin | field value is not in the supplied list 2 |
elem | $elemMatch | field (array or list) has element matches the supplied object |
exists | $exists | field exist (not null in Java term) |
all | $all | field value (array or list) contains all elements supplied by argument |
size | $size | the size of field value (array or list) equals to the supplied argument |
type | $type | The type of the field value equals to the type number specified by argument |
For more operator information please refer to to MongoDB Advanced Queries
1 Using operator eq
has the same effect as no operator:
List<User> users = User.q().filter("department eq", "IT").asList();
is exactly the same as
List<User> users = User.q().filter("department", "IT").asList();
2 When you have the "in
" or "nin
" operators in the filter constraint, you can supply the following types of parameters:
- array
- Any object with class implements
java.lang.Iterable
- a single object. In this case
in
is the same as=
,nin
is the same as!=
Search with regular expression
Application developer could implement regular expression based text search by using java.util.regex.Pattern class. The following query returns all users with firstName contains “john” or “John”
List<User> johns = User.q().filter("firstName",
Pattern.compile("john", Pattern.CASE_INSENSITIVE)).asList();
OR query
You can use the filter()
interface and find()
interface to apply contraints on mutiple fields using “AND” relationship. In order to implement a “OR” based query you need to use MorphiaQuery.or()
and com.google.code.morphia.query.Criteria
interface. The following query search for user who’s first name contains “john” or last name contains “john”:
MorphiaQuery q = User.q();
q.or(q.criteria("firstName").containsIgnoreCase("john"),
q.criteria("lastName").containsIgnoreCase("john"));
List<User> johns = q.asList();
Here q.criteria(<fieldname>).contains(<keyword>)
is one of Morphia’s fluent interface. You should not pass Pattern
object into the contains
interface. Instead, pass String
type object which will be automatically converted to Pattern
by the underline implementation.
Query on embedded objects
To query on the embedded object you need to use the dot notation. Suppose you have the following models:
@Embedded public class Address {
public String number;
public String street;
public String city;
public String country;
}
// User model contains the address object
@Entity public class User {
public String firstName;
public String lastName;
@Embedded Address address;
}
Now the following query find out all users lived in “FuXingJie, DuJiangYan, China”:
List<User> users = Users.find("address.street, address.city, address.country",
"FuXingJie", "DuJiangYan", "China").asList();
Query on referenced objects
PlayMorphia does not support query on referenced objects direct as MongoDB does not support join query. However you could do it in 2 steps:
- Find out the referenced object
- Find out the objects who references that object
Suppose you have an Author
model class defined:
@Entity public class Author extends Model {
public String fullName;
public String email;
}
And then you define a BlobEntry
class which references the Author
model:
@Entity public class BlogEntry extends Model {
public String title;
public Date publishDate;
public String body;
@Reference public Author author;
}
The following code find out all blog entries written by [email protected]:
Key<Author> green = Author.find("email", "[email protected]").getKey();
List<BlogEntry> entries = BlogEntry.find("author", green).asList();
And the following code find out all blog entries written by [email protected] and [email protected]:
String[] emails = {"[email protected]", "[email protected]"};
List<Key<Author>> greens = Author.find("email in", emails).asKeyList();
List<BlogEntry> entries = BlogEntry.q().filter("author in", greens).asList();
If you are using the manually reference approach to define reference model:
@Entity public class BlobEntry {
public String title;
public Date publishDate;
public String body;
public String authorId;
public Author getAuthor() {
return Author.findById(authorId);
}
}
Then you need to query on authorId
:
Object id = Author.find("email", "[email protected]).getKey().getId();
List<BlobEntry) entries = BlobEntry.find("authorId", id).asList();
Caching and avoid N+1
Query through reference relationship is not simple and straightforward as it is in relational database world. MongoDB’s solution is do a little bit of denormalization. Let’s say if you often query BlobEntry
with author’s email, you could cache the email
field in the BlobEntry
model:
@Entity public class BlobEntry {
public String title;
public Date publishDate;
public String body;
public String authorId;
public Author getAuthor() {
return Author.findById(authorId);
}
// cache
public String authorEmail;
}
now the query become easy:
List<BlobEntry> entries = BlobEntry.find("authorEmail", "[email protected]").asList();
People might argue that doing it cause redudant data. Just think how much you pay for the redundant data and how much you gain from simplified query. To update cache field is no doubt expensive. Again you need to make trade off between expensive update and fast query. In our case author’s email is seldom updated while queries happen hundreds of times per day. So cache email
in the BlobEntry
model is not a bad idea.
See MongoDB Data Modeling and Rails to get more information about this topic.
Fetch entities from MorphiaQuery
As shown above examples you can retrieve the query result in a list by invoking asList()
method on the MorphiaQuery
object:
List<User> = User.q().filter(...).asList();
Unlike JPA, you cannot use fetch
to return a list of objects from MorphiaQuery
object. The MorphiaQuery.fetch
method returns an java.lang.Iterable
type object. MorphiaQuery.asList()
is the equivalence of JPAQuery.fetch
.
Limit the number of records returned
The above query returns all instance filtered by the query. To limit the number of models returned, you can invoke the limit
method on the query object:
List<User> = User.q().filter(...).limit(10).asList();
Skip the first N number of records
You can also instruct query to skip first N records:
List<User> = User.q().filter(...).offset(10).asList();
Combining limit
and offset
call you get an nice pagination support from MorphiaQuery
Get only one record
You can also fetch one model instance from the query:
User user = User.q().get();
or
User user = User.get();
Alias of queries methods
PlayMorphia provides aliases for query support to cater to different coding style:
Attaining MorphiaQuery
from your model class:
MorphaiQuery q = User.q();
q = User.createQuery();
q = User.all();
q = User.find();
Filtering query on your model class:
List<User> users = User.q().filter("country", "China").filter("department", "IT").asList();
users = User.q().findBy("country,department", "China", "IT").asList();
users = User.q().findBy("byCountryAndDepartment", "China", "IT").asList();
users = User.find("byCountryAndDepartment", "China", "IT").asList();
users = User.find("country,department", "China", "IT").asList();