2012-09-28

MongoDB with Morphia (and Scala)

I've been working on my Scala application that connects to MySQL on one end (using EclipseLink and JPA), and MongoDB on the other (using Morphia). The source code is available on GitHub. In this post I will show you my setup.

build.gradle

Dependencies...
// ...

repositories {
   mavenLocal()
   mavenCentral()
   mavenRepo name: "EclipseLink", url: "http://download.eclipse.org/rt/eclipselink/maven.repo/"
   mavenRepo name: "MongoDB", url: "http://morphia.googlecode.com/svn/mavenrepo"
   mavenRepo name: "Audiolicious", url: "https://github.com/m1key/repo/raw/master/releases"
}

dependencies {

   compile group: 'org.eclipse.persistence', name: 'eclipselink', version: '2.3.0'
   compile group: 'org.eclipse.persistence', name: 'javax.persistence', version: '2.0.0'
   compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.18'
   compile group: 'org.mongodb', name: 'mongo-java-driver', version: '2.7.3'
   compile group: 'com.google.code.morphia', name: 'morphia', version: '0.99'

   // …
}

// ...

Persistence provider

This is how you expose Mongo to your repositories (or DAOs).
package me.m1key.audioliciousmigration.persistence.mongodb

import com.google.code.morphia.Datastore
import com.mongodb.Mongo
import com.google.code.morphia.Morphia
import me.m1key.audioliciousmigration.entities.mongodb.MongoDbSong

class ProductionMorphiaMongoDbPersistenceProvider extends MorphiaMongoDbPersistenceProvider {

 private var datastore: Datastore = null
 private var mongo: Mongo = null

 def initialise(): Unit = {
    mongo = new Mongo("localhost", 27017)
    datastore = new Morphia().map(classOf[MongoDbSong]).createDatastore(mongo, "audiolicious")
    datastore.ensureIndexes()
 }

 def getDatastore(): Datastore = {
    return datastore
 }

 def getMongo(): Mongo = {
    return mongo
 }

}

Repository using getDatastore

This is a repository which uses the MongoDB datastore.
package me.m1key.audioliciousmigration.repository

import com.google.inject.Inject
import me.m1key.audioliciousmigration.persistence.mongodb.MorphiaMongoDbPersistenceProvider
import scalaj.collection.Imports._
import me.m1key.audioliciousmigration.entities.mongodb.MongoDbSong
import com.google.code.morphia.query.UpdateOperations
import com.google.code.morphia.query.Query

class MorphiaMongoDbRepository @Inject() (private val persistenceProvider: MorphiaMongoDbPersistenceProvider) {

 def save(song: MongoDbSong): Unit = {
    val datastore = persistenceProvider.getDatastore()

    val selectQuery = datastore.createQuery(classOf[MongoDbSong])
     .field("name").equal(song.name).field("albumName").equal(song.albumName)
     .field("artistName").equal(song.artistName).field("songKey").equal(song.songKey);
    val existingSong = selectQuery.get()

    if (existingSong != null) {
     var ops = datastore.createUpdateOperations(classOf[MongoDbSong])

     if (song.genre != null) {
       ops = ops.set("genre", song.genre)
     }
     if (song.year != 0) {
       ops = ops.set("year", song.year)
     }
     if (song.songArtistName != null) {
       ops = ops.set("songArtistName", song.songArtistName)
     }

     val stat = song.statsList.get(0)
     existingSong.addOrEditStats(stat.libraryUuid, stat.percentage, stat.playCount, stat.skipCount)
     ops.set("statsList", existingSong.statsList)

     val updatedCount = datastore.update(selectQuery, ops).getUpdatedCount()

     if (updatedCount != 1) {
       println("Warning. Song updated [%d] times while expected once: [%s]".format(updatedCount, song))
     }
    } else {
     datastore.save(List(song).asJava)
    }
 }

}

getMongo

Here's code that uses getMongo for executing a raw query.
 def mine(): Option[Int] = {
    val mongo = persistenceProvider.getMongo
    val result = mongo.getDB("audiolicious").eval(query)
    result match {
     case double: java.lang.Double => return Some(double.intValue())
     case _ =>
       println("Error while obtaining stats. Result of unknown type [%s].".format(result))
       return None;
    }
 }

Putting it all together...

Before code using datastore or mongo can be executed, the following call has to be made.
    val mongoDbPersistence = injector.getInstance(classOf[MorphiaMongoDbPersistenceProvider])
    mongoDbPersistence.initialise
(This example is using Guice) I don't know if this is the best way of doing things, but there's little documentation available. This way works. If you have a better idea - definitely let me know!

2 comments:

  1. Hey Michał,
    I think that since you're using Scala, you should go with the best scala DSL there is for MongoDB, namely -> https://github.com/foursquare/rogue . I mean, why use Scala if you don't leverage what it can do? Your example rewritten to Rogue would be something like:

    val mds = MongoDbSong (_.name eqs song.name) and (_.albumName eqs song.albumName) and (_.artistName eqs song.artistName) get()

    And it's... Typesafe! :-) Really, give it a shot - you won't regret it.

    ReplyDelete
  2. Morphia doesn't seem to be doing so well, it's been going nowhere for a while, so I guess I might as well give Rogue a try. :)
    Thanks Konrad!

    ReplyDelete