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.
- Use Cases
- Key Benefits
- Supported NoSQL Databases
- NoSQL Databasses Support Road Map
- Getting Started
- Defining DataModel
- Transponder API
- Transponder Annotations
- Support of Multiple Dialects
- Comparison With Other ORMs
- Suppport
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
- Lightweight
- Small memory footprint
- Transferable: datamodel defined for one DB can be reused for another one
- Small learning curve
- 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)
Please create an issue or discussion if you need support of some other DBs or expedite priority for those which are not yet supported.
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>
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:
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();
}
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);
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.
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.
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
If you need any support or questions please create an issue or discussion.