Skip to content

OrienteerBAP/Transponder

Repository files navigation

Java CI Build Status

Transponder_Logo

Transponder

Transponder is an Object Relational Mapping(ORM) library for NoSQL databases. It's lightweight, with very small memory footprint. Transponder dynamically generates bytecode over native DB client classes, so there are NO overheads for reflection and NO double storing of data.

Content

  1. Use Cases
  2. Key Benefits
  3. Supported NoSQL Databases
  4. NoSQL Databasses Support Road Map
  5. Getting Started
  6. Defining DataModel
  7. Transponder API
  8. Transponder Annotations
  9. Support of Multiple Dialects
  10. Comparison With Other ORMs
  11. Suppport

Use Cases

Transponder can be used for

  • Defining a data model in Java source code
  • Automatic creation of a datamodel in a NoSQL DB
  • Generation of Data Access Objects (DAO) - utility classes to work with data
  • Easy customization for specific needs, for example: introduce custom annotation @Sudo to execute code under priviledged access

Key Benefits

  • Lightweight
  • Small memory footprint
  • Transferable: datamodel defined for one DB can be reused for another one
  • Small learning curve

Supported NoSQL Databases

  • OrientDB (maven dependency: org.orienteer.transponder:transponder-orientdb)
  • ArcadeDB (maven dependency: org.orienteer.transponder:transponder-arcadedb)
  • Neo4J (maven dependency: org.orienteer.transponder:transponder-neo4j)
  • MongoDB (maven dependency: org.orienteer.transponder:transponder-mongodb)

NoSQL Databases To Be Supported Soon

Please create an issue or discussion if you need support of some other DBs or expedite priority for those which are not yet supported.

Getting Started

Add the following dependency into your pom.xml:

<dependency>
   <groupId>org.orienteer.transponder</groupId>
   <artifactId>transponder-${NOSQL DB NAME}</artifactId>
   <version>${project.version}</version>
</dependency>

If you are using SNAPSHOT version, please make sure that the following repository is included into your pom.xml:

<repository>
	<id>Sonatype Nexus</id>
	<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
	<releases>
		<enabled>false</enabled>
	</releases>
	<snapshots>
		<enabled>true</enabled>
	</snapshots>
</repository>

Defining DataModel

Use interface class with annotation @EntityType("EntryTypeName") to define a type from your data-model. All getters and setters will be mapped to corresponding properties/columns of an entity in a database. You can use interface default methods for your custom methods. There is a set of annotations supported by Transponder to simplify work with the data model: @EntityIndex, @Query, @Lookup, @Command and etc. Please see corresponding chapter for details. Lets create simple datamodel to mimic file-system:

@EntityType("Entry")
@EntityIndex(name = "nameParent", properties = {"name", "parent"})
public interface IEntry {

	public String getName();
	public void setName(String value);
	
	public IFolder getParent();
	public void setParent(IFolder value);
	
	@Lookup("select from Entry where name=:name and parent=:parent")
	public boolean lookupByName(String name, IFolder parent);
  
  	public default String getFullPath() {
		IFolder parent = getParent();
		return (parent!=null?parent.getFullPath():"")+"/"+getName();
	}
}

@EntityType("Folder")
public interface IFolder extends IEntry {

	public List<IEntry> getChild();
	public void setChild(List<IEntry> value);
}

@EntityType("File")
public interface IFile extends IEntry {

	public byte[] getContent();
	public void setContent(byte[] value);
	
}

After calling transponder.define(IFile.class, IFolder.class) Transponder will create the following datamodel in a database: image

Additionally you can create Data Access Object to be able to query/modify your data:

public interface IFileSystem {
	
	@Query("select from Folder where name = :name and parent is null")
	public IFolder getRoot(String name);
	
	@Query("select from Entry where name=:name and parent=:parent")
	public IEntry lookupByName(String name, IFolder parent);
	
	@Query("select from Entry where name like :search")
	public List<IEntry> search(String search);
	
	@Command("delete from File where content is null")
	public void removeEmpty();
}

Transponder API

To create Transponder instance:

Transponder transponder = new Transponder(driver);
//For example:
Transponder transponder = new Transponder(new ODriver()); //OrientDB
Transponder transponder = new Transponder(new ArcadeDBDriver(arcadeDatabase)); //ArcadeDB

To define datamodel in a database:

transponder.define(IFolder.class, IFile.class, IMyOtherEntity.class, ...);

To create DAO instance from the specified interface or class:

IFileSystem fsDao = transponder.dao(IFileSystem.class);
IFileSystem fsDao = transponder.dao(IFileSystem.class, IOtherDAOClass.class, IYetAnotherDAOClass.class, ...);

After DAO creation you can use it right away:

List<IEntry> textFiles = fsDao.search("%.txt");

To create new wrapped entity:

IFolder folder = transponder.create(IFolder.class);
IFolder folder = transponder.create(IFolder.class, IMyOtherUsefullWrapper.class, ...);

After creation of an entity you can work with it as usual java object:

folder.setName("My Cool Folder");
folder.setParent(myOtherFolder);
String fullPath = folder.getFullPath();

To persist/save entity into DB:

Tranponder.save(folder);

Or you can do simply folder.save() if you mixin the following method into your wrapper:

public default IEntry save() {
  Transponder.save();
  return this;
}

Also you can wrap some existing entity from a database into wrapped one. Example for OrientDB:

ODocument myFolderDoc = ...;
IFolder folder = transponder.provide(myFolderDoc, IFolder.class);
IFolder folder = transponder.provide(myFolderDoc, IFolder.class, IMyOtherUsefulWrapper.class, ...); //To mixin other useful interfaces
IFodler folder = transponder.wrap(myFolderDoc); //More generic version, but corresponding wrapper should be defined by transponder.define(...) in this case

If needed, you can unwrap entity as well. Example for OrientDB:

ODocument myFolderDoc = (ODocument)Transponder.unwrap(folder);

Sometimes it's useful to rewrap the same entity but into different class.

MyNewWrapper newWrapper = Transponder.rewrap(oldWrapper, MyNewWrapper.class, SomeOtherInterface.class, ...);

Also you can upgrade existing wrapper by adding more interfaces to be supported

MyEntity myEntity = ...;
myEntity = Transponder.upgrade(myEntity, SomeNewInterface.class, SomeOtherInterface.class);

If you have wrapped entity and you need obtain Transponder:

Transponder transponder = Transponder.getTransponder(folder);

Transponder Annotations

Annotation Description
@EntityType Current class defines entity type
@EntityIndex/@EntityIndexes Creates indexes on the entity type in a database
@EntityProperty This getter/setter method should be mapped to corresponding property of an entity in a database
@EntityPropertyIndex Creates index in a datasource on current property
@Query Method execute query in a database and return corresponding result
@Lookup Lookup database for an entity according to search criterias and if found: replace underling entity of the current wrapper
@Command Execute some command in a database
@DefaultValue Return provided default value if actual result from this method is null
@OrientDBProperty OrientDB specific additional settings for the property
@ArcadeDBProperty ArcadeDB specific additional settings for the property

Annotations in bytecode generation within Transponder is very flexible (due to Byte Buddy) and can be easily extended to support custom cases. For example: @Sudo - to execute some method under super user, @Count - to count number of invokations for metrics and etc.

Support of Multiple Dialects

Queries and commands for the same functions might vary for different databases. Valid SQL for one NoSQL database, might require correction for another one. That's why Transponder supports polyglot definitions for @Query, @Lookup and @Command. Transponder do translation to corresponding dialect during dynamic generation of a wrapper, so there is no overheads during actual runtime. Every query/command has id. It's either can be defined manually (for example @Query(id="myQuery", value="select ...")) or generated automatically (for example first query for IFileSystem above will have id <fullpackagename>.IFileSystem.getRoot. Then Transponder uses provided resource file by path /META-INF/transponder/polyglot.properties to lookup proper query for a specific dialect. For example, for query with id myQuery for OrientDB library will look for keys orientdb.myQuery and orientdb.myQuery.language. If first one is found - it will be used as actual query for OrientDB. If second one was also found: correspinding language will overload language defined in actual annotation.

Comparison With Other ORMs

Transponder Spring Data Hibernate MyBatis
NoSQL Support
Universal DataModel Markup
No Double Caching
Easy Extendability On User Level Code
Easy SPI For New Drivers
Support of Multi-Inheritance

Disclaimer: Comparison might be subjective. If you have comments: please create an issue or discussion

Suppport

If you need any support or questions please create an issue or discussion.