Skip to content
Provide mongodb access service to play.db.Model via Morphia
Java
Find file
Pull request Compare This branch is 147 commits behind greenlaw110:master.
Latest commit 9ff9d35 Agile Consulting Added a morphiaFixture tag. This allows models to be loaded and delet…
…ed from within Selenium test cases.
Failed to load latest commit information.
app Added a morphiaFixture tag. This allows models to be loaded and delet…
documentation/manual fix bug: IllegalStateException of "User defined ID should be populate…
lib fix bug: IllegalStateException of "User defined ID should be populate…
samples-and-tests findById(null) returns null instead of throw NullPointerException
src fix bug : retrieve the correct field type to get the choices list on …
.gitignore update .gitignore
LICENSE.txt first version
README.textile
build.properties 1.2beta2
build.xml first version

README.textile

Updates in 1.2.1beta6

  • Fix bug: IllegalStateException of “User defined ID should be populated before persist” when saving a subtype model with @Id fields defined in parent type

Updates in 1.2.1beta5

Updates in 1.2.1beta4

  • Upgrade morphia to 1.0-snapshot and mongo lib to 2.6.1

Updates in 1.2.1beta3

  • Limited support for multiple mongodb databases

Updates in 1.2.1beat2

  • “test” db used if no database configured and a warning issued
  • clear idCache when calling MorphiaFixtures.deleteX
  • support auto timestamp

Updates in 1.2.1beta1

  • Make it compatible with play-1.2.x

Updates in 1.2beta-6

  • Support filter (where clause) in Model.Factory.count and Model.Factory.fetch
  • Add @Global annotated ObjectIdBinder to app. This could fix issue #5
  • Revert morphia library to 0.99-Snapshot as a workaround for issue #16

Updates in 1.2beta-5:

Updates in 1.2beta-4:

  • morphia lib updated to 0.99 release
  • mongo driver updated to 0.24 release

Updates in 1.2beta-3:

Updates in 1.2beta-2:

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:

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
      • Note class annotated with @Embedded shall NOT extends play.modules.morphia.Model class
    • Life cycle annotations: PreLoad, PrePersist, PostLoad, PostPersist…
    • and many other useful annotations you can find at here
    • AutoTimestamp(start from version 1.2.1beta2), this is play-morphia (rather than morphia) annotation. When your model is annotated with this annotation, the plugin will automatically add two fields to the class:
      • public long created; // the field get filled with System.currentTimeMills() when an new model is saved. To access the field, call myModel.getCreated()
      • public long modified; // the field get filled with System.currentTimeMills() whenever a model is saved. To access the field, call myModel.getModified()
  • 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.
    • (new in 1.2beta-5) simple where string support in Model.Factory.fetch|count:
      • where string could be one of: “prop = val”, “prop in (val1, val2, …)”, “prop1 = val1 and prop2 = val2 and prop3 in (val1, val2, val3, ..) …”
  • 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.
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;
    }
 
}

Note @Embedded class shall NOT extend play.modules.morphia.Model class

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;
    }

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”, “bob@gmail.com”).first(). The solution is:

User bob = User.filter("email", "bob@gmail.com").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

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 bug of current version (at the time v0.99) of morphia suppress this setting

I get duplicate ID field error.

If you define sub type models Morphia will probably enhance and add @Id field to both sub type and parent type. Therefore you got 2 @Id field in sub type model. To resolve the problem, annotate your sub type or parent type with @NoId annotation (available in morphia-1.2.1beta5)

Something went wrong with that request. Please try again.