Repositories¶
Repository annotations simplify writing dao or repository objects. Repositories are very close to spring-data repositories and following description will follow this approach. But repository methods may be used in any way (like dao or as additional methods for beans).
Repositories mainly cover query definitions (removing all boilerplate code). If you need something like spring-data specifications, you can use orientqb
Example repository query method:
public interface MyRepository {
@Query("select from Model where name=? and nick=?")
List<Model> find(String name, String nick);
}
Repositories implementation is based on extensions (every annotation you'll see is an extension). Custom extensions supported, so you can change almost everything.
Setup¶
To use repository features register repository module in guice context:
install(new RepositoryModule());
Guice abstract types support¶
Repository methods defined with annotations, so interface and abstract methods are ideal candidates to use them. Guice doesn't allow using abstract types, but it's possible with a bit of magic.
Abstract types (abstract class or interface containing repository methods) could be registered directly in guice module:
bind(MyInterfaceRepository.class).to(DynamicClassGenerator.generate(MyInterfaceRepository.class)).in(Singleton.class)
Or dynamic resolution could be used (guice JIT resolution):
@ProvidedBy(DynamicSingletonProvider.class)
public interface MyRepository
When some bean require this dao as dependency, guice will call provider, which will generate proper class for guice.
(dynamic resolution completely replaces classpath scanning: only actually used repositories will be created)
Note, this will automatically make bean singleton, which should be desirable in most cases. If you need custom scope
use DynamicClassProvider
with @ScopeAnnotation
annotation (see details in guice-ext-annotations)
Note
Intellij IDEA will warn you that ProvidedBy annotation is incorrectly typed, but it's ok, because provider is too generic. There is nothing I can do with it and it's the best (the simplest) way I know (without explicit classpath scanning, which is redundant).
Important
Guice will control instance creation, so guice AOP features will completely work!
@Transactional
annotation may be used (generally not the best idea to limit transaction to repository method, but in some cases could be suitable).
You can think of repository interface or abstract class as of usual guice bean (no limitations).
Repository methods are applied using aop (that's why they could be used everywhere).
Repositories overview¶
There 2 types of repository methods:
- Commands - orient data manipulation calls (queries, commands, scripts etc) and build around orient command objects
- Delegates - methods delegate execution to some other beans (useful for generic logic)
Method annotation | Description |
---|---|
@Query | select/update/insert query |
@Function | orient function call |
@Script | script call (sql, js etc) |
@AsyncQuery | asynchronous query call |
@LiveQuery | orient live query subscription call |
@Delegate | delegate call to other bean method |
Defining repository¶
@Transactional
@ProvidedBy(DynamicSingletonProvider.class)
public interface ModelRepository {
@Query("select from Model")
List<Model> selectAll();
@Query("update Model set name = ? where name = ?")
int updateName(String newName, String oldName);
@Query("insert into Model (name) values(:name)")
Model create(@Param("name") String name);
}
Note
Repository methods could be used to supplement existing bean, but suggest to use pure interface repositories.
@Transactional
@ProvidedBy(DynamicSingletonProvider.class)
public abstract class MyDao {
@Query("select from Model")
public abstract List<Model> selectAll();
// normal method
public void doSomething() {
...
}
}
Note
@Transactional
is not required (annotation usage depends on your service architecture, but repository method
must be used inside transaction).
Usage examples¶
Function call:
@Function("function1")
List<Model> function();
Positional parameters:
@Query("select from Model where name=? and nick=?")
List<Model> parametersPositional(String name, String nick)
Named parameters:
@Query( "select from Model where name=:name and nick=:nick")
List<Model> parametersNamed(@Param("name") String name, @Param("nick") String nick)
@Query("select from Model where name=? and nick=?")
List<Model> parametersPaged(String name, String nick, @Skip int skip, @Limit int limit)
@Query("select from Model where ${prop}=?")
List<Model> findBy(@ElVar("prop") String prop, String value)
Fetch plan parameter:
@Query("select from Model")
List<Model> selectAll(@FetchPlan("*:0") String plan);
Sql script:
@Script("begin" +
"let account = create vertex Account set name = :name" +
"let city = select from City where name = :city" +
"let edge = create edge Lives from $account to $city" +
"commit retry 100" +
"return $edge")
Edge linkCity(@Param("name") String name, @Param("city") String city)
Js script:
@Script(language = "javascript", value =
"for( i = 0; i < 1000; i++ ){" +
"db.command('insert into Model(name) values (\"test'+i+'\")');" +
"}")
void jsScript()
Async query:
@AsyncQuery("select from Model")
void select(@Listen OCommandResultListener listener)
Type safe listener (with conversion):
@AsyncQuery("select from Model")
void select(@Listen AsyncQueryListener<Model> listener)
Or with projection:
@AsyncQuery("select name from Model")
void select(@Listen AsyncQueryListener<String> listener)
Dynamic parameters:
@Query('select from Model where ${cond}')
List<ODocument> findWhere(@ElVar("cond") String cond, @DynamicParams Object... params);
Non blocking (listener execute in different thread):
@AsyncQuery(value = "select from Model", blocking = false)
Future<List<Model>> select(@Listen AsyncQueryListener<Model> listener)
Delegate example:
public class SomeBean {
public List getAll() {
...
}
}
@Delegate(SomeBean.class)
List getAll();
Live query:
@LiveQuery("select from Model")
int subscribe(@Listen OLiveResultListener listener)
Type safe listener (with conversion):
@LiveQuery("select from Model")
int subscribe(@Listen QueryResultListener<Model> listener)
Or vertex conversion:
@LiveQuery("select from Model")
int subscribe(@Listen QueryResultListener<Vertex> listener)
Unsubscription (usual command call):
@Query("live unsubscribe ${token}")
void unsubscribe(@ElVar("token") int token)
Read more about method usage:
Tip
For more examples see repository definition examples
Writing extensions:
Return types¶
You can use Iterable
, Collection
, List
, Set
, any collection implementation, array, single element or Iterator
as return type.
Conversion between types will be applied automatically.
@Query("select from Model")
List<Model> selectAll();
@Query("select from Model")
Set<Model> selectAll();
@Query("select from Model")
Model[] selectAll();
@Query("select from Model")
Iterable<Model> selectAll();
@Query("select from Model")
Iterator<Model> selectAll();
If you define single result, when query produce multiple results, first result would be automatically taken:
@Query("select from Model limit 1")
Model selectAll();
Note
Limit is not required, but preferred, as soon as you don't need other results
Projection¶
In some cases simple value is preferred, for example:
@Query("select count(@rid) from Model)
int count();
Orient returns ODocument
from query with single field (count).
Default result converter could recognize when document or vertex contain just one property and return only simple value.
Another case is when you select single field:
@Query("select name from Model")
String[] selectNames()
Read more about projection
Result type definition¶
It is very important to always define exact return type. Connection type defines type of result object:
document connection always return ODocument
, object return mapped objects (but ODocument
for field calls)
and graph - Vertex
and Edge
.
Result type is used internally to detect connection type for query.
For example, if you write:
@Query("select from Model")
List selectAll();
You will actually receive List<ODocument>
, because without generic it's impossible to detect required return type
and document connection used for query.
For example, in this case graph connection would be selected:
@Query("select from Model")
List<Vertex> selectAll();
Result conversion¶
Every repository method result is converted with default converter (as described above).
You can use more specific result conversion extension, for example:
@Query("select from Model")
@NoConversion
List<Model> selectAll();
NoConversion disables conversion mechanism and you receive result object as is.
Read more about converter mechanism and writing custom converters.
Mixins¶
Java support multiple inheritance for interfaces and you can inherit multiple interfaces in classes. So interfaces are ideal for writing small reusable parts (mixins).
Command mixins¶
El variables in commands support references to class generics, so you can use it for generic repository logic:
public interface MyMixin<T> {
@Query("select from ${T}")
List<T> selectAll()
}
@Transactional
@ProvidedBy(DynamicSingletonProvider.class)
public interface ModelRepository extends MyMixin<Model> {}
When you call mixin method from repository instance
repository.selectAll()
Generic value Model will be used for command select from Model
and return type will be resolved as List<Model>
,
which will allow to select proper connection (object if Model is mapped entity).
You may use as many generics as you need. Any repository hierarchy depth will be correctly resolved, so you can even use composition mixins, which wil simply combine commonly used mixins:
public interface RepositoryBase<T> extends Mixin1<T>, Mixin2<T> {}
Note
You don't need to set @ProvidedBy
annotation on mixins, because it's just interfaces and they are not used as repository instances.
Delegate mixins¶
Delegates are also support generalization through extensions:
public class DelegateBean {
public List selectAll(@Generic("T") Class model) {
}
}
public interface MyMixin<T> {
@Delegate(DelegateBean.class)
List<T> selectAll()
}
When delegate bean called from mixin, it will receive generic value (of calling mixin) as parameter.
Read more about mixins usage
Bundled crud mixins¶
Crud mixins are the most common thing: commonly these methods are implemented in AbstractDao
or something like this.
DocumentCrud mixin provides base crud methods for document repository.
public interface MyEntityDao extends DocumentCrud<MyEntity> {}
Set mixin generic value only if you have reference entity class. Generic affects only getAll
and create
methods: if generic not set
you will not be able to use only this method.
ObjectCrud mixin provides base crud methods for object repository:
public interface MyEntityRepository extends ObjectCrud<MyEntity> {}
Now MyEntityRepository has all basic crud methods (create, get, delete etc).
Pagination provides simple pagination for your entity or document (but document should have reference type, at least to specify schema type name (may be empty class))
public interface MyEntityRepository extends ObjectCrud<MyEntity>, Pagination<MyEntity, MyEntity> {}
...
// return page
Page page = repository.getPage(1, 20);
In order to use pagination mixin, crud mixin is not required (used in example just to mention one more time that mixins could be combined). Pagination mixin is the most complex one and good place to inspire how to write more complex reusable logic.
ObjectVertexCrud, EdgesSupport and EdgeTypeSupport mixins allows using graph features from object api.
@vertexType
public class Model {}
@EdgeType
public class ModelConnection {}
@Transactional
@ProvidedBy(DynamicSingletonProvider.class)
public interface ModelRepository extends ObjectVertexCrud<Model>,
EdgeTypeSupport<ModelConnection, Model, Model> {}
@Inject ModelRepository repository;
...
Model from = repository.save(new Model(..));
Model to = repository.save(new Model(..));
ModelConnection edge = repository.createEdge(from, to);
Validation¶
You can use guice-validator to apply runtime validation (jsr 303) for repository methods:
@Query("select from Model where name = ?")
@Size(min = 1)
List<Model> select(@NotNull String name)
Now this query throw ConstraintViolationException if null provided as parameter or no results returned.
Important
Register validator module before guice-persist-orient modules! This way validation will be checked before @Transactional or repository methods logic.