(I am cross-posting this on Clojure and Hibernate-users mailing list.)

DISCLAIMER: I am a Clojure newbie - please let me know if you find any of my assumptions / statements to be incorrect.

Hibernate has an experimental support for working with maps rather than POJOs using the following configuration property:


I have done few simple successful tests using the above-mentioned technique, using Clojure/Hibernate/XML-hbm-files. The stack I tested these with is below:

Windows XP 32-bit SP3
Sun JDK 1.6
Clojure 1.0
Hibernate 3.2
PostgreSQL 8.3
JDBC 3 driver

I am wondering if I can take this idea to a larger implementation scale using Clojure. What I intend to achieve is described below in detail. The intention of this post is to communicate the idea and gather feedback - please provide your input, feedback, suggestions and warnings.

- I am trying to visualize a dual layered easy-ORM for Clojure using a subset of Hibernate. The first layer "Hibernate integration primitives" will provide low level access to Hibernate functionaly using Clojure and another layer "Easy ORM semantics" will build on the top of the first layer and will provide Django / ActiveRecord like ease of use.

- The domain model will be expressed using Clojure syntax (DSL). As a pre-compile step, a utility function will process the domain model to automatically generate Hibernate XML-mapping files. Those XML-mapping files can be used to carry out hbm2ddl (optional) activities. The domain model will also be rich enough to capture metadata for subsequent Hibernate operations.

                    .-----> Auto-generate Hibernate XML as a precompile step
    Clojure ORM Domain Model
                    *-----> Keep model's metadata in Clojure
                          (so that they can be applied in subsequent operations)

- The captured domain model will retain information about entity-identification, property names, associations etc. A fictitious sample (only for example, not necessarily correct syntax) is given below:

    ; define model (HBM = Hibernate Mapping)
    ; extra (optional) attributes appear in the map
    (defmodel :blogentry                      ; assumed :entity-name=:blogentry, :table=(upper-case :blogentry)

      (hbm-id :id :long {:column "AUTO_ID" :generator "sequence" :sequence-name "blogentry_auto_id_seq"})   ; assumed :name=:id, :type=:long, :column="AUTO_ID"

      (hbm-property :title       :string    {:width 100 }) ; assumed :name=:title,       :type=:string,    :column=(upper-case :title)

      (hbm-property :content     :string    {:width 3000}) ; assumed :name=:content,     :type=:string,    :column=(upper-case :content)

      (hbm-property :when-posted :timestamp              ) ; assumed :name=:when-posted, :type=:timestamp, :column=(upper-case (dash-to-underscore :when-posted))

      (hbm-property :is-deleted  :boolean                ) ; assumed :name=:is-deleted,  :type=:boolean,   :column=(upper-case (dash-to-underscore :is-deleted))

      (hbm-property :sample      :integer   {:column "SAMPLE_CHAPTER" }) ; assumed :name=:sample, :type=:integer, :column="SAMPLE_CHAPTER")

      ; associations
      (hbm-many-to-one     :blog {:column "BLOG_ID" :entity :blog-entity})

      (hbm-set-one-to-many :usercomments {:key-column "AUTO_ID", :entity :usercomments-entity} ))

    (defn create-blog-instance []

      (model-instance :blogentry

        { :title "Nandi Hills", :content "Fill here", :when-posted (time-now), :is-deleted false } ))

    ; create instance and persist (HBO = Hibernate Operation)
    (let [blogentry (create-blog-instance)]

      (hbo-persist blogentry))

- There will be a Clojure DSL for persistence operations (that ultimately translates into HQL or JPA QL). Some examples are below:

    (hbo-persist    blogentry-instance)

    (hbo-findby-id  blogentry 45)

    (:blog (hbo-findby-id blogentry 45))                               ; returns the blog entity from many-to-one association

    (hbo-findby-any blogentry (:where [:eq :title "Amarnath Trip"]))

    (hbo-findby-any [blogentry :e usercomments :c] (:where [:eq [:e :id] [:c :entry-id]]) ; Join

    (hbo-findby-any usercomments (:where [:not-in (:select [:id] :from [:spammers])]))    ; Sub-select

    (hbo-updateall  blogentry :set [:title "New title" :is-deleted false])

    (hbo-updateby-any  blogentry :set [:title "New title" :is-deleted false] (:where [:gt :when-posted (parse-date "2009-06-30")]) )

    (hbo-deleteby-id blogentry 57)

- These are the simple design notes:

    ;- DSL Syntax for models should capture both Hibernate mapping and model metadata
    ;- Remain close to Hibernate idioms, but do not inherit the XML-wrinkles
    ;- Important HBM attributes are mandatory parameters in the domain model
    ;- Rest of the (optional) attributes can appear as a map e.g. {:column "CUSTOMER"}
    ;- Simplify in favour of succinctness (Sensible and auto-deduced metadata defaults)
    ;- Overridable simplicity (Can override attributes by providing maps, add children)
    ;- Not everything is covered - use hbm-any for direct Hibernate API access (cook your own)
    ;- Use Clojure metadata support and (Cannot avoid) macros for model definition
    ;- Use multimethods for XML auto-generation
    ;- Use macros and multimethods for persistence operations (render as HQL / JPA QL)