Data Access (ORM)
The JDistil framework uses data managers and data objects to support access to application specific data. The following sections provide detailed
information and examples supporting both these object types.
Data Objects
Data objects are uniquely identifiable domain objects that provide support for modification and version tracking. Application specific data objects are implemented
by extending the "com.bws.jdistil.core.datasource.DataObject" class and providing domain specific properties. The "com.bws.jdistil.core.datasource.DataObject"
class provides unique ID and version properties and a protected method that can be used in domain specific property setters to update the modified status
of the domain object using the current and new property values.
The following highlights the methods provided by the "com.bws.jdistil.core.datasource.DataObject" class.
public abstract class DataObject<I> implements Serializable { public I getId(); public void setId(I newId); public Long getVersion(); public void setVersion(Long newVersion); protected void setModified(boolean newModifiedStatus); public boolean isModified(); protected void updateModifiedStatus(Object oldValue, Object newValue); public void clear(); public String toString(); }
The following provides an example data object implementation.
public class Company extends DataObject<Integer> { private String name = null; private Boolean active = null; private Date created = null; private Integer type = null; private boolean isDeleted; private List<Integer> productIds = new ArrayList<Integer>(); public String getName() { return name; } public void setName(String name) { updateModifiedStatus(this.name, name); this.name = name; } public Boolean getActive() { return active; } public void setActive(Boolean active) { updateModifiedStatus(this.active, active); this.active = active; } public Date getCreated() { return created; } public void setCreated(Date created) { updateModifiedStatus(this.created, created); this.created = created; } public Integer getType() { return type; } public void setType(Integer type) { updateModifiedStatus(this.type, type); this.type = type; } public boolean isDeleted() { return isDeleted; } public void setIsDeleted(boolean isDeleted) { this.isDeleted = isDeleted; } public List<Integer&t; getProductIds() { return Collections.unmodifiableList(productIds); } public void setProductIds(List<Integer> productIds) { // Update modified status updateModifiedStatus(this.productIds, productIds); // Clear existing values this.productIds.clear(); // Set new values if (productIds != null) { this.productIds.addAll(productIds); } } }
Data Managers
Data managers are domain specific implementations of the "com.bws.jdistil.core.datasource.IDataManager" interface. This interface defines standard methods
for retrieving, saving and deleting domain specific data objects. Technically, data manager implementations can leverage any
storage medium for data persistence but the classes provided by the JDistil framework are focused on database persistence. Specifically, the framework
provides the "com.bws.jdistil.core.datasource.database.DatabaseDataManager" and "com.bws.jdistil.core.datasource.database.BoundDatabaseDataManager"
classes.
The "com.bws.jdistil.core.datasource.database.DatabaseDataManager" abstract class is a direct implementation of the "com.bws.jdistil.core.datasource.IDataManager" interface and uses a template design pattern to delegate creation of prepared statements and result sets to descendant classes. This provides the most control over SQL creation and result set processing but requires more work during implementation when compared to the "com.bws.jdistil.core.datasource.database.BoundDatabaseDataManager" class.
The "com.bws.jdistil.core.datasource.database.BoundDatabaseDataManager" abstract class is an extension of the previously mentioned "com.bws.jdistil.core.datasource.database.DatabaseDataManager" class and uses object relationship mapping (ORM) information to create the prepared statements and result sets required by the parent class. The ORM information consists of bindings used to map tables to domain objects and columns to domain object properties. Bindings are also provided for associative and dependent domain object relationships. Associative bindings support a many-to-many relationship between the primary data object and another independently managed data object. Dependent bindings support hierarchical object graphs where the dependent domain objects are managed as part of the primary domain object.
Another benefit of using the "com.bws.jdistil.core.datasource.database.BoundDatabaseDataManager" class is it supports the use of dynamic SQL when retrieving data. Specifically, it defines additional "find" methods where joins, value conditions, and order conditions can be specified programmatically. This is especially helpful when a data manager needs to support joining and filtering against tables outside the target domain.
The "com.bws.jdistil.core.datasource.database.DatabaseDataManager" abstract class is a direct implementation of the "com.bws.jdistil.core.datasource.IDataManager" interface and uses a template design pattern to delegate creation of prepared statements and result sets to descendant classes. This provides the most control over SQL creation and result set processing but requires more work during implementation when compared to the "com.bws.jdistil.core.datasource.database.BoundDatabaseDataManager" class.
The "com.bws.jdistil.core.datasource.database.BoundDatabaseDataManager" abstract class is an extension of the previously mentioned "com.bws.jdistil.core.datasource.database.DatabaseDataManager" class and uses object relationship mapping (ORM) information to create the prepared statements and result sets required by the parent class. The ORM information consists of bindings used to map tables to domain objects and columns to domain object properties. Bindings are also provided for associative and dependent domain object relationships. Associative bindings support a many-to-many relationship between the primary data object and another independently managed data object. Dependent bindings support hierarchical object graphs where the dependent domain objects are managed as part of the primary domain object.
Another benefit of using the "com.bws.jdistil.core.datasource.database.BoundDatabaseDataManager" class is it supports the use of dynamic SQL when retrieving data. Specifically, it defines additional "find" methods where joins, value conditions, and order conditions can be specified programmatically. This is especially helpful when a data manager needs to support joining and filtering against tables outside the target domain.
The following highlights the methods provided by the "com.bws.jdistil.core.datasource.IDataManager" interface.
public interface IDataManager<I, T extends DataObject<I>> { public void save(T dataObject) throws DataSourceException; public void save(T dataObject, IDomain domain) throws DataSourceException; public void save(T dataObject, boolean checkDirty) throws DataSourceException; public void save(T dataObject, boolean checkDirty, IDomain domain) throws DataSourceException; public void delete(T dataObject) throws DataSourceException; public void delete(T dataObject, IDomain domain) throws DataSourceException; public List<T> find() throws DataSourceException; public List<T> find(IDomain domain) throws DataSourceException; public List<I> findIds() throws DataSourceException; public List<I> findIds(IDomain domain) throws DataSourceException; public T find(I id) throws DataSourceException; public T find(I id, IDomain domain) throws DataSourceException; public List<T> find(List<I> ids) throws DataSourceException; public List<T> find(List<I> ids, IDomain domain) throws DataSourceException; public List<T> find(FilterCriteria filterCriteria) throws DataSourceException; public List<T> find(FilterCriteria filterCriteria, IDomain domain) throws DataSourceException; public List<I> findIds(FilterCriteria filterCriteria) throws DataSourceException; public List<I> findIds(FilterCriteria filterCriteria, IDomain domain) throws DataSourceException; }
The following provides an example data manager implementation.
public class CompanyManager extends BoundDatabaseDataManager<Integer, Company> { protected DataObjectBinding createDataObjectBinding() { // Set table name String tableName = "company"; // Create ID column binding IdColumnBinding idColumnBinding = new IdColumnBinding("company_id"); // Create and populate column bindings List<ColumnBinding> columnBindings = new ArrayList<ColumnBinding>(); columnBindings.add(new ColumnBinding("name", DbUtil.STRING, false, false, "Name")); columnBindings.add(new ColumnBinding("active", DbUtil.BOOLEAN, false, false, "Active")); columnBindings.add(new ColumnBinding("created", DbUtil.DATE, false, false, "Created")); columnBindings.add(new ColumnBinding("type", DbUtil.INTEGER, false, false, "Type")); columnBindings.add(new ColumnBinding("is_deleted", DbUtil.BOOLEAN, false, true, "IsDeleted")); columnBindings.add(new ColumnBinding("version", DbUtil.LONG, false, false, "Version")); // Create associate bindings list List<AssociateBinding> associateBindings = new ArrayList<AssociateBinding>(); // Create product ID column binding IdColumnBinding producIdColumnBinding = new IdColumnBinding("product_id"); associateBindings.add(new AssociateBinding("ProductIds", true, "company_product", idColumnBinding, producIdColumnBinding)); // Create company binding DataObjectBinding binding = new DataObjectBinding(Company.class, tableName, idColumnBinding, columnBindings, null, associateBindings); return binding; } }
The following provides an example method implementation using dynamic SQL.
public List<Action> findByTask(List<Integer> taskIds) throws DataSourceException { // Initialize return value List<Action> actions = null; if (taskIds != null && !taskIds.isEmpty()) { // Create join list List<Join> joins = new ArrayList<Join>(1); // Create join conditin JoinCondition joinCondition = new JoinCondition("bws_task_action", "action_id", Operators.EQUALS, "bws_action", "action_id"); // Create and add join to list joins.add(new Join(Join.INNER_JOIN, "bws_task_action", "bws_action", joinCondition)); // Initialize value conditions ValueConditions valueConditions = null; for (Integer taskId : taskIds) { // Create value condition ValueCondition valueCondition = new ValueCondition("bws_task", "task_id", Operators.EQUALS, DbUtil.INTEGER, taskId); // Add condition to value conditions if (valueConditions == null) { valueConditions = new ValueConditions(valueCondition); } else { valueConditions.add(Operators.OR, valueCondition); } } // Retrieve tasks actions = find(joins, valueConditions, null); } return actions; }