-
Notifications
You must be signed in to change notification settings - Fork 6
Second Tutorial Part 10: Behavior Customization
Previous | Main | Next |
---|---|---|
<-- Part 9 | Main | Part 11 --> |
An important part of the Tilda architecture is that it separates the generated classes from the application-level classes that you can customize. Not only can you add new methods in either the Factory or Data classes as we have highlighted in this tutorial, but there are also methods you can override. Based on the object's definition, a variety of methods with empty bodies will be defined in the generated application classes.
🎈 NOTE: Default implementations of required overridable methods will be generated the first time a class is introduced in the system. If an object's definition is changed during the course of development, then the application-class won't be re-generated and a compiler error might occur as a result. For example, you might change the OCC
status of a class and suddenly your App class should define its own touch
method. Most modern IDEs will flag this and even offer a quick way to add the method to our class which you can then complete with an implementation.
For the purpose of this tutorial, we'll look at a Dummy
table in our tutorial defined as such:
- We'll turn off OCC
-
nameNorm
is an AUTO column -
charCount
is a CALCULATED column -
updated
is a DATETIME column to keep track of our own life cycle timestamp
,{ "name":"Dummy"
,"description":"Dummy class for testing purpose"
,"occ":false
,"columns":[
{ "name":"nameFirst", "type":"STRING" , "size":255, "nullable":false
,"description":"A first name"
}
,{ "name":"nameLast" , "type":"STRING" , "size":255, "nullable":false
,"description":"A last name"
}
,{ "name":"nameNorm" , "type":"STRING" , "size":255, "nullable":false
,"mode":"AUTO"
,"description":"A normalized name as upper(nameLast+','+nameFirst)."
}
,{ "name":"charCount", "type":"INTEGER" , "nullable":true
,"mode":"CALCULATED"
,"description":"An app-only count of the character length of nameNorm."
}
,{ "name":"updated" , "type":"DATETIME" , "nullable":true
,"description":"A timestamp to track this row's life cycle"
}
]
,"primary": { "autogen": true, "keyBatch": 500 }
,"indices": [
{ "name":"nameNorm", "columns": ["nameNorm"] }
]
}
The generated Java application class will look like:
public class Dummy_Data extends tilda_tutorial.data._Tilda.TILDA__DUMMY
{
protected static final Logger LOG = LogManager.getLogger(Dummy_Data.class.getName());
public Dummy_Data() { }
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
// Implement your customizations, if any, below.
/////////////////////////////////////////////////////////////////////
@Override
protected void setNameNorm()
throws Exception
{
// Do something to set the value of the auto field.
...
}
@Override
public int getCharCount()
{
// return some value
...
}
@Override
protected boolean beforeWrite(Connection C) throws Exception
{
// Do things before writing the object to disk, for example, take care of AUTO fields.
return true;
}
@Override
protected boolean afterRead(Connection C) throws Exception
{
// Do things after an object has just been read form the data store, for example, take care of AUTO fields.
return true;
}
@Override
public boolean touch(Connection C) throws Exception
{
// Do things here to update your custom life-cycle tracker fields, like timestamp, if any.
... something like setLastUpdatedNow();
// the write the object to complete the touch operation.
return write(C);
}
}
🎈 NOTE: The methods that require an implementation are generated with code that explicitly induces compile errors so it's clear that immediate attention is needed. A body needs to be provided for an implementation that only you can write.
Getters and setters are declared as final in the generated Data base class. They are not overridable and this decision was not taken lightly: because the behavior of a setter or getter is complex, that introduced too many issues in earlier versions of the framework.
However, if a column is defined as "mode":"CALCULATED"
, the getter will be declared as abstract in the base class and therefore will need to be concretized in the application-level Data class. defining a column as CALCULATED delegates the column to only exist in the application-space: no column will actually be declared in the database. It is then your responsibility to provide a getter implementation. Of course, no setter will be generated.
A simple implementation for charCount
would be as follows:
@Override
public int getCharCount()
{
String Str = getNameNorm();
return TextUtil.isNullOrEmpty(Str) == true ? 0 : Str.length();
}
Additionally, if a column is defined as "mode":"AUTO"
, the regular setter will be protected and a protected "auto" setter method without any parameter will be generated. That method will be called by the framework right before a database write (insert of update) and the beforeWrite()
method is called.
A simple implementation for nameNorm
would be as follows:
@Override
protected void setNameNorm()
throws Exception
{
if (hasChangedNameFirst() == true || hasChangedNameLast() == true)
setNameNorm(getNameLast().toUpperCase() + ", " + getNameFirst());
}
🎈 NOTE: Here, we use the hasChangedXxx()
methods to check whether our auto field should be updated at all. It's a simple optimization which becomes useful if the cost of the setter is more extensive.
🎈 NOTE: Your auto setter should eventually call the normal setter for the column. Auto columns exist in the database and so go through all the normal logic for regular columns: the only difference is that you have implemented logic to set the value programmatically, rather than let users of your object set the value themselves. This is useful for caching logic for example and mirrors SQL's computed columns, except that the logic is implemented app-side. A future feature of Tilda will implement support for native computed columns in the database, especially since Postgres 12 now supports the feature.
🎈 NOTE: Because auto setters are called before a Write operation, the columns will effectively not be set between a call to create()
and write()
.
By default, all objects are OCC-enabled and key life-cycle management columns such as created
, lastpdated
and deleted
are automatically generated. However, if the object is declared with "occ":false
, those fields won't be defined and an abstract touch()
method will defined.
An implementation would be as follows:
@Override
public boolean touch(Connection C) throws Exception
{
// Do things here to update your custom life-cycle tracker fields, like timestamp, if any.
setUpdatedNow();
// the write the object to complete the touch operation.
return write(C);
}
🎈 NOTE: Here, we are mimicking the normal behavior of Tilda internally which uses a DateTime column called lastUpdated
, but more complex logic such as some internal counter or a database sequence could be used.
Before an object is written to the database, the beforeWrite()
method is called. After an object has been read from the database, the afterRead()
method is called.
Typically, logic in there can perform additional validation, logging, keep track of cached values which may be used by AUTO or CALCULATED fields etc...
🎈 NOTE: Remember that auto
fields are set prior calling the beforeWrite()
method.
Previous | Main | Next |
---|---|---|
<-- Part 9 | Main | Part 11 --> |