Introduction


Interactions with hrorm have two major points: building Dao objects and then using them.

Dao building is accomplished with the aptly named DaoBuilder class. DaoBuilder objects are part of the one-time (static, singleton) initiation of your application. There is little point in having more than one builder of any entity type. Of course, some care must be taken: by their nature, DaoBuilder objects are mutable, so if you directly expose them to the rest of your application during start up, it's possible that you can do something stupid.

Dao objects themselves are what perform the actual tasks of persisting and instantiating entity objects. To make a Dao requires a Connection object. Since a Dao keeps a stateful Connection to the underlying data store, it is dangerous to share instances across threads. Generally, the idea is to instantiate a Dao when you need it, and then allow it to be garbage collected. It is up the application itself to deal with reaping the Connection, with some exceptions noted below.

Also take a look at the Quick Start and Javadocs.

This page contains more nuts and bolts about what and how hrorm does what it does. For more on the why, return to the hrorm home page.

Dao Builders

One Table


The easiest case for any ORM tool is persisting a single object backed by a single table. Let's work on persisting a model for a person that includes the following elements:

  • Name (a string or text)
  • Weight in kilograms (an integer)
  • Height in meters (a decimal)
  • Birthday (a date)
  • High school graduate? (a boolean)
  • Hair color (an enumerated type)

To model the person entity, we write a Java Person object.

    class Person {
        Long id;
        String name;
        long weight;
        BigDecimal height;
        LocalDateTime birthday;
        boolean isHighSchoolGraduate;
        HairColor hairColor;
    }

A few small points.

  • Use your imagination to fill in getters, setters, equals, hashCode, etc.
  • We have one extra field that we did not have in our logical model: an id field that is to be used for persistence.
  • Assume that there is an enumerated type for hair color: HairColor.Black, HairColor.Brown, etc.

In the database we will create two structures for persisting this class: a table to store the data and a sequence to issue the IDs.

    CREATE SEQUENCE PERSON_SEQUENCE;

    CREATE TABLE PERSON_TABLE (
        ID INTEGER PRIMARY KEY,
        NAME TEXT,
        WEIGHT INTEGER,
        HEIGHT DECIMAL,
        BIRTHDAY TIMESTAMP,
        IS_HIGH_SCHOOL_GRADUATE TEXT,
        HAIR_COLOR TEXT
    );

Note the somewhat different types than in the Java code. In particular, the boolean and enumeration values have become text fields. SQL has a more limited palate for the expression of types than Java.

To translate between the database reprentation and the Java representation, we plan to use a Dao object. We could build that directly, but hrorm provides a DaoBuilder class that makes things much easier. In hrorm, both Dao objects and their builders are parameterized on the type of thing they persist. We start off by simply calling the DaoBuilder constructor.

    DaoBuilder<Person> daoBuilder = new DaoBuilder<>("PERSON_TABLE", Person::new);

The constructor takes two arguments: the name of the table and a no-argument method for creating a new instance of the parameterized type.

Next, we need to define the primary key for this entity.

    daoBuilder.withPrimaryKey("ID","PERSON_SEQUENCE", Person::getId, Person::setId);

The primary key is defined with four elements:

  1. The name of the primary key column in the table ("ID")
  2. The name of the sequence that populates the primary keys ("PERSON_SEQUENCE")
  3. A function that retrieves the primary key (of type Long) from the Person object.
  4. A function that can set the primary key onto the object.

With that covered, we can being to teach the DaoBuilder about the individual data elements. First, we will teach it about the name field.

    daoBuilder.withStringColumn("NAME", Person::getName, Person::setName);

This explains that that table has a column named "NAME" and that the value in the table can be populated from calling getName() on a Person, and that value can be set by calling setName(). There are other methods on the DaoBuilder for other Java types.

For the integer weight value (which should actually be a Long or long, not an int or short).

    daoBuilder.withIntegerColumn("WEIGHT", Person::getWeight, Person::setWeight);

For fractional, decimal, or floating point values, hrorm supports the java.math.BigDecimal type.

    daoBuilder.withBigDecimalColumn("HEIGHT", Person::getHeight, Person::setHeight);

For dates and times, hrorm supports the java.time.LocalDateTime type.

    daoBuilder.withLocalDateTimeColumn("BIRTHDAY", Person::getBirthday, Person::setBirthday);

And similarly for boolean values. Note that the table should declare a text or string value. ANSI SQL does not support a boolean column type. Hrorm will convert between Java's boolean enumeration of true/false with the strings "T" and "F" in the database.

    daoBuilder.withBooleanColumn("IS_HIGH_SCHOOL_GRADUATE", Person::isHighSchoolGraduate, Person::setHighSchoolGraduate);

For the enumerated HairColor type, hrorm needs a bit more help, via an implementation of its Converter interface. We need a simple class that looks like this:

    class HairColorConverter implements Converter<HairColor, String> {
        @Override
        public String from(HairColor item) {
            return item.getColorName();
        }

        @Override
        public HairColor to(String s) {
            return HairColor.forColorName(s);
        }
    }

Once the Converter exists, we can teach the DaoBuilder about it and the hair color field.

    daoBuilder.withConvertingStringColumn("HAIR_COLOR", Person::getHairColor, Person::setHairColor, new HairColorConverter());

Notice that in addition to the usual fields for column name, getter, and setter, we additionally must specify the conversion mechanism.

That completes the DaoBuilder. Now we can actually build a Dao<Person> object, assuming we have a java.sql.Connection.

But before that, we should note that the DaoBuilder supports a fluent interface, so we could write all of the above as:

    DaoBuilder<Person> daoBuilder = new DaoBuilder<>("PERSON_TABLE", Person::new)
                .withPrimaryKey("ID","PERSON_SEQUENCE", Person::getId, Person::setId)
                .withStringColumn("NAME", Person::getName, Person::setName)
                .withIntegerColumn("WEIGHT", Person::getWeight, Person::setWeight)
                .withBigDecimalColumn("HEIGHT", Person::getHeight, Person::setHeight)
                .withLocalDateTimeColumn("BIRTHDAY", Person::getBirthday, Person::setBirthday)
                .withBooleanColumn("IS_HIGH_SCHOOL_GRADUATE", Person::isHighSchoolGraduate, Person::setHighSchoolGraduate)
                .withConvertingStringColumn("HAIR_COLOR", Person::getHairColor, Person::setHairColor, new HairColorConverter());

In just 8 lines of code, we have taught hrorm everything it needs to know to CRUD Person objects.

Sibling Relations


When one entity object contains a reference to another entity object, hrorm calls that a sibling or join relationship.

Consider a model of cities and states, where each city contains a reference to a state.

    class State {
        Long id;
        String name;
    }

    class City {
        Long id;
        String name;
        State state;
    }

This could be backed by this schema.

    CREATE TABLE STATE (
        ID INTEGER PRIMARY KEY,
        NAME TEXT,
    );

    CREATE TABLE CITY (
        ID INTEGER PRIMARY KEY,
        NAME TEXT,
        STATE_ID INTEGER
    );

    CREATE SEQUENCE STATE_SEQUENCE;
    CREATE SEQUENCE CITY_SEQUENCE;

Creating the State DaoBuilder is trivial.

    DaoBuilder<State> stateDaoBuilder = new DaoBuilder<>("STATE", State::new)
            .withPrimaryKey("ID", "STATE_SEQUENCE", State::getId, State::setId)
            .withStringColumn("NAME", State::getName, State::setName);

There is one new trick to creating the City DaoBuilder: using the DaoBuilder.joinColumn() method which will refer to the stateDaoBuilder we just defined.

     DaoBuilder<City> cityDaoBuilder = new DaoBuilder<>("CITY", City::new)
            .withPrimaryKey("ID", "CITY_SEQUENCE", City::getId, City::setId)
            .withStringColumn("NAME", City::getName, City::setName)
            .withJoinColumn("STATE_ID", City::getState, City::setState, stateDaoBuilder);

The withJoinColumn method accepts an extra parameter: a DaoDescriptor. Both DaoBuilder and the Dao class implement this interface. Generally, it's much more convenient to create all the builder objects together.

Sibling or join relationships in hrorm are one-way. One object declares that it has a reference to another. Trying to make a circular relationship will lead to errors.

When hrorm instantiates objects like City from the database, it automatically instantiates the appropriate sibling State objects and sets the field in the City object.

Of course, you could just treat these as two one-table Dao objects, and then right some code to glue things together. In addition to being inconvenient, this will likely have poorer performance, since hrorm will issue a SQL left join to load the City and State objects with one query.

Objects can have several join columns, and those objects can have their own join columns. Hrorm will attempt to transitively load the entire object graph when a select() method is called on the Dao. There is a limit to how many joins hrorm can perform. Additionally, there is a limit to how many joins a database engine will allow. Consider this when designing Dao objects. Also remember, sibling relationships are for reading and populating objects, not for making saves or updates. If a sibling object is mutated, it must be saved itself.

Parent Child Relations


When one entity contains a collection of other entities, hrorm calls that a parent child relation.

Here is a simple model for tracking inventories of stocks of things through time. At each instant that we measure, we want to know what quantity of each product we have.

    public class Inventory {
        Long id;
        LocalDateTime date;
        List<Stock> stocks;
    }

    public class Stock {
        Long id;
        String productName;
        BigDecimal amount;
    }

The Inventory class represents a snapshot in time of what was available in inventory, modeled as a List of Stock items, each of which contains a product name and a decimal quantity of how much of that thing is available. Notice that the Stock model includes a reference to the inventory ID, but not the inventory object itself.

To model this in the database, we make each item in the STOCK table point back to an INVENTORY record, as follows.

    CREATE TABLE INVENTORY (
        ID INTEGER PRIMARY KEY,
        DATE TIMESTAMP
    );

    CREATE TABLE STOCK (
        ID INTEGER PRIMARY KEY,
        INVENTORY_ID INTEGER,
        PRODUCT_NAME TEXT,
        AMOUNT DECIMAL
    );

    CREATE SEQUENCE INVENTORY_SEQUENCE;
    CREATE SEQUENCE STOCK_SEQUENCE;

To model this in hrorm, we need to teach it about the parent-child relationship between the two entities using the DaoBuilder.withParentColumn() and DaoBuilder.withChildren() methods. First we make a Dao for the Stock entity.

    DaoBuilder<Stock> stockDaoBuilder = new DaoBuilder<>("STOCK", Stock::new)
            .withPrimaryKey("ID","STOCK_SEQUENCE", Stock::getId, Stock::setId)
            .withParentColumn("INVENTORY_ID")
            .withStringColumn("PRODUCT_NAME", Stock::getProductName, Stock::setProductName)
            .withBigDecimalColumn("AMOUNT", Stock::getAmount, Stock::setAmount);

The column INVENTORY_ID is marked not as an integer column, but with the special withParentColumn method. An entity can have only one parent. In the Inventory DaoBuilder we use the withChildren method to complete the relationship definition..

    DaoBuilder<Inventory> inventoryDaoBuilder = new DaoBuilder<>("INVENTORY", Inventory::new)
            .withPrimaryKey("ID", "INVENTORY_SEQUENCE", Inventory::getId, Inventory::setId)
            .withLocalDateTimeColumn("DATE", Inventory::getDate, Inventory::setDate)
            .withChildren(Inventory::getStocks, Inventory::setStocks, stockDaoBuilder);

When we create a Dao in this fashion we create a category of entity, the child, that is wholly dependent upon another, the parent. Whenever we insert, update, delete, or select the parent entity, the changes we make flow through the children and transitively to their children.

Be careful, if you do not want the children to be deleted, this is not the relationship you want to build. In particular, remember that issuing an update will result not just in a SQL UPDATE in the database, but possibly a whole series of INSERT, UPDATE, and DELETE queries being run.

Hrorm always understands child objects to be members of type List. No other collection type is supported.

Back-References

If your object model for includes a back-reference from the child to the parent, Hrorm will populate it for you. If in the model above, the Stock class had a reference to its parent Inventory we could use an overloaded withParentColumn() method call on its DaoBuilder as follows:

    .withParentColumn("INVENTORY_ID", Stock::getInventory, Stock::setInventory)

That will cause the reference to the parent object to be automatically set when using any of the Dao select methods.

Constraints


Documentation on constraints to come.

Daos

To create a Dao from a DaoBuilder, just pass it a java.sql.Connection:

    // Assume the existence of some ConnectionPool
    Connection connection = ConnectionPool.connect();
    Dao<Person> dao = daoBuilder.buildDao(connection);

Insert


To create a new record in the database, we create a new instance of the class and pass it to Dao.insert().

    Person person = new Person();
    // set values for the fields we want
    person.setName("Thomas Bartholomew Atkinson Wilberforce");
    person.setHighSchoolGraduate(true);
    person.setWeight(100L);
    long id = dao.insert(person);
    connection.commit();

After that code runs, the record will be stored in the database. Hrorm will have pulled a new sequence value and set it on the object. The following assertions will be true.

    Assert.assertNotNull(person.getId());
    Assert.assertTrue(id == person.getId());
Children

Hrorm will automatically insert child records of the instance being saved, if any.

Siblings

If the record has sibling entities, references to those will be persisted. But be careful, those sibling references must be persisted first. Sibling inserts and updates do not cascade.

Select


Hrorm provides a few methods for reading data out of the database and instantiating entity objects.

All of the selection mechanisms below will fully read and populate the entire relevant object graph including all children and siblings and all their transitive references.

Primary Key

You can read an item from the database if you know its primary key.

    Person person = dao.select(432L);

If you want to read several IDs at once, you can.

    List<Person> personList = dao.selectMany(Arrays.asList(432L,21L,7659L));
All Records

If you want all the records (presumably for a smallish table) just do

    List<Person> personList = dao.selectAll();
By Columns

Most of the time, you do not know up front what ID or IDs you are intersted in, so hrorm allows you to select by columns. To do this, we make an instance of the entity we are searching for and populate it with the values we want to match. Suppose we want to find all the people who are high school graduates that weigh 100 kilograms. Here's what we can do.

    Person personTemplate = new Person();
    personTemplate.setHighSchoolGraduate(true);
    personTemplate.setWeight(100L);

    List<Person> people = personDao.selectManyByColumns(personTemplate, "IS_HIGH_SCHOOL_GRADUATE", "WEIGHT");

Notice that hrorm wants the names of the database columns, not the fields on the object.

If we know that a particular query will only return 0 or 1 results (for instance, if there is a uniqueness constraint on the name field), hrorm provides a convenience method for that.

    Person personTemplate = new Person();
    personTemplate.setName("Rumplestiltskin");

    Person person = personDao.selectByColumns(personTemplate, "NAME");

If you use this method and hrorm finds more than one record, it will raise an exception.

At the moment, hrorm only supports exact matches, not any LIKE syntax.

Update


After making changes to the state of the object, we can call

    dao.update(person);
    connection.commit();

This will issue an update in the database based on the primary key (id) field.

Updates will automatically propagate to children, but not to siblings.

Delete


When we are done with a person, we can issue

    dao.delete(person);
    connection.commit();

To remove the record from the database, using the primary key, as with an update.

Deletes will automatically propagate to children, but not to siblings.

Atomic

In addition to the insert, update, and delete methods, hrorm Dao objects provide variants of those methods called atomicInsert, atomicUpdate, and atomicDelete. These are useful if you do not mind your changes being committed automatically. But they cannot be used inside larger transactions. Additionally, these methods will close the Connection object their enclosing Dao was built with.