What is Reladomo?
A Java Object Relational Mapping Framework.
What is a Code Kata?
A programming exercise which helps hone your skills through practice.
Reladomo, not your typical ORM:
Chaining logic
Object oriented, compiled, type-checked, query language
Transparent multi-schema support
Object oriented batch operations
Unit testable code
Flexible object relationship inflation
A programming exercise which helps hone your skills through practice.
This one is set up as a series of unit tests which fail.
Your task is to make them pass, using Reladomo –
I hear and I forget.
I see and I remember.
I do and I understand.
- Confucius
New concepts are introduced in the slides.
Coding exercises are at the end of each section.
Work in pairs.
Switch off who is in the driver's seat frequently.
Consider a simple object representing a person:
First thing to do, is create the Reladomo XML that describes this object.
Diagram
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.
Person.xml
Your IDE can help you determine what elements, attributes, and values the Reladomo XML can accept, for example, in IntelliJ:
<ctrl><space> will show you options
<ctrl>Q will show you documentation
Reladomo XML
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
Example Reladomo "class list" XML
<Mithra xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation ="mithraobject.xsd" > <MithraObjectResource name ="Person" /> </Mithra>
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 Generation Diagram
Items above the dashed line are always generated by Reladomo, and are never checked in to VCS (i.e. never committed to CVS or Subversion or Git)
Items below the dashed line are generated only once
You add your business specific code here, and check them in
Ant: clean-compile-all
Maven: clean compile
Example Reladomo Generation Targets
Reladomo needs to know the list of objects you want to use 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>
Reladomo can also generate DDL for you
Call the MithraDbDefinitionGenerator build task
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, name , country, age 0 , "Hiro Tanaka" , "JPN" , 24 1 , "John Smith" , "USA" , 36 2 , "Yusuke Sato" , "JPN" , 11 3 , "Yuki Suzuki" , "JPN" , 100 4 , "Yuri Clark" , "JPN" , 37 5 , "Clark Kent" , "USA" , 38 6 , "Kent Beck" , "USA" , 52 7 , "Bob Martin" , "USA" , 55 8 , "Ada Lovelace" , "UK" , 24
Let's fetch all the rows for Person from the DB, and get them 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?
PersonList people = new PersonList( ??? );
"Finder" is your friend.
Reladomo generated a PersonFinder class for us.
PersonFinder is how we describe the criteria of what we want.
PersonFinder.all() is like a SQL SELECT with no WHERE clause
PersonList people = new PersonList(PersonFinder.all()); // Reladomo executes: SELECT * FROM Person Verify.assertSize(9, people); List<Person>jdkListInterface = people;
Let's try to find a specific row, and fetch just that one row as an object.
How can we specify we want the row with personId = 5 ?
In SQL, this would be:
SELECT * FROM PERSON WHERE PERSON_ID = 5
Person person5 = ??? ;
How can we specify we want the row with personId = 5 ?
"Finder" is your friend.
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.name()
etc.
Person person5 = ??? ; PersonFinder.personId() ???;
How can we specify we want the row with personId = 5 ?
Every Reladomo attribute has methods on it to define common operations:
eq()
greaterThan()
etc.
Person person5 = ??? ; PersonFinder.personId().eq(5);
How can we specify we are expecting one object?
Previously, we fetched a list.
Finder is your friend.
Finder defines the findOne() method.
Person person5 = PersonFinder.findOne(PersonFinder.personId().eq(5)); Verify.assertNotNull(person5); Verify.assertEquals(5, person5.getPersonId()); Verify.assertEquals("Clark Kent", person5.getName()); System.out.println(person5); // Person:name=Clark Kent; country=USA; age=38
We can break down what we wrote into more component parts.
Finders have Attributes representing columns in the DB
If you call an operator method on the Attribute, you get an Operation back
An Operation can be used with both new PersonList(operation) and PersonFinder.findOne(operation)
IntegerAttribute personIdAttribute = PersonFinder.personId(); Operation operation = personIdAttribute.eq(5); Person person5 = PersonFinder.findOne(operation); PersonList people = new PersonList(operation); PersonList people = PersonFinder.findMany(operation); //synonym for above Verify.assertSize(1, people);
Different types of attributes have different features
Numeric attributes have mathematical functions available
IntegerAttribute ageAttribute = PersonFinder.age(); IntegerAttribute ageIn10Years = ageAttribute.plus(10);
Different types of attributes have different features
Numeric attributes have mathematical functions available
StringAttributes have String functions
Person clarkNormalNormal = PersonFinder.findOne( PersonFinder.name().eq("Clark Kent")); Verify.assertEquals("Clark Kent", clarkNormalNormal.getName()); Person clarkNormalLower = PersonFinder.findOne( PersonFinder.name().eq("clark kent")); Verify.assertNull(clarkNormalLower); StringAttribute lowercaseName = PersonFinder.name().toLowerCase(); Person clarkLowerLower = PersonFinder.findOne( lowercaseName.eq("clark kent")); Verify.assertEquals("Clark Kent", clarkLowerLower); Person clarkLowerNormal = PersonFinder.findOne( lowercaseName.eq("Clark Kent")); Verify.assertNull(clarkLowerNormal);
Different types of attributes have different features
Numeric attributes have mathematical functions available
StringAttributes have String functions
Person personContains = PersonFinder.findOne( PersonFinder.name().contains("k Ken")); // SQL: WHERE name LIKE '%k Ken%' Verify.assertEquals("Clark Kent", personContains.getName()); Person personWildcard = PersonFinder.findOne( PersonFinder.name().wildcardEq("*l?r*ent")); // SQL: WHERE name LIKE '%l?r%ent' Verify.assertEquals("Clark Kent", personWildcard.getName());
Different types of attributes have different features
Numeric attributes have mathematical functions available
StringAttributes have String functions
Operations can be combined
Operation thisOperation = PersonFinder.name().eq("Kent Beck"); Operation thatOperation = PersonFinder.country().eq("USA"); Operation thisOrThatOperation = thisOperation.or(thatOperation); Verify.assertSize(4, new PersonList(thisOrThatOperation)); Operation thisAndThatOperation = thisOperation.and(thatOperation); Verify.assertSize(1, new PersonList(thisAndThatOperation));
Different types of attributes have different features
Numeric attributes have mathematical functions available
StringAttributes have String functions
Operations can be combined
Other operations:
filterEq() – two columns of an object are the same value
PersonList parentsSameAge = PersonFinder.findMany( PersonFinder.mothersAge().filterEq(PersonFinder.fathersAge()));
Reladomo uses an efficient type of set to deal with primitives.
GS Collections' Primitive Sets:
Integers: IntSet
Double: DoubleSet
Float: FloatSet
Long: LongSet
Short: ShortSet
Use a regular Set for String, BigDecimal, Date, Timestamp.
IntSet intSet = IntSets.mutable.of(1, 2); DoubleSet doubleSet = DoubleSets.mutable.of(1.0, 2.0); FloatSet floatSet = FloatSets.mutable.of(1.0f, 2.0f); LongSet longSet = LongSets.mutable.of(1L, 2L); ShortSet shortSet = ShortSets.mutable.of((short)1, (short)2); Set<String>stringSet = Sets.mutable.of("1", "2");
Reladomo uses an efficient type of set to deal with primitives.
GS Collections' Primitive Sets:
Integers: IntSet
Double: DoubleSet
Float: FloatSet
Long: LongSet
Short: ShortSet
Use a regular Set for String, BigDecimal, Date, Timestamp.
PersonList people = PersonFinder.findMany( PersonFinder.personId().in(IntHashSet.newSetWith(1, 2, 3))); PersonList people = PersonFinder.findMany( PersonFinder.name().in(Sets.mutable.with("John Doe", "Jane Doe")));
Basic Finder Patterns
Find ExercisesBasicFinder.java; it has assertion failures if you run it as a test.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 15 minutes.
Example:
// Question 1 // Get all people. public PersonList getAllPeople() { Verify.fail("Implement this functionality to make the test pass"); return null; } public void testQ1() { Verify.assertSize(16, this.getAllPeople()); }
Use the Java keyword new
Person snoopy = new Person();
Use the Java keyword new
Call setters to assign values to the object attributes
Person snoopy = new Person(); snoopy.setName("Snoopy"); snoopy.setCountry("USA"); snoopy.setAge(12); Verify.assertEquals("Snoopy", snoopy.getName()); Verify.assertEquals(12, snoopy.getAge());
Use the Java keyword new
Call setters to assign values to the object attributes
Creating a custom constructor may help correctness, and aid refactoring
Person snoopy = new Person("Snoopy", "USA", 12); Verify.assertEquals("Snoopy", snoopy.getName()); Verify.assertEquals(12, snoopy.getAge());
Use the Java keyword new
Call setters to assign values to the object attributes
Creating a custom constructor may help correctness, and aid refactoring
Never change the no-arg constructor – Reladomo needs that!
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. } }
Use the Java keyword new
Call setters to assign values to the object attributes
Creating a custom constructor may help correctness, and aid refactoring
Never change the no-arg constructor – Reladomo needs that!
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 name, String country, int age) { super(); this.setName(name); this.setCountry(country); this.setAge(age); } }
Use the Reladomo provided method insert()
Person snoopy = new Person("Snoopy", "USA", 12); snoopy.insert();
Use the Reladomo provided method insert()
int peopleBeforeInsert = new PersonList(PersonFinder.all()).count(); Person snoopy = new Person("Snoopy", "USA", 12); snoopy.insert(); int peopleAfterInsert = new PersonList(PersonFinder.all()).count(); Verify.assertEquals(peopleAfterInsert, peopleBeforeInsert + 1);
Use the Reladomo provided method insert()
// Verify he's not there to start with: Verify.assertNull(PersonFinder.findOne(PersonFinder.name().eq("Snoopy"))); new Person("Snoopy", "USA", 12).insert(); // Now see if he's in the DB: Person snoopy = PersonFinder.findOne(PersonFinder.name().eq("Snoopy")); Verify.assertEquals("Snoopy", snoopy.getName()); Verify.assertEquals(12, snoopy.getAge());
Use the Reladomo provided method insert()
Did Reladomo inflate a new copy of Snoopy from the DB?
Person originalSnoopy = new Person("Snoopy", "USA", 12); originalSnoopy.insert(); Person snoopyFromDb = PersonFinder.findOne( PersonFinder.name().eq("Snoopy")); Assert.assertTrue(originalSnoopy.equals(snoopyFromDb)); // ??? Assert.assertTrue(originalSnoopy == snoopyFromDb); // ???
Use the Reladomo provided method insert()
Reladomo guarantees there is only one copy of a given object in memory a any one given time
Person originalSnoopy = new Person("Snoopy", "USA", 12); originalSnoopy.insert(); Person snoopyFromDb = PersonFinder.findOne( PersonFinder.name().eq("Snoopy")); Assert.assertTrue(originalSnoopy.equals(snoopyFromDb)); // true Assert.assertTrue(originalSnoopy == snoopyFromDb); // true
Use the Reladomo provided method insert()
Reladomo guarantees there is only one copy of a given object in memory a any one given time
Person snoopy = new Person("Snoopy", "USA", 12); snoopy.insert(); /* * Generated SQL: * insert into PERSON(PERSON_ID,NAME,COUNTRY,AGE) * values (9,'Snoopy','USA',12) **/
Use the Reladomo provided method insert()
Reladomo guarantees there is only one copy of a given object in memory a any one given time
Reladomo sets the sequence ID for the new Person we created
Person snoopy = new Person("Snoopy", "USA", 12); snoopy.insert(); /* * Generated SQL: * insert into PERSON(PERSON_ID,NAME,COUNTRY,AGE) * values (9,'Snoopy','USA',12) **/ System.out.println(snoopy.getPersonId()); // Prints 9
Use the Reladomo provided method insert()
Reladomo guarantees there is only one copy of a given object in memory a any one given time
Reladomo sets the sequence ID for the new Person we created
Sequence IDs are declared in the Reladomo XML Object definition
In this case, it's in the Person.xml file
Reladomo Object XML
<MithraObject objectType ="transactional" ... <Attribute name ="personId" javaType ="int" columnName ="PERSON_ID" primaryKey ="true" primaryKeyGeneratorStrategy ="SimulatedSequence" > <SimulatedSequence sequenceName ="Person" sequenceObjectFactoryName ="kata.util.ObjectSequenceObjectFactory" hasSourceAttribute ="false" batchSize ="1" initialValue ="1" incrementSize ="1" /> </Attribute>
Use the MithraList provided method insertAll()
PersonList spiceGirls = new PersonList(); spiceGirls.add(new Person("Emma Bunton", "UK", 20)); spiceGirls.add(new Person("Geri Halliwell", "UK", 21)); spiceGirls.add(new Person("Melanie Brown", "UK", 22)); spiceGirls.add(new Person("Melanie Crisholm", "UK", 21)); spiceGirls.add(new Person("Victoria Beckham", "UK", 20)); spiceGirls.insertAll();
Just make the change!
Person superman = PersonFinder.findOne( PersonFinder.name().eq("Clark Kent")); Verify.assertEquals("Clark Kent", superman.getName()); superman.setName("Christopher Reeve"); superman.setAge(49);
Just make the change!
Reladomo identifies the correct row to change in the DB via the primary key you declared in the Reladomo Object XML
Person superman = PersonFinder.findOne( PersonFinder.name().eq("Clark Kent")); Verify.assertEquals("Clark Kent", superman.getName()); superman.setName("Christopher Reeve"); // SQL:update PERSON set NAME = 'Christopher Reeve' where PERSON_ID=5 superman.setAge(49); // SQL: update PERSON set AGE = 49 where PERSON_ID = 5
What if we want to update a whole bunch of objects, setting the same attribute value to all of them?
PersonList people = new PersonList(PersonFinder.all()); for (Person person : people) { person.setCountry("USA"); }
What if we want to update a whole bunch of objects, setting the same attribute value to all of them?
Reladomo provides set___() methods on the generated list implementations
PersonList people = new PersonList(PersonFinder.all()); people.setCountry("USA");
What if we want to update a whole bunch of objects, setting the same attribute value to all of them?
Reladomo provides set___() methods on the generated list implementations
PersonList people = new PersonList(PersonFinder.all()); people.setCountry("USA"); // SQL: // update with: update PERSON set COUNTRY = 'USA' where PERSON_ID = 0 // update with: update PERSON set COUNTRY = 'USA' where PERSON_ID = 2 // update with: update PERSON set COUNTRY = 'USA' where PERSON_ID = 3 // update with: update PERSON set COUNTRY = 'USA' where PERSON_ID = 4 // update with: update PERSON set COUNTRY = 'USA' where PERSON_ID = 8
Use the Reladomo method delete()
Person superman = PersonFinder.findOne( PersonFinder.name().eq("Clark Kent")); superman.delete();
Use the MithraList method deleteAll()
new PersonList(PersonFinder.country().eq("UK")).deleteAll();
The end of a Reladomo Object XML description can include indexes
Can be unique, or not; Can be single column, or many columns
For a unique index Reladomo will add a convenience method to the Finder for you
Warning: You still need to remember to add the index to your DB table to get the performance benefit
Reladomo will generate the template DDL for the index
And correctness, if it is a "unique" constraint
XML and Java
<Index name ="socSecNumIndex" unique ="true" > ssn</Index> <Index name ="productIndex" unique ="false" > productGroup, productName</Index>
Person fred = PersonFinder.getBySocSecNumIndex("123-45-6789"); ProductList products = PersonFinder.getByProductIndex( "Ford", "Edsel");
A "transaction" is used to cause a set of operations to happen together – they either all happen, or none of them happen.
In SQL terms:
You would BEGIN TRANSACTION, and then perform queries and updates as necessary
If all your changes were successful you can COMMIT TRANSACTION, which makes your changes in the DB
If something went wrong, you could ROLLBACK TRANSACTION
SQL
BEGIN TRANSACTION -- Do some stuff -- If successful COMMIT TRANSACTION -- Else, don't change anything ROLLBACK TRANSACTION
A Reladomo transaction is defined by an implementation of the TransactionalCommand interface
new TransactionalCommand() { public Object executeTransaction(MithraTransaction tx) throws Throwable { new Person("Tweedle Dee", "UK", 10).insert(); new Person("Tweedle Dum", "UK", 10).insert(); return null; } }
A Reladomo transaction is defined by an implementation of the TransactionalCommand interface
A TransactionalCommand is executed using the MithraManager
MithraManagerProvider.getMithraManager().executeTransactionalCommand( new TransactionalCommand() { public Object executeTransaction(MithraTransaction tx) throws Throwable { new Person("Tweedle Dee", "UK", 10).insert(); new Person("Tweedle Dum", "UK", 10).insert(); return null; } });
Reladomo will batch up changes where possible within transactions to make updates more efficient
Person superman = PersonFinder.findOne( PersonFinder.name().eq("Clark Kent")); Verify.assertEquals("Clark Kent", superman.getName()); superman.setName("Christopher Reeve"); // SQL:update PERSON set NAME = 'Christopher Reeve' where PERSON_ID=5 superman.setAge(49); // SQL: update PERSON set AGE = 49 where PERSON_ID = 5
Reladomo will batch up changes where possible within transactions to make updates more efficient
MithraManagerProvider.getMithraManager().executeTransactionalCommand( new TransactionalCommand() { public Object executeTransaction(MithraTransaction tx) throws Throwable { Person superman = PersonFinder.findOne( PersonFinder.name().eq("Clark Kent")); superman.setName("Christopher Reeve"); superman.setAge(49); return null; } });
/* * DEBUG com.gs.fw.common.mithra.transaction.MithraRootTransaction – * Starting transaction * select t0.PERSON_ID,t0.NAME,t0.COUNTRY,t0.AGE from PERSON t0 * where t0.NAME = 'Clark Kent' * update PERSON set NAME = 'Christopher Reeve' , AGE = 49 * where PERSON_ID = 5 * DEBUG com.gs.fw.common.mithra.transaction.MithraRootTransaction - * Committing transaction **/ MithraManagerProvider.getMithraManager().executeTransactionalCommand( new TransactionalCommand() { public Object executeTransaction(MithraTransaction tx) throws Throwable { Person superman = PersonFinder.findOne( PersonFinder.name().eq("Clark Kent")); superman.setName("Christopher Reeve"); superman.setAge(49); return null; } });
Reladomo will batch up changes where possible within transactions to make updates more efficient
PersonList people = new PersonList(PersonFinder.all()); people.setCountry("USA"); // SQL: // update with: update PERSON set COUNTRY = 'USA' where PERSON_ID = 0 // update with: update PERSON set COUNTRY = 'USA' where PERSON_ID = 2 // update with: update PERSON set COUNTRY = 'USA' where PERSON_ID = 3 // update with: update PERSON set COUNTRY = 'USA' where PERSON_ID = 4 // update with: update PERSON set COUNTRY = 'USA' where PERSON_ID = 8
Reladomo will batch up changes where possible within transactions to make updates more efficient
MithraManagerProvider.getMithraManager().executeTransactionalCommand( new TransactionalCommand() { public Object executeTransaction(MithraTransaction tx) throws Throwable { new PersonList(PersonFinder.all()).setCountry("USA")); return null; } });
/* *DEBUG com.gs.fw.common.mithra.transaction.MithraRootTransaction - *Starting transaction *find with: *select PERSON_ID,NAME,COUNTRY,AGE from PERSON retrieved 9 objects *multi updating with: *update PERSON set COUNTRY = 'USA' where PERSON_ID in (0,2,3,4,8) *DEBUG com.gs.fw.common.mithra.transaction.MithraRootTransaction - *Committing transaction **/ MithraManagerProvider.getMithraManager().executeTransactionalCommand( new TransactionalCommand() { public Object executeTransaction(MithraTransaction tx) throws Throwable { new PersonList(PersonFinder.all()).setCountry("USA")); return null; } });
Create, Update, Delete, Transactions
Find ExercisesCrud.java; it has assertion failures if you run it as a test.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 25 minutes.
Consider two objects
Consider two objects
For a given Customer, we could write the query explicitly ourselves
Not very manageable
Customer smith = ...; CustomerAccountList smithAccounts = new CustomerAccountList( CustomerAccountFinder.customerId().eq(smith.getCustomerId()));
Relationships can be expressed in Reladomo's object XML
<MithraObject objectType ="transactional" > <ClassName> Customer</ClassName> ...<Relationship name ="accounts" relatedObject ="CustomerAccount" cardinality ="one-to-many" > this.customerId = CustomerAccount.customerId</Relationship>
Reladomo generates your relationship description into methods on the business objects
Customer smith = ...; // Before CustomerAccountList smithAccounts = new CustomerAccountList( CustomerAccountFinder.customerId().eq(smith.getCustomerId())); // After CustomerAccountList smithAccounts = smith.getAccounts();
Reladomo generates your relationship description into methods on the business objects
How can we find Customers based on some related object?
CustomerList customers = ??? // fetch customers with accounts called "My Account"
Reladomo generates your relationship description into methods on the business objects
How can we find Customers based on some related object?
Finder is your friend!
Relationships are navigable via the Finder
CustomerList customers = new CustomerList( CustomerFinder.accounts().accountName().eq("My Account"));
Reladomo generates your relationship description into methods on the business objects
How can we find Customers based on some related object?
Finder is your friend!
Relationships are navigable via the Finder
CustomerList customers = new CustomerList( CustomerFinder.accounts().accountName().eq("My Account")); /* SQL: * select t0.CUSTOMER_I,t0.NAME_C,t0.COUNTRY_I * from CUSTOMER t0 inner join * (select t1.CUSTOMER_I c0 from CUSTOMER_ACCOUNT t1 * where t1.ACCOUNT_NAME_C = 'My Account' * group by t1.CUSTOMER_I) * as d1 on t0.CUSTOMER_I = d1.c0 **/
Relationships can be expressed in Reladomo's object XML
Dependent relationships can be marked as such
Dependent objects can only exist while the parent exists
If a parent is inserted, so are all it's children
If the parent is removed, then the dependent objects are also removed
<MithraObject objectType ="transactional" > <ClassName> Customer</ClassName> ...<Relationship name ="accounts" relatedObject ="CustomerAccount" relatedIsDependent ="true" cardinality ="one-to-many" > this.customerId = CustomerAccount.customerId</Relationship>
Relationships can be expressed in Reladomo's object XML
Dependent relationships can be marked as such
Dependent objects can only exist while the parent exists
If the parent is removed, then the dependent objects are also removed
You can also specify a reverse relationship name, if appropriate
Reladomo can figure out the reverse join for itself
<MithraObject objectType ="transactional" > <ClassName> Customer</ClassName> ...<Relationship name ="accounts" relatedObject ="CustomerAccount" relatedIsDependent ="true" cardinality ="one-to-many" reverseRelationshipName ="customer" > this.customerId = CustomerAccount.customerId</Relationship>
Relationships can be expressed in Reladomo's object XML
Dependent relationships can be marked as such
Dependent objects can only exist while the parent exists
If the parent is removed, then the dependent objects are also removed
You can also specify a reverse relationship name, if appropriate
Reladomo can figure out the reverse join for itself
Customer customer = new Customer(...); CustomerAccount account = new CustomerAccount(); account.setAccountName("Vacation Account"); account.setAccountType("Savings Account"); account.setCustomer(customer); customer.cascadeInsert();
Relationships can be expressed in Reladomo's object XML
Dependent relationships can be marked as such
Dependent objects can only exist while the parent exists
If the parent is removed, then the dependent objects are also removed
You can also specify a reverse relationship name, if appropriate
Reladomo can figure out the reverse join for itself
CustomerAccount account = ... Customer customer = account.getCustomer();
Relationships can be expressed in Reladomo's object XML
Dependent relationships can be marked as such
You can also specify a reverse relationship name, if appropriate
Relationships can be parameterized
A parameterized relationship has one or more "constraints"
<MithraObject objectType ="transactional" > <ClassName> Customer</ClassName> ...<Relationship name ="accountsOfType" relatedObject ="CustomerAccount" cardinality ="one-to-many" parameters ="String accountType" > this.customerId = CustomerAccount.customerId and CustomerAccount.accountType = { accountType }</Relationship>
Relationship existence:
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()
CustomerList customersWithAtLeastOneAccount = new CustomerList(CustomerFinder.accounts().exists()); CustomerList customersWithNoAccounts = new CustomerList(CustomerFinder.accounts().notExists()); /* * select t0.CUSTOMER_I,t0.NAME_C,t0.COUNTRY_I from CUSTOMER t0 * left join (select t1.CUSTOMER_I c0 from CUSTOMER_ACCOUNT t1) as d1 * on t0.CUSTOMER_I = d1.c0 where d1.c0 is null **/ CustomerList customersWithNoSavingAccounts = new CustomerList( CustomerFinder.accountsOfType("Saving").notExists());
Reladomo can also generate DDL for you
Call the MithraDbDefinitionGenerator build task
Note: Reladomo generated the foreign-key relationship for you
Because of the relationship from Customer to CustomerAccount which is parameterized by accountType, Reladomo can infer that you need an index to make the query efficient
How can we get objects in a certain order?
PersonList peopleByName = ??? ; PersonList peopleByAge = ??? ;
How can we get objects in a certain order?
Use MithraList's addOrderBy() method
Finder is your friend
PersonList peopleByName = new PersonList(PersonFinder.all()); peopleByName.addOrderBy(PersonFinder.name().ascendingOrderBy()); PersonList peopleByAgeDescending = new PersonList(PersonFinder.all()); peopleByAgeDescending.addOrderBy( PersonFinder.age().descendingOrderBy());
How can we get objects in a certain order?
Use MithraList'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
PersonList peopleByAgeThenName = new PersonList(PersonFinder.all()); peopleByName.addOrderBy(PersonFinder.age().descendingOrderBy()); peopleByName.addOrderBy(PersonFinder.name().ascendingOrderBy()); // _or_ peopleByAgeThenName.setOrderBy( PersonFinder.age().descendingOrderBy() .and(PersonFinder.name().ascendingOrderBy()));
Deep fetching means retrieving not just immediate target objects, but also some or all of their related objects
PersonList people= new PersonList(PersonFinder.age().greaterThan(18)); // One hit to DB for (Person person : people) { AddressList addresses= person.getAddresses(); // Another hit to the DB for each person's set of addresses System.out.println(person.getName() + " :: " + addresses.size()) }
Deep fetching means retrieving not just immediate target objects, bu 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
PersonList people= new PersonList(PersonFinder.age().greaterThan(18)); people.deepFetch(PersonFinder.addresses()); // Two hits to the DB, one for People, one for the related Addresses for (Person person : people) { AddressList addresses= person.getAddresses(); // No DB hit here, the accounts are all in cache already System.out.println(person.getName() + " :: " + addresses.size()) }
Relationships
Find ExercisesRelationships.java; it has assertion failures if you run it as a test.
Don't forget that in questions 3 and 4, you will be changing XML, so you will have to generate the Reladomo objects again (ant: clean-compile-all).
Figure out how to get the tests to pass using what you have seen so far.
Should take about 20 minutes.
So far we have specified attributes to match and uniquely identify a given object
Let's pretend a RegistrationRenewal has a relationship back to a VehicleRegistration
Customer customer = ...; CustomerAccountList accounts = new CustomerAccountList( CustomerAccountFinder.customerId().eq(customer.getCustomerId()); VehicleRegistration registration = ...; // PK is on licencePlate *and* stateOfIssue RegistrationRenewalList records = new RegistrationRenewalList( RegistrationRenewalFinder.licencePlate().eq( registration.getLicencePlate() .and(RegistrationRenewalFinder.stateOfIssue() .eq(registration.getStateOfIssue()));
So far we have specified attributes to match and uniquely identify a given object
Reladomo can infer the equality attributes of an objects primary key for us
Customer customer = ...; CustomerAccountList accounts = new CustomerAccountList( CustomerAccountFinder.customer().eq(customer); VehicleRegistration registration = ...; // PK is on licencePlate *and* stateOfIssue RegistrationRenewalList records = new RegistrationRenewalList( RegistrationRenewalFinder.registration().eq(registration));
A Tuple is an ordered list of elements
// tupleType { month, year } // tupleData {"May", 2009 } // tupleData {"August", 2011 }
A Tuple is an ordered list of elements
A Tuple's type (TupleAttribute) can be specified using Reladomo
The Finder is your friend
// tupleType { month, year } TupleAttribute calendarMonthYear = CalendarEventFinder.month().tupleWith(CalendarEventFinder.year()); // tupleData {"May", 2009 } // tupleData {"August", 2011 }
A Tuple is an ordered list of elements
A Tuple's type (TupleAttribute) can be specified using Reladomo
The Finder is your friend
You create tuple data set by adding elements to a MithraArrayTupleTupleSet
// tupleType { month, year } TupleAttribute calendarMonthYear = CalendarEventFinder.month().tupleWith(CalendarEventFinder.year()); MithraArrayTupleTupleSet requestedMonthYears = new MithraArrayTupleTupleSet(); // tupleData {"May", 2009 } requestedMonthYears.add("May", 2009); // tupleData {"August", 2011 } requestedMonthYears.add("August", 2011);
A Tuple is an ordered list of elements
A Tuple's type (TupleAttribute) can be specified using Reladomo
The Finder is your friend
You create tuple data set by adding elements to a MithraArrayTupleTupleSet
You can combine the tuple type with the tuple data set using an in() query
// tupleType { month, year } TupleAttribute calendarMonthYear = CalendarEventFinder.month().tupleWith(CalendarEventFinder.year()); MithraArrayTupleTupleSet requestedMonthYears = new MithraArrayTupleTupleSet(); // tupleData {"May", 2009 } requestedMonthYears.add("May", 2009); // tupleData {"August", 2011 } requestedMonthYears.add("August", 2011); CalendarEventList events = new CalendarEventList(calendarMonthYear.in(requestedMonthYears));
A Tuple is an ordered list of elements
A Tuple's type (TupleAttribute) can be specified using Reladomo
The Finder is your friend
You create tuple data set by adding elements to a MithraArrayTupleTupleSet
You can combine the tuple type with the tuple data set using an in() query
Operation date1 = CalendarEventFinder.month().eq("May") .and(CalendarEventFinder.year().eq(2009)); Operation date2 = CalendarEventFinder.month().eq("August") .and(CalendarEventFinder.year().eq(2011) ); Operation operation = date1.or(date2); CalendarEventList events = new CalendarEventList(operation);
List.size() will cause the list to be populated with data
int customerCount = new CustomerList(CustomerFinder.all()).size();
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
int customerCount = new CustomerList(CustomerFinder.all()).size(); int customerCount = new CustomerList(CustomerFinder.all()).count();
Gives you access to SQL-like features for sum(), average(), min(), max(), etc.
Finder is your friend
AggregateList aggregateList = new AggregateList(StockFinder.all()); aggregateList.addAggregateAttribute( "sumQuantity", StockFinder.quantity().sum());
Gives you access to SQL-like features for sum(), average(), min(), max(), etc.
AggregateList aggregateList = new AggregateList(StockFinder.all()); aggregateList .addAggregateAttribute( "sumQuantity", StockFinder.quantity().sum()); Verify.assertSize(1, aggregateList); int sumQuantity = aggregateList.get(0) .getAttributeAsInt("sumQuantity");
Gives you access to SQL-like features for sum(), average(), min(), max(), etc.
Different data types are also supported
AggregateList aggregateList = new AggregateList(StockFinder.all()); aggregateList.addAggregateAttribute( "sumQuantity", StockFinder.quantity().sum()); aggregateList.addAggregateAttribute( "averageQuantity", StockFinder.quantity().avg()); int sumQuantity = aggregateList.get(0) .getAttributeAsInt("sumQuantity"); double averageQuantity = aggregateList.get(0)
Gives you access to SQL-like features for sum(), average(), min(), max(), etc.
Different data types are supported also
You can also group by attributes, similar to SQL's: GROUP BY clause
AggregateList aggregateList = new AggregateList(StockFinder.all()); aggregateList.addAggregateAttribute( "value", StockFinder.tradeDateValue().sum()); aggregateList.addGroupBy("code", StockFinder.classificationCode()); for (AggregateData row : aggregateList) { String code = row.getAttributeAsString("code"); double value = row.getAttributeAsDouble("value"); System.out.println(code + '\t' + value); } /* * Prints: * EQTY 200.0 * BOND 1600.0 * CCY 13425.62 **/
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
public class DogList extends DogListAbstract { ... public void walkAll() { for (Dog dog : this) { System.out.println(dog.getName() + " walked around the park"); } } }
Cursor is a concept where data is only fetched a row at a time as needed
Most use-cases don't need this – verify with a senior TA before using in a real project
Applies when:
You may only need the first part of a List
The List may be very large (and risk filling your heap-space)
You need to reduce peak memory usage
CustomerList allCustomers = new CustomerList(CustomerFinder.all()); final Counter counter = new Counter(); customer.forEachWithCursor(new DoWhileBlock<Customer>() { public boolean value(Customer customer) { doSomethingWith(customer); counter.increment(); return isNextCustomerNeeded(customer); } });
Reladomo Finders
Reladomo Finders and Attributes are a rich source of MetaData
Hold on to Finder references using AbstractRelatedFinder
From a Finder, you can:
getPersistentAttributes(): Attribute[]
AbstractRelatedFinder finder = PersonFinder.getFinderInstance(); for (Attribute attribute : finder.getPersistentAttributes()) { System.out.println(attribute.getAttributeName()); }
Reladomo Attributes
Reladomo Finders and Attributes are a rich source of MetaData
From an Attribute, you can:
getAttributeName(): String
valueType(): Class
getMaxLength(): integer
isNullable(): boolean
getMetaData(): AttributeMetaData
System.out.println(PersonFinder.personId().getAttributeName()); // personId StringAttribute nameAttribute = CustomerFinder.name(); System.out.println(nameAttribute.getMaxLength() + '\t' + nameAttribute.isNullable() + '\t' + nameAttribute.valueType()); // Prints: 64 false String
Reladomo Attributes
Reladomo Finders and Attributes are a rich source of MetaData
From an attribute, you can also:
valueOf(<MT>): <AVT>
Gets the attribute value associated with an object
setValue(<MT>, <AVT>): void
Sets the attribute value for a given object
Dog fido = ...; System.out.println(DogFinder.name().valueOf(fido)); // The Grand Marquis Fido Of Paris DogFinder.name().setValue(fido, "Supreme Champion Fido of Crufts"); System.out.println(DogFinder.name().valueOf(fido)); // Supreme Champion Fido of Crufts
You can define custom meta-data for your attributes
<Property>element in the Reladomo Object XML
Dog.xml
...<Attribute name ="name" javaType ="String" nullable ="false" maxLength ="50" columnName ="NAME" > <Property key =""integerProperty"" value ="256" /> <Property key =""stringProperty"" value =""Froo-froo ref"" /> </Attribute> ...
You can define custom meta-data for your attributes
<Property> element in the Reladomo Object XML
Access properties using: attribute.getProperty(String key): Object
for (Attribute attribute : DogFinder.getFinderInstance().getPersistentAttributes()) { Object intProperty = attribute.getProperty("integerProperty"); if (intProperty instanceof Integer) { System.out.println(intProperty); } } // Prints: 256
Advanced Finder
Find ExercisesAdvancedFinder.java; it has assertion failures if you run it as a test.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 20 minutes.
Chaining:
Umbrella term to describe a way of storing time series data, audit data, or both, in a relational database
Referred by different terminologies for same or similar functionality.
Most common is milestoning.
Milestoning gives the wrong impression that the data can be snapshotted at various milestones.
Chaining, or Bi-temporal data, is a super set (and in many instances a more efficient implementation) of milestoning.
Audit Only:
All edits to an object must be tracked
Although this audit trail is important, it is rarely, if ever queried
An audit trail is immutable. It's akin 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 Account object
First we insert the new object, say on 1-Jan-2005 10:06 am
We can read this as:
The row came IN existence at 1-Jan-2005 10:06 am, and is valid through "infinity"
Databases can't store "infinity", so we typically choose an arbitrary date-time, such as 1-Dec-9999 11:59:00 pm
Consider an Account object
First we insert the new object, say on 1-Jan-2005 10:06 am
We can read this as:
First we insert the new object, say on 1-Jan-2005 10:06 am
The Trader changes on 4-Feb-2005 3:00 pm
The existing row stops being valid, so we update it's OUT value with "now"
We insert a new row, valid from "now" through "infinity"
Reladomo Object XML defines an AsOfAttribute element
Define one of these for your processingDate below the DefaultTable element, and before the regular Attribute elements
<AsOfAttribute name ="processingDate" fromColumnName ="IN_Z" toColumnName ="OUT_Z" toIsInclusive ="false" isProcessingDate ="true" infinityDate ="[kata.util.TimestampProvider.getInfinityDate()]" defaultIfNotSpecified ="[kata.util.TimestampProvider.getInfinityDate()]" timezoneConversion ="convert-to-utc" />
Chaining: Audit Only: Queries
Query for "current" data
Query just as you would any other data
Account account = AccountFinder.findOne( AccountFinder.accountId().eq(1234)); /* * SQL * SELECT * FROM ACCOUNT t0 * WHERE t0.ACCOUNT_ID = 1234 * AND t0.OUT_Z = '9999-12-01 23:59:00.000' -- Reladomo adds this for you! * */ System.out.println(account); // Prints: 1234 Helen Brown 4-Feb-2005 3:00pm 1-Dec-9999 11:59pm
Chaining: Audit Only: Queries
Query for an as-of processingDate
Query just as you would any other data
Additionally add the processing date you are interested in
Account account = AccountFinder.findOne( AccountFinder.accountId().eq(1234) .and(AccountFinder.processingDate().eq(timestamp4Feb2005))); /* * SQL: * SELECT * FROM ACCOUNT t0 WHERE t0.ACCOUNT_ID = 1234 * AND t0.IN_Z <= '2005-02-04 13:00:00.000' * AND t0.OUT_Z > '2005-02-04 13:00:00.000' * */ System.out.println(account); // Prints: 1234 Joe Smith 1-Jan-2005 10:06am 4-Feb-2005 3:00pm
Chaining: Audit Only: Queries
Query for all processingDate history
Query just as you would any other data
Additionally add the processingDate.equalsEdgePoint()
AccountList accounts = new AccountList( AccountFinder.accountId().eq(1234) .and(AccountFinder.processingDate().equalsEdgePoint())); /* * SQL: * SELECT * FROM ACCOUNT t0 WHERE t0.ACCOUNT_ID = 1234 * * Returns * 1234 Joe Smith 1-Jan-2005 10:06am 4-Feb-2005 3:00pm * 1234 Helen Brown 4-Feb-2005 5:00pm 1-Dec-9999 11:59pm **/
Chaining: Audit Only: Creating
Construct with the processingDate of "infinity"
Set data and insert as normal
Account account = new Account(TimestampProvider.getInfinityDate()); account.setTrader("Joe Smith"); account.insert();
Chaining: Audit Only: Updating
Update as normal
However, perform inside a transaction
MithraManagerProvider.getMithraManager().executeTransactionalCommand( new TransactionalCommand() { public Object executeTransaction(MithraTransaction tx) throws Throwable { Account account = AccountFinder.findOne( AccountFinder.accountId().eq(1234)); account.setTrader("Helen Brown"); return null; } });
Chaining: Audit Only: Terminating
Rows cannot be "deleted" as they are required for audit purposes
However, we can "terminate" and object instead
MithraManagerProvider.getMithraManager().executeTransactionalCommand( new TransactionalCommand() { public Object executeTransaction(MithraTransaction tx) throws Throwable { Account account = AccountFinder.findOne( AccountFinder.accountId().eq(1234)); account.terminate(); return null; } });
Chaining: Audit Only
Find ExercisesAuditOnly.java; it has assertion failures if you run it as a test.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 25 minutes.
Bi-Temporal:
Time Series as well as Audit
The two time axes are identified as "valid time" and "transaction time"
We call these business date and processing date respectively
Business dates usually have a fixed "time" portion For Example: 6:30 PM
A row in the database represents a rectangle in the two dimensional phase space
No two rectangles (for the same data) may overlap
The accepted way to implement this scenario is via four date columns (valid_from, valid_to, transaction_from, transaction_to)
For Example: these columns are (FROM_Z, THRU_Z, IN_Z, OUT_Z)
Terminology:
Academic literature refers to this scenario as "bi-temporal data"
Consider a simple Balance example
On 1-Jan-2005, we insert a new row valid from 1-Jan-2005, for 100
Diagram for balanceId = 1234
Consider a simple Balance example
On 1-Jan-2005, we insert a new row valid from 1-Jan-2005, for 100
On 5-Feb-2005, we change the amount to 250, valid from 5-Feb-2005
The original row gets chained out
A new row shows the finite valid time that the object was 100
A new row shows the valid time that the object is 250
Diagram for balanceId = 1234
On 16-Feb-2005, we find a trade for 40 shares that hadn't been input when it was executed on 12-Jan-2005
Any rows valid on 12-Jan will need to be adjusted!
The two valid existing rows will be chained out
A new row is added up to 12-Jan indicating the original amoun
A new row is added 12-Jan -> 5-Feb indicating amount 140
A new row is added 5-Feb onwards indicating amount 290
Diagram for balanceId = 1234
Currently, what is the latest balance for 1234?
businessDate = "now"
processingDate = "now"
Balance balance = BalanceFinder.findOne(BalanceFinder.balanceId().eq(1234) .and(BalanceFinder.businessDate().eq(timestamp16March2011))); /* * SQL: * SELECT * FROM BALANCE t0 * WHERE t0.ACCOUNT_I = 100 * AND t0.FROM_Z < '2011-03-15 18:30:00.000' * AND t0.THRU_Z >= '2011-03-15 18:30:00.000' * AND t0.OUT_Z = '9999-12-01 23:59:00.000' **/
Diagram for balanceId = 1234
Balance balance = BalanceFinder.findOne(BalanceFinder.balanceId().eq(1234)); /* * SQL: * SELECT * FROM BALANCE t0 * WHERE t0.ACCOUNT_I = 100 * AND t0.FROM_Z < '2011-03-15 18:30:00.000' * AND t0.THRU_Z >= '2011-03-15 18:30:00.000' * AND t0.OUT_Z = '9999-12-01 23:59:00.000' **/ System.out.println(balance.getAmount()); // Prints: 290.0
Querying Bi-Temporal data
Taking revisions into account, what was the balance on 30-Jan-2005?
businessDate = 30-Jan-2005
processingDate = "now"
Balance balance = BalanceFinder.findOne(BalanceFinder.balanceId().eq(1234) .and(BalanceFinder.businessDate().eq(timestamp30Jan2005))); /* * SQL: * SELECT * FROM BALANCE t0 * WHERE t0.ACCOUNT_I = 100 * AND t0.FROM_Z < '2005-01-30 18:30:00.000' * AND t0.THRU_Z >= '2005-01-30 18:30:00.000' * AND t0.OUT_Z = '9999-12-01 23:59:00.000' **/
Diagram for balanceId = 1234
Balance balance = BalanceFinder.findOne(BalanceFinder.balanceId().eq(1234) .and(BalanceFinder.businessDate().eq(timestamp30Jan2005))); /* * SQL: * SELECT * FROM BALANCE t0 * WHERE t0.ACCOUNT_I = 100 * AND t0.FROM_Z < '2005-01-30 18:30:00.000' * AND t0.THRU_Z >= '2005-01-30 18:30:00.000' * AND t0.OUT_Z = '9999-12-01 23:59:00.000' **/ System.out.println(balance.getAmount()); // Prints: 140.0
Querying Bi-Temporal data
What was the balance when we looked at the system on 28-Jan-2005?
businessDate = 28-Jan-2005
processingDate = 28-Jan-2005
Balance balance = BalanceFinder.findOne(BalanceFinder.balanceId().eq(1234) .and(BalanceFinder.businessDate().eq(timestamp28Jan2005)) .and(BalanceFinder.processingDate().eq(timestamp28Jan2005))); /* * SQL: * SELECT * FROM BALANCE t0 * WHERE t0.ACCOUNT_I = 100 * AND t0.FROM_Z < '2005-01-28 18:30:00.000' * AND t0.THRU_Z >= '2005-01-28 18:30:00.000' * AND t0.IN_Z < '2005-01-28 18:30:00.000' * AND t0.OUT_Z >= '2005-01-28 18:30:00.000' **/
Diagram for balanceId = 1234
Balance balance = BalanceFinder.findOne(BalanceFinder.balanceId().eq(1234) .and(BalanceFinder.businessDate().eq(timestamp28Jan2005)) .and(BalanceFinder.processingDate().eq(timestamp28Jan2005))); /* * SQL: * SELECT * FROM BALANCE t0 WHERE t0.ACCOUNT_I = 100 * AND t0.FROM_Z < '2005-01-28 18:30:00.000' * AND t0.THRU_Z >= '2005-01-28 18:30:00.000' * AND t0.IN_Z < '2005-01-28 18:30:00.000' * AND t0.OUT_Z >= '2005-01-28 18:30:00.000' **/ System.out.println(balance.getAmount()); //Prints: 100
Querying Bi-Temporal data
Fetch all the history for a balance
Finder.businessDate().equalsEdgePoint() is your special friend
BalanceList balances = BalanceFinder.findMany(BalanceFinder.balanceId().eq(1234) .and(BalanceFinder.businessDate().equalsEdgePoint())); /* * SQL: * SELECT * FROM BALANCE t0 WHERE t0.ACCOUNT_I = 100 * AND t0.OUT_Z = '9999-12-01 23:59:00.000' **/
Diagram for balanceId = 1234
BalanceList balances = BalanceFinder.findMany(BalanceFinder.balanceId().eq(1234) .and(BalanceFinder.businessDate().equalsEdgePoint())); /* * SQL: * SELECT * FROM BALANCE t0 WHERE t0.ACCOUNT_I = 100 * AND t0.OUT_Z = '9999-12-01 23:59:00.000' **/ System.out.println(balances); /* * Prints: * 1234 100.0 1-Jan-2005 12-Jan-2005 * 1234 140.0 12-Jan-2005 5-Feb-2005 * 1234 290.0 5-Feb-2005 1-Dec-9999 **/
Chaining: Bi-Temporal: Other Methods
Reladomo offers special methods on dated objects:
insertUntil(Timestamp)
insertWithIncrement(), insertWithIncrementUntil(Timestamp)
increment___(T), increment___Until(T, Timestamp)
set___Until(T, Timestamp)
insertForRecovery(), purge(): beyond scope of this session
Chaining: Bi-Temporal: Special Considerations
Bi-Temporal operations require transactions.
A single update or even insert could cause multiple rows to change / be inserted. The operations should happen atomically.
So, operations must occur in a transaction.
Relationships between tables are typically done via a foreign key.
Chaining as described here for a single table, makes it impossible to have enforced foreign keys between tables. The virtual relationship between chained tables is therefore qualitatively different than non-chained tables.
It is important to realize that chained operation on a particular table must not be propagated to its dependent tables.
Chaining: Bi-Temporal
Find ExercisesBiTemporal.java; it has assertion failures if you run it as a test.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 25 minutes.
Create Reladomo Object XML file.
Example: MyObject.xml
Set PackageName, ClassName, and DefaultTableName
Add any AsOfAttributes as necessary.
Example: 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.
Example: MithraTestAppClassList.xml
Add entry to the Reladomo Runtime Configuration.
Example: 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.
Example: data_ObjectFromScratch.txt
Test again!
Reladomo Object from Scratch
Find ExercisesObjectFromScratch.java; it has assertion failures if you run it as a test.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 25 minutes.
Detached Objects are copies of Reladomo objects that support a lot of the original methods, but modifications are not immediately persisted.
A prime use-case for Detached Objects is where an object backs a GUI form
The user may change and edit the form fields several times before submitting or cancelling
Automobile car = AutomobileFinder.findOne(...); boolean shouldSave = editUsingGui(car); if (shouldSave) { // Persist changes to DB } else { // Don't save changes }
Detached Objects are copies of Reladomo objects that support a lot of the originals methods, but do not cause immediate changes to the database/cache
.getDetachedCopy()
.copyDetachedObjectToOriginalOrInsertIfNew()
Automobile car = AutomobileFinder.findOne(...).getDetachedCopy(); boolean shouldSave = editUsingGui(car); if (shouldSave) { // Persist changes to DB car.copyDetachedValuesToOriginalOrInsertIfNew(); } else { // Don't save changes }
Detached Objects are copies of Reladomo objects that support a lot of the originals methods, but do not cause immediate changes to the database/cache
.getDetachedCopy()
.copyDetachedObjectToOriginalOrInsertIfNew()
Automobile car = new Automobile(); // don't even need getDetachedObject() here boolean shouldSave = editUsingGui(car); if (shouldSave) { // Persist changes to DB car.copyDetachedValuesToOriginalOrInsertIfNew(); } else { // Don't save changes }
Detached Objects are copies of Reladomo objects that support a lot of the originals methods, but do not cause immediate changes to the database/cache
.getDetachedCopy()
.copyDetachedObjectToOriginalOrInsertIfNew()
Detached objects can be reset
Automobile car = AutomobileFinder.findOne(...).getDetachedCopy(); UserAction action = editUsingGui(car); switch (action) { case UserAction.SAVE: car.copyDetachedValuesToOriginalOrInsertIfNew(); break; case UserAction.RESET_FORM: car.resetFromOriginalPersistentObject(); break; ... }
Detached Objects are copies of Reladomo objects that support a lot of the originals methods, but do not cause immediate changes to the database/cache
.getDetachedCopy()
.copyDetachedObjectToOriginalOrInsertIfNew()
Detached objects can be reset
You can "clone" data: getNonPersistentCopy();
generateAndSet___();
Automobile mitsubishiEclipse = AutomobileFinder.findOne(...); Automobile eagleTalon = mitsubishiEclipse.getNonPersistentCopy(); eagleTalon.setBrandName("Eagle"); eagleTalon.setModelName("Talon"); // all other attributes remain same. // Example: engine size, number of seats, safety rating, etc. eagleTalon.generateAndSetAutomobileId(); eagleTalon.insert();
Find ExercisesDetachedObjects.java; it has assertion failures if you run it as a test.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 10 minutes.
Consider, a "feed" file which contains updates for a set of tables in the database.
Feed data needs to be loaded in the database as fast as possible.
Feed data can be in any form:
File, Web-Service endpoint output, EJB output, even other Database
Assumption:
Feed contains all the new information for a known subset of the data in our tables
Operations to perform:
Close out whatever is not in the feed
Insert whatever is in the feed but not in the database
Update anything that matches
All this should happen asynchronously and very fast. Let's see how!
Consider 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
Operations which should be performed
Chirpy is in FSet but not in DSet -> Insert Row
Speedy, Wuzzy are in DSet but not in FSet -> Closed Out Rows.
Tabby is in both sets, but age is different -> Update only age.
Fuzzy is in both sets but nothing has changed -> Do Nothing.
D/b data in SERIAL. Input data in SERIAL.
SingleQueueExecutor singleQueueExecutor = new SingleQueueExecutor( NUMBER_OF_THREADS, PetFinder.petName().ascendingOrderBy(), BATCH_SIZE, PetFinder.getFinderInstance(), INSERT_THREADS); MatcherThread <Pet> matcherThread = new MatcherThread<>( singleQueueExecutor, new Extractor[]{PetFinder.petName()}); matcherThread.start(); PetList dbList = PetFinder.findMany(PetFinder.all()); matcherThread.addDbRecords(dbList); matcherThread.setDbDone(); matcherThread.addFileRecords(getInputPetList()); matcherThread.setFileDone(); matcherThread.waitTillDone();
singleQueueExecutor is the Executor which MatcherThread requires
Extractor is the entity on which matcher thread will match
dbList are the database records
FileRecords are the input data
D/b data in PARALLEL. Input data in SERIAL.
SingleQueueExecutor singleQueueExecutor = new SingleQueueExecutor( NUMBER_OF_THREADS, PetFinder.petName().ascendingOrderBy(), BATCH_SIZE, PetFinder.getFinderInstance(), INSERT_THREADS); MatcherThread <Pet> matcherThread = new MatcherThread<>( singleQueueExecutor, new Extractor[]{PetFinder.petName()}); matcherThread.start(); PetList dbList = PetFinder.findMany(PetFinder.all()); DbLoadThread dbLoadThread = new DbLoadThread(dbList, null, matcherThread); dbLoadThread.start(); matcherThread.addFileRecords(getInputPetList()); matcherThread.setFileDone(); matcherThread.waitTillDone();
singleQueueExecutor is the Executor which MatcherThread requires
Extractor is the entity on which matcher thread will match
dbList are the database records
FileRecords are the input data
D/b data in PARALLEL. Input data in PARALLEL.
SingleQueueExecutor singleQueueExecutor = new SingleQueueExecutor( NUMBER_OF_THREADS, PetFinder.petName().ascendingOrderBy(), BATCH_SIZE, PetFinder.getFinderInstance(), INSERT_THREADS); MatcherThread <Pet> matcherThread = new MatcherThread<>( singleQueueExecutor, new Extractor[]{PetFinder.petName()}); matcherThread.start(); PetList dbList = PetFinder.findMany(PetFinder.all()); DbLoadThread dbLoadThread = new DbLoadThread(dbList, null, matcherThread); dbLoadThread.start(); PlainInputThread inputThread = new PlainInputThread(new InputDataLoader(), matcherThread); inputThread.run(); matcherThread.waitTillDone();
singleQueueExecutor is the Executor which MatcherThread requires
Extractor is the entity on which matcher thread will match
dbList are the database records
inputThread is for the input data. PlainInputThread can be used for input sources like files.
MT Loader
Find ExercisesMTLoader.java; it has assertion failures if you run it as a test.
Figure out how to get the tests to pass using what you have seen so far.
Should take about 15 minutes.