Reladomo is not your typical ORM !
Temporal chaining logic
Object oriented, compiled, type-checked, query language
Transparent multi-schema support
Object oriented batch operations
Unit testable code
Flexible object relationship inflation
What is a Code Kata?
A programming exercise which helps hone your skills through practice.
A first object: Person
Reladomo Generation
Reladomo Runtime Class List
Consider a simple object representing a person:
![]() |
First thing to do, is create the Reladomo XML that describes this object.
For the Kata, we have created this object for you.
You can see this definition in the
Person.xml
file.
Reladomo Person definition
![]() |
Reladomo needs to know the list of objects you want to work with.
Use a Reladomo "class list" XML file to declare this
For the kata, it's defined in:
MithraTestAppClassList.xml
The next step is to convert these definitions into objects we can use.
We call this step Reladomo Generation, and it results in Java source files.
Reladomo Class List -> Objects
![]() |
Items above the blue line are always generated by Reladomo, and are never checked in to VCS (i.e. never committed to Git or Subversion).
Items below the blue line are generated once only.
You add your business specific code here, and check them in.
Generated Classes
![]() |
See the FAQ for integrating with Ant, Maven or Gradle.
Structure your build so that generation happens before compile.
For the Kata, the Maven "test" target will generate sources, compile and run tests.
Reladomo needs to know the list of objects and their behavior at runtime.
Typically this is the same as those listed in the Reladomo "class list", however, they can be different, like a subset, for example.
For the kata, it's defined in:
TestMithraRuntimeConfig.xml
Example Reladomo "class list" XML
<MithraRuntime xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../mithra/mithraruntime.xsd"> <ConnectionManager className="com.gs.fw.common.mithra.test.ConnectionManagerForTests"> <MithraObjectConfiguration cacheType="partial" className="kata.domain.Person" /> </ConnectionManager> </MithraRuntime>
Contains connection properties, caching mode and similar properties required at runtime.
Basic fetch
Your Friend: Finder
Fetch all the rows for
Person
from the DB as
Person
objects
Fetch specific rows for
Person
from the DB as
Person
objects
We use a
List
to hold many objects
Reladomo generates a typed-list for us, called
PersonList
How do we tell Reladomo which rows to fetch?
SQL
SELECT * FROM PERSON GO SELECT * FROM PERSON WHERE LAST_NAME = 'Smith' GO
Java
PersonList people = PersonFinder.findMany(PersonFinder.all()); // Reladomo executes: SELECT * FROM PERSON Verify.assertSize(8, people); PersonList smiths = PersonFinder.findMany(PersonFinder.lastName().eq("Smith")); // Reladomo executes: SELECT * FROM PERSON WHERE LAST_NAME = 'Smith' Verify.assertSize(3, smiths);
Fetch a specific row from the DB as a
Person
object.
How do we tell Reladomo which row to fetch?
How do we specify we expect one object?
SQL
SELECT * FROM PERSON WHERE PERSON_ID = 5 GO
Java
Person person = PersonFinder.findByPrimaryKey(5); // Reladomo executes: SELECT * FROM PERSON WHERE PERSON_ID = 5 Person person = PersonFinder.findOne(PersonFinder.personId().eq(5)) ; // Reladomo executes: SELECT * FROM PERSON WHERE PERSON_ID = 5
PersonFinder
is how we describe the criteria of what we want.
A finder has a method for every attribute on your object:
PersonFinder.personId()
PersonFinder.firstName()
Every Reladomo attribute has methods on it to define common operations
Finders have Attributes representing columns in the DB.
Numeric attributes have mathematical functions available
String attributes have String functions
Java
Operation thisOperation = PersonFinder.firstName().eq("Jake"); // SQL: WHERE FIRST_NAME = 'Jake' Operation thatOperation = PersonFinder.firstName().contains("oh"); // SQL: WHERE FIRST_NAME LIKE '%oh%' Operation thisOrThatOperation = thisOperation.or(thatOperation); // SQL WHERE FIRST_NAME = 'JAKE' OR FIRST_NAME LIKE '%oh%' PersonFinder.firstName().eq("Jake") PersonFinder.firstName().notEq("Jake") PersonFinder.firstName().contains("oh") PersonFinder.firstName().eq("Jake") .or(PersonFinder.firstName().contains("oh")) PersonFinder.firstName().eq("Mary") .and(PersonFinder.lastName().eq("Smith")) PersonFinder.personId().in(IntHashSet.newSetWith(1, 2, 3)) PersonFinder.personId().notIn(IntHashSet.newSetWith(1, 2, 3)) PetFinder.petAge().greaterThan(1) PetFinder.petAge().greaterThanEquals(1) PersonFinder.firstName().isNull() PersonFinder.firstName().isNotNull()
Creating an object
Inserting an object
Reladomo Uniquing
Simulated Sequences
Use the Java keyword new
Call setters to assign values to the object attributes
Creating a custom constructor may help correctness, and aid refactoring
Java
// Option 1 Person janeDoe = new Person(); janeDoe.setFirstName("Jane"); janeDoe.setLastName("Doe"); // Option 2 Person janeDoe = new Person("Jane", "Doe");
Creating a custom constructor may help correctness, and aid refactoring
Never change the no-arg constructor – Reladomo uses that!
Always remember to call the no-arg constructor
Java
public class Person extends PersonAbstract { public Person() { super(); // You must not modify this constructor. Reladomo calls this internally. // You can call this constructor. You can also add new constructors. } public Person(String firstName, String lastName) { super(); this.setFirstName(firstName); this.setLastName(lastName); } }
Use the Reladomo provided methodinsert()
.
Java
Assert.assertNull( PersonFinder.findOne( PersonFinder.firstName().eq("Jane"))); // Insert Jane. Do not keep the reference new Person("Jane", "Doe").insert(); Person janeDoe = PersonFinder.findOne(PersonFinder.firstName().eq("Jane")); Assert.assertEquals("Jane", janeDoe.getFirstName()); Assert.assertEquals("Doe", janeDoe.getLastName());
Did Reladomo inflate a new copy of
janeDoe
from the DB?
Reladomo guarantees there is only one copy of a given persisted object in memory at any given time
Java
// Insert Jane. Keep the reference Person janeDoe = new Person("Jane", "Doe"); janeDoe.insert(); Person janeDoeFromDb = PersonFinder.findOne(PersonFinder.firstName().eq("Jane")); Assert.assertTrue(janeDoe.equals(janeDoeFromDb)); Assert.assertTrue(janeDoe == janeDoeFromDb);
Use the Reladomo provided method
insert()
Reladomo sets the sequence ID for the new
Person
we created
Sequence IDs are declared in the Reladomo XML Object definition
SQL
INSERT INTO PERSON(PERSON_ID, FIRST_NAME, LAST_NAME) VALUES (9, 'Jane', 'Doe') GO
Java
Person janeDoe = new Person("Jane", "Doe").insert(); // Generated SQL: // insert into PERSON(PERSON_ID,FIRST_NAME,LAST_NAME) values (9,'Jane','Doe') System.out.println(janeDoe.getPersonId()); // Prints 9
XML
<Attribute name="personId" javaType="int" columnName="PERSON_ID" primaryKey="true" primaryKeyGeneratorStrategy="SimulatedSequence"> <SimulatedSequence sequenceName="Person" sequenceObjectFactoryName="kata.util.ObjectSequenceObjectFactory" hasSourceAttribute="false" batchSize="10" initialValue="1" incrementSize="1"/> </Attribute>
Use the Reladomo provided methods.
Single Insert
new Person("Jane", "Doe").insert();
Single Update
janeDoe.setLastName("Smith");
Single Delete
janeDoe.delete();
Bulk Inserts
PersonList people = new PersonList(
FastList.newListWith(janeDoe, johnDoe));
people.insertAll();
Bulk Updates
people.setLastName("Smith");
Bulk Deletes
people.deleteAll();
Find tests starting with exercise1; they have failures.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 15 minutes.
public void exercise1GetAllPeople() { PersonList people = PersonFinder.findMany(PersonFinder.all()); Verify.assertSize(8, people); } public void exercise1GetAllSmiths() { PersonList smiths = PersonFinder.findMany( PersonFinder.lastName().eq("Smith")); Verify.assertSize(3, smiths); }
public void exercise1GetAllPetsOfSmiths() { MutableList<Pet> smithPets = PetFinder.findMany( PetFinder.owner().lastName().eq("Smith") ).asGscList(); ... } public void exercise1GetAllPetsOlderThan1OrWuzzy() { PetList oldPetsOrWuzzy = PetFinder.findMany( PetFinder.petAge().greaterThan(1) .or(PetFinder.petName().eq("Wuzzy"))); ... }
public void exercise1UpdateSmithsFirstNamesToGeorge() { ... PersonList smiths = PersonFinder.findMany( PersonFinder.lastName().eq("Smith")); smiths.setFirstName("George"); ... } public void exercise1InsertJaneDoe() { ... Person newPerson = new Person(); newPerson.setFirstName("Jane"); newPerson.setLastName("Doe"); newPerson.insert(); ... }
Setting up test data
In the Reladomo Kata, we are using
MithraTestResource
to help us
set up a test database to run our tests and examples against.
Test data is defined as a simple text file, with comma-delimited values which represent the DB data
Example Test Data for the
Person
object
class kata.domain.Person personId, firstName, lastName 1 , "Mary" , "Smith" 2 , "Bob" , "Smith" 3 , "Ted" , "Smith" 4 , "Jake" , "Snake" 5 , "Barry" , "Bird" 6 , "Terry" , "Turtle" 7 , "Harry" , "Hamster" 8 , "John" , "Doe"
Declaring Relationships
Using Relationships
Relationship Existence
Relationships can be expressed in Reladomo's object XML
![]() |
Company.xml
<ClassName>Company</ClassName> <Relationship name="employees" cardinality="one-to-many" relatedObject="Employee" relatedIsDependent="true" reverseRelationshipName="company"> this.companyId = Employee.companyId </Relationship>
Employee.xml
<ClassName>Employee</ClassName> <Relationship name="location" cardinality="many-to-one" relatedObject="Location"> this.locationId = Location.locationId </Relationship>
Reladomo generates your relationship description into methods on the business objects
How can we find
Company
based on some related object?
Finder is your friend!
Relationships are navigable via the Finder
SQL
SELECT T0.COMPANY_ID, T0.COMPANY_NAME FROM COMPANY T0 INNER JOIN (SELECT E1.COMPANY_ID CID FROM EMPLOYEE E1 WHERE E1.LOCATION_ID = 2 GROUP BY E1.COMPANY_ID) as T1 on T0.COMPANY_ID = T1.CID
Java
CompanyList slcEmployees = CompanyFinder.findMany( CompanyFinder.employees().locationId().eq(Location.SLC));
You are interested in whether a relationship exists, but you are not interested in any part of the related object
Finder is your friend:
exists()
notExists()
SQL
SELECT T0.COMPANY_ID, T0.COMPANY_NAME FROM COMPANY T0 LEFT JOIN (SELECT E1.COMPANY_ID CID FROM EMPLOYEE E1) as T1 on T0.COMPANY_ID = T1.CID WHERE T1.CID IS NOT NULL
Java
CompanyList employers = CompanyFinder.findMany(CompanyFinder.employees().exists()); CompanyList nonEmployers = CompanyFinder.findMany(CompanyFinder.employees().notExists());
How can we get objects in a certain order?
UseMithraList
’s
addOrderBy()
method
Finder is your friend
You can order by many different levels
Call
addOrderBy()
many times
Or: call
setOrderBy()
with a complex specification
Java
EmployeeList employees = EmployeeFinder.findMany(EmployeeFinder.all()); employees.addOrderBy(EmployeeFinder.age().descendingOrderBy()); employees.addOrderBy(EmployeeFinder.firstName().ascendingOrderBy());
Or
employees.setOrderBy( EmployeeFinder.age().descendingOrderBy() .and(EmployeeFinder.firstName().ascendingOrderBy()));
Deep fetching means retrieving not just immediate target objects, but also some or all of their related objects
Reladomo supports deep fetching via
MithraList.deepFetch()
Easier than writing code to fetch each set of objects yourself
Allows Reladomo to perform optimizations to keep the query efficient
Use when you know the related objects will be accessed.
Reladomo does not actually execute an operation on a list until the list is actually accessed. This feature is usually totally transparent to the developer.
Strictly, Reladomo might not fetch anything from the DB at all if it knows it has the objects in cache, but it might have to fetch some or all if they are not currently available.
Without deep fetch
CompanyList companies = new CompanyList(CompanyFinder.city().eq("London")); // One hit to DB for (Company company : companies) { EmployeeList employees = company.getEmployees(); // Hit DB each time System.out.println( company.getCompanyName() + " employs:" + employees); }
With deep fetch
CompanyList companies = new CompanyList(CompanyFinder.city().eq("London")); companies.deepFetch(CompanyFinder.employees()); // Two hits to DB. One for Company, one for related Suppliers for (Company company : companies) { EmployeeList employees = company.getEmployees(); // no DB hit System.out.println( company.getCompanyName() + " employs:" + employees); }
Find tests starting with exercise2; they have failures.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 15 minutes.
public void exercise2GetAllPetsDescendingOrderedByAgeAndAscendingByName() { PetList pets = PetFinder.findMany(PetFinder.all()); pets.setOrderBy( PetFinder.petAge().descendingOrderBy() .and(PetFinder.petName().ascendingOrderBy())); ... } public void exercise2GetAllPeopleWithPets() { PersonList petPeople = PersonFinder.findMany(PersonFinder.pets().exists()); ... }
public void exercise2GetAllDogLovers() { PersonList dogLovers = PersonFinder.findMany( PersonFinder.pets().petTypeId() .eq(PetType.DOG_ID)); ... } public void exercise2GetAllObjectsInMinDBHits() { ... PersonList people = PersonFinder.findMany(PersonFinder.all()); people.deepFetch(PersonFinder.pets().petTypes()); ... }
public void exercise2GetAllHamsterLovers() { PersonList hamsterLovers = PersonFinder.findMany( PersonFinder.pets().petTypeId() .eq(PetType.HAMSTER_ID)); ... }
Counting rows
Aggregates
MithraList.size()
will cause the list to be populated with data
MithraList.count()
will issue a SELECT COUNT(*) call and return only the count
Only use
count()
when you know you will not need any of the contents of the list
Java
int companyCount = new CompanyList(CompanyFinder.all()).size(); int companyCount = new CompanyList(CompanyFinder.all()).count();
Gives you access to SQL-like features forsum(), average(), min(), max()
, etc.
Remember your friend: Finder
Use it on Reladomo provided
AggregateList
Java
AggregateList aggregateList = new AggregateList(EmployeeFinder.all()); aggregateList.addAggregateAttribute( "employeeAge", EmployeeFinder.age().sum()) aggregateList.addGroupBy("companyId", EmployeeFinder.companyId()); int sumAge = aggregateList.get(0).getAttributeAsInt("employeeAge");
AggregateList aggregateList = new AggregateList(EmployeeFinder.all()); // Min Employee age aggregateList.addAggregateAttribute( "minAge", EmployeeFinder.age().min()) // Max Employee age aggregateList.addAggregateAttribute( "maxAge", EmployeeFinder.age().max()) // Average Employee age aggregateList.addAggregateAttribute( "avgAge", EmployeeFinder.age().avg()) // Group By Location aggregateList.addGroupBy("locationId", EmployeeFinder.locationId()) // Count aggregateList.addAggregateAttribute( "count", EmployeeFinder.locationId().count())
Commonly referred to as "milestoning".
Chaining or bitemporal data :
Umbrella term that describes a way of storing time series data and audit data in a relational database.
Audit only data:
All edits to an object must be tracked
Audit Trail is immutable. Equivalent to a history book
Terminology:
Academic literature calls the time that a particular piece of data changed as the "transaction time", but we often call it "processing date" (even though it's a date-time)
We tend to call the actual date-times that changes happen, "in" and "out", or sometimes, "IN_Z" and "OUT_Z"
Consider an Employee object
First we insert a new object John on 2014-01-15 10:59 am
![]() |
Consider an Employee object
First we insert a new object John on 2014-01-15 10:59 am
A year later John's age increases
The existing row stops being valid, so we update its OUT value with
We insert a new row, valid from 2015-01-15 10:59 am through "infinity"
The process continues
![]() |
Reladomo Object XML defines an
AsOfAttribute
element
Define one of these for your processingDate below the DefaultTable element, and before the regular Attribute elements
Example
<AsOfAttribute name="processingDate" fromColumnName="IN_Z" toColumnName="OUT_Z" toIsInclusive="false" isProcessingDate="true" infinityDate="[kata.util.TimestampProvider.getInfinityDate()]" defaultIfNotSpecified="[kata.util.TimestampProvider.getInfinityDate()]" />
Query for "current" data:
Query just as you would any other data
Query for an "as-of" processingDate:
SQL
SELECT * FROM EMPLOYEE WHERE FIRST_NAME = 'John' AND OUT_Z = '9999-12-01 23:59:00.00'// Reladomo adds this SELECT * FROM EMPLOYEE WHERE FIRST_NAME = 'John' AND IN_Z<= '2014-02-13 23:59:00.00' AND OUT_Z > '2014-02-13 23:59:00.00'
Java
// Current data Employee john = EmployeeFinder.findOne(EmployeeFinder.firstName().eq("John")); // As-of data Employee john = EmployeeFinder.findOne(EmployeeFinder.firstName().eq("John") .and(EmployeeFinder.processingDate().eq(timestamp13Feb2014)));
All operations as normal except:
Create
Construct with the processingDate of "infinity"
Update
Perform inside a transaction
Reladomo will milestone previous row and insert a new row
Java
Employee john = new Employee(TimestampProvider.getInfinityDate()); john.setFirstName("John"); john.insert(); // Update a row MithraManagerProvider.getMithraManager().executeTransactionalCommand( tx -> { Employee john = EmployeeFinder.findOne( EmployeeFinder.firstName().eq("John")); john.setAge(26); return null; });
Rows are immutable, similar to history book
You cannot delete rows, however you can "terminate" them
Terminate -> Update without inserting a new row
Similar to update perform inside a transaction
Use Reladomo provided
terminate()
method
Java
MithraManagerProvider.getMithraManager().executeTransactionalCommand( tx -> { Employee john = EmployeeFinder.findOne( EmployeeFinder.firstName().eq("John")); john.terminate(); return null; });
Find tests starting with exercise3; they have failures.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 20 minutes.
public void exercise3GetTotalAgeOfAllSmithPets() { ... AggregateList aggregateList = new AggregateList( PetFinder.owner().lastName().eq("Smith")); aggregateList.addAggregateAttribute("petAge", PetFinder.petAge().sum()); aggregateList.addGroupBy("personId", PetFinder.personId()); ... }
public void exercise3UpdateAgeOfSpeedyTo70() { ... MithraManagerProvider.getMithraManager().executeTransactionalCommand( new TransactionalCommand<Pet>() { @Override public Pet executeTransaction(MithraTransaction tx) throws Throwable { Pet speedy = PetFinder.findOne(speedySelect); speedy.setPetAge(70); return null; } }); ... }
public void exercise3MilestoneSpeedy() { ... MithraManagerProvider.getMithraManager().executeTransactionalCommand( new TransactionalCommand<Pet>() { @Override public Pet executeTransaction(MithraTransaction tx) throws Throwable { Pet speedy = PetFinder.findOne(speedySelect); speedy.terminate(); return null; } }); ... }
Assume we have a simple table, Pet with pet's name and age
Primary key name: used to decide whether two sets match
Existing data DSet. Update feed FSet
Chirpy is in FSet but not in DSet so will be inserted
Speedy, Wuzzy are in DSet but not in FSet so will be closed out
Tabby is in both sets, so its age will be updated since it has changed
Fuzzy is in both sets but nothing has changed so we'll do nothing for Fuzzy.
![]() |
![]() |
Just as you can add your own methods to the business objects, you can also add your own methods to the lists
Always add your methods to the concrete class, never to the *Abstract classes
Java
public class PetList extends PetListAbstract { public void play() { this.asGscList().each(new Procedure<Pet>() { @Override public void value(Pet pet) { System.out.println(pet.getPetName() + " plays with everyone"); } }); } }
Create Reladomo Object XML file, e.g.
MyObject.xml
Set PackageName, ClassName, and DefaultTableName
Add any AsOfAttributes as necessary, e.g. businessDate and processingDate
Mark Primary Key ID attributes with SimulatedSequences as needed
Add regular Attributes
Create Relationships as appropriate
Add entry to Reladomo Class List file, e.g.
MithraTestAppClassList.xml
Add entry to the Reladomo Runtime Configuration, e.g.
TestMithraRuntimeConfig.xml
Compile and test; make sure you didn't break anything else
Write tests for your new object
Add data to your test data file, e.g.
data_ObjectFromScratch.txt
Test again!
Find tests starting with exercise4; they have failures.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 20 minutes.
Sharding
Transactional semantics
Bitemporal operations
Reladomo's cache behavior in detail
Composite keys
Temp objects
Embedded value objects
3-tier operation
Notification
DDL generation
Existing schema -> xml generation
Class diagram generation
RUNS integration
Bulk insert for ASE/IQ
Timezone conversion
Modifiable primary keys
Detached objects
Tuples
Update listener
Reladomo interfaces
MTloader/single queue executor in detail
Off-heap cache
Off-heap cache replication
Cache loader
DBextractor
Reladomo Kata (note main and mini versions)