Phoenix ORM library
Guide to PHO (Phoenix-HBase ORM) for entity mapping, query building, execution, and configuration.

PHO is an Object Relational Mapping (ORM) library for building and executing queries on HBase using Apache Phoenix. It provides ORM-style mappings and DSL-style query building. Initially developed and open sourced by eHarmony, it is available on GitHub.
Its interfaces and generic annotations make it possible to switch data store APIs in the future without changing query definitions. Currently, it supports HBase integration through Apache Phoenix, and can be extended with other implementations.
Entity Class
Suppose we have the following TestClass we want to query in our data store:
// class must be annotated with Entity
import com.google.code.morphia.annotations.Embedded;
import com.google.code.morphia.annotations.Entity;
@Entity(value = "user_matches")
public class MatchDataFeedItemDto {
@Embedded
private MatchCommunicationElement communication;
@Embedded
private MatchElement match;
@Embedded
private MatchProfileElement matchedUser;
}
public class MatchElement {
// row key
@Property(value = "UID")
private long userId;
@Property(value = "MID")
private long matchId;
@Property(value = "DLVRYDT")
private Date deliveredDate;
@Property(value = "STATUS")
private int status;
}Query Building
Query building can be done in DSL style. More advanced query building is under development, but for now we will use a combination of QueryBuilder and static, Hibernate-style Restrictions methods to construct queries.
Simple queries
Construct a query to find all user matches delivered in the past two days and not in a closed state.
import com.eharmony.datastore.api.DataStoreApi;
import com.eharmony.datastore.model.MatchDataFeedItemDto;
import com.eharmony.datastore.query.QuerySelect;
import com.eharmony.datastore.query.builder.QueryBuilder;
import com.eharmony.datastore.query.criterion.Restrictions;
@Repository
public class MatchStoreQueryRepositoryImpl implements MatchStoreQueryRepository {
final QuerySelect<MatchDataFeedItemDto, MatchDataFeedItemDto> query = QueryBuilder
.builderFor(MatchDataFeedItemDto.class)
.select()
.add(Restrictions.eq("userId", userId))
.add(Restrictions.eq("status", 2))
.add(Restrictions.gt("deliveredDate", timeThreshold.getTime()))
.build();
Iterable<MatchDataFeedItemDto> feedItems = dataStoreApi.findAll(query);
}Compound queries
Construct a more complex query where we find items older than one day, include multiple status values, order by deliveryDate, and limit result size to 10.
// provided
List<Integer> statusFilters = request.getMatchStatusFilters();
String sortBy = request.getSortBy();
Disjunction disjunction = new Disjunction();
for (Integer statusFilter : statusFilters) {
disjunction.add(Restrictions.eq("status", statusFilter));
}
final QuerySelect<MatchDataFeedItemDto, MatchDataFeedItemDto> query = QueryBuilder
.builderFor(MatchDataFeedItemDto.class)
.select()
.add(Restrictions.eq("userId", userId))
.add(Restrictions.gt("deliveredDate", timeThreshold.getTime()))
.add(disjunction)
.addOrder(new Ordering(sortBy, Order.DESCENDING))
.build();
Iterable<MatchDataFeedItemDto> feedItems = dataStoreApi.findAll(query);By default, expressions are combined with AND when added separately.
Query Interface
The following query components are supported:
// equals
EqualityExpression eq(String propertyName, Object value);
// does not equal
EqualityExpression ne(String propertyName, Object value);
// less than
EqualityExpression lt(String propertyName, Object value);
// less than or equal
EqualityExpression lte(String propertyName, Object value);
// greater than
EqualityExpression gt(String propertyName, Object value);
// greater than or equal
EqualityExpression gte(String propertyName, Object value);
// between from and to (inclusive)
RangeExpression between(String propertyName, Object from, Object to);
// and - takes a variable list of expressions as arguments
Conjunction and(Criterion... criteria);
// or - takes a variable list of expressions as arguments
Disjunction or(Criterion... criteria);Resolving Entity and Property Names
Always use the property names of your Java objects in your queries. If these names differ from those used in your datastore, use annotations to provide mappings. Entity resolvers are configured to map entity classes to table or collection names. Property resolvers are configured to map object variable names to column or field names.
The following annotations are currently supported for the indicated data store type.
Custom EntityResolvers and PropertyResolvers are straightforward to configure and create.
See Morphia annotations for entity class annotation mappings.
Query Execution
The QueryExecutor interface supports the following operations:
// return an iterable of type R from the query against type T
// (R and T are often the same type)
<T, R> Iterable<R> findAll(QuerySelect<T, R> query);
// return one R from the query against type T
<T, R> R findOne(QuerySelect<T, R> query);
// save the entity of type T to the data store
<T> T save(T entity);
// save all entities in the provided iterable to the data store
<T> Iterable<T> save(Iterable<T> entities);
// save entities in batches with a configured batch size
<T> int[] saveBatch(Iterable<T> entities);Configuration
Here are some example Spring configuration files for HBase using Apache Phoenix.
HBase
Configuration properties:
hbase.connection.url=jdbc:phoenix:zkhost:2181<util:list id="entityPropertiesMappings">
<value>com.eharmony.datastore.model.MatchDataFeedItemDto</value>
</util:list>
<bean id="entityPropertiesMappingContext" class="com.eharmony.pho.mapper.EntityPropertiesMappingContext">
<constructor-arg ref="entityPropertiesMappings"/>
</bean>
<bean id="entityPropertiesResolver" class="com.eharmony.pho.mapper.EntityPropertiesResolver">
<constructor-arg ref="entityPropertiesMappingContext"/>
</bean>
<bean id="phoenixHBaseQueryTranslator" class="com.eharmony.pho.hbase.translator.PhoenixHBaseQueryTranslator">
<constructor-arg name="propertyResolver" ref="entityPropertiesResolver" />
</bean>
<bean id="phoenixProjectedResultMapper" class="com.eharmony.pho.hbase.mapper.PhoenixProjectedResultMapper">
<constructor-arg name="entityPropertiesResolver" ref="entityPropertiesResolver" />
</bean>
<bean id="phoenixHBaseQueryExecutor" class="com.eharmony.pho.hbase.query.PhoenixHBaseQueryExecutor">
<constructor-arg name="queryTranslator" ref="phoenixHBaseQueryTranslator"/>
<constructor-arg name="resultMapper" ref="phoenixProjectedResultMapper" />
</bean>
<bean id="dataStoreApi" class="com.eharmony.pho.hbase.PhoenixHBaseDataStoreApiImpl">
<constructor-arg name="connectionUrl" value="${hbase.connection.url}"/>
<constructor-arg name="queryExecutor" ref="phoenixHBaseQueryExecutor"/>
</bean>