There are various technologies available for mapping EMF-objects to SQL databases, like the probably most established ones in the Eclipse ecosystem (both developed by Martin Taal)
- Teneo
- Texo
We more than contentendly used these two in quite some projects until we had – at the end of 2013 – to rethink our RCP architecture due to various (most of all performance) challenges with teneo (or more precisely hibernate).
I want to point out that these technical issues we faced were not caused by design problems of hibernate, but origin from the different lifespans of sessions to be handled – application servers with short living sessions on the one hand and RCP with typically fairly long running sessions.
Anyways this blog post is not about discussing why we left Teneo & Hibernate behind and developed our own EMF to SQL mapper named EMap but introducing this new mapping choice.
What is EMap
On first sight EMap is a DSL allowing you to:
- define how an EMF entity is mapped to an SQL table
- define how to retrieve EMF-Entities from your SQL-Database
But going deeper into it … EMap is a lot more, as the following example illustrates.
Let’s assume we have an EMF entity like the following:
interface TreeItem { public long getId(); public void setId(long id); public String getName(); public String setName(String name); public List<TreeItem> getChildren(); // containment, opposite of parent public TreeItem getParent(); // opposite of children }
This would be mapped into a SQL database like this:
==================================== | T_ID | T_NAME | T_REF_PARENT | ==================================== | 1 | Root 1 | NULL | ------------------------------------ | 2 | Root 2 | NULL | ------------------------------------ | 3 | Child 1.1 | 1 | ------------------------------------ | 4 | Child 1.2 | 1 | ------------------------------------ | 5 | Child 2.1 | 2 | ------------------------------------ | ....
The EMaping for that would look like this:
package sample; entity TreeItem { etype "http://www.bestsolution.at/emap/sample"#TreeItem attributes { primarykey id => T_ID generatedby { "Oracle" seqnext "SEQ_ID_TREEITEM", "h2" autokey } name => T_NAME children => resolve TreeItem.selectChildren(T_ID) parent => resolve TreeItem.selectById(T_REF_PARENT) } queries { single selectById(long id) { default TreeItem FROM '"TREE_ITEM"' WHERE '"T_ID" = ${id}' }, selectAllItems() { default TreeItem FROM '"TREE_ITEM"' } selectChildren(long parentId) { default TreeItem FROM '"TREE_ITEM"' WHERE '"T_REF_PARENT" = ${parentId}' }, selectRootItems() { default TreeItem FROM '"TREE_ITEM"' WHERE '"T_REF_PARENT" IS NULL' } } }
And having this in EMap brings along the following stuff – automatically generated:
- DDL file to create the SQL tables
- a set of SQL files holding the named SQL queries
- a set of Java interfaces & classes allowing you to
retrieve, insert, update and delete entities
Most important here are the Java interfaces which look as follows for our TreeItem:
interface TreeItemMapper extends at.bestsolution.persistence.ObjectMapper<TreeItem> { TreeItem selectById(long id); List<TreeItem> selectChildren(long parentId); List<Treeitem> selectRootItems(); List<Treeitem> selectAllItems(); MappedQuery<TreeItem> selectAllItemsMappedQuery(); public static class Expression { public static final TreeItemMapper.ExpressionStringExpressionFactory<TreeItem> name(); public static final LongExpressionFactory<TreeItem> id(); public static final LongExpressionFactory<TreeItem> parent_fk(); public static final TreeItemMapper.Join<TreeItem> parent(); public static final TreeItemMapper.Join<TreeItem> children(); } static class Join<O> { StringExpressionFactory<O> name(); LongExpressionFactory<O> id(); LongExpressionFactory<O> parent_fk(); } static class Order { OrderColumnFactory<TreeItem> id(); OrderColumnFactory<TreeItem> name(); } } // at.bestsolution.persistence.ObjectMapper interface ObjectMapper<O> { void update(O object); void insert(O object); void delete(O object); // .... }
who have the API you’ll use in your code to interact with the backend store.
Access data in your application
At runtime only the generated artifacts are needed so you don’t get extra dependencies e.g. on Xtext, … beside naturally some emap components who do the mapping.
EMap is fully integrated into OSGi and makes use of the OSGi-Service registry to e.g. retrieve the configuration to access datasources but it publishes itself as well into the OSGi-Service-Registry.
Let’s assume you would have an e4 component with which you would like to present the TreeItem structure. What would you have to do?
import static ...TreeItemMapper.Expression public class MyView { private Session session; @Inject public MyView(SessionFactory factory) { session = factory.createSession(); } private void printTree2Level() { TreeItemMapper m = session.createMapper(TreeItemMapper.class); for( TreeItem r : m.selectRootItems() ) { System.out.println("+ " + r.getName()); for( TreeItem c : r.getChildren() ) { System.out.println(" + " + c.getName()); } } } private void findAllItemsNameStartWith(String namePrefix) { TreeItemMapper m = session.createMapper(TreeItemMapper.class); MappedQuery<TreeItem> q = m.selectAllItemsMappedQuery(); Expression<TreeItem> filter = name().like(namePrefix+"%"); q.where(filter).list().map( (i) -> i.getName() ); } private void renameItem(TreeItem item) { String name = ...; TreeItemMapper m = session.createMapper(TreeItemMapper.class); item.setName(newName); session.runInTransaction( (s) -> { m.update(item); return true; } ); } @PreDestroy public void destroy() { session.close(); } }
Cool isn’t it!
And all this is typesafe!
And EMap brings along much more like this (for example FETCH JOINS similar to Hibernate, …), but going into these would go beyond the scope of this blog post.
Supported platforms, databases and the sourcecode
The sources are all available under EPL from our github repository at https://github.com/BestSolution-at/emap.
In current version EMap offers production ready support for
- Firebird 2.5
- Oracle 11
and runs on Java6 and above.
What’s next?
At the moment, we are looking for further funding on EMap allowing us to
add additional databases and further features. Just drop us a note for
sponsored development possibilities.