The Concept is addressed to developers who want to make use of the IS 7 Persistent Objects Layer (Data Layer) to create new persistent objects or re-use already existing ones .
Before dealing with persistent object development Intershop recommends to be familiar with Enfinity Definition Language, EDL modeling and business object development in common.
Functionality encoded by a cartridge typically requires persistent storage of information in the database. If no suitable database tables exist, new tables have to be created. In order to make these database tables accessible to application components such as pipelets, new persistent objects need to be created at the persistence layer. It may also be necessary to implement additional manager classes, which operate on the new persistent objects.
Term | Description |
---|---|
EDL | Intershop 7 includes Enfinity Definition Language(EDL), a textual domainspecific language (DSL) for modeling persistent objects for Intershop 7. |
PO | persistent object |
CAPI | Cartridge API |
For a comprehensive introduction to EDL, see Reference - EDL , Concept - EDL Modeling and Cookbook - EDL Modeling .
For more detailed information on business object development, see Concept - Business Objects and Cookbook - Business Objects .
Persistent objects (POs) are Java objects used to store and read data to/from the database. A PO consists of four (optional five) files:
PO
, such as ProductPO.java
.*.orm
, such as ProductPO.orm
.POFactory
, such as ProductPOFactory.java
.POKey
, such as ProductPOKey.java
.POAlternateKey
, such as
Preference
DefinitionPOAlternateKey
. java
.These classes are usually part of the internal package of a cartridge and not exposed publicly. Apart from this abstract base classes of persistent objects may participate in the public API.
Many POs in Intershop 7 are derived from the general base classes RegionalSettingsPO
or ExtensibleObjectsPO
, which are both part of com.intershop.beehive.core.capi.domain. PersistentObjectPO
automatically provides a range of attributes, ensuring these attributes are defined in a way compatible with Intershop 7 mechanisms and processes. Attributes automatically provided by RegionalSettingsPO
include:
UUID
as primary keyoca
)domainID
attribute RegionalSettingsPO
automatically inherit methods to get and set the domainID
, as well as methods to get and set domain instances for a persistent object referenced by the domainID
.Domain getDomain() void setDomain(Domain aDomain) String getDomainID() void setDomainID(String aDomainID)
In addition, ExtensibleObjectPO
(a sub-class of RegionalSettingsPO
) provides functionality which allows developers to add custom attributes at runtime, as described in Reference - Attributes of Persistent Objects- Extensible Object Attributes.
The following EDL snippet defines a persistent object, including a relation respective a dependency to other objects.
namespace com.intershop.training.bc_warehouse.internal { orm class WarehousePO extends ExtensibleObjectPO implements Warehouse { index(addressID); attribute name : string<256> required; attribute location : string<256>; attribute capacity : int; attribute description : string localized; attribute addressID : uuid; dependency address : Address handler "com.intershop.beehive.core.capi.profile.ProfileMgr" { foreign key(addressID); } relation stockItemPOs : StockPO[0..n] inverse warehousePO implements stockItems; } }
Typically, your object model consists of a set of POs that do not stand alone, but are connected in various ways. In the object model, these connections between classes are modeled as relations or dependencies.
The following sections describe all aspects of relationships in more detail.
Intershop 7 is based on two basic relationship types:
Relations
A relation expresses a bi-directional semantic connection between classes.
The following EDL snippets define a relation between the two objects WarehousePO
and StockPO
. Relation definition at the WarehousePO
side:
relation stockItemPOs : StockPO[0..n] inverse warehousePO implements stockItems;
Relation definition on the StockPO
side:
relation warehousePO : WarehousePO[1..1] inverse stockItemPOs implements warehouse { foreign key(warehouseID) -> (UUID); }
Dependencies
A dependency expresses a uni-directional relationship between classes. "Uni-directional" in this context means that the relationship is only navigable in one direction. Dependencies are commonly used to build relationships between persistent classes of different cartridges.
The following EDL snippet (from WarehousePO
) models the dependency connecting WarehousePO
and Address
. It expresses that we only navigate the relationship from WarehousePO
to Address
, never the other way around.
dependency address : Address handler "com.intershop.beehive.core. … capi.profile.ProfileMgr" { foreign key(addressID); }
Compared to relations, dependencies are internally treated in a very different way. Dependencies are not registered in the ORM deployment descriptor file and they do not require to re-create the referenced class. Therefore, dependencies can be used to link custom PO classes to CAPI objects that Intershop 7 provides (such as Product
).
Depending on the multiplicity types on each side of the relationship, three basic kinds of relationships are commonly distinguished:
For relations the multiplicity is declared in the definition. For dependencies the multiplicity is defined only indirect by the code
One-to-many relations are common in relational database design, allowing the representation of complex data sets in an economic way. For example, each instance of WarehousePO points to the instances of StockPO that belong to it. Likewise, each StockPO
points to its WarehousePO
.
To relate warehouse and stock data as described, a special column is needed in the StockPO
table, identifying the correct WarehousePO
instance for each StockPO
instance. This special column acts as the foreign key, and it commonly maps onto the primary key of the related table, in our example, the WarehousePO
table.
Returning to the representation of relations, the implication is that you must introduce a special attribute that serves as the foreign key and maps onto the primary key attribute of the associated PO class. In a one-to-many relation, the foreign key is defined within the class on the many-side, e.g., StockPO
, and it maps onto the primary key of the class on the one-side, e.g., WarehousePO
.
EDL snippet from the many-side, e.g., StockPO
:
index(warehouseID); attribute warehouseID : uuid required readonly; relation warehousePO : WarehousePO[1..1] inverse stockItemPOs implements warehouse { foreign key(warehouseID) -> (UUID); }
Note
One-to-one relations are organized in precisely the same way, with the exception that the foreign key can be assigned to either class.
For one-to-many relations, the code generator creates special methods for both classes involved, the class on the many-side and the class on the one-side. Methods differ depending on which class is considered.
Many-Side Methods
The methods generated for the class on the many-side, e.g., StockPO
, allow you to access the role representing the class on the one-side, e.g., WarehousePO
.
public WarehousePO getWarehousePO()
One-Side Methods
For the class on the one-side, e.g., WarehousePO
, the code generator creates a method to access associated the role representing the class on the many-side, e.g., StockPO
:
public Collection getStockItemPOs() public Iterator createStockItemPOsIterator()
In addition, the code generator creates relationship wrapper methods. These methods check whether a particular element participates in the relationship:
public boolean isInStockItemPOs(StockPO anElement) public int getStockItemPOsCount()
Dependencies are unidirectional relationships to classes which implement PersistentObject
. Unidirectional means that they can be traversed only in one direction: from the source class to the target class. The target class always has a multiplicity of 0..1. In the EDL snippet from WarehousePO
below, the dependency expresses that each instance of WarehousePO
is associated with exactly one instance of Address
. In contrast to normal association relationships, the dependency does not express the opposite statement, namely that each instance of Address
can be associated with one or more instances of WarehousePO
. The Address
has no knowledge about the existence of the WarehousePO
and the relationship between both entities. However, if the business logic requires this feature, it would be possible to determine all instances of WarehousePO
which are related to a given address by using a query. The result may contain many instances or just one single instance if this is restricted by the implementation. Thus, dependent on the implementation a dependency may be a one-to-many or one-to-one relationship.
dependency address : Address handler "com.intershop.beehive.core.capi.profile.ProfileMgr" { foreign key(addressID); }
The code that code generator creates for dependencies only affects the source class, not the target class. Therefore, dependencies are particularly useful if you create new POs coupled with existing objects (such as Address
) which you are unable to recompile or change.
Another common use case is hiding the relation to the One-Side-PO of a PO located in the same cartridge, because it is not intended to make this relation "public" for this PO. In that case the handler must not be defined and the factory of the PO is called to locate the related instance by a primary key lookup. Another use case is avoiding the creation of bi-directional relations to mass data.
Note
A foreign key is needed inside the source class, e.g., Warehouse
, mapping onto the primary key of the target class, e.g., Address
. The foreign key enables you to identify the particular target class instance, e.g., a particular product, to which a source class instance , e.g., a particular WarehousePO
, is connected.
The following EDL snippet from WarehousePO
declares the foreign key attribute addressID
:
attribute addressID : uuid; index(addressID);
Typically, a dependency connects a custom PO with other persistent objects represented by a CAPI interface.
In our example, the dependency connects the custom PO WarehousePO
with the standard Intershop 7 CAPI interface Address
. When modeling a dependency like this, you have to provide the symbolic name of the manager which provides access to the object behind the CAPI interface.
The code generator uses the provided manager to create the accessor methods.
Note
The handler class has to provide a resolve<ClassName>FromID() method. Going by example, when constructing a dependency from a custom class to the CAPI interface Address
, the respective manager would be the ProfileMgr
(which provides a resolveAddressFromID()
method).
In addition, the business object behind the interface has to provide a getUUID()
method. Otherwise, compile errors may result since the setter method of the foreign key calls getUUID()
of the interface.
The code generator creates a getter method which allows you to access the target class instance to which a particular source class instance is connected and a setter method to assign a source instance to a target instance. For example, for the dependency connecting WarehousePO
with Address
, the following methods are generated:
public Address getAddress() { if (getAddressIDNull()) { return null; } ProfileMgr factory = (ProfileMgr) NamingMgr.getInstance().lookupManager(ProfileMgr.REGISTRY_NAME); return (Address) factory.resolveAddressFromID(getAddressID()); } public void setAddress(Address address) { if (address == null) { setAddressIDNull(true); } else { setAddressID(address.getUUID()); } }
Many-to-many relations are common in Intershop 7 applications. For example, consider the relation between product and supplier: each supplier may deliver more than one product, while each product may be delivered by more than one supplier. Or, product bundles, which combine products to form a new product, e.g., a computer, mouse, and monitor may be bundled into a complete computer package. Here too, a many-to-many relationship exists, because each product bundle can contain many different products, and each product can be part of different product bundles.
Note
This relationship is reflexive, because product bundles are also products. Intershop 7 uses so-called assignment classes to simulate many-to-many relationships.
Consider the figure below which expresses the relationship between products and product bundles (which again are products). Via the assignment class BundleAssignmentPO
, product bundles are paired with products by pairing values for bundleID
(a foreign key attribute referencing UUIDs of product bundles) with values of productID
(a foreign key attribute referencing products that participate in a bundle). Both foreign keys map to the primary key of Product
( UUID
).
Note
The BundleAssignmentPO
is a PO but is not derived from ExtensibleObjectPO
. BundleAssignmentPO
has a natural primary key, composed of the two foreign key attributes.
There are many use cases where a persistent object implements a CAPI interface. This interface can be modeled as well or imported as an external type. An example of a set of CAPI interfaces modeled on top of custom POs is shown in the following EDL snippets:
import "enfinity:/demo/edl/com/intershop/demo/capi/Warehouse.edl"; namespace com.intershop.demo.internal { orm class WarehousePO extends ExtensibleObjectPO implements Warehouse { ... } } import "enfinity:/demo/edl/com/intershop/demo/capi/Stock.edl"; namespace com.intershop.demo.internal { oca orm class StockPO implements Stock { ... } }
The PO StockPO implements the interface Stock.
The interfaces are part of the capi package while the implementation classes are part of the internal package.
Note that POs and interfaces use different base classes:
The PO WarehousePO inherits from the abstract class com.intershop.beehive.core.capi.domain.ExtensibleObjectPO
.
The interface Warehouse extends the interface com.intershop.beehive.core.capi.domain.ExtensibleObject
.