Introducing Hrorm: A Simple, Declarative, Type-Checked ORM
Learn about a simple, declarative, type-checked ORM called Hrorm.
Join the DZone community and get the full member experience.
Join For FreeA Question
A lot of cyber ink has been spilled about the problem of connecting models in Java code with models in relational (SQL) databases. The topic of object-relational mappings (ORMs), is indeed rich, and the full scope of things you might want to do to transfer information between an application and a database is enormous. Many ORM tools attempt to cover as much of that space as they possibly can; their designs and implementations provide for a great deal of flexibility and they are packed with features.
Of course, to achieve that flexibility, they are burdened with complexity. This is not their fault: Java and SQL are quite different worlds and covering all (or even many) of the ways to pull them together is intrinsically complex. What about a different approach? What if instead of designing an ORM tool to handle a wide variety of object and schema models and all the accompanying use-cases, an ORM tool was designed with some assumptions in mind about how objects and schemas look and work and how they are used? How simple could an ORM tool be?
The JDBC
The Java Database Connectivity (JDBC) API defines how almost all interactions between relational databases and Java applications happen. Database makers provide an implementation of the interfaces defined in the java.sql package and clients can do many database tasks through those interfaces, almost everything except actually starting the database.
In greatly over-simplified terms, the JDBC defines three interfaces: java.sql.Connection
, java.sql.Statement
, and java.sql.ResultSet
. A Connection
represents a pipe or socket between the application and the database engine. Messages in the form of Strings
can be passed through the pipe using a Statement
and responses read from the ResultSet
.
The JDBC is a low-level interface, but it actually specifies very little of how applications and databases interact since it takes no position on what form the SQL being sent to the database takes. You can use the JDBC to send arbitrary strings of SQL to the database. It is entirely up to the database to parse and execute it, assuming it even is SQL. So, databases that provide quite different dialects of SQL with wildly varying extensions are all supported by the JDBC, so long as they conform to the basic pattern of accepting queries as SQL strings and responding with tabular data.
Depending on how you look at it, this makes the JDBC tremendously powerful (you can do almost anything) or terribly anemic (the abstractions are not suitable for high-level programming). In theory though, simply by using the JDBC interfaces, almost any interaction a Java application needs to have with a relational database can be managed. It just might take a lot of repetitive code to get there.
The repetitive nature of the code cannot be overstated. First, you have to write all the SQL yourself, generally as literal strings. The JDBC provides nothing to help with this. Additionally, the somewhat varied syntax of SQL means the selects, inserts, updates, and deletes all have to be written to some extent separately. Then, each must be passed down a Connection
within a Statement
and the ResultSet
(if relevant) handled slightly differently. Then, the results must be parsed into the application's object model. Throughout this, care must be taken to correctly release resources lest the database run out of connections or cursors or what have you. And of course, even the close()
methods all throw the checked SQLException
, so everything has to be wrapped in (possibly nested) try-catch-finally blocks. All this before things like joins and the whole universe of other possible optimizations have been considered.
It's tedious code to write and inevitably cut-and-paste errors — or worse — end up in anyone's work.
ORMs
Therefore, one of the primary reasons to use an ORM is simply to avoid writing and maintaining all the boilerplate code that directly using the JDBC requires. If you had an army of low-paid, unambitious interns, you might never bother.
The JDBC is a direct modeling of database concepts as Java classes and interfaces. The code that results is therefore somewhat stilted in addition to being quite verbose. One approach to improving matters is to provide a more modern, idiomatic set of classes for clients to use. Libraries like JDBI take this approach. Everything is still in the application developer's hands, in fact, the JDBI webpage claims it is not an ORM since it does not provide for caching or state tracking and the like. It still requires developers to write SQL themselves. It greatly diminishes the number of code applications contained compared with raw JDBC though. Sometimes, JDBI is referred to as "a better JDBC."
Other ORMs (assuming we can call JDBI an ORM, meaning no offense to the authors) have more ambition in terms of managing the how an application manages interactions with the database. The granddaddy of Java ORM tools is Hibernate, which actually helped to define how the javax.persistence package was built.
Hibernate is now an extensive ecosystem of tools that reach far beyond the original goal of reducing the boilerplate code needed to write applications directly using the JDBC. There are tutorials and documentation and conferences and books about Hibernate. And for some, that itself is the problem. What started out as a straight-forward problem of a mass of repetitive code being shaved down to size is now a whole domain problem in itself, requiring the investment of time and study. Learning all about Hibernate presumably has rewards of its own, but it does come with (at least) two problems. First, Hibernate's power and flexibility mean that even the simple things are actually complicated under the hood. Unexpected behaviors like caching can be introduced when you do not want them. Second, when it comes time to do some optimization (pass a query hint to the DB, create a join in a particular way, etc) it can be hard to know how to get Hibernate to cooperate with that effort. Gone are the days when you could just pass strings of SQL directly to the database.
Introducing Hrorm
For many projects, a domain model of Java-bean like classes can be backed with a store of tables one-to-one. Hrorm is a library that exists to make it easy to create data access objects (DAOs) that can perform the basic CRUD operations for Java-bean like classes. It has some restrictions on how your object model and schema are designed and it won't do everything you can possibly want when communicating from your application to your database, but you won't have to invest in a library of O'Reilly books to use it.
Hrorm allows the easy creation of Dao<T>
objects, which allow you to insert, select, update, and delete instances of type T
. To create a Dao object, you use the DaoBuilder class, which provides a declarative, fluent, type-safe interface for describing the relationship between a database schema and a Java object model. An application with an ingredient domain model like this:
class Ingredient {
// getters and setters omitted, use your imagination
long id;
long recipeId;
String name;
int amount;
}
That was backed by a database table that looks like this:
TABLE INGREDIENT (
ID INTEGER PRIMARY KEY,
RECIPE_ID INTEGER,
NAME VARCHAR,
AMOUNT INTEGER
);
Could have its persistence managed by a Hrorm Dao
that was created like this:
DaoBuilder<Ingredient> ingredientDaoBuilder = new DaoBuilder<>("INGREDIENT", Ingredient::new)
.withPrimaryKey("ID", "INGREDIENT_SEQUENCE", Ingredient::getId, Ingredient::setId)
.withParentColumn("RECIPE_ID")
.withStringColumn("NAME", Ingredient::getName, Ingredient::setName)
.withIntegerColumn("AMOUNT", Ingredient::getAmount, Ingredient::setAmount);
Using a DaoBuilder and a java.sql.Connection
object, you can create a Dao
, like this.
Dao<Ingredient> ingredientDao = ingredientDaoBuilder.build(connection);
And we're done. No writing SQL. No exception handling or resource management. No iterating through results. Just a simple Dao<T>
object that has methods for doing the standard CRUD operations insert()
, update()
, and delete()
that act on objects of type T
. In addition, there are a variety of select()
methods for reading data from the database that return individual T
instances or List<T>
.
There are some restrictions on how domain models must be designed to work with Hrorm that are reflected here. For instance, entities must have an integer (in Java terms, long
) valued primary key, and the primary key must be generated from a database sequence that Hrorm can access.
To say that's all there is to Hrorm would be untrue. There is some more to learn, primarily about how to handle one-to-one and many-to-one relations within your code and schema. You can read about that in the Hrorm documentation. But really, you've already learned most of what you need to know to start using Hrorm. It's a library, not a framework: it does not require any configuration, have any external dependencies, start any background threads, or pollute your domain model with annotations.
And since Hrorm only needs a standard JDBC Connection
object, it's easy to outgrow. You can replace Hrorm generated Dao
objects with your custom, optimized code piece by piece, and only as necessary. Since Hrorm takes a quasi-functional approach to its implementation (no hidden caching or state tracking) there is no perplexing behavior to debug. Calling a Hrorm method results in SQL being sent to the database on the calling thread.
Hrorm does not even try to manage the full scope of things that you might want to do with your database or application. But it quickly gets your objects persisted with the minimum fuss. For many of us, modern ORM tools provide functionality that we do not need at a price that we should not be required to pay.
Opinions expressed by DZone contributors are their own.
Comments