Updaets in 1.2beta-4:
- morphia lib updated to 0.99 release
- mongo driver updated to 0.24 release
Updates in 1.2beta-3:
- Fixed issues: 7,8,9, see http://github.com/greenlaw110/play-morphia/issues
- Add new configuration: morphia.defaultWriteConcern
Updates in 1.2beta-2:
- Fix bug: Play reload cause MappingException (https://github.com/greenlaw110/play-morphia/issues#issue/6)
- Supress some debug information (change debug to trace) in MorphiaEnhancer and MorphiaPlugin
Updates in 1.2beta:
- Upgrade Morphia Library to 0.99-SNAPSHOT, mongodb driver to 2.3
- Better generic support in Model query methods. Now you don’t need type cast for methods return a of entities. But if you use field or criteria in your expression, type cast is still needed
- Support new Or query interface which is released since morphia-0.97
Note using Long as IdType (configured with “morphia.id.type”) and you have @Reference annotation in your model entities, then you will be in trouble. Check this thread for detail. Yabe sample has been updated and now use ObjectId as ID type for the same reason
Known Issues:
1. StackOverflowError with unbind
2. Play reload cause MappingException
Updates in 1.1d:
- Fix bug: Play reload cause MappingException (https://github.com/greenlaw110/play-morphia/issues#issue/6)
- Supress some debug information (change debug to trace) in MorphiaEnhancer and MorphiaPlugin
Updates in 1.1c:
- Fix bug in MorphiaFixture: delete(Class
) -> delete(Class<? extends Model)
Updates in 1.1b:
- Yabe unit test passed
- Add isNew() method to Model
- Model.save() now return Model object which is comply to JPAModel; Use Model.save2() to return Key
Updates in 1.1a:
- Fix problems with User defined @Id field
- Add information to document on how to create user defined @Id field entity
Morphia module
The Morphia module bridge play.db.Model to mongodb.
This module depends on morphia project hosted on googlecode
Motivation
In the begining I want to use existing play-mongo plugin. However it lack some features I need, including:
- support map (relatively long) java name to (short) mongo db name
- create (inique) index on field
- life cycle management: @PreLoad, @PostPersist etc.
I’ve come across with morphia project and feel it is exactly what I need. Juust need to write some code to bridge it to existing data model of play framework. And it comes out this plugin.
features
Morphia module provides complete bridge from play.db.Model to mongo, meaning your application needs very limited change to migrate from JPA/RDBMS to mongodb:
- Annotations:
- Entity (support short collection name), like javax.persistence.Entity combined with javax.persistence.Table
- Id, like javax.persistence.Id. Usually you don’t need to provide @Id marked fields as MorphiaEnhancer will generate one for you.
- Property (support short field name), like javax.persistence.Column
- Indexed (support unique and dropDups), enable you mark fields needs index
- Transient, mark fields which should be ignored
- Embedded, mark a entity which should be embedded in another entity, or a field of host entity which is an embedded entity
- Life cycle annotations: PreLoad, PrePersist, PostLoad, PostPersist...
- and many other useful annotations you can find at here
- play.db.Model support
- Yes, you can use almost all existing interface without changing your code. You can even load yml data by Fixtures.load(). You can also use find(“byXAndY”, ...)
style query. However SQL/JQL query not supported. Take a look at the YABE sample code to see how things like "Post.find(“postedAt < ? order by postedAt desc”, postedAt).first();“ be translated into ”(Post) Post.filter(“postedAt <”, postedAt).order(“-postedAt”).get();". There is also an example in Tag.java showing you how SQL aggregation is completed using mongodb mapReduce. Note, you can’t simply use Fixtures.deleteXX() methods to get rid of testing data, use MorphiaFixtures.deleteXX() instead.
- Dynamic Id support
- MongoDB suggest use org.bson.types.ObjectId as the type for Id field, there are good reason to use that b/c u don’t have go to database for new id generation. However
if you like use Long (an obvious motivation is CRUD’s route file recongnize number only...), you are free to do it. Just add this line in your application.conf: “morphia.id.type=Long” Morphia will handle everything for you Long type ID generation. You are free to mark your own Id field with any type (better to be String) like Tag.name. In that case, Morphia will
not manage your Id field
- CRUD support
- Yeah! Morphia support CRUD transparently. However there are some inherited limitation with Embedded entity type: MongoDb prevent Id field for any embedded document, thus you cannot manage embedded entities using CRUD like Comment in YABE sample.
- Data binding and validation
- Feel free to your faviourite "Binder.bind(user, “User”, params.all());" or "User.edit(user, “User”, params.all());", and call “Validation.valid(user);” to validate
http request params.
- Indexing
- You don’t need to create index manually. Just map your field with @Indexed. Morphia will ensure the index is created upon your play application startup
Limitation
- The generic is not working well, sometimes you need type casting. If someone could help, that would be very appreciate.
- “Or” condition query is doable but the interface might changed in future, use it with caution.
- “where” condition not support in the following interface of play.db.Model.Factory implementation:
public List<play.db.Model> fetch(int offset, int size, String orderBy, String order, List<String> searchFields, String keywords, String where);
TODO List
- Support storing files to GridFS
Dependencies
- Play: Play-1.1 release. It’s okay to use the current trunk but not 1.1 beta1
- Morphia: morphia-0.97 + this patch. It’s safe to use the bundled jar file.
You really really need the latest Play (even late than 1.1beta) and morphia@googlecode (even late than morphia-0.96-SNAPSHOT.jar). In the progress of development, there are many interactions with Play and morphia team and have some code changes in the latest trunk of both product. It’s safe to use the morphia-0.96-SNAPSHOT.jar file included in this plugin. But don’t use the one distibuted at http://code.google.com/p/morphia/.
Usage
Configuration
## Morphia module configuration
# load morphia module
module.morphia=${play.path}/modules/morphia
# where your mongodb server located?
morphia.db.host=ckweb
# what's your mongodb server port
morphia.db.port=27017
# what's your database name
morphia.db.name=yabe
# Authentication to your mongodb server
#morphia.db.username=user
#morphia.db.password=pass
# configure your ID field type
# could be either ObjectId or Long, default to ObjectId
morphia.id.type=Long
# Set default write concern, see http://api.mongodb.org/java/current/com/mongodb/class-use/WriteConcern.html
#morphia.defaultWriteConcern=safe
Create domain model using Morphia
package models;
import play.data.validation.Email;
import play.data.validation.Required;
import play.modules.morphia.Model;
import com.google.code.morphia.annotations.Entity;
@Entity
public class User extends Model {
@Email
@Required
public String email;
@Required
public String password;
public String fullname;
public boolean isAdmin;
public User(String email, String password, String fullname) {
this.email = email;
this.password = password;
this.fullname = fullname;
}
public static User connect(String email, String password) {
return find("byEmailAndPassword", email, password).first();
}
public String toString() {
return email;
}
}
User.java is almost not changed from what it is in Play sample_and_tests. The only differences is Model is now play.modules.morphia.Model and Entity becomes com.google.code.morphia.annotations.Entity
Migrate JQL query to MongoDB query
JPA Style
public Post previous() {
return Post.find("postedAt < ? order by postedAt desc", postedAt).first();
}
Morphia style:
public Post previous() {
return (Post) Post.filter("postedAt <", postedAt).order("-postedAt").get();
}
Aggregation with MapReduce
JPA style:
public static List<Map> getCloud() {
List<Map> result = Tag.find(
"select new map(t.name as tag, count(p.id) as pound) from Post p join p.tags as t group by t.name"
).fetch();
return result;
}
h4.. Morphia style:
private static final String m_ = "function() {this.tags.forEach(function (t) {emit (t, {count:1});});}";
private static final String r_ = "function(v, vs){var t=0;for(var i=0;i<vs.length;++i){t+=vs[i].count}return{tag: v, count:t}}";
public static List<Map<String, Integer>> getCloud(Query<? extends Model> query) {
List<Map<String, Integer>> result = new ArrayList<Map<String, Integer>>();
Datastore ds = MorphiaPlugin.ds();
DBCollection dbCol = ds.getCollection(Post.class);
MapReduceOutput out = dbCol.mapReduce(m_, r_, null, query == null ? null : ((QueryImpl<? extends Model>)query).getQueryObject());
for (Iterator<DBObject> itr = out.results().iterator(); itr.hasNext();) {
DBObject dbo = itr.next();
DBObject k_v = (DBObject)dbo.get("value");
Map<String, Integer> m = new HashMap<String, Integer>();
m.put((String)k_v.get("tag"), ((Double)k_v.get("count")).intValue());
result.add(m);
}
return result;
}
This part looks not so elegant. I hope it could be simplified in the future.
FAQ
Why do I get the following error while running my play app in prod mode?
play.exceptions.JavaExecutionException: Cannot load fixture initial-data.yml: The JPA context is not initialized. JPA Entity Manager automatically start when one or more classes annotated with the @javax.persistence.Entity annotation are found in the application.
at play.jobs.Job.call(Job.java:119)
at Invocation.Job(Play!)
Caused by: java.lang.RuntimeException: Cannot load fixture initial-data.yml: The JPA context is not initialized. JPA Entity Manager automatically start when one
or more classes annotated with the @javax.persistence.Entity annotation are found in the application.
at play.test.Fixtures.load(Fixtures.java:214)
at Bootstrap.doJob(Bootstrap.java:21)
at play.jobs.Job.doJobWithResult(Job.java:37)
at play.jobs.Job.call(Job.java:110)
This problem happens on Play v1.1-beta1. Please switch to latest 1.1 version
How to create an “OR” relation query?
Query q = myPost.createQuery(); // create a Query
q.or(q.criteria("title").contains("mongodb"), q.criteria("content").contains("mongodb"), ...);
List<play.db.Model> l = new ArrayList<play.db.Model>();
l.add(q.order("-postedAt").limit(10).asList()); // sort the result by postedAt desc order and fetch at most 10 items
return l;
How to do aggregation?
Scroll up this page until you see “Aggregation with MapReduce”.
There is a lot of compilation and runtime errors with this plugin!
Make sure you have checked “Dependences” section already
Why do you choose Morphia? I’ve heard that GuiceyMongo is faster
Well the goal of Play-Morphia is to provide a way to help Play app developer to migrate their existing apps from RDBMS (via JPA) to MongoDB with least effort. Morphia is by far the best thing I can find provides JPA style (annotation) access to mongodb. If you are developing new Play application, and would like to try a different way, GuiceyMongo might be a good choice for you.
How to create Entity with user defined @Id field?
- Add @com.google.code.morphia.annotations.Id annotation to the field you want to use as an ID field, e.g. : @Id name;
- Override the following methods declared in play.modules.morphia.Model:
@Override public Object getId() {return name;}
@Override protected void setId_(Object id) {name = id.toString();}
protected static Object processId_(Object id) {return id.toString();}
Make sure you always populated your @Id field before calling model.save(), don’t let framework to generate Id for you b/c framework has no knowledge on how to do it
It’s better to keep your @Id field type to be java.lang.String.
I got “Can not use dot-notation past ...” exception when I try to query entity using relationship, what happened?
Basically this error occurred when you try to query an entity using referenced entity property. E.g. Post p = Post.filter(“author.email”, “[email protected]”).first(). The solution is:
User bob = User.filter("email", "[email protected]").first();
Post p = Post.filter("author", bob).first();
If the relationship is embedded rather than reference, you can safely use “dot” notation to do the query
h3. How do I know if an entity object is an new object just constructed in the memory or represents a data (either by save or load from db) in a db?
Use obj.isNew()
I try to call Fixtures.deleteAll() in my unit test setup methods, but I found the data is still there, what happened?
The deleteAll() methods defined in play.test.Fixtures are hooked with JPA based database, try to use MorphiaFixtures.deleteAll() instead. Actually you are encouraged to use MorphiaFixtures in replace of Fixtures to deal with your Morphia models for all methods call.
Why setting morphia.defaultWriteConcern does not work?
A <a href=‘http://code.google.com/p/morphia/issues/detail?id=209’>bug of current version (at the time v0.99) of morphia suppress this setting