New SimpleORM Whitepaper

Anthony Berglas <[email protected]>
Newsgroups gmane.comp.java.orm.simpleorm
Message-ID <[email protected]>
Over the last few days I have been completely reworking the white 
paper.  And as I do so, I review the code and design and test 
cases.  It is all looking quite good.

I am most of the way through it, and have attached the current 
version.  Please read through it and tell me what you think.  What's 
wrong, what's missing, what's superfluous.

It is currently checked in under 
simpleorm\trunk\simplewebapp\common\doc\simpleorm-webSite.  (I need 
to reorganize this when we make the Franck branch the main 
branch.)  I just edit it with Komposer, but any lightweight editor 
will do (not MS Word!).

The most substantial change to SimpleORM as a result of the review is 
changing the default locking mode from SFOR_UPDATE to 
SBASIC.   Optimistic locking is always performed.  This felt more 
natural to me, but maybe SFOR_UPDATE would be better given that it is 
mainly used for transactional processing.  But we don't want to lock 
too much.  Opinions welcome.  We could also make the default mode 
settable on the session, but we really do not want too many modes and 
options -- hard to test etc.

Regards,

Anthony

Dr Anthony Berglas, [email protected]       Mobile: +61 4 4838 8874
Just because it is possible to push twigs along the ground with ones nose
does not necessarily mean that is the best way to collect firewood.

------------------------------------

Yahoo! Groups Links

<*> To visit your group on the web, go to:
    http://groups.yahoo.com/group/SimpleORM/

<*> Your email settings:
    Individual Email | Traditional

<*> To change settings online go to:
    http://groups.yahoo.com/group/SimpleORM/join
    (Yahoo! ID required)

<*> To change settings via email:
    mailto:[email protected] 
    mailto:[email protected]

<*> To unsubscribe from this group, send an email to:
    [email protected]

<*> Your use of Yahoo! Groups is subject to:
    http://docs.yahoo.com/info/terms/
whitepaper.html (text/html, 60.4 KB)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"><title>SimpleORM White Paper</title>

<link rel="STYLESHEET" type="text/css" href="styles.css">
</head>
<body style="background-color: rgb(255, 255, 255);">
<table border="0" width="100%">
<tbody>
<tr valign="bottom">
<td>
<h1 style="font-weight: normal;"><a class="mozTocH1" name="mozTocId415962"></a><font color="blue" size="6">SimpleORM White
Paper</font></h1>
<font size="5"><i>Simple Java Object
Relational Mapping</i></font> </td>
<td halign="right"><img src="logo.png" alt="Logo" style="width: 175px; height: 107px;" title="">
<br>
<i>Power without complexity.</i> </td>
</tr>
</tbody>
</table>
<hr>
Dr Anthony Berglas<br>
July 2008<br>
SimpleORM v 3<br>
<br>
<ul id="mozToc"><!--mozToc h2 1 h3 2 h3 3 h4 4 h5 5 h6 6--><li><a href="#mozTocId396825">Why
SimpleORM?</a></li><li><a href="#mozTocId26310">SimpleORM
Generalized Records</a></li><li><a href="#mozTocId807387">Data Definition Example</a></li><li><a href="#mozTocId38196">Data
Manipulation Example</a></li><li><a href="#mozTocId968781">Queries</a></li><li><a href="#mozTocId436695">SimpleORM
Transactions</a></li><li><a href="#mozTocId911291">Record
Locking and Isolation
</a></li><li><a href="#mozTocId716390">SimpleORM
DataSets</a></li><li><a href="#mozTocId784116">Associations Between Records</a></li><li><a href="#mozTocId381239">Flexible Record Defintions</a></li><li><a href="#mozTocId719249">Business
Rule
Framework</a></li><li><a href="#mozTocId975885">Configuration</a></li><li><a href="#mozTocId627792">Performance
and
Benchmarks</a></li><li><a href="#mozTocId484049">Conclusion</a></li><li><a href="#mozTocId93172">
</a></li><li><a href="#mozTocId339228">Hibernate</a><ul><li><a href="#mozTocId794489">&nbsp;
Load, supposedly lazy</a></li></ul></li></ul>
<br>
<h2><a class="mozTocH2" name="mozTocId396825"></a>Why
SimpleORM?</h2>
SimpleORM provides a full featured object relational mapping and
persistence system in a tiny package that is easy to understand and
safe to work with. <br>
<br>
A key technology is to represent objects as essentially a strongly
typed map of attributes to their values. This avoids
the&nbsp;complex reflection and byte code generation needed to
create&nbsp;the pseudo POJOs of other ORMs. &nbsp;The structure
also
facilitates a
clean, string free query language and provides access to extended field
state information such as the initial&nbsp;queried value.
&nbsp;SimpleORM also packages objects into "DataSets",
which&nbsp;enables it to provide well defined semantics for
relationships and
detached records (unlike other ORMs). &nbsp; SimpleORM also
provides transparent access
to user extensible meta data, and can also enable different modules to
extend that meta data which can provide very flexible application
architectures.<br>
<br>
Other less flexible ORMs are far too complex for ordinary developers to
understand their detailed semantics or implementation. &nbsp;This
introduces substantial technical risk when problems arrise.
&nbsp;On the other hand, the absense of complex reflection,
query parsers, byte code generation etc. makes the SimpleORM behaviour
and source code very
transparent. &nbsp;If necessary any comptetent developer can
read from the SimpleORMl&nbsp;API to the underlying JDBC calls in a
very few
clicks of the mouse. &nbsp;&nbsp;The entire SimpleOrm jars are
just 75K and 52K in size, with
only one small and optional dependency (Slf4j). &nbsp;(Hibernate is
over 2400K plus about 2000K of dependent Jars.)<br>
<h2><a class="mozTocH2" name="mozTocId26310"></a>SimpleORM
Generalized Records</h2>
The key feature of SimpleORM is the fact that it represents individual
records as a map of fields to values.&nbsp;
The use of rich record structures enables greater control of ORM
functionality while simplifying their use.<br>
<span style="font-style: italic;"><br>
</span>Record and field <span style="font-style: italic;">definitions</span>
are just instances of SimpleORM classes which are normally declared as
constants.&nbsp; For example:-<br>
<br>
<pre> class <b>Employee</b>...<br> private static final SRecordMeta <b>EMPLOYEE</b> // the record<br> = new SRecordMeta(<b>Employee.class, "EMPLOYEE_TABLE"</b>);<br> ...<br> public static final SFieldString <b>PHONE_NR</b> // a field<br> = new SFieldString(<b>EMPLOYEE, "PHONE_NR", 30</b>);<br> <br> public String <b>getPhoneNr</b>() { // Optional get method if you like them<br> meta.getString(<b>PHONE_NR</b>);<br> }<br><br></pre>
<code>EMPLOYEE </code>contains an object that represents
the Employee<code></code>
record&nbsp;(SQL Table) meta data, and <code>PHONE_NR</code>&nbsp;
contains meta data
for a Phone Number field (SQL Column) that might be contained within
that record.<br>
<br>
One can then access the field as<br>
<pre> Employee <b>employee</b> = session.<b>mustFind</b>(Employee.meta, "Emp101");<br> String <b>phone</b> = <b>employee</b>.getString(Employee.<b>PHONE_NR</b>);<br>&nbsp;String <b>phone</b> = <b>employee</b>.<b>getPhoneNr</b>(); // Alternative if get* methods are defined.<br></pre>
<br>
This generalized structure has the following advantages:-<br>
<ul>
<li>The ORM declarations are very succinct and thus easy to
maintain.&nbsp; Only one Java statement per field is normally
required. &nbsp;No XML.</li>
<li>We have an accessible object that represents the field
itself
independently of the getters
and setters.&nbsp; So one can write efficient, fully compile time
checked
code that is easy to refactor such as<br>
<pre> newQuery(EMPLOYEE).greaterThan(<b><span style="font-style: italic;">Employee.NAME</span></b>, "J")<br></pre>
instead of run-time string based queries like<br>
<pre> createQuery("from EMPLOYEE E where E.NAME &gt; 'J'");</pre>
or
<pre> createCriteria(EMPLOYEE.class).add(Expression.greaterThan("NAME", "J");</pre>
</li>
<li>likewise <code><tt>isNull(Employee.SALARY)</tt></code>,
<tt><code>isValid(Employee.MANAGER)</code>,</tt><code><tt>
</tt></code><code><tt>Employee.PHONE_NR.</tt></code><code><tt>addValidator(validator)</tt></code>
etc.&nbsp; There are many things
that one needs
to do with <span style="font-style: italic;">persisted</span>
fields
other than just get and set their values.</li>
<li>The
rich record structure makes it easy to distinguish null from zero from
invalid.&nbsp; Likewise one can distinguish an Employee without a
Department from an Employee whose
Department has simply not been retrieved (for detached
records).&nbsp;&nbsp; You can also access the
<code>Employee.DEPT_ID</code>&nbsp;scalar field
without having to retrieve
the Department record.&nbsp; <code>myRec.getInitialValue(FIELD)</code>
retrieves the value as read from the database and used for optimistic
locking. &nbsp; Pseudo POJOs do not provide a place to store
anything other than&nbsp;shallow
field values.<br>
</li>
<li>Attempts
to access data that is inconsistent with the current transaction state
can produce fail fast exceptions rather than obscure errors.&nbsp;
We
can also automatically fail fast for common restrictions such as
strings that are too long.</li>
<li>SimpleORM records live within a DataSet. &nbsp;This
provides well defined semantics for relationships and detached records.
&nbsp;Unlike&nbsp; Hibernate (say) a child's parents and the
parent's children must alway be consistent.</li>
<li>It is easy to access and also create metadata.
&nbsp;For
example,&nbsp;<br>
<pre> EMPEE_ID...putUserProperty("DISPLAY_LABEL", "Telephone Number")...<br> ... EMPEE_ID.getMaxSize() ...</pre>
</li>
<li>The generalized design makes it very easy for <i>users</i>
to build powerful
extra
layers with methods like <code><tt>makeHtmlInput(SFieldMeta
theField)</tt></code>.
One can simply write code like <code><tt>getString(theField)</tt></code>
without any need for reflection. &nbsp; Labels (like "<code>Telephone
Number</code>") can default from the meta data.
&nbsp;SimpleWebApp provides meta data driven JSP tags that do this.</li>
<li>It
is easy to augment or change schema definitions at run time -- they are
just simple data structures.&nbsp; This can be be used to build
very customizable applications in which add on modules augment schema
definitions in a core module. &nbsp;(This is very difficult to do
with reflection.)</li>
<li>It is also quite easy to associate real POJOs with
SimpleORM
records, but
this is rarely done in practice.</li>
<li>There is no need for complex reflection or byte code post
processing
to access and intercept pseudo POJO field values.&nbsp; For
example,&nbsp; in
Hibernate <code>session.load(MyClass.class, 13)</code>
often does not return a MyClass object, rather it returns a magic
byte coded subclass of MyClass to handle lazy loading.&nbsp; This
can
cause nasty bugs if it
is processed by other reflecting software such as a web service
serializer. &nbsp;</li>
<li>If record values are accessed using normal <code>get</code>
and <code>set</code> methods, the code required to access
field values is identical for SimpleORM records or pseudo POJOs -- eg. <code>employee.getPhoneNr()</code>.
&nbsp;There is really no advantage to using pseudo POJOs for
persistence.</li>
<li>Keeping it simple means that <span style="font-style: italic;">you</span>
can really understand what is going on.&nbsp; This is critical if
problems arrise&nbsp;and greatly reduces the technical risk of
using an overly complicated tool.</li>
</ul>
<br>
SimpleORM <span style="font-style: italic;">Automates</span>
persistence, but unlike Hibernate
and JPA
it does not pretend to
make persistence <span style="font-style: italic;">Transparent.</span>&nbsp;
Transparent persistence attempts to hide persistence issues from the
business logic.&nbsp; However, for most information systems
persisting
and querying the information is pervasive and tends to dominate the
"business
logic".&nbsp; So&nbsp;running the "business logic" without the
persistence layer is meaningless in practice. &nbsp;And SimpleORM's
independent DataSet layer facilitates unit tests etc. &nbsp;Thus we
believe
that the advantage of having a rich
yet
simple record structure easily outweighs the loss of transparency for
most applications. &nbsp;SimpleORM does not pretend that
persistence can be added to an existing application as an afterthought.
&nbsp;<br>
<br>
It should be noted that SimpleORM is a "real" ORM.&nbsp; DataSets
provide a proper&nbsp;object cache, and each record is only
represented once within each
transaction.&nbsp; Object identity is tied to database
identity.&nbsp;
This means that business rules can be written
largely independently of each other without the need to coordinate data
retrieval.&nbsp; This distinguishes SimpleORM from iBATIS, Apache
DdlUtils, Butler etc.&nbsp; <br>
<br>
SimpleORM is full featured.&nbsp; For example, it is possible
to
detach DataSets and then reattach them to a new transaction using
optimistic locks.&nbsp; Several primary key generators are
provided,
but unlike many other&nbsp;ORMs it also provides good support for
multi
column primary&nbsp;keys.<br>
<br>
SimpleORM is open source with an&nbsp;Apache style licence.
<h2><a class="mozTocH2" name="mozTocId807387"></a><a name="example"></a>Data Definition Example</h2>
The following code fragments show everything that is required to
declare and use a mapping to an Employee table. Each
<code>SRecordInstance</code> represents an individual
record
instances while <code>SRecordMeta</code> objects represent
the meta-data
associated with each instance.&nbsp;<code>SFieldMeta</code>
objects describe the fields within the record.
<p>Note that in SimpleORM a database has "tables" and
"columns", while a persisted class is a "record"
which has "fields". A non persisted class is just a
"class" which has "instance variables". </p>
<pre>public class <b>Employee</b> extends SRecordInstance { // ie. a class mapped to a table.<br> <br> private static final SRecordMeta <b>EMPLOYEE</b> = new SRecordMeta(Employee.class, "XX_EMPLOYEE");<br>&nbsp;<br> public static final SFieldString <b>EMPEE_ID</b> <br>   = new SFieldString(EMPLOYEE, <b>"EMPEE_ID"</b>, 20, <b>SPRIMARY_KEY</b>);<br><br> public static final SFieldString <b>NAME</b> <br>   = new SFieldString(EMPLOYEE, "ENAME", 40, S<b>MANDATORY</b>, S<b>DESCRIPTIVE</b>);<br> <br> public static final SFieldString <b>PHONE_NR</b> <br>   = new SFieldString(EMPLOYEE, "PHONE_NR", 20)<br>     .putUserProperty("DISPLAY_LABEL", "Telephone Number");<br><br> public static final SFieldDouble <b>SALARY</b> <br>   = new SFieldDouble(EMPLOYEE, "SALARY").addValidator(new SValidatorGreaterEqual(0));<br><br> public static final SFieldString <b>DEPT_ID</b> = new SFieldString(EMPLOYEE, "DEPT_ID", 10);<br>	<br> static final SFieldReference&lt;Department&gt; <b>DEPARTMENT</b> <br>   = new SFieldReference(EMPLOYEE, Department.DEPARTMENT, "DEPT");<br><br> public enum EType{PERMANENT, CASUAL, CONTRACT};<br> static final SFieldEnum&lt;Type&gt; ETYPE = new SFieldEnum(EMPLOYEE, "ETYPE", EType.class);<br><br> static final SFieldString <b>RESUME</b> // Curriculam Vitae<br>   = new SFieldString(EMPLOYEE, "RESUME", 200, SUNQUERIED)<br>     .overrideSqlDataType("VARCHAR ( 200)"); // Maybe LONG VARCHAR for Oracle etc.<br><br> public @Override SRecordMeta&lt;Employee&gt; getMeta() {return EMPLOYEE;}; <br><br> // Completely optional get/set methods.<br> public String <b>getPhoneNumber</b>() {return getString(PHONE_NR);}<br> public Employee <b>setPhoneNumber</b>(String value) {setString(PHONE_NR, value); return this;}<br>}<br></pre>
<p>The Employee record is mapped to the <tt>XX_EMPLOYEE</tt>
table. It has a primary key <code>EMPEE_ID</code>, a <code><tt>NAME</tt></code>
and a <code>PHONE_NR</code>
field, and a direct reference to the <code><tt>Department</tt></code>
record. All these
definitions simply define static constants which are threaded
together via the <code><tt>EMPLOYEE</tt></code>
object.&nbsp; The <code>EMPLOYEE</code>
object is tied to the <code>Employee.class</code> using
the <code>getMeta</code>
method.</p>
<h2><a class="mozTocH2" name="mozTocId38196"></a>Data
Manipulation Example</h2>
<p>The following code then manipulates this data.<br>
</p>
<pre> SSessionJdbc session = SSessionJdbc.open(myDataSet, "MyInformativeLoggingLabel"); <br> session.begin();<br> ...<br> Employee employee = session.mustFind(Employee.EMPLOYEE, SFOR_UPDATE, "123-45-6789");<br><br> String name = employee.getString(Employee.NAME);<br> String phone = employee.getPhoneNr(); // if getter defined<br><span style="font-weight: bold;"></span> employee.setPhoneNr("(123) 456 7890");<br> Employee.EType etype = e100a.getEnum(e100a.ETYPE);<br> ...<br> println(employee.getString(Employee.DEPT_ID));<br> Department department = employee.findReference(employee.DEPARTMENT);<br> ...<br> oldEmployee.delete();<br> ...<br> session.commit();<br> session.begin(); ... session.commit();<br> session.close();</pre>
<p>A SimpleORM session is created based on an
ordinary&nbsp;java.sqlx.DataSet.&nbsp; A new transaction is
then
started.</p>
<code><tt>session.mustFind(Employee.EMPLOYEE, SFOR_UPDATE,
"123-45-6789")</tt></code>&nbsp;
finds a Employee
object based on the
primary key. If the object had been previously retrieved in the same
transaction a pointer to it is returned, otherwise a JDBC query is
issued to retrieve the row. If "<code><tt>123-45-6789</tt></code>"
does
not exist
in the database then a meaningful exception is thrown whose message
includes the unfound primary key.<code>&nbsp; find(...)</code>
returns
null if the row was not found, <code>create(...)</code>
creates a new
record, while <code>findOrCreate(...)</code> can be used
to insert a
new row using exactly the same
techniques as used to update a record. &nbsp; <code><tt>SFOR_UPDATE
</tt></code>adds a locking clause to the SQL for MVCC
databases, but it is not necessary as optimistic locking is always used.<br>
<p>The <code>NAME</code> and <code><tt>PHONE_NR</tt></code>
fields
are
then retrieved from the record, the first using the generalized syntax
and the second using the get method that had been defined.&nbsp;
The <code>PHONE_NR</code>,
is then set to a new value. The generalized design makes it easy for
SimpleORM to mark the field and record as dirty when the <code><tt>PHONE_NR</tt></code>
is set.&nbsp; SimpleORM never needs to compare 100
records at commit time just to find the 10 that have changed.
</p>
<p>The employee's <code>DEPARTMENT</code> record is
then retrieved
from either
the cache or database by <code>findReference</code>.
&nbsp;Note that the actual scalar foreign key value&nbsp;<code>Employee.DEPT_ID</code>
is also available and can be accessed without the need to retrieve the
department record.</p>
<p>An <code><tt>oldEmployee</tt> </code>is
then flagged for deletion
before the
transaction is finally committed. As part of this process SimpleORM
flushes all pending dirty objects to the database. Thus there is no
need to manually track which records need to be updated. There is
also no risk that separate parts of the same application will
accidentally override changes to different in memory copies of the
same database record.&nbsp;</p>
<h2><a class="mozTocH2" name="mozTocId968781"></a>Queries</h2>
SimpleORM queries are defined as a <code>SQuery</code>
object that can be executed in a <code>SSession</code> to
produce a <code>List</code> of &nbsp;records.
&nbsp;For example<br>
<code></code>
<pre>Department d100q = session.findOrCreate(Department.DEPARTMENT, "100"); <br>SQuery&lt;Employee&gt; query = new SQuery(Employee.EMPLOYEE)<br> .eq(Employee.DEPARTMENT, d100q) // and<br> .like(Employee.NAME, "%One%")<br> .descending(Employee.SALARY);<br>List&lt;Employee&gt; emps = session.query(query); </pre>
<p>The
query object contains details of the query, and is independent of the
session. &nbsp;It can then be executed to produce a list of
records.
&nbsp;By default the cache is flushed (but not committed) before
the
query is executed so that query results take into account any changed
values.</p>
<p>Note that the <code>.eq</code> refers directly to
Department, and SimpleORM
determines that the key to Department is <code>DEPT_ID</code>
( it
could also be a multi column key).&nbsp;&nbsp;</p>
Records
can be joined in to retrieve parents along with children and so avoid
the "N+1" problem. &nbsp;In the following example all Employees are
retrieved along with their departements in a single query.
&nbsp;(If
the number of parents is substantially less than the number of children
then it is faster to simply fetch the parents lazily using <code>findReference()</code>.)<br>
<br>
<pre>SQuery&lt;Employee&gt; joinQ1 = new SQuery&lt;Employee&gt;(Employee.EMPLOYEE)</pre>
<pre>&nbsp; .join(Employee.DEPARTMENT)</pre>
<br>
Not all columns need to be retrieved by a query.
&nbsp;Both&nbsp;<code>SQuery</code> and&nbsp;<code>findOrCreate</code>()
calls can take a list of fields to retrieve, or a <code>SSelectMode</code>
which defines which types of fields to retrieve. &nbsp; In the <code>Employee</code>
example above <code>RESUME</code> is flaged as&nbsp;<code>SUNQUERIED</code>
so it is not retrieved by default, unless <code>SSelectMode.ALL</code>
is specified. &nbsp;Likewise, if<code>
SSelectMode.DESCRIPTIVE </code>is specified then only fields
marked as <code>DESCRIPTIVE </code>are returned, ie. <code>NAME</code>
in the example. &nbsp;Thus one never has to
retrieve all 100
columns of an Employee Table just to obtain the Employee's
Name. &nbsp;&nbsp;And any attempt to access a field that has
not been
queried produces fail fast exceptions.
<p><code>SQuery.rawPredicate(query, parameters...)</code>
can be used to append an arbitray predicate to the <code>WHERE</code>
clause. &nbsp; SimpleORM also provides convenience methods for
performing ad hoc business intelligence JDBC queries that do not
produce normal objects are a result. &nbsp;For example:-</p>
<pre> List&lt;Map&gt; sals = ses.rawQueryMaps(<br> "SELECT E.DEPT_ID, SUM(E.SALARY) as \"SUMSAL\" FROM XX_EMPLOYEE E " +<br> " WHERE E.DEPT_ID IS NOT NULL" +<br> " GROUP BY E.DEPT_ID ORDER BY E.DEPT_ID");<br> for (Map sal: sals) {<br> &nbsp;...sal.get("SUMSAL")...} <br></pre>
<br>
<h2><a class="mozTocH2" name="mozTocId436695"></a>SimpleORM
Transactions</h2>
All
SimpleORM database manipulations occur within a SSessionJdbc object,
which encapsulates an ordinary JDBC connection. &nbsp;The
connection is
derived from an ordinary java.sqlx.DataSource object which would
normally be obtained from connection pool software such as the DBCP
provided in Tomcat, an application server, C3PO etc. &nbsp;If the
number of transactions is not large it can be simpler and safer to just
use the old java.sql.DriverManager to create a non-pooled connections (<code>examples/TestUte.java</code>
has a few lines of code that demonstrates how to do this).
&nbsp;But
SimpleORM itself does not care how the DataSource has been created.<br>
<br>
The following shows the general flow:-<br>
<br>
<pre> SSessionJdbc session0 = SSessionJdbc.open(myDataSet, "MyInformativeLoggingLabel"); <br> ...<br> SSessionJdbc session = SSessionJdbc.getThreadLocalSession();<br> session.begin(); <br> ... SSessionJdbc session = SSessionJdbc.getThreadLocalSession(); ...<br> session.commit();<br> session.begin(); ... session.commit();<br> session.close();<br></pre>
<br>
When
sessions are opened they are automatically associated with the current
thread. &nbsp; This means that they can be easily retrieved using <code>getThreadLocalSession</code>
without the need to pass the session object around explicitly.
&nbsp;
It also means that unclosed sessions left over from previous uses of
the thread can be detected and exceptions thrown. &nbsp;Normally a
given session can only be accessed by one thread, otherwise exceptions
are thrown&nbsp;(<code>detachFromThread</code> can
override this.)
&nbsp;The session description&nbsp;is displayed in
logging messages and so can be helpful in multithreaded application.
&nbsp;An optional third parameter can override the default
SimpleORM&nbsp;database driver which&nbsp;is normally defaulted
from
the URL in the DataSource.<br>
<br>
SimpleOrm guarantees that each session will only contain&nbsp;one
record from&nbsp;with a given primary
key (and the same SRecordMeta). &nbsp;Thus if a record were to be
retrieved
several times by different parts of an application within the <i>same</i>
session then the same SRecordInstance object will be returned each
time. &nbsp;(If the a record with the same key
is&nbsp;retrieved in <i>different</i> session then
different SRecordInstance objects will be returned and normal database
and optimistic locking will maintain databae integrity.)<br>
<br>
Records
are normally flushed from the
session to the database when a transaction commits. &nbsp;Records
are
also normally flushed before queries are executed to ensure that their
results
represent the current state of the database.
&nbsp;Records&nbsp;can
also be flushed manually without committing the transaction. &nbsp;
They are flushed in the order that they were first made dirty, which
provides a simple and&nbsp;<i>predictable</i> update
order. &nbsp;Fail fast exceptions are raised if this would create a
referential integrity violation.<br>
<br>
Records
may also be purged from a DataSet to force a requery should the
database be updated using raw JDBC calls or stored procedures.
&nbsp;Raw JDBC can be <em>much</em>
more efficient than any ORM
for certain types of operations. For example a single SQL
<code><tt>DELETE</tt></code> statement that
deletes all transaction
details over 12
months old is much more efficient than having to read each of the
transaction details into the JVM and then delete them
individually.<br><p>SimpleORM retrieves values from records in a very similar
mechanism
to ordinary JDBC.&nbsp; Multiple <code><tt>get*</tt></code>
methods
retrieve values in
different ways. <code><tt>getString</tt></code>
returns the value as a
String,
<code><tt>getInt</tt></code> returns an int
(not an Integer) or 0 if
the column
is null.&nbsp; <code><tt>isNull</tt></code>can
then be used to
determine whether the
column is actually null.&nbsp; Note that the actual column type is
somewhat independent of the get method used, eg. <code>getString</code>
can be used on an integer column -- it will just return the value as a
string.&nbsp; Like basic JDBC, <code><tt>getObject</tt></code>
provides the most general access
and can be used for any type, inlcuding Blobs, Clobs, Arrays and any
new ones that may be added in the future.
</p>
<p>Internally each field type is processed by its own driver
which
handles conversions etc. This makes it easy to add new types.
Existing types include <code><tt>TimeStamp</tt> </code>and
<code><tt>BigDecimal</tt></code>.&nbsp;
There are several <code>SFieldBoolean*</code>
types depending on how one wants to represent a boolean, eg. 'Y'/'N',
'T'/'F', 1/0 (SQL does not have an intrinsic boolean type).<br>
</p>
SimpleORM
can be used to automatically generate keys using a number of different
approaches depending what the DataBase supports. &nbsp;For example,
the following code causes a <code>INVOICE_NR</code> field
to be created.<br>
<pre> public static final SFieldInteger INVOICE_NR = new SFieldInteger(meta, "INVOICE_NR", SFieldFlags.PRIMARY_KEY)<br> .setGeneratorMode(SSEQUENCE, "invoice_seq");<br> ...<br> Invoice inv1 = session.createWithGeneratedKey(Invoice.meta);<br><br></pre>
Generating
sequence numbers is difficult due to record locking problems.
&nbsp;Sequence mode uses a database specific sequence object to do
this, which is named&nbsp;<code>invoice_seq</code> in
the example. &nbsp;Other methods are provided, including a generic <code>SELECT_MAX</code>
&nbsp;method that will work on any database albeit with dubious
locking
semantics. &nbsp;The identities are normally generated when the
record
is first <code>create</code>d by SimpleORM which avoids
problems with the identitities of unsaved pseudo objects as
described&nbsp;in <a href="http://www.onjava.com/pub/a/onjava/2006/09/13/dont-let-hibernate-steal-your-identity.html">http://www.onjava.com/pub/a/onjava/2006/09/13/dont-let-hibernate-steal-your-identity.html.</a><br>
<br>
(While
generated "surrogate" keys can be very useful, we would suggest that
they be avoided where solid natural keys are available.
&nbsp;Excessive
use of hidden surrogate keys complicates the design, makes it hard to
do ad
hoc queries, and are somewhat less efficient. &nbsp;If secondary
unique
indexes are not declared appropriately then surrogate keys can also
enable invalid data to be stored. &nbsp;Solid application keys such
as
Product Numbers do not
change.&nbsp; This is because
they are distributed accoss many databases, including suppliers,
customers and regulators.&nbsp; Updating one database is the least
of
the
issues involved if they were to change.)<br>
<h2><a class="mozTocH2" name="mozTocId911291"></a>Record
Locking and Isolation<br>
</h2>
Proper record locking and transaction isolation is essential to
maintain database integrity in a multi transaction production
environment. &nbsp;But it is also very complex and
has&nbsp;subtly but
importantly different semantics in each type of database.
&nbsp;SimpleORM avoids
mush of this complexity&nbsp;by using optimistic locks that work
largely independently of individual database locking behaviors.
&nbsp;<br>
<br>
When
a record would be updated in the database an optimistic locking
strategy&nbsp;simply checks that each record has not in fact been
changed since it was queried. &nbsp;This can be performed
efficiently
by adding redundant terms to the <code>WHERE</code> clause
of&nbsp; <code>UPDATE</code> and <code>DELETE</code>
statements. &nbsp; If the value had been changed by a different
transaction then an exception is thrown.<br>
<br>
These exceptions can be avoided by explicitly locking the record when
it is queried using the <code>SQueryMode.SFOR_UPDATE.</code>
This adds <code>FOR UPDATE</code> clauses on many
databases, but the optmistic lock checks are still performed for
safety. &nbsp;<code>SFOR_UPDATE</code> reduces broken
optimistic lock exceptions, but can increase deadlock exceptions.
&nbsp;It should be used on records that are likely to be updated,
or whose current value is critical for an update, but not for general
queries.<br>
<br>
SimpleORM always sets&nbsp;AutoCommit Off, but it does
not&nbsp;change the database's default isolation level
(see&nbsp;<code>session.setIsolationLevel</code>).&nbsp;
&nbsp;But one should strongly consider raising it to <code>TRANSACTION_SERIALIZABLE</code>
to avoid potential integridy issues. &nbsp;In particular if a
transaction is summing a value over a number of detail records to store
the total in a master record, then the detail records should be
retrieved&nbsp;<code>SFOR_UPDATE&nbsp;</code>and the transaction should
be serializable to avoid the details being changed while the master is
being updated. &nbsp;The performance and concurrency issues with
serializable transactions are often greatly overstated -- start safe
and then tune for performance based on actual measurements.<br>
<br>
To understand the effect of <code>SFOR_UPDATE</code>
one needs to understand the advanced Multi Version Concurrency Control
(MVCC) now provided by most databases.&nbsp; These include Oracle,
PostgreSQL, DaffodilDB,&nbsp;MS SQL after 2005 ("Snapshots"), but
not
Cloudscape/Derby, DB2. &nbsp;We describe this below because much of what has been written about
locking and isolation levels is
misleading because it implicitly assumes a conventional, non MVCC
locking
mechanims.<br>
<br>
MVCC means that queries normally retrieve values at the time that the
transaction <i>started</i>, not their current values.
&nbsp;Consider the following sequence:-<br>
<br>
<table border="1" cellpadding="2" cellspacing="0" width="100%">
<tbody>
<tr>
<td>Step</td>
<td>Transaction &nbsp;A</td>
<td>Transaction &nbsp;B</td>
<td></td>
</tr>
<tr>
<td>1</td>
<td><code>BEGIN; SELECT something...</code></td>
<td></td>
<td></td>
</tr>
<tr>
<td>2</td>
<td><code></code></td>
<td><code>BEGIN</code></td>
<td></td>
</tr>
<tr>
<td>3</td>
<td><code></code></td>
<td><code>SELECT BALANCE INTO :BAL FROM LEDGER WHERE
ID=123</code></td>
<td>returns 1000</td>
</tr>
<tr>
<td>4</td>
<td><code></code></td>
<td><code>UPDATE LEDGER SET BALANCE = BAL + 100 WHERE
ID=123</code></td>
<td>set to 1100</td>
</tr>
<tr>
<td>5</td>
<td><code>SELECT BALANCE INTO :BAL FROM LEDGER WHERE
ID=123</code></td>
<td><code></code></td>
<td></td>
</tr>
<tr>
<td>6</td>
<td><code><br>
</code></td>
<td><code>COMMIT</code></td>
<td><b>MVCC returns 1000, not 1100</b></td>
</tr>
<tr>
<td>7</td>
<td><code>UPDATE LEDGER SET BALANCE = BAL + 200 WHERE
ID=123</code></td>
<td></td>
<td>Set to 1200, NOT 1300. </td>
</tr>
<tr>
<td>8</td>
<td><code>COMMIT</code></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<br>
In
a conventional locking system the query in step 5 would normally wait
until transaction B commits, and then return 1100. &nbsp;However,
&nbsp;an MVCC system would immediately return 1000 in step
5&nbsp;
because that was the value of the balance at the <i>beginning</i>
of
the transaction in step 1. &nbsp;So MVCC provides a consistent
snapshot
of the data, which is very good for reporting. &nbsp;However, for
transaction processing it can lead to lost and inconsistent
data&nbsp;as the update from Transaction B is clobbered by the
update
in Transaction A.<br>
<br>
The precise behaviour is database dependent.
&nbsp;Oracle will detect the anomaly if and only if it is set to
the
strictest&nbsp;Serializable transaction level, otherwise it will be
silently ignored unless opimistic locks are also used. &nbsp;Many
authors dangerously advise against Serialization mode. &nbsp;(Note
that
this use of Serialization has nothing to do with "Phantom" records.)<br>
<br>
SimpleORMs optimistic locking would change the query in step 7
to&nbsp; <code>UPDATE LEDGER SET BALANCE = BAL + 200 WHERE
ID=123 <i>AND BALANCE = 1000</i></code>.
&nbsp;All databases seem to apply the <code>WHERE</code>&nbsp;condition&nbsp;to
the current value that is
in the database (unlike the <code>SELECT</code> in step 5)
so the update will fail
because by step 7 the balance&nbsp;is 1100. &nbsp;This failure
can be detected by
SimpleORM and an exception thrown. &nbsp;(It is also possible to
add
special timestamp or update number columns to implement optimistic
locking, but this is generally unnecessary with SimpleORM because it
does not use pseudo POJOs and therefor always knows the&nbsp;value
of each
column when it was retrieved.)<br>
<br>
The exception can be prevented by&nbsp;adding a <code>FOR
UPDATE</code>-like clause to&nbsp;<code>SELECT</code>
statements. &nbsp; This produces conventional locking behaviour in
which Transaction A waits at step 5 for Transaction B to commit.
&nbsp;(The lock is also normally an exclusive lock, not a shared
read
lock.) &nbsp; However, if too many <code>FOR UPDATE </code>locks
are
held then concurrency will be severely reduced and excessive deadlocks
will arrise. &nbsp;(There will always be some exceptions in a
highly
concurrent database system, applications have to be programmed to cope
with
them.)<br>
<br>
Locking is very
important&nbsp;not because locking problems cause many errors, but
rather because they cause very few errors. Badly constructed
transactions that do not lock data properly will work perfectly
when unit tested. They will also work most of the time in production.
But every so often records will be corrupted in ways that are
impossible to reproduce and thus extremely difficult to fix. And worse,
the problems usually only
arise&nbsp;after the project&nbsp;becomes heavily used. <br>
<br>
<h2><a class="mozTocH2" name="mozTocId716390"></a>SimpleORM
DataSets</h2>
SimpleORM
actually consists of two distinct modules, namely the DataSet module
that actually stores the SRecordInsances and the&nbsp;SSessionJDBC
module that moves data between the DataSet and the JDBC accessed
database.&nbsp;<br>
<br>
A
DataSet is simply a collection of hetrogeneous SRecordInstances and
their meta data.
&nbsp;Records can be added, removed or updated within a dataset.
SimpleORM
SRecordInstances are only ever stored within an SDataSet.
&nbsp;DataSets
are independent of any database manipulation.<br>
<br>
By
default,
each SSessionJdbc has a default SDataSet that is created each time a
transaction is begun. &nbsp;The DataSet stores the "cache" of
records. &nbsp; It is&nbsp;normally destroyed when the
transaction
is committed or rolled back. &nbsp;When the DataSet is destroyed,
so
are all the records within it and any attempt to access them will
result in a fail fast exception (unlike pseudo POJO based ORMs).<br>
<br>
It
is also possible to detach the DataSet&nbsp;from a transaction,
serialize it, move it to a different JVM, manipulate it without access
to the database, and then
reattach it to a different transaction. &nbsp;The DataSet
automatically
tracks the records that have changed and the previous values of fields
used for optimistic locking when &nbsp;the record is reattached.
&nbsp;This is much easier and more correct&nbsp;than
creating&nbsp;special Data
Transfer Objects (DTOs). &nbsp;For Example:-<br>
<br>
<pre>session.begin();<br><br>Department dept400a = ses.mustFind(Department.DEPARTMENT, "400"); <br>Employee emp200e = ses.mustFind(Employee.EMPLOYEE, "200"); ...<br>Department dept500a = ses.mustFind(Department.DEPARTMENT, "500");<br><br>SDataSet ds = ses.commitAndDetachDataSet();<br>ses.close();<br><br>// DataSet ds now contains the three records.<br>// ds has been detached from database, can be moved far away.<br> <br>Department d400d = ds.find(Department.DEPARTMENT, "400");<br>d400d.setDouble(d400d.BUDGET, 50001);<br><br>Department d500d = ds.find(Department.DEPARTMENT, "500");<br>d500d.deleteRecord(); // Deletion will actually happen upon reattachment.<br> <br>e200d.setReference(e200d.DEPARTMENT, d400d);<br> <br>// Reattach the dataset to the database. <br>session.begin(ds);<br>session.commit();</pre>
<br>
There
are several semantic issues that are resoved by having a well defined
concept of DataSet. &nbsp;For example, records can only reference
other
records within the same DataSet, which must be detached as a whole.
&nbsp;This resolves the issues of dangling references to
non-detached
records. &nbsp;It also clarrifies the meaning of the many end of
associations, which will be discussed below. &nbsp;(Microsoft ADO
uses
a similar approach.)<br>
<br>
DataSets&nbsp;also
provides a clean architecture for SimpleORM internally.
&nbsp;The&nbsp;two modules&nbsp;are packaged into two jars,
namely simpleorm-dataset.jar and simpleorm-sessionJdbc.jar.
&nbsp;The
DataSet module contains the record and field meta data, plus a list of
all the record instances. &nbsp; The sessionJdbc module is
responsible
for communicating with generating SQL and communicating with the Jdbc
driver. &nbsp; There is no dependency from DataSet to sessionJdbc,
so
other session modules could be developed.
&nbsp;simpleorm-dataset.jar may also be used on its own for unit
testing without a database.<br>
<br>
<h2><a class="mozTocH2" name="mozTocId784116"></a>Associations Between Records</h2>
SimpleOrm
stores relations between records in SFieldReference objects that point
directly to the referenced record and also track&nbsp;the
underlying scalar fields that make up the foreign key. &nbsp;<br>
<br>
By
default the scalar field is assumed to have the same name as the
primary key column(s) of the referenced record. &nbsp;So in the
following example the <code>EMPLOYEE.DEPARTMENT</code>
foreign key uses the <code>DEPT_ID</code> string field
because the primary key of <code>DEPARTMENT</code> is
named "<code>DEPT_ID</code>".<br>
<pre> public static final SFieldString <b>DEPT_ID</b> = new SFieldString(EMPLOYEE, "DEPT_ID", 10);<br>&nbsp;static final SFieldReference&lt;Department&gt; <b>DEPARTMENT</b> <br> = new SFieldReference(EMPLOYEE, Department.DEPARTMENT, "DEPT");<br></pre>
<br>
findReference
can then be used to retrieve the record lazily from either the database
or the DataSet if it had already been retrieved. &nbsp;(Finding a
record in the DataSet is just a pointer dereference and so very fast.)<br>
<pre> Department department = employee.findReference(employee.DEPARTMENT);<br></pre>
<br>
The normal way to find all Employees in a Department (say) is to simply
query the database<br>
<pre>&nbsp; &nbsp;...new SQuery(Employee.EMPLOYEE).eq(Employee.DEPARTMENT, department)...</pre>
<br>
But it is also possible to query a dataset directly which is useful if
the dataset is detached from the database. &nbsp;For example<br>
<pre> List&lt;Employee&gt; emps = dataset.queryReferencing(department, Employee.DEPARTMENT)</pre>
retrieves all the Employees in&nbsp;<code>department</code>
<i>that are in the dataset. &nbsp;</i>So if just
Employees with <code>.gt(Employee.SALARY,
100000).ascending(Employee.NAME)</code>
had been loaded into the dataset then just those would be returned, and
in the sorted order. &nbsp;It does not attempt to retrieve all
Employees in the department in a random order. &nbsp;In this way
datasets provide sound and useful semantics to associations. &nbsp;<code>queryReferencing</code>
can never throw a LazyInitializationException.<br>
<br>
Many to many relationships&nbsp;handled in a similar way, but
the intersection table needs to be explicitly referenced.
&nbsp;This is almost always required in practice anyway because
there is almost always important information pertaining to the
relationship itself. &nbsp;For example many Students may be
enrolled in many Subjects, but one probably wants to know the
grade achieved for each Student for each Subject. &nbsp;Likewise
many Players may play for many Teams, but one probably wants to
record the position of each Player in each Team.<br>
<p>There is no attempt to cascade deletion of parents to their
children.&nbsp; This avoids many thorny problems.&nbsp; So
it is the programmer's responsibility to explicitly delete any
dependent rows. We also recommend against declaring <code><tt>DELETE
CASCADE</tt></code> at the database level as this will
confuse
SimpleORM (and
most other tools).</p><p>It should be noted that trying to model associations as in POJOs
has major problems because&nbsp;each end of the association
normally
needs to be updated as
distinct statements:-<br>
</p><pre>&nbsp; sales.getEmployees().add(fred);<br>  fred.setDepartment(sales);<br></pre>
Some pseudo POJO&nbsp;ORMs such as Hibernate actually attempt to
map both
these statements into the one <code>EMPLOYEE.DEPARTMENT</code>
column.&nbsp; But in Java there is no need for these two statements
to
be consistent, one could write:-<br>
<pre>&nbsp; <span style="font-weight: bold;">sales</span>.getEmployees().add(fred);<br>  fred.setDepartment(<span style="font-weight: bold;">engineering</span>);<br></pre>
The semantics of the above statements is not well defined.&nbsp;
The
essential problem is that POJOs make you have to update the<code>
department.employees</code> index manually, where a database does
it
automatically.<br>
<br>
(In Hibernate the semantics seems to depend upon the order in which
they each happen to
update the database.&nbsp; One can also specify <code>inverse="true"</code>
which means just ignore one end of the association. The excellent
<span style="font-style: italic;">Hibernate In Action</span>
book is
somewhat appologetic on this point,&nbsp;
p106.) <br>
<br>
<h2><a class="mozTocH2" name="mozTocId381239"></a>Flexible Record Defintions</h2>SimpleORM's
generalized record definitions allow plug in modules to extend the
schema of a core application with extra information that they
need.&nbsp; For example, a security plug in could include code such as
the following.<br><pre><br>static final SFieldString PUBLIC_KEY ;<br>@Override void extendApplication() {<br>  PUBLIC_KEY = new SFieldString(Employee.EMPLOYEE, "PUBLIC_KEY", 200);<br>}<br>...<br>  ses.begin();<br>  Employee fred = ses.create(Employee.EMPLOYEE, "100");<br>  fred.setString(fred.NAME, "Fred");<br>  fred.setString(PUBLIC_KEY, "ae6d82f9a7e9c...");<br>  ses.commit();<br></pre>The core application calls <code>SecurityPlugIn.extendApplication</code> at the appropriate time, and the <code>PUBLIC_KEY</code> field is added. &nbsp;The plug in can then access the new field. &nbsp;The new field will be stored in the database etc. &nbsp;<br><br>The
ability to extend a core application with plug ins is important for
modern, flexible software. &nbsp;But a plug in is of little value if it
cannot store additional information in the database.
&nbsp;SalesForce.com use this technique extensively in there extension
architecture. &nbsp;This type of extenisbility cannot be achieved with
ridgid pseudo POJOs.<br><br>Another example&nbsp;of the generalized structures is the following code which&nbsp;creates 13<code> MONTH_nn</code>&nbsp;columns:-
<pre>&nbsp; SFieldBigDecimal MONTHLY_TOTALS = new SFieldBigDecimal[13];<br>&nbsp; static {<br>&nbsp;&nbsp;&nbsp;&nbsp; for (int mx=0; mx&lt;SFieldBigDecimal.length; mx++)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MONTLY_TOTALS[mx] = new SFieldBigDecimal(meta, "MONTH_" + mx);<br>&nbsp; }<br>&nbsp;&nbsp; ...<br>&nbsp; total = myRecord.getBigDecimal(MonthlyTotals[3]);<br></pre>
<p>There is nothing special about this code, it just creates
SFieldMeta
objects and references them using an array instead of individual
constants.&nbsp; Simple.</p>Basic subtypes and inheritence can be easily implemented using
SimpleORM's generalized structure as demonstrated in <code>examples.SubTypeTest</code>.
&nbsp;But more complex mapping between Java (sub)classes and SQL
tables are not supported.&nbsp; There are many subtle and confusing
issues that arrise with more complex mappings. &nbsp;And&nbsp;
simple relational Views can provide much more powerful mappings than
any ORM should that really be required.<br><h2><a class="mozTocH2" name="mozTocId719249"></a>Business
Rule
Framework</h2><p>SimpleORM<code></code> provides events that allow&nbsp;business rules to be added to database operations. &nbsp;&nbsp;In particular <code>SRecordInstance.onValidateField</code> is called each time a field is set to a value,&nbsp;<code>SRecordInstance.</code><code>onValidateRecord</code> whenever a record is about to be flushed to the database, and&nbsp;<code>SRecordInstance.</code><code>onQueryRecord</code>
when a record is read from the database. &nbsp;These can simply be
overridden to provide extra validation or other functionality.</p><p>The folowing example chekcs
that an employee's salary does not excede the department's maximum
salary. &nbsp;(It needs to be on the record flush rather than the field
to ensure that both <code>DEPARTMENT</code> and <code>SALARY</code> are valid.)</p><pre>static public class Validated extends SRecordInstance {...<br>  @Override public void onValidateRecord() {<br>    double sal = getDouble(SALARY);<br>    Department dept = findReference(Validated.DEPARTMENT);<br>    double max = dept.getDouble(Department.MAX_SALARY);<br>    if (<b>sal &gt; max</b>) throw new SException.Validation(<br>        "Salary " + sal + " is greater than the Departments Maximum " + max, max);<br>  }</pre><p>It is also possible to add validators to individual fields.</p><pre>  public static final SFieldString THIS_THAT = new SFieldString(meta, "THIS_THAT", 20)<br>   .addValidator(new SValidatorEnumeratedValues("THIS", "THAT"));</pre>
<p>If <code>SException.Validation</code> are thrown then SimpleORM adds extra information to the exception such as the record instance and the new field value. &nbsp;These can be trapped at a higher level, or simply reported directly to the end user.</p><p>This allows data oriented business rules to be associated with
the
data. No matter what modules use Employee, the salary rule will be
enforced. &nbsp;&nbsp;It is, of course, also possible to add arbitrary methods on
the
Employee class for business rules related to Employees that are
explicitly called.&nbsp;</p><p> The goal is to reduce the amount of
indirection.&nbsp; We do not recommend a DAO approach (although it
is
certainly possible).&nbsp; Try, in general, to reduce the amount of
indirection and code.&nbsp; Just get the job done directly.<br>
</p>
<p>It also should be noted that this approach means that one only
needs to add code for real business rules. The traditional approach
of writing getter and setter methods means that one needs to write two
dummy methods for <i>every</i> field in an object just to
allow for
the <i>possibility</i> of adding a rule later. This
framework makes
that unnecessary because the generalized <code><tt>get*</tt></code>
and
<code><tt>set*</tt></code> methods are used in
the same way regardless
of whether
there is a validation&nbsp;<code></code>method is actually written for the
field. In
practice this substantially reduces the amount of code that needs to
be read.
</p><h2><a class="mozTocH2" name="mozTocId975885"></a>Configuration</h2>
There isn't any. &nbsp; &nbsp;At least nothing beyond occasionally setting a property on a class.<br><br>SimpleORM does not care where the DataSource comes from. &nbsp;The user simply&nbsp;writes a small method such as <code>TestUte.initializeTest</code> to provide it.<br><br>SLog.setLogClass can be used to provide more complex logging, the provided SLogSlf4j interface is recommended. &nbsp; <br><p>SimpleORM itself is&nbsp; built with Ant.&nbsp; But it
minimizes the
reliance on Ant so as to be easy to use with IDEs.&nbsp; For
example,
the test cases are classes with a main method, and there is one TestAll
case that simply calls the others.<br>
</p>There should be a single source of truth for the database table
definitions. If most of the development is to be done with SimpleORM
then this source of truth should be the SimpleORM Record definitions.<tt>&nbsp;
SRecordMeta.createTableSQL</tt> returns a string that
contains the <tt>CREATE TABLE</tt> statement. It is also
possible to
augment and override the basic SimpleORM definitions with database
specific physical clauses. This makes it easy to generates a complete
<tt>.sql</tt> script that recreates the database.<br>
<p>
There is also a separate SimpleORMGenerate package that can generate
SimpleORM definitions from an existing database schema.<br>
</p>
<p>Tight integration with and Entity-Relationship style tool
remains
future work. Existing tools do not seem to have the flexibility to
add SimpleORM specific annotations. UML tools tend to be too low
level for database modeling.
</p>
<p>If you wish to temporarily extend SimpleORM itself then note
that
most local variables and methods have package scope rather than
private scope, and the .jar file is not sealed. Thus rather than
changing the actual source, you can usually just poke extra classes
into the <tt>SimpleORM.core</tt> package and then access
everything
with static methods. This is obviously not supported, and you should
make sure that your changes are folded back into the main source.<br>
</p>
One more feature is a convenient way to bulk load data into the
database, which is particularly useful for testing. This is provided
in the <tt>DataLoad</tt> class. The generalized design
makes this
easy to implement and use.<br><br>=============== END of REVISION =================
<h2><a class="mozTocH2" name="mozTocId627792"></a>Performance
and
Benchmarks</h2>
Last but by no means least it is very important that an ORM not add
any significant overheads to an application that is running in an
enterprise application. To check that that is the case a careful
series of benchmarks have been constructed.
<p>The tests are based on the classic Department/Employee schema.
Rows
are inserted, queried and updated, both sequentially and randomly. The
results are for PostgreSQL. (It would be interesting to see how they
compare with other datbases but care is required, eg. HSQL has
checkpointing overhead.) </p>
<p>The tests were done on a single PC with no network overheads.
Each
test is run in its own single transaction. All the benchmarks
actually do something, eg. calculate a total salary and budget, so
that one can have confidence that they are all actually working
correctly.</p>
<p>The tests and times are are as follows. All times are quoted
in
terms
of the number of milliseconds per iteration, with at least 1,000
iterations depending on the test. (The times vary about 10% between
runs due to database buffer state etc.)</p>
<p>
<table border="1">
<tbody>
<tr>
<td>Test</td>
<td>Description</td>
<td>Raw JDBC</td>
<td>SimpleORM</td>
</tr>
<tr>
<td>Insert</td>
<td>Insert Emp
rows.</td>
<td>2.4</td>
<td>2.5</td>
</tr>
<tr>
<td>Query Sequential</td>
<td>Retrieve all the Employees and Departments
sequentially.</td>
<td>0.31</td>
<td>0.39</td>
</tr>
<tr>
<td>Query Random</td>
<td>Query the Employees
randomly.</td>
<td>5.4</td>
<td>1.7</td>
</tr>
<tr>
<td>Query Field</td>
<td>Queries five fields in one row,
repeatedly.</td>
<td>0.12</td>
<td>0.0050</td>
</tr>
<tr>
<td>Update Random</td>
<td>Give random Employees pay
rises.</td>
<td>7.8</td>
<td>3.4</td>
</tr>
<tr>
<td>Update Bulk</td>
<td>Update all rows in single Update
statement. </td>
<td>0.17</td>
<td>0.18</td>
</tr>
</tbody>
</table>
</p>
<p>The first result is that SimpleORM adds no measurable overhead
to
using raw JDBC. The queries for the Insert and QuerySequential were
very similar, and so were the results. The over head for building the
<tt>SRecordInstant</tt> structure and adding it to the
cache was a
negligable 0.1 milliseconds/record.</p>
<p>The cache then made SimpleORM over 3.0 milliseconds per record
<em>faster</em> than JDBC on the QueryRandom and
UpdateRandom trials
because the cache minimized expensive database hits. If a network had
been used the reduction in database hits would have produced even more
dramatic improvements. SimpleORM is also faster for repeated field
access, which is significant because there are typically many fields
per record.</p>
<p>The bulk update test updates many rows with one SQL statement.
The
times are the same because they are the same SQL statement. But note
that
this is an order of magnitude faster than using individual update
statements for each row, even without the network overhead. Thus it
is important that an ORM allow the buffer to be flushed and purged so
that this can be done safely.</p>
&lt;&gt;But probably the biggest performance issue is not
tested by the
benchmark, namely transaction management. JDBC programmers on large
systems tend to loose track of the connection object across modules,
and so create far too many transactions. EJB users often use CMT,
which also tends to produce far too many transactions (eg. the
Weblogic demos). By associating the connection with the current
thread, but still giving explicit control over transactions, SimpleORM
encourages transaction structures that are efficient and correct.
This performance consideration can dwarf all others<br>
<br>
<h2><a class="mozTocH2" name="mozTocId484049"></a>Conclusion</h2>
<p>A good object-relational mapping tool helps manage the
interaction between objects in memory and data in a database.
SimpleORM achieves this by providing a thin layer over JDBC. It
provides methods to retrieve data into objects and flush changes
back to the database. Objects in memory are indexed to ensures
that each row in the database is represented by at most one
object in memory.</p>
<p>SimpleORM provides <span style="font-style: italic;">Automated</span>
persistence, but not <span style="font-style: italic;">Transparent</span>
persistence.&nbsp; The generalized design makes it easy for it to
intercept <code><tt>get*</tt></code> and <code><tt>set*</tt></code>
methods to object
instance variables. This means that it can easily detect when objects
are changed, lazily retrieve related objects, and raise exceptions if
attempts are made to access stale or undefined data. This is all
achieved with a tiny fraction of the amount of user written code
required by other O/R systems. The small and simple design also
produces very good run time performance.</p>
<p>SimpleORM takes great care with persistent database semantics.
The
behaviour of the cache is well defined, and it is possible to both
flush and purge the cache selectively. Records are locked
appropriately, and any attempts to update unlocked data are
detected. The caching strategy allows for access to a database from
multiple JVMs and even from non-Java applications. And tracing and
error handling are taken seriously.</p>
<p>Advanced features include a well defined mechanism for
handling
inter-transaction updates as occur in typical web applications. Common
business rules can be specified declaratively, and more complex rules
can be specified a both a field and record level.<br>
</p>
<p>The ability to <span style="font-style: italic;">easily</span>
build UI layers on SimpleORM offers the possibility of building very
efficient development tools.<br>
</p>
<p>And last but not least, SimpleORM is open source, ie. free. It
can be integrated into projects without concern for licencing
costs. Moreover, great care has been taken to keep the source
code concise, transparent and documented so that it can be easily
understood. There are no nasty tricks, not even any XML and
hardly any reflection.</p>
<p>We believe that SimpleORM is a natural choice for most
Object/Relational mapping requirements.</p>
<br>
<x-sigsep></x-sigsep>
<p><i>Just because it is possible
to push
twigs along the ground with ones nose does not necessarily mean that is
the best way to collect firewood.</i></p>
Some people, when
confronted with a
problem, think "I know, I'll use XML." Now they have two problems.
-- Jamie Zawinski / James Robertson<br>
<br>
The superior pilot uses
his superior
judgment to avoid the need for his superior skills. -- Should
also apply to software!<br>
<br>
S<i>ome programmers are
clever enough to
write clever code, other programmers are clever enough not to.</i><br>
<br>
<br>
<h2><a class="mozTocH2" name="mozTocId93172"></a><br>
</h2>
<br>
<h2><a class="mozTocH2" name="mozTocId339228"></a>Hibernate</h2>
We
often compare SimpleORM to Hibernate because Hibernate has become the
most widely used ORM. &nbsp;Unlike other early open source ORMs it
takes database semantics and locking seriously. &nbsp;It is very
complex, and described in the book "Java Persistence with Hibernate" by
Bauer &amp; King which contains over 800 pages of dense text.
&nbsp;(Compared to the few pages required to document SimpleORM.)<br>
<br>
<br>
Hibernate <br>
It is full featured,&nbsp;<br>
(Hibernate is
over 2400K plus about 2000K of dependent Jars.)<br>
<br>
Given<br>
@Entity<br>
public class Event {&nbsp;&nbsp; <br>
&nbsp;&nbsp;&nbsp; @Id @GeneratedValue<br>
&nbsp;&nbsp;&nbsp;&nbsp; Long id;<br>
&nbsp;&nbsp;&nbsp;&nbsp; @Column<br>
&nbsp;&nbsp;&nbsp;&nbsp; String
title;&nbsp;&nbsp;&nbsp;&nbsp; <br>
&nbsp;&nbsp;&nbsp;&nbsp; @OneToMany(mappedBy="event",
fetch=FetchType.LAZY) // Lazy reccommened pp JPWH p570<br>
&nbsp;&nbsp;&nbsp;&nbsp; Set&lt;Activity&gt;
activities = new
HashSet();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
&nbsp;&nbsp;&nbsp;&nbsp; public Event getThis() {return
this;}<br>
}<br>
<br>
and<br>
<br>
@Entity<br>
public class Activity {<br>
&nbsp;&nbsp;&nbsp; @Id @GeneratedValue<br>
&nbsp;&nbsp;&nbsp; long id;<br>
&nbsp;&nbsp;&nbsp; @ManyToOne(fetch=FetchType.LAZY) <br>
&nbsp;&nbsp;&nbsp; Event event;<br>
&nbsp;&nbsp;&nbsp; @Column<br>
&nbsp;&nbsp;&nbsp; String name;&nbsp; <br>
}<br>
<br>
Then <br>
&nbsp;&nbsp;&nbsp;&nbsp; Event event2bPre =
(Event)session.get(Event.class, 2L);<br>
&nbsp;&nbsp;&nbsp;&nbsp; Activity act4bPre =
(Activity)session.get(Activity.class, 4L);&nbsp; <br>
&nbsp;&nbsp;&nbsp;&nbsp; assertEqual("Second Event",
act4bPre.event.title);<br>
Works.<br>
<br>
But <br>
&nbsp;&nbsp;&nbsp;&nbsp; Activity act4b =
(Activity)session.get(Activity.class, 4L);<br>
&nbsp;&nbsp;&nbsp;&nbsp;
assertTrue(act4b.event.getClass().getName().contains("Event$$EnhancerByCGLIB"));
// Ahh!<br>
&nbsp;&nbsp;&nbsp;&nbsp; Event event2aPost =
(Event)session.get(Event.class, 2L);<br>
&nbsp;&nbsp;&nbsp;&nbsp; assertTrue(null ==
act4b.event.title);<br>
Does not work -- act4b.event.title should not be null.<br>
<br>
I think that the weird proxy class is to blame.&nbsp; I have
sometimes even got Entity.getThis() != this(!).<br>
<br>
The
definitions for the classes are as recommended by the book,
AFAIK.&nbsp; I suspect that the annotations are not properly
supported/documented, and have weird JPA issues, and so I would
probably be better off sticking to XML.&nbsp; (Have not tried.)<br>
<br>
But I have spent several hours on this trivial example.&nbsp; <br>
<br>
Needless complexity is evil.<br>
<br>
<h3><a class="mozTocH3" name="mozTocId794489"></a>&nbsp;
Load, supposedly lazy</h3>
<pre> println("\n=================== Acitivity Identity ==================\n");<br> <br> Activity actC1 = (Activity)session.load(Activity.class, 1L);<br> println("ACTIVITY " + actC1.id + System.identityHashCode(actC1) + " " + actC1.id + actC1.getClass().getName());<br>//LE ACTIVITY 021886820 0simpleorm.play.hibernate1.Activity$$EnhancerByCGLIB$$ec22ae8b<br> println(" " + actC1.name);<br>// LE null <br> println(" " + actC1);<br>//LE Hibernate: select activity0_.id as id1_1_, activity0_.event_id as event3_1_1_, activity0_.name as name1_1_, event1_.id as id0_0_, event1_.title as title0_0_ from Activity activity0_ left outer join Event event1_ on activity0_.event_id=event1_.id where activity0_.id=?<br>//LE {Activity 1 FirstAct1 EVENT: First Event 9299042 simpleorm.play.hibernate1.Activity}<br> <br> println(" Act1Event " + actC1.event);<br>//LE Act1Event null<br><br></pre>
&nbsp;
<br>
http://iablog.sybase.com/paulley/2008/05/hibernate-transaction-semantics-are-critical/<br>
"The reader may very well criticize me at this point that this is
simply a bug in Hibernate, and I shouldn&#8217;t harp on it - all products
have bugs, including SQL Anywhere. I agree. My point, however, is that <strong>transactional
semantics are very difficult to get right</strong>, and the
complexities offered by Hibernate, particularly with Hibernate&#8217;s
implementation of caching to &#8220;solve&#8221; performance problems, can easily
lead an application developer down a garden path, only to discover
correctness issues after it is too late."&nbsp;
<br>
&nbsp;
<br>
&nbsp;
<p>&nbsp;<img src="http://www.uq.net.au/cgi-bin/counter/width=6/SORMWHITEPAPER" height="20" width="90">
</p>
</body></html>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.