My Scala Base

Adventures in Scala

Steve Yen

More Hibernate In Scala: Annotations versus external XML versus inline XML

I've been tweaking the Hibernate in Scala example from my previous post.

The experiment is to interleave the hbm.xml mapping information right into your plain old scala object code. Sort of like a poor man's annotation, so you can keep your meta information right snug up to your properties. This helps get around Scala's current limitations around deep annotations (so I heard).

Here's an example of defining and using two Scala classes -- scalanate.Event and scalanate.Person, and Hibernate-izing them...
package scalanate  import scala.xml._  import java.util.Date  import org.hibernate._ import org.hibernate.cfg._  class Event extends Hibernate {   var id: Long = 0L      hibernate(<id name="id"                    column="EVENT_ID">                   <generator class="native"/>               </id>)    var title: String = null     hibernate(<property name="title"/>)    var date: Date = null      hibernate(<property name="date"                          type="timestamp"                          column="EVENT_DATE"/>)    var participants: java.util.Set = new java.util.HashSet     hibernate(<set name="participants"                     table="PERSON_EVENT"                     inverse="true">                    <key column="EVENT_ID"/>                    <many-to-many column="PERSON_ID"                                   class="scalanate.Person"/>               </set>)      override def toString = "Event " + id + " " + title + " " + date              hibernateReady }  class Person extends Hibernate {   var id: Long = 0L      hibernate(<id name="id"                    column="PERSON_ID">                   <generator class="native"/>               </id>)    var age: Int = 0     hibernate(<property name="age"/>)    var name: String = null       hibernate(<property name="name"/>)    var events: java.util.Set = new java.util.HashSet     hibernate(<set name="events"                     table="PERSON_EVENT">                    <key column="PERSON_ID"/>                    <many-to-many column="EVENT_ID"                                   class="scalanate.Event"/>               </set>)    override def toString = "Person " + id + " " + name + " " + age               hibernateReady }
The magic is in the repeated calls to hibernate() and hibernateReady(), which together capture the XML mapping information that Hibernate needs. The fragments of XML should be familiar to anyone who's had to sling around a hibernate X.hbm.xml mapping file. Now, we can crank up a program and do some entity creation, saving, and querying...
object Main {    def main(args: Array[String]) {     println("main: " + args.mkString(","));          // Use throwaway instances to force Hibernate to      // configure these classes.      //     new Person     new Event          Hibernate.config =       <hibernate-configuration>             <session-factory>           <!-- Database connection settings -->           <property name="connection.driver_class">org.h2.Driver</property>           <property name="connection.url">jdbc:h2:mem:test_mem</property>           <property name="connection.username">admin</property>           <property name="connection.password"></property>               <property name="dialect">org.hibernate.dialect.H2Dialect</property>            <property name="current_session_context_class">thread</property>            <!-- Emit generated sql for debugging -->           <property name="show_sql">true</property>            <!-- Auto-drop/recreate schema on startup -->           <property name="hbm2ddl.auto">create</property>          </session-factory>           </hibernate-configuration>            args(0) match {       case "test0" => test0       case "test1" => test1     }   }    import Hibernate.withTxSession    def test0 =      withTxSession(session => {       val e1 = new Event       e1.title = "hi"       e1.date = new Date       session.save(e1)     })        def test1 = {     withTxSession(session => {       val e1 = new Event       e1.title = "hi"       e1.date = new Date       session.save(e1)       val e2 = new Event       e2.title = "bye"       e2.date = new Date       session.save(e2)       val p1 = new Person       p1.name = "p1"       p1.age = 21       session.save(p1)     })          println("test1.a")      import scala.collection.jcl.MutableIterator.Wrapper          withTxSession(session => {       val p1 = session.createQuery("from Person").list.get(0).asInstanceOf[Person]       println(p1)              for (e <- new Wrapper[Event](session.createQuery("from Event").list.iterator)) {         p1.events.add(e)         e.participants.add(p1)       }     })      println("test1.b")      withTxSession(session => {       val p1 = session.createQuery("from Person").list.get(0).asInstanceOf[Person]       println(p1)       println(p1.events)        for (e <- new Wrapper[Event](session.createQuery("from Event").list.iterator)) {         println(e)         println(e.participants)       }     })      println("test1.c")   } }
And, to make the above work, you'll need these utility helpers.
object Hibernate {   lazy val sessionFactory = configuration.buildSessionFactory   lazy val configuration =      if (config != null)       (new Configuration).configure(configDoc).addDocument(mappingDoc)     else       (new Configuration).configure.addDocument(mappingDoc)          var config: Node = null // App can to init this, otherwise, uses hibernate.cfg.xml.    def configDoc = parseDoc(config)        def mappingDoc = parseDoc(<hibernate-mapping default-access="field">                               {ready.keys.map(                                 cls => { val attrs = ready.getOrElse(cls, Map.empty)                                          <class name={cls.getName}                                                  table={attrs.getOrElse("table", tableName(cls.getName))}>                                            {mappings.getOrElse(cls, Text(""))}                                          </class>                                        }                                 )                               }                             </hibernate-mapping>)      def tableName(className: String) = className.substring(className.lastIndexOf('.') + 1)      def withTxSession(f: (Session) => Any) = {     val s = sessionFactory.getCurrentSession     val t = s.beginTransaction     val r = f(s)     t.commit     r   }    // ---------------------------------    val mappings = new scala.collection.mutable.HashMap[Class, NodeSeq]   val ready    = new scala.collection.mutable.HashMap[Class, Map[String, String]]    def mappingAppend(cls: Class, x: => NodeSeq): Unit =     if (!ready.contains(cls))       mappings += (cls -> NodeSeq.fromSeq(mappings.getOrElse(cls, Text("\n")) ++ x))    def mappingReady(cls: Class, m: => Map[String, String]): Unit =     if (!ready.contains(cls))       ready += (cls -> m)    // ---------------------------------    lazy val parseDocFactory = javax.xml.parsers.DocumentBuilderFactory.newInstance    def parseDoc(x: Node) = {     import org.xml.sax.InputSource     import java.io.StringReader      parseDocFactory.newDocumentBuilder.parse(new InputSource(new StringReader(x.toString)))   } }  // ---------------------------------  trait Hibernate {   def hibernate(x: => NodeSeq): Unit =      Hibernate.mappingAppend(this.getClass, x)    def hibernateReady: Unit =      Hibernate.mappingReady(this.getClass, Map.empty)          def hibernateReady(m: => Map[String, String]): Unit =      Hibernate.mappingReady(this.getClass, m)   }
Above, you can see the Hibernate helper singleton object and companion Hibernate trait are awfully short. In all, I think, quite an interesting exercise in the DSL possibilities of Scala.

Share 

1 Comment

Tyler Weir Comment by Tyler Weir on January 25, 2008 at 6:44am
Hey Steve, does Ning allow you to change the width of the main content? All the code example spill to the right, under the right column. A bit hard to read. :)

Add a Comment

You need to be a member of My Scala Base to add comments!

Join this social network

About

Steve Yen Steve Yen created this social network on Ning.

Create your own social network!

© 2009   Created by Steve Yen on Ning.   Create Your Own Social Network

Badges  |  Report an Issue  |  Privacy  |  Terms of Service