PHO (Phoenix-HBase ORM)
PHO is a Object Relational Mapping (ORM) library for building and executing queries on HBase using Apache Phoenix. It provides ORM-like mappings and DSL-style query building. Initially developed and open sourced by eHarmony, it is available here.
Its interfaces and generic annotations allows the ability to switch the data store api in the future without changing the queries. Currently, it only supports HBase integration using Apache Phoenix. However, it’s very easy to plugin other implementations if need be.
Entity Class
Suppose we have the following TestClass we want to query against 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 the QueryBuilder and the static, Hibernate-style Restrictions methods to construct our queries.
Simple Queries
Construct a query to find all user matches which are delivered in past 2 days and not in 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 not only do we want to find items with a date older than a day ago, but also find the matches in different status and order the results by deliveryDate and limit the results 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);
Note: by default, expressions will be ANDed together when added separately.
Query Interface
The following query components are supported:
// equals EqualityExpression eq(String propertyName, Object value); // does not equal (not equals); 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 you will use annotations to provide the mappings. Entity Resolvers are configured to map the entity classes to table/collection names. Property Resolvers are configured to map the names of your object variables to column/field names.
The following annotations are currently supported for the indicated data store type. Custom EntityResolvers and PropertyResolvers are easy 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 will often be the same type) <T, R> Iterable<R> findAll(QuerySelect<T, R> query); // return aone 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 of the entities in the provided iterable to data store <T> Iterable<T> save(Iterable<T> entities); // saves all the entities in batches with configured batch size <T> int[] saveBatch(Iterable<T> entities);
Configuration
Here are some example Spring configuration files for HBase using Apache Phoenix.
HBase
configuration proeprties hbase.connection.url=jdbc:phoenix:zkhost:2181
<!-- Register your entity bean here --> <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>