How I do “ORM”, Part 2

Khun Yee Fung, Ph.D.
Programming is Life
6 min readJan 27, 2024

Even though I don’t believe in mapping a relational database into a object-oriented hierarchy, because that is fundamentally not a good idea, I do want some kind of simple mapping that does not try to create a object-oriented version of a relational database. The easiest way is to map a table to a class, an attribute to a field in a class, and the relational operations possible to tables and attributes to methods of the classes. Actually, we really are not mapping anything, just representations. In other words, we are not creating a model, just some loose representations of the database in Java. That is more flexible, but more “leaky”, in terms of semantics. So,

  1. A table is represented by a class. A row in the table is represented by an object of the class. I don’t really want to blindly cache the whole table in the memory, even though that has its uses, so there is no representation of all the rows in a table.
  2. An attribute of a table is represented by a field in the class representing the table. Of course, to do that, we need the types of the database represented by types in Java as well. We do need to convert back and forth of these types between Java and the database.
  3. Triggers, and other procedures are not represented at all in Java. In general, I avoid stored procedures and other procedural features in the database as much as possible as they are too global for my liking. I prefer business logic to be out of the database, other than those needed for referential integrity. If something should be done in the database, I will make sure it is done in the database and not have two versions floating around, one in the database as procedure of some sort, and the other one in Java code. That is just looking for trouble.

So, this is not an ORM at all, of course. But that is really the point of this exercise. I am not trying to come up with an object-oriented model of a relational database. I am just trying to make my life easier as a programmer who wants to make sure the relational database is really relational and have referential integrity, and at the same time making sure I don’t have to go through all kind of convoluted semantic warping tricks in Java to perform business logic against the data in the database.

First, as I wrote before, I want each “true” primary key to be a separate Java type. So, if I have two tables, emp and employer, with their own primary keys emp_id and employer_id, I have two classes, KEmpId and KEmployerId in Java. They can extend from the same abstract class, KKey<?>, but they are not of the same type.

Primary foreign keys are not a different type, and will inherit from the originating table, just like foreign keys.

Key classes (the K classes) are very simple:

public class KEmpId extends KKey<Integer> implements Serializable
{
private Integer empId;

public static KEmpId getInstance(Integer empId)
{
if (empId == null)
{
return null;
}
return new KEmpId(empId);
}

private KEmpId()
{
}

private KEmpId(Integer empId)
{
super(empId);
this.empId = empId;
}

...
}

Of course, we need equals(), hashCode(), toString(), etc. as well, to have a proper class. The equals() method is actually very important here; the reason is left as an exercise.

The reason why I have a getInstance() instead of a public constructor is because I want to make sure the key can’t be null. If you pay attention, you will see that the empId field is not final. That is because GWT still does not serialize final fields properly. If you don’t use GWT, then of course the field should be a final field.

Nothing to it. In fact, if the compiler/interpreter is good, classes like this can be optimized to the ID field alone, and avoid the potential “numerous small objects” garbage problem. Of course I wish Java had something that would allow us to signal to the compiler what is going on, but, hey, again, I will trade garbage collection for run-time headache any time. That is for another day about language design and compiler implementation.

What about the emp table itself? Well, I chop the representation into three classes. One for the fields and some common methods, one for an immutable POJO class, and the last one for the operations involving only that table alone. So, I call them the M class, the W class, and the X class.

The M class is basically a class with the fields and some common operations on them, using jOOQ.

public class MEmp extends MPersistence<DBEmpRecord>
{
private KEmpId empId; // [serial][PK]
private KPersonId createdById; // [NN FK]

...

public void setEmpId(KEmpId empId)
{
this.empId = empId;
}

public KEmpId getEmpId()
{
return empId;
}

...

public void updateCreatedById(DSLContext ctx, KPersonId createdById)
throws MNotUpdatedException
{
if (createdById == null)
{
throw new MNotUpdatedException("The attribute [createdById] of entity [MEmp] is not nullable but is now updating to null.");
}
ctx.update(EMP) //
.set(EMP.CREATED_BY_ID, createdById == null ? null : createdById.getPersonId())//
.where(EMP.EMP_ID.equal(empId.getEmpId())) //
.execute();
this.createdById = createdById;
}

...

@Override
public void persist(DSLContext context)
throws MNotInsertedException
{
DBEmpRecord result = getInsertStatement(context) //
.returning(EMP.EMP_ID) //
.fetchOne();
if (result == null)
{
throw new MNotInsertedException("DBEmpRecord not inserted");
}
empId = KEmpId.getInstance(result.getValue(EMP.EMP_ID));
}

...

@Override
public InsertSetMoreStep<DBEmpRecord> getInsertStatement(DSLContext context)
throws MNotInsertedException
{
if (createdById == null)
{
throw new MNotInsertedException("The attribute [createdById] of entity [MEmp] is not nullable but is null");
}
...

return context.insertInto(EMP) //
.set(EMP.CREATED_BY_ID, createdById == null ? null : createdById.getPersonId())//

...

;
}

...

Notice that the class DBEmpRecord is generated by jOOQ, and the MPersistence class is the abstract root class for all the M classes. This M class allows us to persist a new row, get the insert SQL statement for further jOOQ operations, and to update the fields of the row represented by the object.

Again, an object of an M class represents a row of the table represented by the table.

The W classes simply have the fields, all final. And both the W class and the M class have methods to convert itself to the other form, for convenience. In this way, The W classes are used in situation where the rows are read and not updated, which is most of the situations. The M classes are used when updating is required, potentially. In this way, rows are not updated accidentally, and we can be sure the values of the W objects have not been altered, either accidentally or deliberately.

The [NN] in the comment means this attribute is not null in the database. In the same vein, [PK] means the field so marked is the primary key of the table, and [serial] means the primary key is an auto-incremental synthetic key in Postgresql of the type serial. And [FK] means it is a foreign key. All these are meant for the programmer so that they don’t have to go to the database or the database design tool to find out what these fields are.

I would not have the set methods if I had to do it again. Instead, I would use the constructor to set the values (from the database), and the update methods to modify the fields, both in the object and the database. In that way, we will never have the case that a field has been updated in the object but not in the database. Of course, this is a design choice, as I can see the advantages of having the set methods as well.

As for the case of the K classes, the M classes are all generated from the database schema file, not from the database, as initially I was thinking I might need to use a different database than Postgresql eventually. Since the XML file for Dezign database design tool is the same for whatever database, it would be more portable to generate code from the XML file than from the database itself. If I had to do this again, I would generate the X, W, and M classes from the database directly, as in the case for jOOQ.

Alright, the next part of this series is going to be on the X classes. After that, how these classes are generated, what tools I use, etc.

--

--

Khun Yee Fung, Ph.D.
Programming is Life

I am a computer programmer. Programming is a hobby and also part of my job as a CTO. I have been doing it for more than 40 years now.