Jump to: navigation, search

Teneo/Hibernate/EMF Hibernate Integration Details

Setting the EPackage.Registry used by Teneo

During the mapping generation and at runtime Teneo accesses the EPackage.Registry to resolve references to EClassifier. As a default Teneo will use the global EPackage.Registry. It is possible to configure Teneo to use another EPackage.Registry. This can be done through the org.eclipse.emf.teneo.PackageRegistryProvider. This class is a singleton. You can put in your own implementation by calling the static setInstance method in this class or you can set the EPackage.Registry used by this class by doing this:

PackageRegistryProvider.getInstance().setPackageRegistry(myPackageRegistry);


Foreign-Key Constraint: deleting parent in parent-child relation (inverse=true)

For a containment list relation Teneo will generate a hibernate one-to-many mapping with cascade all, delete-orphan. Hibernate will create a foreign-key in the database from the child to the parent in the relation. When the parent is deleted then hibernate will first nullify the foreign-key column in the child before removing the child. This will not work if the foreign-key column is non-nullable.

See here for more information: hibernate forum, EMF Bugzilla.

The hibernate forum gives as a solution to set inverse=true on the one-to-many. However, inverse="true" is normally not set by Teneo because it can interfere with the EMF bidirectional relation handling.

To solve this the following solution is offered: to set inverse="true" on a relation set the mappedBy attribute of the OneToMany or ManyToMany annotations: @OneToMany(mappedBy="MyOtherSideProp", indexed=false). Note that inverse=true only works on non-indexed collections therefore indexed=false is also required.


How to do composite-ids with many-to-one

In many traditional database schemas, containment parent-child relations are modelled by defining a multi-field primary key for the child: the primary key of the child then consists of the primary key of the parent (as a foreign key) plus an additional field to make the child unique. For example an order-orderline model: the order has as a primary key an order number, the primary key of the child is then order number and order line number. Often When mapping to an existing database this same structure has to be defined in the model using jpa annotations.

This section explains in short what needs to be done to let Teneo handle this correctly.

The following example model is used in this description: xsd + ecore. This model consists of two main types: Claim and ClaimLine. Claim has a primary key of two fields in the db. ClaimLine is contained in Claim and has a primary key consisting of the two fields of Claim and one additional field. Claim and ClaimLine have a bidirectional one-to-many/many-to-one relation.

To handle this correctly the following steps need to be taken:

For each class with a primary key with multiple fields create a separate key type. See ClaimCompositeKey and ClaimLineCompositeKey in the example model. In addition see here, and here for some specific requirements for the key class.

The key type has eattributes/ereferences corresponding to the fields of the primary key. In the example the child type has a many-to-one to the parent, this many-to-one is part of the primary key of the child so therefore it should also be added to the key type of the child (see ClaimLineCompositeKey).

The many-to-one ereference from the child to the parent needs to be annotated with @Transient (see the xsd) otherwise Hibernate will throw a duplicate column exception.

For each main type with a composite id there needs to be an ereference from the main type to the key type. This ereference needs to be annotated with @EmbeddedId.

Using the resulting model the code can be generated and Teneo will map the structure correctly.

As a last step: in the generated java code it is possible to automatically populate the composite key object of the child when adding the child to the parent. Using the example: when a ClaimLine is added to the claimLine collection of Claim then the Claim feature in the ClaimLine ClaimLineCompositeKey also needs to be set. EMF will automatically generate stubs were to this. In this example in ClaimLine there is a basicSetClaim method were the relevant line can be added. Note that javadoc @generated has to be changed to @generated NOT otherwise this change will be overwritten when the code is regenerated.

/**
 * @generated NOT
 */
public NotificationChain basicSetClaim(Claim newClaim, NotificationChain msgs) {
	msgs = eBasicSetContainer((InternalEObject) newClaim, ClaimPackage.CLAIM_LINE__CLAIM, msgs);
	getClaimLineCompositeKey().setClaim(newClaim); // this is line sets the Claim in the composite key
	return msgs;
}


Manual Generation of OR Mapping

Standard Teneo will automatically map the model to Hibernate when a datastore is initialized. However, it can sometimes make sense to manually adapt the mapping or use a specific mapping file. For this purpose Teneo also allows you to manually generate the mapping file. To do this right click on one or more .ecore files and choose the relevant option in the Teneo submenu.

Note: the generation of the hibernate.hbm.xml will only work if the org.eclipse.emf.teneo.hibernate.runtime and the plugin with the hibernate libraries plugin has been added to the dependencies of the projects of the selected .ecore files.

Org.eclipse.emf.teneo.gen or.gif

The hibernate.hbm.xml is created in the folder of the selected .ecore file. To direct Teneo to use this mapping file you need to do the following:

Copy the hibernate.hbm.xml to the generated EPackage source tree and let it be copied to the output directory/destination when building.

Pass the following option: PersistenceOptions.USE_MAPPING_FILE with the value "true" to the HbDataStore.

Note that with manual generation of the mapping, Teneo is not able to find the java instance class names (unless the ecore contains the java instance classnames). This means that when doing manual generation that cglib proxying is disabled. To generate a mapping with cglib proxying enabled either perform the mapping programmatically (see here) or use an ecore with java instance class names.


Classloader

Teneo performs explicit classloading in specific locations. It is possible to set the classloader which is being user by Teneo. This can be done through the org.eclipse.emf.teneo.ClassLoaderResolver class. In this class you can register a org.eclipse.emf.teneo.classloader.ClassLoaderStrategy. The registered ClassLoaderStrategy is used by Teneo to explicitly load classes.


Requirements on EObjects: InternalEObject

The EMF Hibernate persistency layer only requires that the persisted objects implement the org.eclipse.emf.ecore.InternalEObject interface.


Relational Validation and EObject persistency

When an object is made persistent by session.save() then at that moment also constraints, such as nullable fields, are checked. So when calling the session.save method the passed EObject should be valid.


Automatic creation of tables/Database Schema

The EMF Hibernate persistency layer will automatically update the database schema when a new SessionFactory is registered. The Hibernate class org.hibernate.tool.hbm2ddl.SchemaUpdate class is used for this purpose.

An option can be used to control if the database schema should be updated when a new HbDataStore is created.


Escaping of table and column names

Teneo will escape table and column names of primitive types. Escaping is done by surrounding the name with backtics (`). The escape character can be changed or disabled by setting the PersistenceOptions.SQL_NAME_ESCAPE_CHARACTER option (see here).

Note that:

  • Join table names are not escaped: Hibernate will in some cases automatically create a table to store the contents of a collection, for example the contents of a list with primitive types. Hibernate will not automatically escape this name. To escape such a join table you need to add a JoinTable annotation with an escaped name or a non-keyword name.
  • Teneo will not escape column names of manually specified Column annotations

Querying using eclass names, class names or interface names

  • Teneo supports hql queries which use eclass (entity) names and class and interface names. The last two are supported when the option ALSO_MAP_AS_CLASS is set to true (which is the default).

It is possible that the entitymanager.getReference/session.get methods won't work with interface classes, if so please look here.

Lazy loading/fetching, Proxy

Teneo supports Hibernate cglib proxying. To enable this the option SET_PROXY has to be set to true or the @Proxy annotation has to be set on the eclass.

If an entity is proxyable then the many-to-one/one-to-one relations to this entity will be set to lazy=proxy.

ELists are lazily loaded.


Automatic creation of TypeDef for EDataType

When you define an EDataType in your EPackage Teneo will automatically map this EDataType to a TypeDef if the instanceClass of the EDataType can not be handled by native Hibernate.

The TypeDef will make use of the UserType org.eclipse.emf.teneo.hibernate.mapping.DefaultToStringUserType. This user type persists the value in a single varchar column. This means that the EDataType needs to be convertable from/to a String. EMF will generate the appropriate methods in the generated EFactory for your EPackage. Many times EMF will throw an UnsupportedOperationException. You need to manually change this code to convert your type correctly from and to a String.


Client-Server scenario: requires explicitly modelled id and version properties

When Teneo detects that a certain type does not have an id or version annotated property then it will add these automatically (see next point). The synthetic id and version are hidden for the developer. However, the synthetic id and version will not work when objects are (de)serialized for example in a client-server situation. In this case you need to explicitly model an id and a version property and annotate them as such using jpa annotations.


Default ID Feature Name

When Teneo searches for the id-feature of an eclass it searches for an efeature with the @id annotation. If no efeature with such an annotation can be found Teneo will search for a feature with the name specified in the PersistenceOptions.DEFAULT_ID_FEATURE_NAME option (see persistence options here). A possible use to set this option to "id" and add a feature with the name "id" to each eclass. This "id" feature is then automatically used as the persistence identifier by Teneo.


@Version as timestamp

An EAttribute is tagged as the Version property by annotating it with the @Version annotation. If the EAttribute is a date time then this does not work directly as EMF uses the XMLGregorianCalendar as a default java type for these XML Schema types.

For versions of Teneo build after 23th May 2009 you just have to set the instance class of the EAttribute (its EDataType) to java.util.Date or one of its subclasses (so that the generated source code uses that class).

For earlier versions you also have to set the PersistenceOptions.USER_XSDDATETIME_TYPE to timestamp or set the @Temporal(TIMESTAMP) annotation on the EAttribute.


Synthetic ID and Version properties

When Teneo detects that a certain type does not have an id or version annotated property then it will add these automatically. The synthetic id and version are hidden for the developer. You can retrieve the hidden synthetic id or version through the class org.eclipse.emf.teneo.hibernate.mapping.identifier.IdentifierCacheHandler. You can prevent the automatic adding of a synthetic version property by setting the PersistenceOptions.ALWAYS_VERSION to false, see here. If this property is false then only efeatures with a version annotation are translated into a version mapping.

Synthetic id and version use object equality (==), this means that synthetic id and version will not work if objects are transferred to other systems and back (for example in case of client-server communication).


Move an EObject between EContainers or support cut and paste in the EMF editor

In the standard approach it is not possible to move an EObject from one containment relation to another containment relation. A move between containment relations corresponds to a cut and paste in the EMF editor. The reason is that Teneo will specify an orphan-delete cascade for a containment relation. This has as a consequence that Hibernate will throw an exception (deleted object would be re-saved by cascade, remove deleted object from associations) when you move an EObject from its container to another container. See here for a description.

Solution: as a solution you can prevent the orphan-delete from being set as the cascade style, by specifying an annotation on the relevant EReference features or by setting the global runtime property PersistenceOptions.SET_CASCADE_ALL_ON_CONTAINMENT to false (see here). As an example of the first approach:

<xsd:element maxOccurs="unbounded" minOccurs="0" name="writers" type="lib:Writer">
	<xsd:annotation>
		<xsd:appinfo source="teneo.jpa">@OneToMany(cascade={MERGE,PERSIST,REFRESH,REMOVE})</xsd:appinfo>
	</xsd:annotation>
</xsd:element>

This annotation means that cascading deletes are still enforced but a child (the Writer) can exist without its parent (the Library).

When this annotation is used in the context of a Hibernate Resource then when an EObject is removed from its container then it will also be removed from the resource and from the database. However, when you set this annotation and not work with Hibernate Resources then the removed EObject is not removed from the database and will be present without a container!


Validation

When storing and retrieving EMF objects from a Hibernate store it is not required to work with a EMF type Resource. However the standard EMF validator checks if every EObject is present in an EMF resource and that all referenced EObjects are in the same resource. So, if this standard validator is used unnecessary errors are thrown.

To prevent this situation you can register your own validator which does not perform this resource check. See the example here below.

Validators are registered using a call to put method of the EValidator.Registry.INSTANCE object.

 
public class MyValidator extends EObjectValidator
{
	/** 
	* Overrides the method from the superclass to prevent this check because it
 	* is not required in the context of a hibernate store. Note that this assumes that 
 	* an object and its references are all stored in the same hibernate database. 
 	*/
	public boolean validate_EveryReferenceIsContained(EObject eObject, DiagnosticChain diagnostics, Map context) 
	{
		return true;
	}
}


Default cacheprovider is org.hibernate.cache.HashtableCacheProvider

Default cacheprovider has been set to org.hibernate.cache.HashtableCacheProvider because of ehcache issues which occur when using ehcache with the default values.

Encountered error message was: Attempt to restart an already started EhCacheProvider. Use sessionFactory.close() between repeated calls to buildSessionFactory. Consider using net.sf.ehcache.hibernate.SingletonEhCacheProvider. Error from ehcache was: Cannot parseConfiguration CacheManager. Attempt to create a new instance of CacheManager using the diskStorePath "/tmp" which is already used by an existing CacheManager. The source of the configuration was classpath.

The HashtableCacheProvider is not for production use. You can override this default setting by setting the hibernate.cache.provider_class property with the cache provider class you want to use.


Convert e_container_feature_id to e_container_feature_name

From the 1.0.4 build of mid-March 2009 the econtainer relations are stored differently. Instead of the feature id, the feature name is stored. The reason for the change is that the feature id is not stable when the model changes. However, this means that current users when upgrading need to convert the feature id to the featurename approach. The conversion consists of the following steps:

  1. set the ECONTAINER_FEATURE_PERSISTENCE_STRATEGY option (see here) to the value "both" and let Teneo update the database schema (this adds an econtainer_feature_name column to each table).
  2. write a program which iterates through all objects in all tables and generates an update sql statement to update the econtainer_feature_name column. An example program is shown below.
  3. use the generated sql to update the database
  4. set the ECONTAINER_FEATURE_PERSISTENCE_STRATEGY option to the value "featurename". The conversion is finished and the econtainer_feature_id column is not used anymore.

Example program for iterating through all EClasses and generating a SQL update statement (note the eObject.getId() call which is just a fake, you need to use your own code to get the id of an object):

for (Iterator<?> it = dataStore.getClassMappings(); it.hasNext();) {
	final Object opc = it.next();
	final PersistentClass pc = (PersistentClass) opc;
	final Session s = dataStore.getSessionFactory().openSession();
	s.beginTransaction();
	final List<?> list = s.createQuery("from " + pc.getEntityName())
			.list();
	for (Object o : list) {
		final EObject eObject = (EObject) o;
		if (eObject.eContainer() != null) {
			final EObject container = eObject.eContainer();
			final String featureNameValue = EContainerFeatureIDUserType
					.convertEContainerRelationToString(container
							.eClass(), eObject.eContainmentFeature());
			System.err.println("update " + pc.getTable().getName()
					+ " set e_container_feature_name='"
					+ featureNameValue + "'  where id=" + eObject.getId());
		}
	}
	s.getTransaction().commit();
}


Indexes on EStructuralFeatures, Discriminator and columns

You can define indexes by using the @Index annotation (hibernate specific) on an EStructuralFeature. The value of the index can be a comma separated list of index names.

In addition the Column annotation has an additional index attribute which can be used for the same purpose.

The DiscriminatorColumn annotation has a column attribute which can contain a full @Column annotation with an index.

@DiscriminatorColumn(name="DISCRIMINATOR", discriminatorType=STRING, column=@Column(index="discrIndex,ohterindex", length=120))

Implementing your own (EMF)Tuplizer

Sometimes you need to implement your own Hibernate tuplizer to add specific Hibernate behavior. You can do this but you have to extend the Teneo EMF Tuplizer to ensure that specific EMF integrations will still work. Here are the steps to take:

  • implement your own Tuplizer extending the EMFTuplizer
  • subclass the HbContext class and override the getEMFTuplizer method returning your own Tuplizer class
  • tell Teneo to use your custom HbContext class by setting this as an extension (before calling dataStore.initialize()):
dataStore.getExtensionManager().registerExtension(HbContext.class.getName(), YourCustomHbContext.class.getName());