Saturday, June 30, 2012

Datomic: An Initial Comparative Analysis


/*---[ Day of Datomic: Initial Thoughts ]---*/

Yesterday I attended a "Day of Datomic" training course, taught by Stuart Halloway. It was well taught and had quite a bit of lively Q&A and discussion. If you have the opportunity to take the Day-of-Datomic course I highly recommend it.

At the bar at the end of class, one of my fellow students and I were discussing Rich Hickey and he said "Rich has clarity". Rich has thought long, deep and critically enough to see through to essence of key issues in modern computer science, bypassing things that are so ingrained as to be a familiarity blindness in the rest of us. Datomic is the latest brain-child of Rich Hickey and was co-created by him, Stu and others at ThinkRelevance, here in Durham, where I live.

To be clear, I have not yet attained "clarity" on Datomic. The training course was an excellent deep dive that lays the groundwork for you to climb the hill of understanding, but that hill still lies ahead of me. As Fogus said, this is something very new, at least to most of us who are used to thinking in terms of relational algebra, SQL, and structured tables. Even for those of us now comfortable with document databases (Mongo and Couch) and key-value stores, this is still different in many important ways.

I won't have time to dive deeper into Datomic for a while (too many other things on my to-do/to-research list), but I wanted to capture some key notes and thoughts that I can come back to later and may help others when starting out with Datomic.

I am not going to try to give an overview of Datomic. There are lots of good resources on the web now, starting with the Datomic web site.

I reserve the right to retract any thoughts I lay out here, as I have not yet done my hammock time on Datomic.


/*---[ Compare it to what you know ]---*/

When you get something new, you want to start by comparing it to what you know. I think that's useful for finding the boundaries of what it's not and tuning the radio dial to find the right way to think about it. There is enough new here that comparisons alone are not sufficient to get a handle on Datomic, but it's one helpful place to start.

Here is a partial list of things that could be useful to compare Datomic to:

  1. Git
  2. Document dbs (MongoDB and CouchDB)
  3. Graph dbs (Neo4j)
  4. RDF Triple Stores
  5. Event Sourcing
  6. VoltDB (and other "NewSQL" databases)


/*---[ Datomic v. Git ]---*/

I've seen the "Datomic is like Git" analogy come up a couple of times now - first in the Relevance podcast with Stu, once while describing Datomic to a work colleague and again yesterday in the class. This seems to resonate with many so use it as a starting point if it helps you, but the analogy falls short in a number of areas.

In fact, the analogy is useful really only in one way - for the most part we aren't used to thinking in terms of immutability. Stu emphasized this in the course - immutability changes everything. Rich Hickey, in his Clojure West talk on Datomic, said "you cannot correctly represent change without immutability ... and that needs to be recognized in our architectures".

Git has given us this idea that I have immutable trees (DAGs actually) of data in my history, wrapped in a SHA1 identifier that guarantees that tree has not changed since you committed it. These trees of history could have been created through sequential commits or a variety of branches and merges. Regardless, I can always go back to that point in time.

That idea does resonate with Datomic's design, but after that the analogy falls apart. Datomic doesn't have multiple branches of history - there is only one sequential trail of history. Datomic is not meant to be a source code repository. Another difference is that Datomic is intended to deal with much larger volumes of data than git. Git does not separate storage from query or transactional commits, as Datomic does, because it doesn't need to. You should never have a source code repo that is so big that you need to put it on a distributed storage service like AWS EC2.


/*---[ Datomic v. Document data stores ]---*/

"Schema" in Datomic is the thing I struggled with the most in the class. I think it will be important carefully and systematically spell out what is (and isn't) a schema in Datomic.

In my view, the Datomic schema model is non-obvious to a newcomer. On the one hand I got the impression from the earlier talks I'd seen on Datomic that Datomic is just a "database of facts". These facts, called "datoms", do have a structure: Entity, Attribute, Value and Time/Transaction (E/A/V/T for short). But "Datomic is a database of facts" sounds superficially like a set of records that have no other structure imposed on them.

On the other hand, my naive assumption coming into the training was that Datomic is actually quite close to the Mongo/Couch model. When written out, datomic records look like constrained JSON - just vectors of E/A/V/T entries and some of those can be references to other JSON-looking E/A/V/T facts, which seems like a way to link documents in a document datastore.

It turns out that neither of those presuppositions are right. Datoms are not JSON documents. Stu made the comment that of all the databases available out there, the Mongo/document-store model is probably the most different from the Datomic model (see Figure 1 below). That was actually quite surprising to me.

I have to go off and think about the differences of these models some more. It will take a bit of tuning of the radio dial back and forth to understand what the Datomic schema is.


/*---[ Datomic v. Graph databases (Neo4j) ]---*/

One area not covered much in the day-of-datomic training was a comparison of Datomic to graph databases (Neo4j in particular).

A significant part of the object-relational impedance mismatch is the translation of relational rectangles to object graphs. When people come to a graph database they get really excited about how this impedance falls away. Walking hierarchies and graphs seems beautifully natural and simple in a graph database, compared to the mind-bending experience of doing that with SQL (or extensions to SQL).

So can Datomic function as a graph database? I don't know. Maybe. This type of db was not on Stu's slides (see figure 1 below). When setting up attributes on an entity, you have to specify the type of an attribute. One type of attribute is a ref -- a reference to another entity. By setting up those refs in the schema of attributes, is that basically the same as setting up a Relationship between Nodes in a graph db? Or does Neo4j do things that Datomic is either not suited for or not as performant as?


[Update: 02-July-2012]: Stu Halloway sent me an email that gave some feedback on this question. First, he pointed me to an article by Davy Suvee in which he gives a nice set of basic examples of how Datomic keeps a graph of relations that can be queried using Datalog.

Stu pointed out that one difference between Datomic and a graph database like Neo4j is that the latter "reifies edges". In graph theory you have two basic concepts: the Node (Vertex) and the Relationship (Edge) between two Nodes. Both are first class (reified) entities of the graph model - they can have identities and attributes of their own.

Datomic does not make the Relationship a thing unto itself - it is just the "ref" between two entities (nodes). In Stu's words: "We believe many of the common business uses for reified edges are better handled by reified transactions". More food for thought! :-)

That being said, you can wrapper Datomic into a full blown graph API and that is just what Davy Suvee has done. He implemented Datomic as a backend to the Blueprints Graph API and data model, which is pretty good evidence that Datomic can function as a graph database for at least some important subset of use cases.



/*---[ Datomic v. RDF Triple Stores ]---*/

Interestingly, the part of the NoSQL world that seems to get the least attention (at least in the circles I mingle in, including sifting through Hacker News with some regularity) is the database whose data model comes closest to Datomic's - RDF Triple Stores.

Rich, in his Clojure West talk, says that RDF triple stores almost got it right. Triple stores use subject-predicate-object to represent facts. This maps to Datomic's entity-attribute-value concept. The piece missing is time, which Datomic models as, or wraps into, transactions. Transactions in Datomic are first-class creatures. They encapsulate timestamp, the order of history and can have additional attributes added to them (such as what user id is associated with this transaction).

Frankly, I don't know a lot about RDF triple stores, so I'm just going to include this slide from Stu's training session for your consideration:

Fig. 1: Comparison of Datomic to other databases using Agility as a metric. (From Stuart Halloway's training slides)


/*---[ Datomic v. Event Sourcing ]---*/

Event sourcing is an intriguing and compelling idea that changes to state should be recorded as a sequence of events. Those events are immutable and often written to an append-only log of "facts". When you do event sourcing with a relational database, you have to decide how to log your facts and then what to represent in the relational schema. Will you keep only "current state"? Will you keep all of history? Will you set up a "write-only" or "write-predominant" database where up-to-date state is kept and then read-only databases that somehow get populated from this main datastore?

There are lots of ways to set it up and thus lots of decisions to make in your architecture. Read Fowler's write up on it if you are new to the idea. The newness of this idea is a something of a risk when trying to figure out the best way to implement it.

As I understand it so far, Datomic is an implementation of an event sourcing model that also answers for you many of these ancillary questions about how to implement it, such as: where and in what format will you write the transaction log? How will you structure your query tables? Will you replicate those tables? Will you project those tables into multiple formats for different types of queries and analytics? Will transactions will be first-class entities?

Datomic's answers to those questions include:

  1. There is a component of the system called a transactor and it writes immutable transactions in a variant of "append-only", since it writes transactional history to trees. As I'll talk about in the next section all transactions are handled in serialized (single-threaded) mode.
  2. You structure your data into E/A/V/T facts using the Datomic schema model constraints. Queries are done on the Peers (app tier) and those Peers have read-only datasets.
  3. You don't need to make multiple projections of your data. Datomic already gives you four "projections" (indexes) out of the box - one index by transaction/time and three other indexes:
    1. An EAVT index, meaning an indexed ordered first by entity (id), then by attributes, then by values and finally by transaction
      • This is roughly equivalent to row-level index access in a relational system
    2. An AEVT index order, which is the analog of a column store in an analytics database
    3. A VEAT index order, for querying by known values first
    4. Lastly, you can also specify that Datomic keep an AVET index, which is useful for range queries on attribute values. It is not turned on by default, since it signifcantly slows down transactional write throughput
  4. Transactions are first-class and have their own attributes. One can query the database based on transactions/time and you can go back to a point in history and see what the state of the database was at that time.

I think one of the most exciting things about Datomic is that it gives you an event-sourced system with many of the design decisions already made for you.


/*---[ Datomic v. VoltDB ]---*/

I did a short write-up on VoltDB and the "new SQL" space a few months ago.

Like VoltDB, Datomic posits that we do not have to give up ACID transactions in order to improve scalability over traditional relational models. And VoltDB and Datomic share one key core premise:

all transactional writes should be serialized by using a single writer.

In VoltDB, there is one thread in one process on one server that handles all transactions. In Datomic, there is one transactor on one machine (either in its own thread or process, not sure which) that handles all transactions. Transactions, unlike most NoSQL systems like MongoDB, can involve any number of inserts/updates/deletes (VoltDB) or additions/retractions (Datomic).

Another similarity is that both VoltDB and Datomic allow you to add embedded functions or stored procedures to the transactor that will run inside the transaction. In both cases, you add or compile the embedded transaction functions to the transactor itself. Those functions do not run in the app tier.

Michael Stonebraker, key designer of VoltDB, based this design on the fact that traditional relational databases spend on the order of 80% of their cycles doing non-productive work: managing locks and latches, commit logs and buffer caches. By moving to a single-threaded model, the overhead of write contention goes away. VoltDB claims their transactional write throughput is on the order of 40x to 50x faster than traditional relational databases.

Where Datomic and VoltDB part ways is that VoltDB retains SQL and a fully relational model, using an in-memory database that is shared across many nodes in a cluster.

Another key difference is that VoltDB really focuses on the OLTP side, not on analytics. Datomic can target both areas.


/*---[ Datomic v. Stonebraker's "10 Rules" ]---*/

In the summer of 2011, Michael Stonebraker and Rick Cattell published an ACM article called "10 Rules for Scalable Performance in ‘Simple Operation’ Datastores". By "Simple Operation Datastore" they mean databases that are not intended to be data warehouses, particularly column store reporting databases. So traditional relational databases and most NoSQL databases, including Datomic, are "Simple Operation" by this definition.

How does Datomic fare against their 10 rules? As I said, I'm still too new to Datomic to give a definitive answer, but here's my first pass at assessing Datomic against them. I'd invite others to chime in with their own analysis of this. These axes will provide another way for us to analyze Datomic.


Rule 1: Look for Shared Nothing Scalability

A shared-nothing architecture is a distributed or clustered environment in which each node shares neither main memory nor disk. Instead, each node has its own private memory, disks and IO devices independent of other nodes, allowing it to be self-contained and possibly self-sufficient, depending on the usage conditions. Typically in a shared nothing environment the data is sharded or distributed across nodes in some fashion.

An alternative architecture is shared disk - a disk that is accessible from all cluster nodes. In a shared disk environment, write contention must be managed by the disk or database server.

So is Datomic a shared nothing architecture? One could argue that the Peers are not entirely self-sufficient, as they rely on the storage service to pull data. And the transactor is not self-sufficient as it has no storage of its own - it depends on the storage service.

However, I think Datomic is shared nothing in that there will never be write contention among nodes - there is only one (active) transactor.

I also don't quite know how the storage service is architected with Datomic. If it appears as a monolithic service to the Peers on the app tier, then if it goes down, none of the Peers can function unless they have cached all the data they need.


Rule 2: High-level languages are good and need not hurt performance.

This is a direct shot at the fact that many NoSQL databases have had to reinvent APIs for querying data and many have given us low-level programming APIS, not DSLs or high level languages. (To be fair, some have - Cassandra has CQL and Neo4j has the excellent Cypher language.)

Datomic fares very well on this one - it gives us Datalog, which Stu pointed out in the training is mathematically sound like the relational algebra and is transformable into relational algebra.


Rule 3. Plan to carefully leverage main memory databases.

Datomic allows you spin up in memory arbitrary amounts of data from the datastore. The amount is limited by (at least) two things:

  1. how much memory you can put on the app/Peer machine

  2. how much memory you can put into a JVM without hittings a size where GC pauses interfere with the application

On the gc-end, you have a couple of options to ramp up. First, Datomic has a two-tier cache on the Peer - one in main memory (on-heap) and one that is "compressed" or treated as a byte array, which could be on or off-heap, but in either case puts less pressure on the garbage collector.

Second, if you want really big JVMs with on-heap memory and no GC pauses, then you can levearge Azul's JVM, though it will cost you extra.

Datomic scores a reasonably high score here, but it is not architected to be a sharded in-memory database. However, with a very fast network and SSDs on AWS, Datomic's performance may not suffer significantly. We await some good case studies on this.


Rule 4. High availability and automatic recovery are essential for SO scalability.

From the Stonebraker paper:

Any DBMS acquired for SO applications should have built-in high availability, supporting nonstop operation.

With an AWS deployment, you inherit any weaknesses of AWS model (e.g., a few prominent outages recently), but overall it's a highly redudant and highly available storage service. With Datomic you can have any number of Peers on the app tier, so replicate those as needed.

You are not required to use AWS. Currently Datomic can also be deployed on relational systems and they hope to be able use Riak in the future.

The single point of failure would appear to be the single transactor, but Datomic will provide a failover mode to an online back transactor. We'll need to learn if this failover will be provided as an automatic service or requires configuration/set-up by the user/administrator.


Rule 5. Online everything.

This overlaps rule 4 in terms of always being up, but adds to it the idea that schema changes, index changes, reprovisioning and software upgrades should all be possible without interruption of service.

I don't have hard data here from the Datomic guys, but my guess is that Datomic will pass this test with flying colors. Most indexes are already turned on by default and I suspect turning on the other one (see above) can be done without database downtime.

Schema changes ... hmm. Stu said in the training course that attributes cannot (currently) be removed from the schema, but you can add new ones. Would this require rebuilding the indexes? I doubt it, but I'd need more information on this one.

For reprovisioning and software upgrades, it is easy to envision how to add more storage and Peers since you have redundancy built in from the start. For the transactor, reprovisioning would not mean spinning up more nodes to process transactions, it would be mean getting a bigger/faster box. I suspect you could reprovision offline while your existing transactors are in motion, then swap to the new improved system in real-time using the failover procedure.


Rule 6: Avoid multi-node operations.

From the Stonebraker paper:

For achieving SO scalability over a cluster of servers .... Applications rarely perform operations spanning more than one server or shard.

Datomic does not share data sets. The only operations that need to be performed over more than one server is when a Peer needs to access data that has not yet been cached on the app tier. It then makes a network lookup to pull in those data. This is certainly slower than a complete main memory database, but Stu said that AWS lookups are very fast - on the order of 1ms.


Rule 7: Don’t try to build ACID consistency yourself.

Unlike most NoSQL datastores, Datomic has not abandoned ACID transactions. It handles transactions of arbitrary complexity in a beautiful, fast serialized model. A+ on this one.


Rule 8: Look for administrative simplicity.

From the Stonebraker paper:

One of our favorite complaints about relational DBMSs is their poor out-of-the-box behavior. Most products include many tuning knobs that allow adjustment of DBMS behavior; moreover, our experience is that a DBA skilled in a particular vendor’s product, can make it go a factor of two or more faster than one unskilled in the given product.

As such, it is a daunting task to bring in a new DBMS, especially one distributed over many nodes; it requires installation, schema construction, application design, data distribution, tuning, and monitoring. Even getting a high-performance version of TPC-C running on a new engine takes weeks, though code and schema are readily available.

I don't have a lot data one way or the other on this for Datomic. I doubt there will be a lot of knobs to turn, however. And if you are deploying to AWS, there is a Datomic AMI, so deployment and set up of the storage side should be quite simple.


Rule 9: Pay attention to node performance.

Not every performance problem can or should be solved with linear scalability. Make sure your database can have excellent performance on individual nodes.

This one is a no-brainer: A+ for Datomic. You can run all pieces on the same box, entirely on different boxes, or mix and match as desired to put the faster systems in place that you need for each piece. You can also put your high intensity analytics queries on one box and your run-of-the-mill queries on another and they will not interfere. Performance of the system will be entirely dependant on individual node performance (and data lookups back into your data store).


Rule 10. Open source gives you more control over your future.

Datomic is a commercial product and is not currently open source or redistributable, as I understand it.

From the Stonebraker paper:

The landscape is littered with situations where a company acquired a vendor’s product, only to face expensive upgrades in the following years, large maintenance bills for often-inferior technical support, and the inability to avoid these fees because the cost of switching to a different product would require extensive recoding. The best way to avoid “vendor malpractice” is to use an open source product.

VoltDB, as an example, is open source and seeks commercial viability by using the support model that has worked well for Red Hat. Datomic is still young (less than 6 months old), so we will see how its pricing and support model evolve.


/*---[ Coda ]---*/

Remember that Datomic is more than the union of all these comparisons, even assuming I got all my facts right. Datomic not only combines the best of many existing solutions, but brings new stuff to the table. Like anything of significant intellectual merit, it will take a investment of your time to learn it, understand it and for us, as a community of users, to figure out best practices with it.

Datomic is interesting and fascinating from an intellectual and architectural point of view, but it also eminently practical and ready to use. I look forward to following its progress over the coming months and years.

Lastly, if it is of any use to anyone, here are my raw notes from the day-of-datomic training.

Sunday, June 24, 2012

Setting up Emacs 24 on Ubuntu to use Swank-Clojure

I've tried a couple of times now to get Clojure with Swank/Slime working in order to use the "clojure-jack-in" command to start the Swank Clojure server. My attempt a few months ago with emacs23 on Xubuntu 11.10 failed. My recent attempt with emacs24 on Fedora 17 failed (see angry tweet).

I finally had success, so I thought I'd do a brief write up on what I did.

First, I did this on my main machine: Xubuntu 11.10. Canonical still does not have an official emacs24 bundle. I decided not to install the emacs24 package from the Cassou PPA, as I've heard others have had problems with it and I didn't know if I would have to uninstall emacs 23 to use it. I toyed with using Nix, but didn't want to make things even more complicated since I've never tried Nix.

I intend to switch over to the Canonical emacs24 package once they have it, so I left my emacs 23 package intact and built emacs 24 from source and installed it into a local directory, rather than the standard global one.


/* ---[ Installing emacs 24 ]--- */

I downloaded the source from http://alpha.gnu.org/gnu/emacs/pretest/.

I installed some required packages in order to compile emacs:

$ sudo apt-get install xorg-dev
$ sudo apt-get install libjpeg-dev libpng-dev libgif-dev libtiff-dev libncurses-dev

I unpacked the source tarball, specified a local install dir, compiled and installed:

$ tar xvfz emacs-24.1-rc.tar.gz
$ cd emacs-24.1
$ ./configure prefix=/home/midpeter444/apps/emacs24
$ make
$ make install  # no sudo required, as it installs 'locally'

Then I reset the global links to emacs

$ ls -l /usr/bin/emacs
lrwxrwxrwx 1 root root 23 /usr/bin/emacs -> /etc/alternatives/emacs
$ ls -l /etc/alternatives/emacs
lrwxrwxrwx 1 root root 18 /etc/alternatives/emacs -> /usr/bin/emacs23-x
$ rm /etc/alternatives/emacs
$ ln -s /home/midpeter444/apps/emacs24/bin/emacs /etc/alternatives/emacs

Next I moved my .emacs.d (from emacs23) and made it into a symlink:

$ mv ~/.emacs.d ~/.emacs23.d 
$ mkdir ~/.emacs24.d 
$ ln -s ~/.emacs24.d ~/.emacs.d

I copied over my init.el and macros.el files into ~/.emacs24.d, started emacs and the installed a bunch of packages via package.el. In order to use the marmalade repository (in addition to GNUs more limited package repo), I added this to my init.el:

(require 'package)
(package-initialize)
(add-to-list 'package-archives
             '("marmalade" . "http://marmalade-repo.org/packages/") t)

Then I restarted emacs and installed a bunch of packages:

> M-x package-list-packages

Here's what I chose and it installed into my ~/.emacs.d/elpa directory:

~/.emacs.d$ ls elpa/
archives                         key-chord-0.5.20080915
clojure-mode-1.11.5              markdown-mode-1.8.1
clojurescript-mode-0.5           paredit-22
clojure-test-mode-1.6.0          php-mode-1.5.0
coffee-mode-0.3.0                scala-mode-0.0.2
color-theme-6.5.5                thumb-through-0.3      
color-theme-vim-insert-mode-0.1  tidy-2.12              
feature-mode-0.4                 windsize-0.1           
find-things-fast-20111123        yaml-mode-0.0.7        
groovy-mode-20110609             yasnippet-0.6.1        
guru-mode-0.1                    yasnippet-bundle-0.6.1 
haml-mode-3.0.14                 zen-and-art-theme-1.0.1
js2-mode-20090814                zenburn-theme-1.5
json-1.2

Notice that I did NOT install slime or swank - only clojure-mode. (That may have been my problem previous times I tried this.)

I had to move a few other things over from my .emacs23.d that were not in the package.el directory.

If you are new to using package.el, this blog post helped me: http://batsov.com/articles/2012/02/19/package-management-in-emacs-the-good-the-bad-and-the-ugly/

If you are interested in my emacs setup, I have it on GitHub: https://github.com/midpeter444/emacs-setup


/* ---[ Swank Clojure and clojure-jack-in ]--- */

At this point, after a few minor tweaks to my init.el, my emacs is back in working order and looking good, all nicely upgraded to v24. Now time to tackle this Swank, Slime, clojure-jack-in mystery.

Following the instructions at the swank-clojure project, I cd'd into one of my existing leiningen projects and added [lein-swank "1.4.4"] to the :plugins section of the project.clj file, so it looks like this:

(defproject learn-congomongo "1.0.0-SNAPSHOT"
  :description "Learn the CongoMongo Clojure MongoDB driver"
  :plugins      [[lein-swank "1.4.4"]]
  :dependencies [[org.clojure/clojure "1.4.0"]
                 [congomongo "0.1.9"]])

Then the magic moment:

> M-x clojure-jack-in

After an excruciatingly long wait with bated breath, it downloaded slime and swank and it loaded the REPL successfully. Success!

Now I just have to learn how to use it and all those fancy keystrokes it allows.


[22-Aug-2012 Update]: Phil Hagelberg tweeted that Swank Clojure is now deprecated in favor of nrepl.el.

I never did get Clojure Swank to work right on my system, so I dropped back to doing M-x inferior-lisp which works pretty good with Clojure 1.3, 1.4 and 1.5-alpha (the ones I've been using lately). I look forward to trying out nrepl.el.


Saturday, June 23, 2012

HTML <script> tags 101: a JavaScript refresher

Time to revisit the basics with a quiz.

What is the output of this code when run on an otherwise valid HTML page?

<script>
  console.log("DEBUG 1");
  var foo = ;  // Javascript syntax error
  console.log("DEBUG 2");
</script>
<script>
  console.log("FOO");
  console.log("BAR");
</script>

You will get a syntax error along the lines of  Uncaught SyntaxError: Unexpected token ; Beyond that what will be printed to the Javascript console?

Answer Choices

  1. All console log lines print out:

    DEBUG 1
    DEBUG 2
    FOO
    BAR

  2. Console line with DEBUG 2 fails because of the syntax error before it:

    DEBUG 1
    FOO
    BAR

  3. The first script block fails because of the syntax error and the second block is fine:

    FOO
    BAR

  4. The line above the syntax error prints, but all subsequent Javascript is hosed:

    DEBUG 1

  5. No output, since the syntax error stops all Javascript in its tracks.

Before you read any farther, follow Kent Beck's advice: stop and answer out loud.

In many large and aging JavaScript and HTML code bases, there are little syntax errors that get ignored in the heat of getting a release out the door and moving on to higher priority features and bug fixes. It passes QA so the syntax error noise gets ignored.

But sometimes those seemingly harmless little syntax errors can cause big problems. More on that in a minute.

The answer to the quiz above is choice #3.

JavaScript is not a line-by-line parsed scripting language, like a bash script, but it is a script-by-script parsed language.

If I run a bash script like this:

#!/bin/bash
echo "foo"
eho "bar"  # syntax error
echo "quux"

it will print out:

foo
./myscript.bash: line 3: eho: command not found
quux

A bash script just parses and executes each line independently of the others. Syntax errors only affect the current line.

HTML <script> tags are fully parsed, possibly compiled (depending on the context/browser) and only run if the previous steps worked. But the next <script> tag is independent of the previous in terms of whether it will run.


/* ---[ Lessons Learned ]--- */

So why is this important?

Suppose you are doing Javascript module requires. With RequireJS, you do a require like this:

<script>
  require(['customerModule', 'orderModule'], function(c, o) {

    var customers = c.getCustomers();
    // do something with customers ...

    var orders = o.getOrders();
    // do something with orders ...
  });
</script>

or with earlier versions of Dojo, you might have a series of requires of its dijit widget library:

<script>
  dojo.require("dijit.form.Button");
  dojo.require("dojox.layout.ContentPane");
</script>

What if you have a simple syntax error in the block of script code? All the requires will fail, but the page will chug along and try to execute the rest of the javascript in other script tags or files.

If you have code like this:

<script>
  require(['customerModule', 'orderModule'], function(c, o) {

    var customers = c.getCustomers();
    // do something with customers ...

    var orders = o.getOrders();
    // do something with orders ...
  });

  var foo = ;  // our syntax error again
</script>

Your app will probably fail now. So easy, just fix the obvious syntax error and assign something to foo. The tricky part is maybe that that assignment value is set server-side in ASP, JSP, or PHP code and some code path ended up having a null value, ending up in that JavaScript syntax error.

So you need defensive programming here on both sides - on the server side protect variable interpolation from delivering null or empty entries when dynamically creating Javascript code.

That could be something like this JSP/JSTL code:

<c:if test="${myval}">var foo = ${myval};</c:if>

On the Javascript side, separate your set up code (such as module requires) from "regular" executable other code as much as possible.

Sunday, May 27, 2012

MyBatis Koans: A Path to Enlightenment


/*---[ MyBatis Koans ]---*/

For much of this month, I've been working on developing a set of koans for the MyBatis data mapper framework. I now have enough completed and tested that I'm sending out notices for others to try them out: https://github.com/midpeter444/mybatis-koans

The first koans I ever tried are the excellent and challenging set of Neo4j koans by Jim Webber and colleagues, which was the subject of my first blog post.

Then I tried the Clojure koans (and then finished about half of the even more challenging 4Clojure koans).

A koan is a question or statement to be meditated upon in order to improve and test a student's progress. Among programmers, software koans have become a clever way to learn a software language or tool. As the Ruby koan website says: "The Koans walk you along the path to enlightenment" -- in this case to learn and practice with the MyBatis 3 data mapper framework.

A software koan comes in the form of a broken unit test that you must fix to get it to pass, usually by filling in the blanks or entire missing sections. The koan is intended to teach one or a small set of cohesive features about the language or tool being studied.


/*---[ Rationale ]---*/

The MyBatis data mapper framework doesn't have the mindshare in the Java world that it should, so this and my earlier "User Guide Companion" are my contributions to helping others learn to use it. I hope others will suggest and contribute new koans.

I'm actually not a MyBatis expert and writing these was my way of learning the framework better. Critiques and suggestions for improvements are welcome.

The koans focus on just the MyBatis Persistence Framework. Be aware that the MyBatis project has other features available such as a Migration Tool and code generator.


/*---[ Getting Started ]---*/

On the MyBatis koans github page, I've tried to provide detailed instructions on how to get set up and do each koan. As this is a relational database mapping framework, you will need to set up a database and install the sakila example schema and dataset.

I've tested the koans with PostgreSQL and MySQL. See the GitHub page for details.


[26-July-2012 Update]: Thanks to some help from collaborator Andrei Pozolotin, the koans have been mavenized to make it easier to get dependencies in place. Andrei also added the Java H2 database with the sakila database set up so you can get started without having to install an enterprise grade database.

Sunday, April 8, 2012

Git as RCS: The Joy of Personal Incremental Savepoints


/*---[ Distributed Version Control: The Best of Both Worlds ]---*/


"The enjoyment of one's tools is an essential ingredient of successful work."
— Donald E. Knuth

Not long after I first learned to program and gain enough proficiency with emacs to start to enjoy it, the person who was closest to what I would call a mentor told me that my next step was to learn to use a version control system. Having gotten me started as an emacs user, he suggested that I use RCS: Revision Control System from GNU.

RCS was developed in the 1980s. It is "local-only" - it has no concept of a central server. It also works on single files, not a project of files. It's been a while since I used RCS and I never really deeply mastered it, but I used it as a tool to make sure I had "version backups" of important files that I was working on. I never used it as part of a team.

If I have my history right, CVS, in the open source world, was the next generation version control system after RCS. CVS built a model where you could centrally share version controlled files with members of a team and think in terms of projects, not just individual files. Of course I'm ignoring lots of other version control systems, mostly proprietary. I'm just giving a little history based on my own personal history, as the next thing I learned after RCS was CVS (and then a very brief descent into ClearCase and then SVN and now git).

Nevertheless, with RCS I remember well a sort of thrill at the idea of having this local, personal manager of my history of edits. It could be used as a set of savepoints on the way to completing a program. CVS always felt more heavyweight (which sounds insane compared to ClearCase and some other tools), because it wasn't local. I couldn't put my finger on why back then, but now that I've used git I have achieved the next rung on the path to software guru enlightenment (... is there a special badge for that on coderwall?).

Git has been my first experience with a distributed version control system. Soon after trying it, the primary emotion I experienced was that initial joy of my brief RCS-days. I have this wonderfully powerful intelligent version control system and I can run it completely locally: standalone, off-the-network doing all the things RCS used to do and far far more. All my personal savepoints are back. I can use it as a local lever to help try new things, back out of messes and alter history as appropriate before I push it to some public or shared repo.

This is not possible with the CVS/SVN-type model. There everything you commit is immediately shared. Worse, if your team has a rule that you can't publish to the repo partial code that breaks the build, you can't do any local savepoints. It's analogous to being in a video game where you can only save your progress after you finish some level, rather than incrementally at any point you choose. Perhaps that's an intended extra challenge of some video games, but we need to have our version control system make software development easier, not harder.


/*---[ Lots of small savepoints that git calls commits ]---*/

If you come from the mindset of a non-distributed version control system, like SVN, a commit is a serious things. You are publicly pushing your changes to a central repo off of which (ideally) some automated Continuous Integration tool pulls your changes and runs unit tests, static code analysis tools, perhaps functional tests and even performance tests. You'd better have your act together when you commit or there will be finger-pointing!

A commit in git is far less ominous. It just means I'm saving my work to my local repo, just like saving my progress in that video game. The git equivalent of an SVN-commit is a push. This is the beauty of git. Purists would disagree, but if you are learning git, it can be helpful to think of it as both RCS and CVS combined together with a much more intelligent tree (DAG) based view of commits.


/*---[ Using git rebase, amend and reset to alter local history ]---*/

I struggle with CSS. In fact, one of my all-time favorite blog posts is from Zed Shaw on the joys he experiences with CSS. For fun, I'll quote my favorite passage (with a bit of editing of Zed's flowery language):

My first problem with CSS is simply that it just never does what you tell it to. I say, "make this a column CSS" and it goes, "What? No that should go over here totally on the left and [deleted] you I like apples." I say, "make this fill all of the parent div" and CSS says, "Sharks love tiny needles, and no that will only take up the top part." I say, "Hey, CENTER THIS" and CSS says, "My shoes have centered worms but your heading will stay to the left."

In the battle with CSS, git is your friend. Any time you achieve a small success and get what you want, commit it to your local repo and put in lots of exclamation points of celebration or cursing in the commit message, because later you can alter this history (more on that later). Now with the save point, you can start the next salvo against CSS. At some points, you may be in so far over your head that the only sound tactic is full-scale retreat. git reset is your path back to sanity. Or you can make a branch and try some insane experiment that just might work. Branches are cheap and easy in git.

Another great value of doing lots of little commits is that not only is backing out with a reset not a big deal, but you can do a git diff to see only the very few changes you've recently made and quickly grok the problem. This can help you decide whether to trudge on and change what you have or back out entirely.

In the heat of battle, you might make 20 commits. That's going to look a little messy and honestly just a bunch of repo noise when you finally push that out. git commit --amend and git rebase stand ready to save the day again.


/*---[ A short tutorial on using git "RCS-style" ]---*/

If you haven't used git or these features of git, this might sound intriguing, but how do I actually do all that? Below I present a tutorial to illustrate. For this I assume you have a basic understanding of how git works. If you are new to git or want more information there are plethora of good git books and web sites. Start with these:

To start, I have a very simple git repo with an initial commit of my html and css file:

$ tree
.
|-- css
|   |-- style.css
|-- main.html

$ git log
commit 1fdf8ad40a67140803eea81effaee4a4fabf29f6
Author: Michael <blahblah@gmail.com>
Date:   Sun Apr 1 19:50:23 2012 -0400

    Init. CSS Design in Progress

Now, I open main.html in the browser and style.css in emacs. Fiddle fiddle fiddle. The changes work reasonably well and I commit - only after a few minutes.

$ git add .
$ git commit -m "Intmd commit: Changed width of main divs to 600.
$ git log    # just to show you the messages
commit 27fcb7fef6a2ff007f9ba313db9574d7d0036be9
Author: Michael <blahblah@gmail.com>
Date:   Sun Apr 1 19:52:35 2012 -0400

    Intmd commit: Changed width of main divs to 600.

commit 1fdf8ad40a67140803eea81effaee4a4fabf29f6
Author: Michael <blahblah@gmail.com>
Date:   Sun Apr 1 19:50:23 2012 -0400

    Init. CSS Design in Progress

If I know that I'm going to subsume this commit into one larger commit, then the commit message will be changed later. It's always smart to say what and why you did what you did, but I also often put in "intermediate commit" so I can see that I didn't intend on keeping this little savepoint.

So I continue and make three more commits over the next few minutes. Here's the log after that:

$ git log    
commit 4e4666204e2a82c257e1fdcc5bb5ab5abdf4ca56
Author: Michael <blahblah@gmail.com>
Date:   Sun Apr 1 19:54:38 2012 -0400

    Intmd commit: header: has bottom border, padding decent now.

commit ac05681b6baff0ec3442f0880a00e31f98439241
Author: Michael <blahblah@gmail.com>
Date:   Sun Apr 1 19:53:56 2012 -0400

    Intmd commit: Header region: so far width + height good.

    Set background color to white - may want to adjust later.

commit 27fcb7fef6a2ff007f9ba313db9574d7d0036be9
Author: Michael <blahblah@gmail.com>
Date:   Sun Apr 1 19:52:35 2012 -0400

    Intmd commit: Changed width of main divs to 600.

commit 1fdf8ad40a67140803eea81effaee4a4fabf29f6
Author: Michael <blahblah@gmail.com>
Date:   Sun Apr 1 19:50:23 2012 -0400

    Init. CSS Design in Progress

Now that the basic layout is done, I start the hard stuff. One smart move here would be to make a new branch and futz around on that. If I don't like it, just move back to the main ("master") branch and delete the branch that didn't work. But since this isn't a tutorial on git branching, I'll stick to the master branch.

So on master I get to it. [Work, work, curse, futz, work ~~CSS pain~~]. OK, this isn't working. Let's dump this and get back to where we were. For that we will use git reset. There are actually three flavors of git reset, just to make life interesting.

The three flavors are:

  1. git reset --soft
  2. git reset (same as git reset --mixed)
  3. git reset --hard

Which one do we want? To explain the difference takes a full blog post on its own. You have to understand all the "trees" in git of which there are three (actually there are three locally and at least a fourth one if you have a remote, often centralized, repo).

But here is my simplified explanation. After reading Scott Chacon's blog (the one above) and reading some of the comments to the post, I have set up the following aliases for reset:

uncommit = reset --soft HEAD~
unstage = reset HEAD

git uncommit (my alias for git reset --soft HEAD~) means to move HEAD from pointing at the last commit I did, to its parent (the commit before it). Thus, "undo the last commit to the repo" or "uncommit".

git unstage (my alias for git reset HEAD) means to make the files in the index (staging area) look like what was in the last commit. So if I've done git add to add new changes/files to the index and then I run git unstage, those changes are removed from the staging area and the index now looks like the previous commit (which is what HEAD points to).

Neither of the above commands affect my working directory. To do that I have to issue git --hard reset, which I leave unaliased, since the "hard" should remind me that I am about to overwrite my working directory with the files from HEAD the last commit. (git --hard reset is the same as git --hard reset HEAD: HEAD is assumed if you don't provide an argument to reset.)

Only git reset --hard is destructive and will cause you to lose work, since it will overwrite your working directory, so use this command carefully.

So, which one do I want here? In my case working with the CSS, since I've been keeping savepoints every few minutes, throwing away the last few minutes of non-productive work is a blessing. I can I always go back to the previous savepoint, just like in a video game where if my player dies or loses too many energy points trying to get past some hurdle, I can just hit the reset button and try again.

So we are going to hit the reset button "hard":

$ git status -s
M css/style.css
$ git reset --hard
HEAD is now at 4e46662 Intmd commit: header: has bottom border, padding decent now.
$ git status -s

The second time I run git status -s it shows no output because the working directory has been reset to look like the previous commit.

In this case, because I was only dealing with one file and I had not yet staged it with git add ., I could have also done git checkout -- css/style.css and it would have checked out the last version of style.css from the index. That's the key thing to remember about git checkout - it pulls from the index, not the commit repo.


/*---[ Rolling up lots of little commits ]---*/

So now, suppose after an hour of toil and labor with CSS, I have something close to what I hoped for. In the process I've done 9 commits, with lots of "Intmd commit" prefixes in the comments. I want to remove those little commits and roll them into one big commit before I push them to the remote repo and make them "public" (or least "shared").

Here's an abbreviated view of all 9 commits using:

$ git reflog
74f7e9a HEAD@{0}: commit: Like how it looks now. Ship it!
14efb50 HEAD@{1}: commit: Much pain, but progress: header.css along with adjustments to main.html.
02ed6fc HEAD@{2}: commit: Intmd: header is shaping up. More to do.
85e8dcb HEAD@{3}: commit: Split out header styles from style.css into header.css.
3ab3101 HEAD@{4}: commit: Intmd commit: Got buttons styled well now.
4e46662 HEAD@{5}: commit: Intmd commit: header: has bottom border, padding decent now.
ac05681 HEAD@{6}: commit: Intmd commit: Header region: so far width + height good.
27fcb7f HEAD@{7}: commit: Intmd commit: Changed width of main divs to 600.
1fdf8ad HEAD@{8}: commit (initial): Init. CSS Design in Progress

I'd like to rollup the last 8 commits, so I do:

$ git rebase -i HEAD~8

or I can specify a particular SHA-1 commit id. Here I choose the SHA of the initial commit:

$ git rebase -i 1fdf8ad

I asked for an interactive rebase (-i), so it opens an editor with all the commit messages from those 8 commits:

pick 27fcb7f Intmd commit: Changed width of main divs to 600.
pick ac05681 Intmd commit: Header region: so far width + height good.
pick 4e46662 Intmd commit: header: has bottom border, padding decent now.
pick 3ab3101 Intmd commit: Got buttons styled well now.
pick 85e8dcb Split out header styles from style.css into header.css.
pick 02ed6fc Intmd: header is shaping up. More to do.
pick 14efb50 Much pain, but progress: header.css along with adjustments to main.html.
pick 74f7e9a Like how it looks now. Ship it!

# Rebase 1fdf8ad..74f7e9a onto 1fdf8ad
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.

What this is asking is for you edit the commands in front of each commit. To merge (roll up) an intermediate commit into the previous one, change it to "squash". The key to making this work is to squash all the commits except the first one listed. If you squash them all, you will not get what you expect. Here is the rebase editor screen after I squash ("s") all but the first one listed:

pick 27fcb7f Intmd commit: Changed width of main divs to 600.
s ac05681 Intmd commit: Header region: so far width + height good.
s 4e46662 Intmd commit: header: has bottom border, padding decent now.
s 3ab3101 Intmd commit: Got buttons styled well now.
s 85e8dcb Split out header styles from style.css into header.css.
s 02ed6fc Intmd: header is shaping up. More to do.
s 14efb50 Much pain, but progress: header.css along with adjustments to main.html.
s 74f7e9a Like how it looks now. Ship it!

# Rebase 1fdf8ad..74f7e9a onto 1fdf8ad

Now I save. Git does the rebase and then pops up another editor window that says:

# This is a combination of 8 commits.
# The first commit's message is:
Intmd commit: Changed width of main divs to 600.

# This is the 2nd commit message:

Intmd commit: Header region: so far width + height good.

Set background color to white - may want to adjust later.

# This is the 3rd commit message:

Intmd commit: header: has bottom border, padding decent now.

# This is the 4th commit message:

Intmd commit: Got buttons styled well now.

# This is the 5th commit message:

Split out header styles from style.css into header.css.

# This is the 6th commit message:

Intmd: header is shaping up. More to do.

# This is the 7th commit message:

Much pain, but progress: header.css along with adjustments to main.html.

# This is the 8th commit message:

Like how it looks now. Ship it!

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   css/header.css
#   modified:   css/style.css
#   modified:   main.html

This is your chance to modify the commit message to the one "squashed" single commit you just created. Here's how I edited the commit message:

Like how it looks now. Ship it!

Header region: so far width + height good.
Set background color to white - may want to adjust later.
header: has bottom border, padding decent now.
Got buttons styled well now.
Split out header styles from style.css into header.css.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   css/header.css
#   modified:   css/style.css
#   modified:   main.html

After the rebase finishes I get the message:

 3 files changed, 19 insertions(+), 3 deletions(-)
 create mode 100644 css/header.css
Successfully rebased and updated refs/heads/master.

Now when I run git log, you see that the savepoints I made have all been squashed into one master commit:

$ git log
commit f52b1c2ba8543c999ba00e862af829e440f0b027
Author: Michael <blahblah@gmail.com>
Date:   Sun Apr 1 19:52:35 2012 -0400

    Like how it looks now. Ship it!

    Header region: so far width + height good.
    Set background color to white - may want to adjust later.
    header: has bottom border, padding decent now.
    Got buttons styled well now.
    Split out header styles from style.css into header.css.

commit 1fdf8ad40a67140803eea81effaee4a4fabf29f6
Author: Michael <blahblah@gmail.com>
Date:   Sun Apr 1 19:50:23 2012 -0400

    Init. CSS Design in Progress

Now I'm ready to push it to the central or community repo (such as GitHub).


/*---[ Caveats ]---*/

A caution about rebase - it is very powerful and used for more things than shown here. If you do it wrong, you can create a bit of mess to clean up (but you can always recover, just don't panic). But there are two things to be very careful of:

  1. Don't delete commits from the rebase message message when doing interactive rebase. Note the message in the editor during an interactive rebase that says "# If you remove a line here THAT COMMIT WILL BE LOST."
  2. Do not rebase past where you have already pushed to the central or community repo. If I've done 10 commits, and only the last 2 are local, then don't rebase farther back than the last two commits.

Lastly, I recommend you read the Rewriting History chapter of Pro Git in order to be clear on what you are doing with rebase.

Once you get comfortable with it, rebase gives you freedom to use git with any level of mini-savepoints you want and rewrite your local history to reduce repo noise. Here is a good example of where a version control system is giving you freedom, rather than restricting it.

[Update: 08-Apr-2012]: On Hacker News today there is a link to an article about the "many faces of git rebase". Worth reading in combination with my post to get a feel for how rebase can be used.

Tuesday, March 27, 2012

Refactoring to Java Enums

There are only two books (so far) I've ever read three times in full: Dune by Frank Herbert and The Pragmatic Progammer by Dave Thomas and Andy Hunt. From these you could rightly infer what I consider the pinnacle of SF and programming/software engineering literature. (Cryptonomicon will certainly join this list at some point.)

There is a much larger number of books I've read twice in full, so I'll spare you (and myself) the tedium of listing them here.

Then there are those "reference style" books where you have read parts of them dozens of times and the whole thing through at least once, maybe twice. Two that fall into that category for me are Josh Bloch's Effective Java and Martin Fowler's (et al.) Refactoring.

In fact, I recently just re-read Effective Java cover to cover and decided to read most of Refactoring again. The former, with the 2nd edition, has been updated to use many of the latest features of Java 5 and 6. The latter has not.


/* ---[ Java 5 enums: powerful, underutilized ]--- */

One of the themes in Effective Java (and there are many) is that Java enums are powerful and underutilized. For example, in Item 30 Bloch says:

Java's enum types are full-fledged classes, far more powerful than their counterparts in these other languages [C,C++,C#], where enums are essentially int values....

In addition to rectifying the deficiencies of int enums, enum types let you add arbitrary methods and fields and implement arbitrary interfaces. They provide high-quality implementations of all the Object methods, [and] they implement Comparable and Serializable...

So why would you want to add methods or fields to an enum type? For starters, you might want to associate data with its constants.... You can augment an enum type with any method that seems appropriate. An enum type can start life as a simple collection of enum constants and evolve over time into a full-featured abstraction.

While reading Chapter 1 of Refactoring, I felt the urge to update the example Fowler works through - not only to replace Vectors and Enumerations with Lists and Iterators, but in particular to explore the power of the Java enum.


/* ---[ Refactoring, Ch. 1, in brief ]--- */

So today I focus on the section of Ch. 1 where Fowler is extracting a switch statement into the State pattern and replacing a conditional with polymorphic classes.

You may want to peruse that chapter again to refresh yourself on the context (if you don't own the book and you are a programmer, you should obtain one post-haste), but I will review the part that is relevant to the refactoring of Fowler's code that I did.

Fowler starts with a Movie class that uses the old int constants style to doing enums to distinguish between three types of Movies that can be rented at a video rental store (and thus the example is showing its age in more ways than one).

package refactor;

public class Movie {
  public static final int REGULAR = 0;
  public static final int NEW_RELEASE = 1;
  public static final int CHILDRENS = 2;
  private String _title;
  private int _priceCode;

  public Movie(String title, int priceCode) {
    _title = title;
    _priceCode = priceCode;
  }

  public int getPriceCode() {
    return _priceCode;
  }

  public void setPriceCode(int arg) {
    _priceCode = arg;
  }

  public String getTitle (){
    return _title;
  };
}

At the starting point of the refactoring exercise, there are three classes: Customer, Rental and Movie. A Customer can have multiple Rentals and a Rental (in this case) is allowed to have only a single Movie. Only Customer has any business logic, the other classes are just value objects.

Fowler proceeds to refactor functionality out of Customer, first into Rental and then into Movie. In particular, since the Movie class has the "int enum" of REGULAR, CHILDRENS and NEW_RELEASE, he refactors Movie to provide two pieces of functionality that were originally in the Customer class:

  • getCharge: calculates the charge for renting the movie, which varies by Movie enum type
  • getFrequentRenterPoints: calculates the rental agency's bonus "frequent renter" points, which also varies by Movie enum type

So far he has basically been changing the location of a switch statement based on the int constants in Movie.

  switch (rental.getMovie().getPriceCode()) {
  case Movie.REGULAR:
    thisAmount += 2;
    if (rental.getDaysRented() > 2)
      thisAmount += (rental.getDaysRented() - 2) * 1.5;
    break;
  case Movie.NEW_RELEASE:
    thisAmount += rental.getDaysRented() * 3;
    break;
  case Movie.CHILDRENS:
    thisAmount += 1.5;
    if (rental.getDaysRented() > 3)
      thisAmount += (rental.getDaysRented() - 3) * 1.5;
    break;
  }    

After he has the switch statement in the right class, now he works to replace the conditional with polymorphism. He decides that the right way to do it is not to make subclasses of Movie (e.g., RegularMovie, ChildrensMovie, etc.), because a specific movie can change its type while the application is running (e.g., from "New Release" to "Regular"). Thus, he uses the Replace Type Code with State/Strategy refactoring pattern.

In the end, he creates an abstract Price state class that is used by Movie to implement the state-dependent methods getCharge and getFrequentRenterPoints. Here is the UML diagram:

State Pattern Using Price class

Here is the code for the abstract Price class and its concrete subclasses:

// in Price.java file
public abstract class Price {
  public abstract int getPriceCode();
  public abstract double getCharge(int daysRented);
  public int getFrequentRenterPoints(int daysRented) {
    return 1;
  }
}

// in ChildrensPrice.java file
public class ChildrensPrice extends Price {

  @Override
  public int getPriceCode() {
    return Movie.CHILDRENS;
  }

  @Override
  public double getCharge(int daysRented) {
    double result = 1.5;
    if (daysRented > 3)
      result += (daysRented - 3) * 1.5;
    return result;
  }

}

// in NewReleasePrice.java file
public class NewReleasePrice extends Price {

  @Override
  public int getPriceCode() {
    return Movie.NEW_RELEASE;
  }

  @Override
  public double getCharge(int daysRented) {
    return daysRented * 3;
  }

  @Override
  public int getFrequentRenterPoints(int daysRented) {
    if (daysRented > 1)
      return 2;
    else
      return 1;
  }
}

// in RegularPrice.java file
public class RegularPrice extends Price {

  @Override
  public int getPriceCode() {
    return Movie.REGULAR;
  }

  @Override
  public double getCharge(int daysRented) {
    double result = 2;
    if (daysRented > 2)
      result += (daysRented - 2) * 1.5;
    return result;
  }
}

Note that Price is an abstract class, rather than an interface. Because the getFrequentRenterPoints method is shared between two of the subclasses, it has been pulled up into the abstract base class. When I refactor this code further, you'll see that we can duplicate this behavior with an enum as well.

In the end, Fowler has achieved a clean design that adheres to the open/closed principle. It is open for extension by adding new subclasses to Price and it is closed for modification in that one never needs to modify the Price class in order to add a new type or state.


/* ---[ Refactoring to use Java 5 enums ]--- */

It is easy to see how to use a Java 5 enum for the original code (before Fowler's refactoring). It would just be a matter of replacing this:

public class Movie {
  public static final int REGULAR = 0;
  public static final int NEW_RELEASE = 1;
  public static final int CHILDRENS = 2;

  ...
}

with this:

public class Movie {

  public enum Price {
    REGULAR, NEW_RELEASE, CHILDRENS;
  }
  ...
} 

... and then refactoring the dependent classes to refer to Movie.Price.REGULAR, etc.

And that is how most people, from what I've seen and read, use Java enums. But as Bloch said earlier, Java enums are much more powerful than that. They are type-safe full-fleged Java immutable (final) classes where each enum entry (REGULAR, NEW_RELEASE, etc.) is a singleton for that entry. They can have constructors, implement arbitrary methods and implement interfaces. In fact, you can even put abstract methods on the "base" enum to require the concrete enum singleton entries to implement a method (as we'll see in my example below).

Here is the end product of my refactoring starting from Fowler's end product:

public enum Price {
  REGULAR {
    @Override
    public double getCharge(int daysRented) {
      double result = 2;
      if (daysRented > 2)
        result += (daysRented - 2) * 1.5;
      return result;
    }
  }, 
  CHILDRENS {
    @Override
    public double getCharge(int daysRented) {
      double result = 1.5;
      if (daysRented > 3)
        result += (daysRented - 3) * 1.5;
      return result;
    }
  }, 
  NEW_RELEASE{
    @Override
    public double getCharge(int daysRented) {
      return daysRented * 3;
    }

    @Override
    public int getFrequentRenterPoints(int daysRented) {
      if (daysRented > 1) return 2;
      else        return 1;
    }
  };

  public abstract double getCharge(int daysRented);

  /**
   * Default implementation of getFrequentRenterPoints for all 
   * types in the enum. May be overridden by specific enum types
   * if they give fewer or higher numbers of bonus points.
   * 
   * @param daysRented number of days the movie was rented
   * @return number of bonus points
   */
  public int getFrequentRenterPoints(int daysRented) {
    return 1;
  }
}

If you haven't used the advanced features of enums, this may look surprising.

First, notice that you can declare methods in the "main body" of the enum, and that they can even be abstract. In this case, I have created the getCharge and getFrequentRenterPoints methods. This means that all specific (singleton) entries of the enum have these methods. In the case of the abstract method, the compiler will require you to implementat that method in each entry body.

Which brings me to the second point - enum entries can have bodies that are specific to that entry and not shared with the other entries. Methods outside the entries are common to all entries and methods inside an entry are specific to the entry.

The zone of "inside an enum entry" is demarcated by a matching pair of curly braces after the declaration of the entry's name (e.g., REGULAR). This is just like the notation for a class body.

You create specific data fields and methods inside the enum entry body. In this case these entries don't have any state to retain (their name is the representation of the state needed), so I only have methods, not fields.

If one did need fields, how you would populate them with user/client-provided data? You would define a constructor, which would go in the section outside the entries (but inside the enum class body of course). Effective Java has a nice example of when you might need to have enum constructors.


/* ---[ Using the Price enum ]--- */

If you scroll back up and review the UML diagram, you'll see that the Movie class has both getCharge and getFrequentRenterPoints methods. In my refactoring the Movie class does the same thing. The only difference is that Price is an enum, not a regular Java class.

public class Movie {
  private String _title;
  private Price priceCode;

  public Movie(String title, Price priceCode) {
    _title = title;
    this.priceCode = priceCode;
  }

  public Price getPriceCode() {
    return priceCode;
  }

  public String getTitle() {
    return _title;
  }

  double getCharge(int daysRented) {
    return priceCode.getCharge(daysRented);
  }

  public int getFrequentRenterPoints(int daysRented) {
    return priceCode.getFrequentRenterPoints(daysRented);
  } 
}

Users of the Movie class would then need references the standalone Price enum when creating a Movie:

Movie m1 = new Movie("Grease", Price.REGULAR);
Movie m2 = new Movie("Cars", Price.CHILDRENS);


/* ---[ Analysis ]--- */

So is this better? When are enums more appropriate?

First of all, Java 5 enums are always more appropriate than using the "int enum" pattern. Their primary purpose is to replace that pattern. They bring type-safety to those constants, they bring a toString() method that prints out their name, they implement Comparable and Serializable and allow for the addition of arbitrary behavior.

Java 5 enums are appropriate when you need singleton behavior from each entry - only one copy of the REGULAR, CHILDREN and NEW_RELEASE enum objects will ever be created. In fact, they are such perfect singleton implementations, both in terms of proper initialization and thread-safety, that Josh Bloch recommends them as the best way to implement the Singleton pattern in Java now. If you want it to be a true singleton, then you only create one "entry", which you might call INSTANCE, like so:

public enum MySingleton {
  INSTANCE {
    // put instance fields and methods here
  };
}

Creating a singleton with guaranteed creation thread-safety and no known ways to create two (such as by Serialization attacks) has never been so easy.

Of course, as singletons, enums inherit some well-known pitfalls of the Singleton pattern, including the fact that they are difficult to test, as you can't dependency inject a mock or stub version of them. With a good dependency injection framework, like Spring, singletons are frequently not needed any more.

In any case, an enum with extra behavior is appropriate when you need behavior without state or where the state of each enum entry would the be same for any objects using them. In the case of my refactored version, the only state the Price enum needs is to know its type (which is what the enum is for) and do some calculations based on that state. There is only a need for one of them in the entire app, no matter how many Movie objects I need to create, so using a set of singleton immutable enums works. And the behavior of those enums does not leverage any external dependencies or resources - they just do some simple arithmetic and return an answer, so I don't need to mock or stub them for testing.

So finally, what about the open/closed principle? Well, with enums in my refactored version, one could argue that they do violate this principle. It isn't open for extension, since enums are final and cannot be subclassed. And it isn't closed for modification, as new entries of the enum cannot be created without editing the Price enum java source code file directly.

True critique. Enums do not support the open/closed principle and thus should only be used in situations where you have (and preferably own) the source code and can modify it yourself or when you actively want to prevent anyone from creating any other types. It is closed/closed intentionally.

But is my refactoring really worse than Fowler's on this criterion? Actually no, because even though his Price class follows the open/closed principle, the Movie class does not - it still expects its clients to indirectly reference the Price class via its int constants. To add a new int constant one would have to edit the Movie java file source code. So Fowler's overall refactored design doesn't follow the open/closed principle either.

If we truly wanted to follow that principle, we would have to refactor to some third design. I'll leave that as an exercise for any reader that might be interested in trying that out.


/* ---[ Antipattern: double bad ]--- */

A final side note: Fowler's example code has a smell in it that he didn't correct: using double to handle monetary values. A refactoring should also be done to use long, BigDecimal or a self-constructed Money class instead.

And that's the joy of refactoring - with new language features evolving and the list of code smells you are aware of growing, you will frequently be able to go back to old code and find a way to improve it.

Sunday, March 11, 2012

MyBatis: A User Guide Companion


/*---[ Rationale ]---*/

One of the frustrations I have about reading guides on how to learn a new software technology is that they tend to focus on showing a snippet of code that they want to talk about rather than all the code in context. I have seen others comment that this seems to be a trend in many programming books and online guides these days and was not the case in days when Kernighan and Ritchie wrote the seminal The C Programming Language.

The MyBatis 3 User Guide is a case in point. It is easy for someone new to it to quickly get confused because they don't understand the context of the snippets. Simple complete working examples that you can try on the command line or in Eclipse would solve this problem. The authors also sometimes refer to getting the example source code, but finding that is not trivial. I ended up pulling their source code from their SVN repo and that has a couple of different test apps mixed together (the blog and jpetstore are two).

They do provide the JPetStore full code "sample", but it is large and intertwined with Stripes and Spring, so not a place for a newbie to start.

So, since I really like MyBatis and want to promote people learning this, here is my contribution to present the simplest possible MyBatis setup that will allow you to read the MyBatis3 documentation and have working code to allow you to try out its examples.

There are a number of good tutorials for MyBatis (exhibit A and exhibit B) on the web, but they either typically jump right into a full fledged example that can be overwhelming or are a full stack example including servlets and app servers, etc. when you just want to understand the basics and try it out isolation. That's the gap I'm trying to fill in this tutorial companion.


/*---[ "User Guide Companion" Overview ]---*/

This is a companion to, not a substitute for, the MyBatis 3 User Guide, so make sure you download that and the mybatis code bundle from the MyBatis website.

This tutorial companion comes in two parts. Since I'm a big believer in "provide an example of the simplest thing that works" - that's what part 1 is: a bare bones, but fully working, setup of a MyBatis-based system. Part two is the set up you'll need for the Blog example that the MyBatis 3 User Guide largely uses. By having this foundation, you can tweak and test a working system as you read through the User Guide.

Here I assume you know how to put jar files on your CLASSPATH either from the command line or in an IDE like Eclipse.

Download the latest MyBatis bundle from the MyBatis website here. Put mybatis-3.x.x.jar in your CLASSPATH (Java Build Path > Libraries in Eclipse). You will need to also download the JDBC jar for the database you are using.

I provide .sql files for creating tables and initial data sets for both PostgreSQL and MySQL. You can get all the code I reference here from my GitHub repo.



Tutorial Companion Part One: MyBatis101 - The Simplest Thing That Could Work

I call this first application "MyBatis101" and I've created a directory with that name. In that directory, I have created 7 files, including an Ant build.xml file, so in the end it looks like this:

MyBatis101$ tree
.
|-- build.xml
|-- MyBatis101.sql
|-- src
    |--MyBatis101-config.xml
    |-- mybatis101
        |-- Main.java
        |-- Mapper.java
        |-- MyBatis101-mapper.xml 
        |-- User.java


/*---[ First: Set up a Database ]---*/

First let's set up a very simple database with one table, having two columns and two rows of data.

As I said above, I will show this in both PostgreSQL and MySQL. I don't describe how to install and set up those databases. If you need to start there, here are links to good documentation:

In my setup I am using PostgreSQL 9.1 and MySQL 5.1.

I also show these using a Linux command line, but it should work the same on Mac and Windows. I list all the files one by one below, but to avoid copy and paste, be sure to pull them from my from my GitHub repo:

git clone git@github.com:midpeter444/MyBatis-UserGuide-Companion-Code.git


/*---[ PostgreSQL ]---*/

Create the MyBatis101 database and check that it is there:

$ createdb MyBatis101
$ psql --list
                                   List of databases
    Name    |    Owner    | Encoding | Collation  |   Ctype     
------------+-------------+----------+------------+-------------
 depot      | midpeter444 | UTF8     | en_US.utf8 | en_US.utf8 
 MyBatis101 | midpeter444 | UTF8     | en_US.utf8 | en_US.utf8 
 postgres   | postgres    | UTF8     | en_US.utf8 | en_US.utf8 
 template0  | postgres    | UTF8     | en_US.utf8 | en_US.utf8 

 template1  | postgres    | UTF8     | en_US.utf8 | en_US.utf8 

(5 rows)


Create the file MyBatis101.sql:

drop table if exists users;

create table users (
  id integer,
  name varchar(20)
);

insert into users (id, name) values(1, 'User1');
insert into users (id, name) values(2, 'User2');


Create the tables and load test data into the MyBatis101 database and check that it is there:

$ psql MyBatis101 < MyBatis101.sql
$ psql MyBatis101
psql (9.1.3)
Type "help" for help.

MyBatis101=> \d
          List of relations
 Schema | Name  | Type  |    Owner    
--------+-------+-------+-------------
 public | users | table | midpeter444
(1 row)

MyBatis101=> select * from users;
 id | name  
----+-------
  1 | User1
  2 | User2
(2 rows)


/*---[ MySQL ]---*/

Create the MyBatis101 database and check that it is there:

$ mysql -p
mysql> create database MyBatis101;
Query OK, 1 row affected (0.03 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| MyBatis101         |
| mysql              |
+--------------------+
3 rows in set (0.03 sec)


Create the file MyBatis101.sql: (Same file as above)


Create the tables and load test data into the MyBatis101 database and check that it is there:

$ mysql -p MyBatis101 < MyBatis101.sql
$ mysql -p MyBatis101

mysql> show tables;
+----------------------+
| Tables_in_MyBatis101 |
+----------------------+
| users                |
+----------------------+
1 row in set (0.00 sec)

mysql> desc users;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

Now that the databases is set up with a table and a little sample table, now we can try out MyBatis.


/*---[ The MyBatis Set Up Files ]---*/

Create the MyBatis101-config.xml in the src directory:

Note: Use the correct driver and url according to which database you are using.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="UNPOOLED">
        <property name="driver" value="org.postgresql.Driver" />
        <property name="url" value="jdbc:postgresql:MyBatis101" />
        <!--  <property name="driver" value="com.mysql.jdbc.Driver" />   -->
        <!--  <property name="url" value="jdbc:mysql://localhost:3306/MyBatis101" /> -->        
        <property name="username" value="" />
        <property name="password" value="" />
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <mapper resource="MyBatis101-mapper.xml" />
  </mappers>

</configuration>


Create MyBatis101-mapper.xml in the src/mybatis101 directory:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="mybatis101.Mapper">

    <select id="getUser" parameterType="int" resultType="mybatis101.User">
        select * from users where id = #{id}
    </select>

</mapper>


Create src/mybatis101/Mapper.java:

package mybatis101;

public interface Mapper {
  User getUser(Integer id);
}


Create src/mybatis101/User.java:

package mybatis101;

public class User {

  private Integer id;
  private String name;

  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}


Create src/mybatis101/Main.java:

package mybatis101;

import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class Main {

  private static SqlSessionFactory sessionFactory;

  public static void initSessionFactory() throws Exception {
    Reader rdr = Resources.getResourceAsReader("MyBatis101-config.xml");
    sessionFactory = new SqlSessionFactoryBuilder().build(rdr);
    rdr.close();
  }

  // uses the old iBATIS style of lookup
  public static void lookUpUserOldWay() throws Exception {
    SqlSession session = sessionFactory.openSession();
    try {
      User user = (User) session.selectOne(
          "mybatis101.Mapper.getUser", Integer.valueOf(1));
      System.out.println(user.getName());  // should print out "User1"

    } finally {
      session.close();
    }
  }

  // uses the new MyBatis style of lookup
  public static void lookUpUser() throws Exception {
    SqlSession session = sessionFactory.openSession();

    try {
      Mapper mapper = session.getMapper(Mapper.class);
      User user = mapper.getUser(2);
      System.out.println(user.getName());  // should print out "User2"

    } finally {
      session.close();        
    }
  }

  public static void main(String[] args) throws Exception {
    initSessionFactory();
    lookUpUserOldWay();
    lookUpUser();
  }
}


Create build.xml ant script (not required if you are doing this in Eclipse) and run it:

<project name="MyBatis101" default="run" basedir=".">
  <!-- Template based on: http://sourceforge.net/apps/mediawiki/import-ant/index.php?title=Snippets -->
  <description>Build script for simple MyBatis101 app</description>

  <!-- load environment variables as properties -->
  <property environment="env"/>

  <!-- default folder location properties -->
  <property name="src.dir" value="src"/>
  <property name="build.dir" value="bin"/>
  <!-- TODO: EDIT THESE -->
  <property name="lib.dir" value="/home/midpeter444/java/lib"/> 
  <property name="jdbc.jar" value="postgresql.jar"/>
  <!-- <property name="jdbc.jar" value="mysql-connector-java.jar"/> -->

  <!-- project classpath -->

  <path id="project.classpath">
    <!-- compiled classes -->
    <pathelement location="${build.dir}" />
    <!-- libraries -->
    <fileset dir="${lib.dir}">
      <include name="${jdbc.jar}" />  
      <include name="mybatis.jar" />    <!-- TODO: EDIT THIS -->
    </fileset>
  </path>

  <!-- basic -->

  <target name="init">
    <mkdir dir="${build.dir}"/>
  </target>

  <!-- compile -->

  <target name="prepare-resources" depends="init">
    <copy todir="${build.dir}" overwrite="true">
      <fileset dir="${src.dir}" includes="**/*.xml" />
    </copy>
  </target>

  <target name="compile" depends="init,prepare-resources"
          description="Compile java classes">
    <javac
        srcdir="${src.dir}"
        destdir="${build.dir}"
        includeantruntime="false"> <!-- to overcome misfeature in An t1.8 -->
      <classpath refid="project.classpath" />
    </javac>
  </target>


  <!-- run on console -->

  <property name="run.main-class" value="mybatis101.Main"/>
  <property name="run.args" value=""/>

  <target name="run" depends="compile"
          description="Run MyBatis101 program">
    <java classname="${run.main-class}" fork="true">
      <arg line="${run.args}" />
      <classpath>
        <path refid="project.classpath" />
      </classpath>
    </java>
  </target>
</project>
$ ant run
Buildfile: /home/midpeter444/databases/postgresql/mybatis-learn/MyBatis101/build.xml

init:

prepare-resources:
     [copy] Copying 2 files to /home/midpeter444/databases/postgresql/mybatis-learn/MyBatis101/bin

compile:

run:
     [java] User1
     [java] User2

BUILD SUCCESSFUL
Total time: 2 seconds

I'm not going to explain much about this code. Read it along with the User Guide or the MyBatis Getting Started tutorial and it should start to make sense pretty quickly.




Tutorial Companion Part Two: Blog App in the MyBatis3 User Guide

The MyBatis101 code was intended just to get your feet wet with MyBatis. In this section, we briefly peruse the companion code as part of the "blog" application that is mostly referenced in the MyBatis3 User Guide. Note: the references to the blog database structure and codebase are not consistent in the MyBatis3 User Guide, so I've done the best I can at finding something close to most examples.

This code is intended as starter code - it is a fully working example, with a Main.java that exercises a couple of MyBatis query mappings and one insert mapping. I have also provided one JUnit 4 test that only tests one of the query mappings.

This code along with the User Guide could be used as a sort of poor man's koan - a series of exercises to be filled out with more functionality.

The routine will be the same as above, we just have more of it. Again, pull all the files from my GitHub account.

I have created a directory called "blog" and the code base structure I provide looks like this:

blog$ tree
.
|-- blogdb-ddl-mysql.sql
|-- blogdb-ddl-postgres.sql
|-- blogdb-dml.sql
|-- build.xml
|-- src
    |-- main
        |-- java
            |-- org
                |-- mybatis
                    |-- example
                        |-- Author.java
                        |-- AuthorMapper.xml
                        |-- Blog.java
                        |-- BlogMapper.java
                        |-- BlogMapper.xml
                        |-- config.properties
                        |-- Configuration.xml
                        |-- Main.java
    |-- test
        |-- java
            |-- org
                |-- mybatis
                    |-- example
                        |-- BlogMapperTests.java

We start by creating the database and tables for the Blog and Author classes they discuss in the MyBatis3 User Guide.


/*---[ PostgreSQL ]---*/

Create the database:

$ createdb blogdb


Create the DDL for the blog and author tables and save it in a file called "blogdb-ddl-postgres.sql":

DROP TABLE    IF EXISTS blog;
DROP TABLE    IF EXISTS author; 
DROP SEQUENCE IF EXISTS blogdb_blog_seq;
DROP SEQUENCE IF EXISTS blogdb_author_seq;
CREATE SEQUENCE blogdb_blog_seq;
CREATE SEQUENCE blogdb_author_seq;

CREATE TABLE author (
  id               integer PRIMARY KEY DEFAULT nextval('blogdb_author_seq') NOT NULL,
  username         varchar(255) NOT NULL CHECK (username <> ''),
  hashed_password  varchar(255) NOT NULL CHECK (hashed_password <> ''),
  email            varchar(100) NOT NULL CHECK (email <> ''),
  bio              text
);

CREATE TABLE blog (
  id          integer PRIMARY KEY DEFAULT nextval('blogdb_blog_seq') NOT NULL,
  title       varchar(255) NOT NULL CHECK (title <> ''),
  author_id   integer NOT NULL references author(id)
);


Create some fake data to load into the tables and saved it in a file called "blogdb-dml.sql":

INSERT into author (username, hashed_password, email, bio)
VALUES('aaron1', 'aaron1', 'aaron@pobox.com', 'Aaron is "The Dude".');

INSERT into author (username, hashed_password, email)
VALUES('barb2', 'barb2', 'barb@pobox.com');

INSERT into author (username, hashed_password, email, bio)
VALUES('carol3', 'carol3', 'carol@pobox.com', 'Carol is an avid atom-smasher and street luger.');

INSERT into blog (title, author_id)
VALUES('Why I am "The Dude"', (select id from author where username='aaron1'));

INSERT into blog (title, author_id)
VALUES('A Day in the Life of "The Dude"', (select id from author where username='aaron1'));

INSERT into blog (title, author_id)
VALUES('Sanity is my strong suit', (select id from author where username='barb2'));

INSERT into blog (title, author_id)
VALUES('I are smart?', (select id from author where username='carol3'));

INSERT into blog (title, author_id)
VALUES('The Large-Hadron Collider will not create a black hole that ends the universe', (select id from author where username='carol3'));


Run the DDL and DML scripts against the database:

$ psql blogdb < blogdb-ddl-postgres.sql
$ psql blogdb < blogdb-dml.sql


/*---[ MySQL ]---*/

Create the database:

$ mysql -p
mysql> create database blogdb;
Query OK, 1 row affected (0.03 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| MyBatis101         |
| blogdb             |
| mysql              |
+--------------------+
4 rows in set (0.03 sec)


Create the DDL for the blog and author tables and save it in a file called "blogdb-ddl-mysql.sql":

DROP TABLE    IF EXISTS blog;
DROP TABLE    IF EXISTS author; 

CREATE TABLE author (
  id               integer NOT NULL AUTO_INCREMENT PRIMARY KEY,
  username         varchar(255) NOT NULL CHECK (username <> ''),
  hashed_password  varchar(255) NOT NULL CHECK (hashed_password <> ''),
  email            varchar(100) NOT NULL CHECK (email <> ''),
  bio              text
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;

CREATE TABLE blog (
  id          integer NOT NULL AUTO_INCREMENT PRIMARY KEY,
  title       varchar(255) NOT NULL CHECK (title <> ''),
  author_id   integer NOT NULL,
  FOREIGN KEY (author_id) REFERENCES author(id)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;


Create some fake data to load into the tables and save it in a file called "blogdb-dml.sql": (Same file as in the PostgreSQL section.)


Run the DDL and DML scripts against the database:

$ mysql -p blogdb < blogdb-ddl-mysql.sql
$ mysql -p blogdb < blogdb-dml.sql


/*---[ Work Through The User Guide ]---*/

Next, start by looking at Main.java in src/main/java/org/mybatis/example. Follow each of its steps to see how the MyBatis system works. This example includes a complex mapping - what MyBatis calls a resultMap. On p. 29 of the User Guide they go into some detail about it, as it is one of the most important features of MyBatis.

From this code base you should be able to work through the entire User Guide, trying out new features as you go. Make sure to write tests for everything you do. I've intentionally made the JUnit test very bare bones so that is the place you can learn by doing.

After doing this, I recommend trying out one of the MyBatis tutorials on the web, such as either of those that I already mentioned above:

Good luck and feedback is welcome.


[Update 28-May-2012]: I've recently published a set of koans to learn MyBatis, so consider trying those out as well. My May 2012 blog entry describes these and how to get started.