- 1 Mapping indexed 1:n relations
- 2 Mapping non-indexed relations
- 3 Mapping indexed many-to-many (nm) relations
- 4 Mapping non-indexed nm relations
- 5 Force a join table for 1:n relations
- 6 Embedded components
- 7 Setting the index column name
- 8 Storing container relation
- 9 Storing external references (to non-persisted objects), customizing persisting references
- 10 Storing references to Ecore Model Elements
Mapping indexed 1:n relations
This is the default for an EMF association. Indexed 1:n relations are mapped using the Hibernate list tag. This results in an additional column in the database to keep track of the order in the list.
Mapping non-indexed relations
EMF only supports indexed 1:n and n:m relations. However many times the indexing of relations is not relevant for the model and the index gives a performance penalty when updating the relation in the database. By using annotations in the model it is possible to specify for a model element that it should not be indexed in the database.
As an example see the the following xml schema model:
<xsd:element name="containedItem" type="this:ContainedItem" maxOccurs="unbounded"> <xsd:annotation> <xsd:appinfo source="teneo.jpa">@OneToMany(indexed=false)</xsd:appinfo> </xsd:annotation> </xsd:element>
In this example it is specified that the item member (an elist) should not be indexed. For EMF and java the relations will be represented as an elist, however the order of the elements in the list is not saved in the database.
By adding the indexed annotation the system can identify that this relation should not be ordered/indexed in the database. This means that no index column is added to the relevant tables. This also means that the order of a list when it is saved is not guaranteed to be the same as the order when the list is retrieved..
The indexed attribute is also supported on the ManyToMany annotation.
The above annotation can also be added in UML. Please use the same values for the source and the key.
The indexed annotation does not exist in the EJB3 spec and is a Teneo extension.
Mapping indexed many-to-many (nm) relations
A normal approach to model a nm relation in a database is to use a join table which contains foreign keys to both sides of the nm relation. EMF many-to-many relations have the additional special feature that they are indexed. So the join table should also contain the index for both sides of the nm relation. However Hibernate can not handle indexed collections on both sides of the nm relation.
Therefore to enable support for nm relations in Hibernate two join tables are created, one for each side of the relation. Although this is less efficient it at least makes it possible to model nm relations.
As an example consider the relation between books and authors. One author can write multiple books and a book can have multiple authors. To support this model 4 tables are created: Author to store authors, Book to store Books, AuthorBook to store the elist with books in the author object and BookAuthor to store the elist with authors in the book object.
Mapping non-indexed nm relations
Non-indexed relations can be persisted using 1 join table. To specify that a nm relation is non-indexed both sides of the relation should have the indexed is false annotation. If Teneo encounters a non-indexed nm relations then only 1 join table will be used.
Force a join table for 1:n relations
In a relational database 1:n relations are often modeled by adding a foreign-key (to the parent) in the child table/object. The disadvantage of this approach is that duplicates are not supported. To support duplicates in an elist a join table is required. To signal to Teneo, that a join table should be used, the annotation as in this example can be used:
<xsd:element name="joinedItem" type="xsd:anyURI" ecore:reference="this:Item" maxOccurs="unbounded"> <xsd:annotation> <xsd:appinfo source="teneo.jpa">@OneToMany(indexed=false unique=false)</xsd:appinfo> </xsd:annotation> </xsd:element>
Based on this annotations Teneo will generate a join table for this model. Note that this annotation can be combined with the indexed annotation.
Note that currently hibernate does not support an indexed bi-directional relation with a join table. For these type of relations a join table makes less sense because of the bidirectional nature duplicates are not possible anyway.
The unique annotation does not exist in the EJB3 spec and is a Teneo extension.
Teneo supports relations in which the child should be embedded in the parent table. Embedding is defined on type level using an annotation:
<xsd:element name="firstEmbedded" type="this:Embeddable"> <xsd:annotation> <xsd:appinfo source="teneo.jpa">@Embedded</xsd:appinfo> </xsd:annotation> </xsd:element>
By adding the Embedded is true annotation to a type this type will always be embedded in its parent table. The column names for an embedded type are not made unique. To prevent column name clashes you should add AttributeOverride(s) annotations (see the EJB3 spec). Only embedded component types with primitive properties have been tested, embedded components with associations to other types may work but this has not been tested.
Setting the index column name
When mapping a list there will be both a foreign key column (to point from the element in the list to the owner of the list), but also an index column to hold the index in the list. Teneo uses a default naming strategy which ensures unique index column names. When you want to control the index column name in more detail you can set the @ListIndexColumn(name="my_index_column_name") annotation.
Storing container relation
From the 0.2.1 release the system will also store the relation from a eobject to its container. This relation is stored in three fields in the database table:
- ECONTAINER_CLASS: stores the type of the container eobject
- ECONTAINER: stores the database identification of the container eobject (as a varchar),
- ECONTAINER_FEATURENAME: stores the name of the container feature which holds the eobject
See also the ECONTAINER_FEATURE_PERSISTENCE_STRATEGY option here.
These fields are not present as properties in the hibernate.hbm.xml file. These fields are added at startup because at that moment it is possible to determine which classes are contained in other classes.
For normal single column identifiers the system will use the Hibernate toString and fromStringValue methods of the org.hibernate.type.NullableType class.
In case the identifier type is not a subclass of the hibernate NullableType (for example in case of a composite-id) then the system will set the ECONTAINER field differently. In this case the ECONTAINER field will contain the classname of the identifier object, a separator (a ;) and the value of the identifier object as a String. The String value of the identifier object is obtained by calling toString(). When reading the ECONTAINER field the system will use a constructor with a single String parameter. So the identifier class must have a constructor with a single String argument. In addition the toString method and this constructor must correctly translate to/from a String.
These fields do not enforce referential integrity constraints, these are enforced by the relation from the container to the contained eobject.
The container is eagerly loaded, so when a child object is loaded its container is also loaded. Note that the collections in the container are lazily loaded. It was required to eagerly load the container because the EMF editors access this value.
The relation to the container is stored separately from any 1:n relation which is stored from the container to the contained object. This is required because one eobject type can be contained by multiple other eobject types which are not known when the hibernate.hbm.xml is generated.
The econtainer mapping creation can be disabled by setting the PersistenceOptions.DISABLE_ECONTAINER_MAPPING to true, see also here.
Storing external references (to non-persisted objects), customizing persisting references
Teneo supports references to objects which are not part of the model which is being persisted in the Teneo datastore. These references are stored as EMF URI's in a string/varchar column in the database (this is customizable see below).
For example: assume that an Object A has an efeature b pointing to B. A is persisted in the database while B is loaded from a XML file. Then by annotating the efeature b with the annotation @External Teneo will store the reference of B (the URI) in a varchar column in the database. Then when retrieving A, B is created as a proxy object.
- The External annotation can be used on single EReference efeatures as well as on many=true (lists) EReferences
- External references can only be persisted when the refered object is contained in a resource.
Loading the refering object from the database should always occur using a resource which is contained in a resource set. Otherwise the external references can not be resolved.
- The External annotation has a type attribute which allows you to specify a specific Hibernate UserType class to store the reference to the non-persisted object. The default user type (implemented by Teneo) is the org.eclipse.emf.teneo.hibernate.mapping.ExternalType. This UserType will persist the URI and create a proxy object when reading from the database. However, using your own hibernate UserType you can choose your own persistence logic and even use other types of columns to persist the external reference.
- An eclass can also be annotated with @External.
- In that all ereferences to that EClass will automatically get the @External annotation.
- No table is created for the EClass, it is assumed to be stored externally.
Storing references to Ecore Model Elements
A common use case is to refer to the model itself from your domain model. Concretely this means that your domain object references an EClass, EPackage or EReference. Teneo supports persisting Ecore models themselves. However, for this case where only the reference to the model needs to be stored and not the whole model, this is too much. In addition it is not correct to persist model elements in the database which are also initialized in-memory directly by the EPackage implementation classes. This because loading the model from the database will 'clash' with the model which is present in memory.
To support the case of only persisting references Teneo has a special custom user type: org.eclipse.emf.teneo.hibernate.mapping.EcoreModelElementType. This user type can be specified for an EReference using the type annotation. Here is an example of setting the usertype on EReferences to model elements (in this case the Ecore Change model is being annotated):
<?xml version="1.0" encoding="ISO-8859-1"?> <persistence-mapping xmlns="http://www.eclipse.org/emft/teneo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <epackage namespace-uri="http://www.eclipse.org/emf/2003/Change"> <eclass name="FeatureChange"> <property name="feature"> <type type="org.eclipse.emf.teneo.hibernate.mapping.EcoreModelElementType"/> </property> </eclass> <eclass name="ListChange"> <property name="feature"> <type type="org.eclipse.emf.teneo.hibernate.mapping.EcoreModelElementType"/> </property> </eclass> <eclass name="FeatureMapEntry"> <property name="feature"> <type type="org.eclipse.emf.teneo.hibernate.mapping.EcoreModelElementType"/> </property> </eclass> </epackage> </persistence-mapping>
The custom user type used here will persist the reference to the model element as a varchar/string.
Note that instead of using the Type annotation one can also use the External annotation (see section above) with the type attribute set to: org.eclipse.emf.teneo.hibernate.mapping.EcoreModelElementType.