class Loader
extends java.lang.Object
The structure of the data in the database is assumed to be:
id
parent__id
, which is a foreign key to the primary table's
primary key id
list__index
, representing the 0-based index of that row's
data in the DMO's corresponding extent field(s) of that extent size
The naming convention for each secondary table is: <primary_table>__<extent_size>
. All
of the DMO's extent fields of that extent size are represented together in this table, one
element (each) per row.
Each secondary table must contain a number of rows equal to the size of the extent field, for each primary key in the primary table.
Consider, for example, a DMO with a backing, primary table foo
. The DMO has one scalar
field (bar
), and three extent fields (bat
, bax
, and baz
). Each
extent field is of size 10.
The primary table foo
will have a primary key column and one data column, bar
:
column | type | modifiers | purpose --------+--------+-----------+----------------------- id | bigint | not null | surrogate primary key bar | (any) | | business data
A secondary table foo__10
stores the extent field data. In addition to its foreign
key and list index columns, the table has 3 business data columns: bat
, bax
,
and baz
:
column | type | modifiers | purpose -------------+---------+-----------+----------------------- parent__id | bigint | not null | surrogate primary key bat | (any) | | business data bax | (any) | | business data baz | (any) | | business data list__index | integer | not null | surrogate primary key
This secondary table will contain 10 records for each record in the primary table. Each such
set of 10 records will have the same foreign key parent__id
, but a different list__index
value, the latter ranging from 0 through 9.
Denormalized extent fields will have each of their elements represented by a column in the primary table. They are treated as scalar fields for loading purposes.
The above table structure is mapped to a one-dimensional array of Object
instances
in the corresponding DMO instance. The array is organized as follows:
The layout of the DMO's data array and the mapping to the corresponding table data for the example above would be as follows:
index | table.column | list index -------+--------------+------------ [00] | foo.bar | n/a [01] | foo__10.bat | 0 [02] | foo__10.bat | 1 [03] | foo__10.bat | 2 [04] | foo__10.bat | 3 [05] | foo__10.bat | 4 [06] | foo__10.bat | 5 [07] | foo__10.bat | 6 [08] | foo__10.bat | 7 [09] | foo__10.bat | 8 [10] | foo__10.bat | 9 [11] | foo__10.bax | 0 [12] | foo__10.bax | 1 [13] | foo__10.bax | 2 [14] | foo__10.bax | 3 [15] | foo__10.bax | 4 [16] | foo__10.bax | 5 [17] | foo__10.bax | 6 [18] | foo__10.bax | 7 [19] | foo__10.bax | 8 [20] | foo__10.bax | 9 [21] | foo__10.baz | 0 [22] | foo__10.baz | 1 [23] | foo__10.baz | 2 [24] | foo__10.baz | 3 [25] | foo__10.baz | 4 [26] | foo__10.baz | 5 [27] | foo__10.baz | 6 [28] | foo__10.baz | 7 [29] | foo__10.baz | 8 [30] | foo__10.baz | 9
The purpose of this flat data structure is to make copying the internal data of DMOs within the FWD runtime as efficient as possible.
Important: this loader will only work properly if the above assumptions of table structure and naming convention are correct. Relying on these assumptions allows us to improve loading performance by composing more compact SQL query statements and using positional reads from the queries' result sets, rather than using column name lookups.
TODO: better re-use of prepared statements; currently, we prepare new statements for each load, relying on connection pool statement caching for performance.
Modifier and Type | Field and Description |
---|---|
private Session |
session
Active database session
|
Constructor and Description |
---|
Loader(Session session)
Constructor which stores a reference to the active database session.
|
Modifier and Type | Method and Description |
---|---|
(package private) static java.lang.String[] |
composeLoadStatements(java.lang.String table,
PropertyMeta[] allPropMeta,
boolean temp)
Compose the SQL statements used to load a single DMO from the database, given its primary
key.
|
(package private) <T extends BaseRecord> |
load(java.lang.Class<T> dmoClass,
java.lang.Long id)
Load a single record from the database, given its DMO class and primary key.
|
(package private) <T extends BaseRecord> |
load(T dmo,
java.lang.Long id)
Load a single record from the database into the given DMO instance, with the given primary
key.
|
(package private) static int |
readExtentData(BaseRecord r,
java.sql.ResultSet rs,
PropertyMeta[] props,
int start)
Read all values from the given result set for the current series of extent fields.
|
private static int |
readScalarData(BaseRecord r,
java.sql.ResultSet rs,
int rowOffset,
PropertyMeta[] props)
Read all values from the given result set for the series of scalar fields.
|
private final Session session
Loader(Session session)
session
- Active database session.static java.lang.String[] composeLoadStatements(java.lang.String table, PropertyMeta[] allPropMeta, boolean temp)
table
- SQL name of the primary table. The naming convention assumed for secondary tables
is: <primary_table>__<extent_size>
.allPropMeta
- Array of PropertyMeta
objects in their natural order.temp
- Pass true
for _temp
DMOs. In this case the select
statement
will be build with the mandatory _multiplex
field for temp-tables.PropertyMeta.compareTo(PropertyMeta)
<T extends BaseRecord> T load(java.lang.Class<T> dmoClass, java.lang.Long id) throws PersistenceException
null
will be returned.
TODO: a SQL statement is prepared and closed for each load statement executed by this method. We rely on statement caching at the connection pool level to make this performant, but perhaps there is some predictive analysis we can do to make this more efficient.
dmoClass
- DMO implementation class.id
- Surrogate primary key.dmoClass
, loaded with data from the database, or null
if the record was not found. The DMO is instantiated either way, but it will
be discarded if the record is not found.PersistenceException
- if an error occurred accessing the database.<T extends BaseRecord> T load(T dmo, java.lang.Long id) throws PersistenceException
null
will
be returned.
TODO: a SQL statement is prepared and closed for each load statement executed by this method. We rely on statement caching at the connection pool level to make this performant, but perhaps there is some predictive analysis we can do to make this more efficient.
dmo
- DMO implementation class.id
- Surrogate primary key.dmoClass
, loaded with data from the database, or null
if the record was not found. The DMO is instantiated either way, but it will
be discarded if the record is not found.PersistenceException
- if an error occurred accessing the database.private static int readScalarData(BaseRecord r, java.sql.ResultSet rs, int rowOffset, PropertyMeta[] props) throws java.sql.SQLException, PersistenceException
ResultSet
's cursor is positioned on the right row.r
- The Record
into which data is loaded.rs
- Result set from the database query.rowOffset
- The offset where the actual data begins. If there record starts at the first column
use 0. If the record data is prepended by a single column (Ex: primaryKey), pass 1.props
- Array of property metadata objects.java.sql.SQLException
- if an error occurred reading the data.PersistenceException
static int readExtentData(BaseRecord r, java.sql.ResultSet rs, PropertyMeta[] props, int start) throws java.sql.SQLException, PersistenceException
r
- The Record
onto which data is loaded.rs
- Result set from the database query.props
- Array of property metadata objects.java.sql.SQLException
- if an error occurred reading the data.PersistenceException