Git Product home page Git Product logo

guice-persist-orient's Introduction

#Guice integration for OrientDB License Build Status Coverage Status

About

OrientDB is document, graph and object database (see intro). Underlying format is almost the same for all database types, which allows us to use single database in any way. For example, schema creation and updates may be performed as object database (jpa style) and graph queries for complex cases.

Features:

  • Integration through guice-persist (UnitOfWork, PersistService, @Transactional, dynamic finders supported)
  • Support for document, object and graph databases
  • Database types support according to classpath (object and graph db support activated by adding jars to classpath)
  • Auto mapping entities in package to db scheme or using classpath scanning to map annotated entities
  • Auto db creation
  • Hooks for schema migration and data initialization extensions
  • All three database types may be used in single unit of work (but each type will use its own transaction)

Setup

Releases are published to bintray jcenter (package appear immediately after release) and then to maven central (require few days after release to be published).

Download Maven Central

Maven:

<dependency>
<groupId>ru.vyarus</groupId>
<artifactId>guice-persist-orient</artifactId>
<version>1.0.2</version>
<exclusions>
  <exclusion>
      <groupId>com.orientechnologies</groupId>
      <artifactId>orientdb-graphdb</artifactId>
  </exclusion>
  <exclusion>
      <groupId>com.orientechnologies</groupId>
      <artifactId>orientdb-object</artifactId>
  </exclusion>
</exclusions>
</dependency>

Gradle:

compile ('ru.vyarus:guice-persist-orient:1.0.2'){
    exclude module: 'orientdb-graphdb'
    exclude module: 'orientdb-object'       
}

By default, only document database support is enabled.

Remove exclusions to enable object and graph db support.

NOTE: It's very important for object db to use exact javassist version it depends on. If other libraries in your classpath use javassist, check that newer or older version not appear in classpath.

Install the Guice module

install(new OrientModule(url, user, password));

See orient documentation for supported db types. In short:

  • 'memory:dbname' to use in-memory database
  • 'plocal:dbname' to use embedded database (no server required, local fs folder will be used); db name must be local fs path
  • 'remote:dbname' to use remote db (you need to start server to use it)

By default use 'admin/admin' user.

Default transactions configuration may be specified as additional module parameter. By default, OPTIMISTIC transactions used (use optimistic locking based on object version, same way as hibernate optimistic locking). NOTX mode disables transactions. Read more about transactions in orient

For example, to switch off transactions use:

install(new OrientModule(url, user, password)
                .defaultTransactionConfig(new TxConfig(OTransaction.TXTYPE.NOTX));

If provided scheme initializers used it's better to specify model package (more details below):

install(new OrientModule(url, user, password)
                .schemeMappingPackage("my.model.package");

There are two shortcut modules with predefined scheme initializers from objects:

  • PackageSchemeOrientModule when all model classes in the same package
  • AutoScanSchemeOrientModule when model classes spread across application (module use classpath scanning to find @Persistent annotated entities)

Dynamic finders

To use dynamic finders register finders module:

install(new FinderModule()
            .addFinder(FinderInterface.class));

Default connection type could be specified to use when connection type can't be detected from finder return type (by default it will be document connection):

install(new FinderModule()  
            .defaultConnectionType(DbType.OBJECT)
            .addFinder(FinderInterface.class));

To avoid manual addition of all finder interfaces, you can use module with classpath scanning instead:

install(new AutoScanFinderModule("package.to.search.finders.in"));

Usage

Lifecycle

You need to manually start/stop persist service in your code (because only you can control application lifecycle). On start connection pools will be initialized, database created (if required) and scheme/data initializers called. Stop will shutdown all connection pools.

@Inject
PersistService orientService

public void onAppStartup(){
    orientService.start()
}

public void onAppShutdown(){
    orientService.stop()
}
Unit of work (transaction)

To define unit of work use:

  • @Transactional annotation on guice bean or single method (additional @TxType annotation allows to define different transaction type for specific unit of work)
  • Inject TxTemplate or SpecificTxTemplate beans into your service and use them

Example of no tx unit of work with annotation:

@Transactional
@TxType(OTransaction.TXTYPE.NOTX)
public void method()

TxTemplate example:

txTemplate.doInTransaction(new TxAction<Void>() {
        @Override
        public Void execute() throws Throwable {
            // something
            return null;
        }
    });

TxTemplate with custom config:

txTemplate.doInTransaction(new TxConfig(OTransaction.TXTYPE.NOTX), new TxAction<Void>() {
        @Override
        public Void execute() throws Throwable {
            // something
            return null;
        }
    });

TxTemplate is a generic template to define unit of work, but you will need to use provider to obtain connection. If you need only specific connection type, use specific template:

specificTxTemplate.doInTransaction(new SpecificTxAction<Object, OObjectDatabaseTx>() {
        @Override
        public Object execute(OObjectDatabaseTx db) throws Throwable {
            // something
            return null;
        }
    })

To obtain connection use one of the following providers:

  • Provider<OObjectDatabaseTx> for object database connection
  • Provider<ODatabaseDocumentTx> for document database connection
  • Provider<OrientBaseGraph> for graph database connection (transactional or not)
  • Provider<OrientGraph> for transactional graph database connection (will fail if notx transaction type)
  • Provider<OrientGraphNoTx> for non transactional graph database connection (will provide only for notx transaction type, otherwise fail)

NOTE: in contrast to spring default proxies, in guice when you call bean method inside the same bean, annotation interceptor will still work. So it's possible to define few units of work withing single bean using annotations:

public void nonTxMethod(){
    // all called methods are annotated
    doTx1();
    doTx2();
    doTx3();
}

Dynamic finders

Dynamic finders allows to define queries using method annotation and pass parameters from method arguments, e.g.:

@Finder(query = "select from Model where name=? and nick=?")
List<Model> parametersPositional(String name, String nick);

If finder used in interface, all interface methods must be finder methods and interface must be manually registered in FinderModule. If AutoScanFinderModule used, finder interfaces will be registered automatically. @Transactonal annotation is supported within interface finders (generally not the best idea to limit transaction to finder method, but in some cases could be suitable)

Finders may be used with beans (e.g. to supplement usual dao methods):

@Finder(query = "select from Model")
public List<Model> selectAll() {
    throw new UnsupportedOperationException("Should be handled with finder interceptor");
}
Annotations

@Finder allows you

  • to use function with namedQuery attribute (there are no named queries in orient, but functions are close concept (but better))
  • define query with query attribute
  • override result collection implementation with returnAs attribute

Function call:

@Finder(namedQuery = "function1")
List<Model> function();

Query with positional parameters:

@Finder(query = "select from Model where name=? and nick=?")
List<Model> parametersPositional(String name, String nick)

Query with named parameters:

@Finder(query = "select from Model where name=:name and nick=:nick")
List<Model> parametersNamed(@Named("name") String name, @Named("nick") String nick)

Both com.google.inject.name.Named and javax.inject.Named annotations supported

Update query example:

@Finder(query = "update Model set name=?")
int updateWithCount(String name)

Update query return type could be void, int, long, Integer and Long.

@FirstResult and @MaxResults may be used to limit query results (pagination):

@Finder(query = "select from Model where name=? and nick=?")
List<Model> parametersPaged(String name, String nick, @FirstResult int start, @MaxResults int max)

You can use Long, Integer, long and int for start/max values. First result is used as orient SKIP declaration (you can think of it as first result, counting from 0)

For function call only @MaxResults may be used.

@Use annotation may be used to specify connection type to use for query:

@Finder(query = "update Model set name='changed'")
@Use(DbType.OBJECT)
void updateUsingObjectConnection()

It may be important for proper return type (different connection return different objects: ODocument for document, model instances for object and Vertex and Edge for graph). For update queries connection type can't be detected and it always executed with default connection (document, but may be changed in module configuration), but it may be important to execute it in the same transaction with other changes (e.g. if you work in object connection and want update in the same connection).

Return types

You can use: Iterable, Collection, List, Set, any collection implementation, array, single element or Iterator. Query execution result will be converted in accordance with specified return type.

@Finder(query = "select from Model")
Model selectAll()

First result will be returned (query implicitly limited to one element)

@Finder(query = "select from Model")
Model[] selectAll()

Returned collection will be converted to array.

Sometimes it may be desirable to change default returned collection implementation, e.g. to sort elements:

@Finder(query = "select from Model", returnAs = TreeSet.class)
Set<Model> selectAll()

TreeSet collection will be returned. The same result will be if set method return type to TreeSet (but it's not best practice to define implementation as return type).

Connection type detection

Connection type can be detected for queries using method return type. If you specify generic for returned collection (or iterator) or use typed array (not Object[]) or in case of single element, connection type will be detected like this:

  • If returned class is registered with object entity manager, object connection will be used
  • If ODocument class returned - document connection used
  • If Vertex or Edge classes returned - graph connection

But, for example, even if you try to select not object itself, but fields you will get ODocument, even in object connection (kind of result set in jdbc). For such case document connection will be selected (according to return type), but it may be part of object connection logic (e.g. all other queries in object connection). In this case @Use annotation will help.

Schema initialization

Module will automatically create database if it's not exist and perform default graph initialization if graph database support enabled (jar available).

To initialize (or migrate) scheme register implementation of

ru.vyarus.guice.persist.orient.db.scheme.SchemeInitializer

Example:

install(new OrientModule(url, user, password));
bind(SchemeInitializer.class).to(MySchemeInitializer.class);

Scheme initializer is called in notx unit of work (orient requires database schema updates to be performed without transaction).

By default, no-op implementation enabled.

Two default implementations provided for schema initialization from pojos (jpa like):

  • PackageSchemeInitializer - use all classes in package to init or update scheme (package should be specified as module constructor argument)
  • AutoScanSchemeInitializer - search classpath for entities annotated with @Persistent annotation and use them to create/update scheme (search scope may be reduced by specifying package in module constructor).

There are predefined shortcut modules for each initializer: PackageSchemeOrientModule and AutoScanSchemeOrientModule

Both initializers support grahp compatible entities creation (default orient scheme auto creation used). By default, you will be able just query stored records with graph connection. To be able to create graph vertixes and edges, classes must extend V and E classes in scheme.

You can use special annotations on entities:

  • @VertexType to make your type derive from V in scheme
  • @EdgeType to make your type derive from E in scheme

This allows to use objects for scheme update for graphs too. You can freely use such annotated entities for object or document queries (difference will be only for graph - it will be able to create).

Remember that scheme created from objects maintain same hierarchy as your objects. E.g. if you use provided VersionedEntity class as base class for entities, it will be also registered in scheme (nothing bad, you may not notice it). But for graphs hierarchies more important: both vertex and edge objects can't extend same class (root class in hierarchy must extend V or E). So if you use @VertexType or @EdgeType annotations make sure their hierarchy not intersect.

Object mapping specifics

See orient object mapping documentation for object mapping (and general object database page).

  • Orient ignore package, so class may be moved between packages
  • When entity field removed, orient will hold all data already stored in records of that field
  • When entity field type changes, orient WILL NOT migrate automatically (you need to handle it manually, using custom scheme initializer or through orient studio).
  • When class renamed orient will register it as new entity and you will have to manually migrate all data (it's possible to use sql commands to rename entity in scheme)
  • To use entity within optimistic transaction, it must have version field (annotated with @Version). You should add field manually or extend all entities from provided base class: VersionedEntity
  • JPA annotations can be used to define cascades

Data initialization

To initialize (or migrate) data register implementation of

ru.vyarus.guice.persist.orient.db.data.DataInitializer

By default, no-op implementation enabled.

Example:

bind(DataInitializer.class).to(YourDataInitializerImpl.class);

Initializer is called WITHOUT predefined unit of work, because of different possible requirements. You should define unit of work (maybe more than one) yourself (using annotation or template).

Under the hood

Orient module initialize connection pool for each database type. Unit of work didn't trigger direct connection acquire from pool to not acquire redundant resources (for example, if all three database types initialized, most likely you will use one or two during single unit of work).

To configure pool sizes use global configuration (default is min 1 max 20):

OGlobalConfiguration.DB_POOL_MIN.setValue(1)
OGlobalConfiguration.DB_POOL_MAX.setValue(20)

Each pool (each connection type) will start it's own independent transaction within unit of work. It should not be limitation with proper scopes definition (use one connection type just for reads and other for updates or update different entities within different connection types or define smaller units of work). Each pool acquires connection and opens transaction only after requesting connection through appropriate provider.

Transaction is thread-bound (the same as unit of work).

IF you use more than one connection type within same unit of work and one transaction will fail, other connection will still be committed and rollback will be called only on failed connection.

You can fine-tune transaction to rollback only in soecific exceptions or not rollback on some exceptions (see @Transactional annotation and TxConfig).

Read more about orient transactions

Default transaction manager implementation could be overridden by simply registering different implementation of TransactionManager interface:

bind(TransactionManager.class).to(CustomTransactionManagerImpl.class)

Pools implementation could also be changed. For example, current graph pool is actually document pool (because graph api 'sits' above document connection). So it is possible to share document and graph connections: just implement graph pool to acquire connection from document pool. It wasn't done, because of inconsistency: document and object transactions would be different and graph will share document transaction.. a bit confusing.

Custom pools registration example:

public class MyOrientModule extends OrientModule {

    @Override
    protected void configurePools() {
        bindPool(ODatabaseDocumentTx.class, DocumentPool.class);
        bindPool(OObjectDatabaseTx.class, ObjectPool.class);
        bindPool(OrientGraph.class, MyCustomGraphPool.class);
        // note that for graph few entities could be provided: OrientGraph, OrientGraphNoTx, OrientBaseGraph.
        // default implementation registers additional providers to handle all cases
        // see ru.vyarus.guice.persist.orient.support.pool.GraphPoolBinder
    }
}

Dynamic finders

By analogy with orient module pools, finder module use executor instances for each connection type. Executor is responsible for query execution in exact connection. Default executors could be overridden by overriding FinderModule:

public class MyFinderModule extends FinderModule {
    @Override
    protected void configureExecutors() {
          bindExecutor(CustomDocumentFinderExecutor.class);
          bindExecutor(CustomObjectFinderExecutor.class);
          bindExecutor(CustomGraphFinderExecutor.class);
    }      
}

Sql command object builder, used by default executors may be overridden:

bind(CommandBuilder.class).to(CustomCommandBuilder.class);

Custom query execution result converter may be defined:

bind(ResultConverter.class).to(MyCustomResultConverter.class);

Orient configuration

Configuration could be done on instance:

db.getStorage().getConfiguration()

Or globally:

OGlobalConfiguration.MVRBTREE_NODE_PAGE_SIZE.setValue(2048);

Read about all configuration options

Might also like

  • dropwizard-orient-server - embedded orientdb server for dropwizard
  • guice-validator - hibernate validator integration for guice (objects validation, method arguments and return type runtime validation)
  • guice-ext-annotations - @Log, @PostConstruct, @PreDestroy and utilities for adding new annotations support

Slush java lib generator

guice-persist-orient's People

Contributors

xvik avatar bertvanbrakel avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.