All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.flipkart.hbaseobjectmapper.AbstractHBDAO Maven / Gradle / Ivy

Go to download

HBase ORM is a light-weight, thread-safe and performant library that enables: [1] object-oriented access of HBase rows (Data Access Object) with minimal code and good testability [2] reading from and/or writing to HBase tables in Hadoop MapReduce jobs

There is a newer version: 1.19
Show newest version
package com.flipkart.hbaseobjectmapper;

import com.flipkart.hbaseobjectmapper.codec.Codec;
import com.google.common.reflect.TypeToken;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.client.*;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.util.*;

/**
 * A Data Access Object (DAO) class that enables simple random access (read/write) of HBase rows.
 * 

* This class is thread-safe. *

* This class is designed such that only one instance of each DAO class needs to be maintained for the entire lifecycle of your program. * * @param Data type of row key, which should be '{@link Comparable} with itself' and must be {@link Serializable} (e.g. {@link String}, {@link Integer}, {@link BigDecimal} etc. or your own POJO) * @param Entity type that maps to an HBase row (this type must have implemented {@link HBRecord} interface) * @see Data access object */ @SuppressWarnings("WeakerAccess") public abstract class AbstractHBDAO, T extends HBRecord> { protected final HBObjectMapper hbObjectMapper; protected final Connection connection; protected final Class rowKeyClass; protected final Class hbRecordClass; protected final WrappedHBTable hbTable; private final Map fields; /** * Constructs a data access object using your custom {@link HBObjectMapper} *

*
* Note: If you want to use the default {@link HBObjectMapper}, just use the constructor {@link #AbstractHBDAO(Connection)} * * @param connection HBase Connection * @param hbObjectMapper Your custom {@link HBObjectMapper} * @throws IllegalStateException Annotation(s) on base entity may be incorrect */ @SuppressWarnings("unchecked") protected AbstractHBDAO(Connection connection, HBObjectMapper hbObjectMapper) { this.connection = connection; this.hbObjectMapper = hbObjectMapper; hbRecordClass = (Class) new TypeToken(getClass()) { }.getRawType(); if (hbRecordClass == null) { throw new IllegalStateException("Unable to resolve HBase record type"); } this.hbObjectMapper.validateHBClass(hbRecordClass); rowKeyClass = (Class) new TypeToken(getClass()) { }.getRawType(); if (rowKeyClass == null) { throw new IllegalStateException("Unable to resolve HBase rowkey type"); } hbTable = new WrappedHBTable<>(hbRecordClass); fields = hbObjectMapper.getHBColumnFields0(hbRecordClass); } /** * Constructs a data access object using your custom {@link HBObjectMapper} *

*
* Note: If you want to use the default {@link HBObjectMapper}, just use the constructor {@link #AbstractHBDAO(Configuration)} * * @param configuration Hadoop configuration * @param hbObjectMapper Your custom {@link HBObjectMapper} * @throws IOException Exceptions thrown by HBase * @throws IllegalStateException Annotation(s) on base entity may be incorrect */ protected AbstractHBDAO(Configuration configuration, HBObjectMapper hbObjectMapper) throws IOException { this(ConnectionFactory.createConnection(configuration), hbObjectMapper); } /** * Constructs a data access object using your custom codec *

*
* Note: If you want to use the default codec, just use the constructor {@link #AbstractHBDAO(Connection)} * * @param connection HBase Connection * @param codec Your custom codec. If null, default codec is used. * @throws IllegalStateException Annotation(s) on base entity may be incorrect */ protected AbstractHBDAO(Connection connection, Codec codec) { this(connection, HBObjectMapperFactory.construct(codec)); } /** * Constructs a data access object using your custom codec *

*
* Note: If you want to use the default codec, just use the constructor {@link #AbstractHBDAO(Configuration)} * * @param configuration Hadoop configuration * @param codec Your custom codec. If null, default codec is used. * @throws IOException Exceptions thrown by HBase * @throws IllegalStateException Annotation(s) on base entity may be incorrect */ protected AbstractHBDAO(Configuration configuration, Codec codec) throws IOException { this(ConnectionFactory.createConnection(configuration), HBObjectMapperFactory.construct(codec)); } /** * Constructs a data access object * * @param connection HBase Connection * @throws IllegalStateException Annotation(s) on base entity may be incorrect */ protected AbstractHBDAO(Connection connection) { this(connection, (Codec) null); } /** * Constructs a data access object * * @param configuration Hadoop configuration * @throws IOException Exceptions thrown by HBase * @throws IllegalStateException Annotation(s) on base entity may be incorrect */ protected AbstractHBDAO(Configuration configuration) throws IOException { this(configuration, (Codec) null); } /** * Get specified number of versions of a row from HBase table by it's row key * * @param rowKey Row key * @param numVersionsToFetch Number of versions to be retrieved * @return HBase row, deserialized as object of your bean-like class (that implements {@link HBRecord}) * @throws IOException When HBase call fails */ public T get(R rowKey, int numVersionsToFetch) throws IOException { try (Table table = getHBaseTable()) { Result result = table.get(new Get(toBytes(rowKey)).readVersions(numVersionsToFetch)); return hbObjectMapper.readValueFromResult(result, hbRecordClass); } } /** * Get a row from HBase table by it's row key * * @param rowKey Row key * @return HBase row, deserialized as object of your bean-like class (that implements {@link HBRecord}) * @throws IOException When HBase call fails */ public T get(R rowKey) throws IOException { return get(rowKey, 1); } /** * Creates an HBase {@link Get} object, for enabling specialised read of HBase rows. *

* Typically, this is used in {@link #getOnGet(Get)} method * * @param rowKey Row key * @return HBase's Get object * @see #getOnGet(Get) */ public Get getGet(R rowKey) { return new Get(toBytes(rowKey)); } /** * Fetch an HBase row for a given {@link Get} object * * @param get HBase's Get object, typically formed using the {@link #getGet(Serializable) getGet(R)} method * @return HBase row, deserialized as object of your bean-like class (that implements {@link HBRecord}) * @throws IOException When HBase call fails */ public T getOnGet(Get get) throws IOException { try (Table table = getHBaseTable()) { Result result = table.get(get); return hbObjectMapper.readValueFromResult(result, hbRecordClass); } } /** * @param gets List of {@link Get} objects for which rows have to be fetched * @return List of rows corresponding to row keys passed, deserialized as objects of your bean-like class * @throws IOException When HBase call fails */ @SuppressWarnings("unused") public List getOnGets(List gets) throws IOException { List records = new ArrayList<>(gets.size()); try (Table table = getHBaseTable()) { Result[] results = table.get(gets); for (Result result : results) { records.add(hbObjectMapper.readValueFromResult(result, hbRecordClass)); } } return records; } /** * Get specified number of versions of rows from HBase table by array of row keys (This method is a bulk variant of {@link #get(Serializable, int) get(R, int)} method) * * @param rowKeys Row keys to fetch * @param numVersionsToFetch Number of versions to be retrieved * @return Array of HBase rows, deserialized as object of your bean-like class (that implements {@link HBRecord}) * @throws IOException When HBase call fails */ public T[] get(R[] rowKeys, int numVersionsToFetch) throws IOException { List gets = new ArrayList<>(rowKeys.length); for (R rowKey : rowKeys) { gets.add(new Get(toBytes(rowKey)).readVersions(numVersionsToFetch)); } @SuppressWarnings("unchecked") T[] records = (T[]) Array.newInstance(hbRecordClass, rowKeys.length); try (Table table = getHBaseTable()) { Result[] results = table.get(gets); for (int i = 0; i < records.length; i++) { records[i] = hbObjectMapper.readValueFromResult(results[i], hbRecordClass); } } return records; } /** * Get records by array of row keys (This method is a bulk variant of {@link #get(Serializable) get(R)} method) * * @param rowKeys Row keys to fetch * @return Array of HBase rows, deserialized as object of your bean-like class (that implements {@link HBRecord}) * @throws IOException When HBase call fails */ public T[] get(R[] rowKeys) throws IOException { return get(rowKeys, 1); } /** * Get specified number of versions of rows from HBase table by list of row keys (This method is a multi-version variant of {@link #get(List)} method) * * @param rowKeys Row keys to fetch * @param numVersionsToFetch Number of versions to be retrieved * @return Array of rows corresponding to row keys passed, deserialized as objects of your bean-like class * @throws IOException When HBase call fails */ public List get(List rowKeys, int numVersionsToFetch) throws IOException { List gets = new ArrayList<>(rowKeys.size()); for (R rowKey : rowKeys) { gets.add(new Get(toBytes(rowKey)).readVersions(numVersionsToFetch)); } List records = new ArrayList<>(rowKeys.size()); try (Table table = getHBaseTable()) { Result[] results = table.get(gets); for (Result result : results) { records.add(hbObjectMapper.readValueFromResult(result, hbRecordClass)); } } return records; } /** * Get records by list of row keys (This method is a bulk variant of {@link #get(Serializable) get(R)} method) * * @param rowKeys Row keys to fetch * @return List of rows corresponding to row keys passed, deserialized as objects of your bean-like class * @throws IOException When HBase call fails */ public List get(List rowKeys) throws IOException { return get(rowKeys, 1); } /** * Get specified number of versions of rows from HBase table by a range of row keys - start key (inclusive) to end key (exclusive) *

* This method is a multi-version variant of {@link #get(Serializable, Serializable) get(R, R)} *

* Caution: If you expect large number or rows for given start and end row keys, do not use this method. Use the iterable variant {@link #records(Serializable, boolean, Serializable, boolean, int, int) records(R, boolean, R, boolean, int, int)} instead. * * @param startRowKey Row start * @param endRowKey Row end * @param numVersionsToFetch Number of versions to be retrieved * @return List of rows corresponding to row keys passed, deserialized as objects of your bean-like class * @throws IOException When HBase call fails */ public List get(R startRowKey, R endRowKey, int numVersionsToFetch) throws IOException { Scan scan = new Scan() .withStartRow(toBytes(startRowKey)) .withStopRow(toBytes(endRowKey)) .readVersions(numVersionsToFetch); return get(scan); } /** * Get specified number of versions of rows from HBase table by a range of row keys - start key (inclusive) to end key (exclusive) *

* Caution: If you expect large number or rows for given start and end row keys, do not use this method. Use the iterable variant {@link #records(Serializable, boolean, Serializable, boolean, int, int) records(R, boolean, R, boolean, int, int)} instead. * * @param startRowKey Row start * @param endRowKey Row end * @param startRowInclusive whether we should include the start row when scan? * @param endRowInclusive whether we should include the end row when scan? * @param numVersionsToFetch Number of versions to be retrieved * @return List of rows corresponding to row keys passed, deserialized as objects of your bean-like class * @throws IOException When HBase call fails */ public List get(R startRowKey, boolean startRowInclusive, R endRowKey, boolean endRowInclusive, int numVersionsToFetch) throws IOException { Scan scan = new Scan() .withStartRow(toBytes(startRowKey), startRowInclusive) .withStopRow(toBytes(endRowKey), endRowInclusive) .readVersions(numVersionsToFetch); return get(scan); } /** * Get records from HBase table for a given {@link Scan} object. *

* Caution: If you expect large number or rows for given scan criteria, do not use this method. Use the iterable variant {@link #records(Scan)} instead. * * @param scan HBase's scan object * @return Records corresponding to {@link Scan} object passed, deserialized as objects of your bean-like class * @throws IOException When HBase call fails */ public List get(Scan scan) throws IOException { List records = new ArrayList<>(); try (Table table = getHBaseTable(); ResultScanner scanner = table.getScanner(scan)) { for (Result result : scanner) { records.add(hbObjectMapper.readValueFromResult(result, hbRecordClass)); } } return records; } /** * Get records whose row keys match provided prefix *

* Caution: If you expect large number or rows for given row key prefix, do not use this method. Use the iterable variant {@link #recordsByPrefix(byte[], int)} instead. * * @param rowPrefix Prefix to scan for * @param numVersionsToFetch Number of versions to be retrieved * @return Records corresponding to provided prefix, deserialized as list of objects of your bean-like class * @throws IOException When HBase call fails */ public List getByPrefix(byte[] rowPrefix, int numVersionsToFetch) throws IOException { Scan scan = new Scan() .setRowPrefixFilter(rowPrefix) .readVersions(numVersionsToFetch); return get(scan); } /** * Get records whose row keys match provided prefix *

* Caution: If you expect large number or rows for given row key prefix, do not use this method. Use the iterable variant {@link #recordsByPrefix(byte[])} instead. * * @param rowPrefix Prefix to scan for * @return Records corresponding to {@link Scan} object passed, deserialized as objects of your bean-like class * @throws IOException When HBase call fails */ public List getByPrefix(byte[] rowPrefix) throws IOException { return getByPrefix(rowPrefix, 1); } /** * Get an iterable to iterate over records matching given {@link Scan} object * * @param scan HBase's scan object * @return An iterable to iterate over records matching the scan criteria * @throws IOException When HBase call fails */ public Records records(Scan scan) throws IOException { return new Records<>(connection, hbObjectMapper, hbRecordClass, hbTable.getName(), scan); } /** * Get an iterable to iterate over records matching given row key prefix * * @param rowPrefix Prefix to scan for * @return An iterable to iterate over records matching the scan criteria * @throws IOException When HBase call fails */ public Records recordsByPrefix(byte[] rowPrefix) throws IOException { return recordsByPrefix(rowPrefix, 1); } /** * Get an iterable to iterate over records matching given row key prefix and fetch specific number of versions * * @param rowPrefix Prefix to scan for * @param numVersionsToFetch Number of versions to be retrieved * @return An iterable over objects of your bean-like class * @throws IOException When HBase call fails */ public Records recordsByPrefix(byte[] rowPrefix, int numVersionsToFetch) throws IOException { Scan scan = new Scan() .setRowPrefixFilter(rowPrefix) .readVersions(numVersionsToFetch); return records(scan); } /** * Get an iterable to iterate over records matching range of row keys (start to end) * * @param startRowKey Row start (inclusive) * @param endRowKey Row end (exclusive) * @return An iterable over objects of your bean-like class * @throws IOException When HBase call fails */ public Iterable records(R startRowKey, R endRowKey) throws IOException { Scan scan = new Scan() .withStartRow(toBytes(startRowKey)) .withStopRow(toBytes(endRowKey)); return records(scan); } /** * Get an iterable to iterate over records matching range of row keys (start to end) and other criteria *

* Note: If you need advanced scanning, consider using {@link #records(Scan)} * * @param startRowKey Row start * @param startRowInclusive whether we should include the start row when scan * @param endRowKey Row end * @param endRowInclusive whether we should include the end row when scan * @param numVersionsToFetch Number of versions to be retrieved * @param numRowsForCaching Number of rows for caching (higher values are faster but take more memory) * @return An iterable over objects of your bean-like class * @throws IOException When HBase call fails * @see HBase Scan caching */ public Records records(R startRowKey, boolean startRowInclusive, R endRowKey, boolean endRowInclusive, int numVersionsToFetch, int numRowsForCaching) throws IOException { Scan scan = new Scan() .withStartRow(toBytes(startRowKey), startRowInclusive) .withStopRow(toBytes(endRowKey), endRowInclusive) .readVersions(numVersionsToFetch) .setCaching(numRowsForCaching); return records(scan); } /** * Get an iterable to iterate over records matching range of row keys (start to end) and other criteria * * @param startRowKey Row start * @param endRowKey Row end * @param numVersionsToFetch Number of versions to be retrieved * @return An iterable over objects of your bean-like class * @throws IOException When HBase call fails */ public Records records(R startRowKey, R endRowKey, int numVersionsToFetch) throws IOException { Scan scan = new Scan() .withStartRow(toBytes(startRowKey)) .withStopRow(toBytes(endRowKey)) .readVersions(numVersionsToFetch); return records(scan); } private WrappedHBColumn validateAndGetLongColumn(String fieldName) { Field field = getField(fieldName); if (!Long.class.equals(field.getType())) { throw new IllegalArgumentException(String.format("Invalid attempt to increment a non-Long field (%s.%s)", hbRecordClass.getName(), fieldName)); } return new WrappedHBColumn(field, true); } /** * Increments field by specified amount * * @param rowKey Row key of the record whose column needs to be incremented * @param fieldName Field that needs to be incremented (this must be of {@link Long} type) * @param amount Amount by which the HBase column needs to be incremented * @return The new value, post increment * @throws IOException When HBase call fails */ public long increment(R rowKey, String fieldName, long amount) throws IOException { WrappedHBColumn hbColumn = validateAndGetLongColumn(fieldName); try (Table table = getHBaseTable()) { return table.incrementColumnValue(toBytes(rowKey), hbColumn.familyBytes(), hbColumn.columnBytes(), amount); } } /** * Increments field by specified amount * * @param rowKey Row key of the record whose column needs to be incremented * @param fieldName Field that needs to be incremented (this must be of {@link Long} type) * @param amount Amount by which the HBase column needs to be incremented * @param durability The persistence guarantee for this increment (see {@link Durability}) * @return The new value, post increment * @throws IOException When HBase call fails */ public long increment(R rowKey, String fieldName, long amount, Durability durability) throws IOException { WrappedHBColumn hbColumn = validateAndGetLongColumn(fieldName); try (Table table = getHBaseTable()) { return table.incrementColumnValue(toBytes(rowKey), hbColumn.familyBytes(), hbColumn.columnBytes(), amount, durability); } } /** * Gets (native) {@link Increment} object for given row key, to be later used in {@link #increment(Increment)} method. * * @param rowKey HBase row key * @return Increment object */ public Increment getIncrement(R rowKey) { return new Increment(toBytes(rowKey)); } /** * Performs HBase {@link Table#increment} on the given {@link Increment} object
*
* Note:

    *
  • You may construct {@link Increment} object using the {@link #getIncrement(Serializable) getIncrement(R)} method
  • *
  • Unlike the {@link #increment(Serializable, String, long)} methods, this method skips some validations (hence, be cautious)
  • *
* * @param increment HBase Increment object * @return Partial object containing (only) values of fields that were incremented * @throws IOException When HBase call fails */ public T increment(Increment increment) throws IOException { try (Table table = getHBaseTable()) { Result result = table.increment(increment); return hbObjectMapper.readValueFromResult(result, hbRecordClass); } } /** * Appends value of a field with the value provided. If the field is empty, the value provided becomes the value of the field. *

* This operation applies to fields of types such as {@link String}. Use this on non-{@link String} fields only if you understand the semantics. * * @param rowKey Row key of the record * @param fieldName Name of the field whose value needs to be appended * @param valueToAppend Value to be appended * @return Partial object containing (only) value of field that was appended * @throws IOException When HBase call fails * @see Table#append(Append) * @see #append(Serializable, Map) append(R, Map) */ public T append(R rowKey, String fieldName, Object valueToAppend) throws IOException { Map one = new HashMap<>(1); one.put(fieldName, valueToAppend); return append(rowKey, one); } /** * Appends values of fields with the values provided. For empty fields, the value provided becomes the value of the field. *

* This operation applies to fields of types such as {@link String}. Use this on non-{@link String} fields only if you understand the semantics. * * @param rowKey Row key of the record * @param valuesToAppend Map of field name of value to be appended * @return Partial object containing (only) values of fields that were appended * @throws IOException When HBase call fails * @see Table#append(Append) * @see #append(Serializable, String, Object) append(R, String, Object) */ public T append(R rowKey, Map valuesToAppend) throws IOException { Append append = getAppend(rowKey); for (Map.Entry e : valuesToAppend.entrySet()) { String fieldName = e.getKey(); Field field = getField(fieldName); WrappedHBColumn hbColumn = new WrappedHBColumn(field, true); Object value = e.getValue(); if (!field.getType().isAssignableFrom(value.getClass())) { throw new IllegalArgumentException(String.format("An attempt was made to append a value of type '%s' to field '%s', which is of type '%s' (incompatible)", value.getClass(), fieldName, field.getType())); } append.addColumn(hbColumn.familyBytes(), hbColumn.columnBytes(), hbObjectMapper.valueToByteArray((Serializable) value, hbColumn.codecFlags()) ); } return append(append); } /** * Gets HBase's (native) {@link Append} object for given row key, to be later used in {@link #append(Append)} method. * * @param rowKey HBase row key * @return HBase's {@link Append} object */ public Append getAppend(R rowKey) { return new Append(toBytes(rowKey)); } /** * Performs HBase's {@link Table#append} on the given {@link Append} object
*
* Note:
    *
  • You may construct {@link Append} object using the {@link #getAppend(Serializable) getAppend(R)} method
  • *
  • Unlike the {@link #append(Serializable, String, Object) append(R, String, Object)} and related methods, this method skips some validations. So, use this only if you need access to HBase's native methods.
  • *
* * @param append HBase's {@link Append} object * @return Partial object containing (only) values of fields that were appended * @throws IOException When HBase call fails */ public T append(Append append) throws IOException { try (Table table = getHBaseTable()) { Result result = table.append(append); return hbObjectMapper.readValueFromResult(result, hbRecordClass); } } /** * Get specified number of versions of rows by a range of row keys (start to end) * * @param startRowKey Row start * @param endRowKey Row end * @return List of rows corresponding to row keys passed, deserialized as objects of your bean-like class * @throws IOException When HBase call fails */ public List get(R startRowKey, R endRowKey) throws IOException { return get(startRowKey, endRowKey, 1); } /** * Persist your bean-like object (of a class that implements {@link HBRecord}) to HBase table * * @param record Object that needs to be persisted * @return Row key of the persisted object, represented as a {@link String} * @throws IOException When HBase call fails */ public R persist(T record) throws IOException { Put put = hbObjectMapper.writeValueAsPut0(record); try (Table table = getHBaseTable()) { table.put(put); return record.composeRowKey(); } } /** * Persist a list of your bean-like objects (of a class that implements {@link HBRecord}) to HBase table (this is a bulk variant of {@link #persist(HBRecord)} method) * * @param records List of objects that needs to be persisted * @return Row keys of the persisted objects, represented as a {@link String} * @throws IOException When HBase call fails */ public List persist(List records) throws IOException { List puts = new ArrayList<>(records.size()); List rowKeys = new ArrayList<>(records.size()); for (T record : records) { puts.add(hbObjectMapper.writeValueAsPut0(record)); rowKeys.add(record.composeRowKey()); } try (Table table = getHBaseTable()) { table.put(puts); } return rowKeys; } /** * Delete a row from an HBase table for a given row key * * @param rowKey row key to delete * @throws IOException When HBase call fails */ public void delete(R rowKey) throws IOException { Delete delete = new Delete(toBytes(rowKey)); try (Table table = getHBaseTable()) { table.delete(delete); } } /** * Delete HBase row by object (of class that implements {@link HBRecord} * * @param record Object to delete * @throws IOException When HBase call fails */ public void delete(T record) throws IOException { this.delete(record.composeRowKey()); } /** * Delete HBase rows for an array of row keys * * @param rowKeys row keys to delete * @throws IOException When HBase call fails */ public void delete(R[] rowKeys) throws IOException { List deletes = new ArrayList<>(rowKeys.length); for (R rowKey : rowKeys) { deletes.add(new Delete(toBytes(rowKey))); } try (Table table = getHBaseTable()) { table.delete(deletes); } } /** * Delete HBase rows by object references * * @param records Records to delete * @throws IOException When HBase call fails */ public void delete(List records) throws IOException { List deletes = new ArrayList<>(records.size()); for (T record : records) { deletes.add(new Delete(toBytes(record.composeRowKey()))); } try (Table table = getHBaseTable()) { table.delete(deletes); } } /** * Get HBase table name * * @return Name of table read as String */ public String getTableName() { return hbRecordClass.getAnnotation(HBTable.class).name(); } /** * Get the mapped column families and their versions (as specified in {@link HBTable} annotation) * * @return A {@link Map} containing names of column families as mapped in the entity class and number of versions */ public Map getColumnFamiliesAndVersions() { return hbObjectMapper.getColumnFamiliesAndVersions(hbRecordClass); } /** * Get list of fields (private variables of your bean-like class) * * @return A {@link Set} containing names of fields */ public Set getFields() { return fields.keySet(); } /** * Get reference to HBase table * * @return {@link HTable} object * @throws IOException When table reference couldn't be resolved through connection */ public Table getHBaseTable() throws IOException { return connection.getTable(hbTable.getName()); } private Field getField(String fieldName) { Field field = fields.get(fieldName); if (field == null) { throw new IllegalArgumentException(String.format("Unrecognized field: '%s'. Choose one of %s%n", fieldName, fields.keySet())); } return field; } private void populateFieldValuesToMap(Field field, Result result, Map> map) { if (result.isEmpty()) { return; } WrappedHBColumn hbColumn = new WrappedHBColumn(field, true); List cells = result.getColumnCells(hbColumn.familyBytes(), hbColumn.columnBytes()); for (Cell cell : cells) { Type fieldType = hbObjectMapper.getFieldType(field, hbColumn.isMultiVersioned()); @SuppressWarnings("unchecked") final R rowKey = hbObjectMapper.bytesToRowKey(CellUtil.cloneRow(cell), hbTable.getCodecFlags(), (Class) field.getDeclaringClass()); if (!map.containsKey(rowKey)) { map.put(rowKey, new TreeMap<>()); } map.get(rowKey).put(cell.getTimestamp(), hbObjectMapper.byteArrayToValue(CellUtil.cloneValue(cell), fieldType, hbColumn.codecFlags())); } } /** * Fetch value of column for a given row key and field * * @param rowKey Row key to reference HBase row * @param fieldName Name of the private variable of your bean-like object (of a class that implements {@link HBRecord}) whose corresponding column needs to be fetched * @return Value of the column (boxed), null if row with given rowKey doesn't exist or such field doesn't exist for the row * @throws IOException When HBase call fails */ public Object fetchFieldValue(R rowKey, String fieldName) throws IOException { final NavigableMap fieldValues = fetchFieldValue(rowKey, fieldName, 1); if (fieldValues == null || fieldValues.isEmpty()) { return null; } else { return fieldValues.lastEntry().getValue(); } } /** * Fetch multiple versions of column values by row key and field name * * @param rowKey Row key to reference HBase row * @param fieldName Name of the private variable of your bean-like object (of a class that implements {@link HBRecord}) whose corresponding column needs to be fetched * @param numVersionsToFetch Number of versions to be retrieved * @return {@link NavigableMap} of timestamps and values of the column (boxed), null if row with given rowKey doesn't exist or such field doesn't exist for the row * @throws IOException When HBase call fails */ public NavigableMap fetchFieldValue(R rowKey, String fieldName, int numVersionsToFetch) throws IOException { @SuppressWarnings("unchecked") R[] array = (R[]) Array.newInstance(rowKeyClass, 1); array[0] = rowKey; return fetchFieldValues(array, fieldName, numVersionsToFetch).get(rowKey); } /** * Fetch values of an HBase column for a range of row keys (start and end) and field name * * @param startRowKey Start row key (scan start) * @param endRowKey End row key (scan end) * @param fieldName Name of the private variable of your bean-like object (of a class that implements {@link HBRecord}) whose corresponding column needs to be fetched * @return Map of row key and column value * @throws IOException When HBase call fails */ public Map fetchFieldValues(R startRowKey, R endRowKey, String fieldName) throws IOException { final Map> multiVersionedMap = fetchFieldValues(startRowKey, endRowKey, fieldName, 1); return toSingleVersioned(multiVersionedMap, 10); } private Map toSingleVersioned(Map> multiVersionedMap, int mapInitialCapacity) { Map map = new HashMap<>(mapInitialCapacity, 1.0f); for (Map.Entry> e : multiVersionedMap.entrySet()) { map.put(e.getKey(), e.getValue().lastEntry().getValue()); } return map; } /** * Fetch specified number of versions of values of an HBase column for a range of row keys (start and end) and field name * * @param startRowKey Start row key (scan start) * @param endRowKey End row key (scan end) * @param fieldName Name of the private variable of your bean-like object (of a class that implements {@link HBRecord}) whose corresponding column needs to be fetched * @param numVersionsToFetch Number of versions to be retrieved * @return Map of row key and column values (versioned) * @throws IOException When HBase call fails */ public NavigableMap> fetchFieldValues(R startRowKey, R endRowKey, String fieldName, int numVersionsToFetch) throws IOException { Field field = getField(fieldName); WrappedHBColumn hbColumn = new WrappedHBColumn(field, true); Scan scan = new Scan().withStartRow(toBytes(startRowKey)).withStopRow(toBytes(endRowKey)); scan.addColumn(hbColumn.familyBytes(), hbColumn.columnBytes()); scan.readVersions(numVersionsToFetch); NavigableMap> map = new TreeMap<>(); try (Table table = getHBaseTable(); ResultScanner scanner = table.getScanner(scan)) { for (Result result : scanner) { populateFieldValuesToMap(field, result, map); } } return map; } /** * Fetch column values for a given array of row keys (bulk variant of method {@link #fetchFieldValue(Serializable, String) fetchFieldValue(R, String)}) * * @param rowKeys Array of row keys to fetch * @param fieldName Name of the private variable of your bean-like object (of a class that implements {@link HBRecord}) whose corresponding column needs to be fetched * @return Map of row key and column values * @throws IOException Exception from HBase */ public Map fetchFieldValues(R[] rowKeys, String fieldName) throws IOException { final Map> multiVersionedMap = fetchFieldValues(rowKeys, fieldName, 1); return toSingleVersioned(multiVersionedMap, rowKeys.length); } /** * Fetch specified number of versions of values of an HBase column for an array of row keys * * @param rowKeys Array of row keys to fetch * @param fieldName Name of the private variable of your bean-like object (of a class that implements {@link HBRecord}) whose corresponding column needs to be fetched * @param numVersionsToFetch Number of versions to be retrieved * @return Map of row key and column values (versioned) * @throws IOException When HBase call fails */ public Map> fetchFieldValues(R[] rowKeys, String fieldName, int numVersionsToFetch) throws IOException { Field field = getField(fieldName); WrappedHBColumn hbColumn = new WrappedHBColumn(field, true); List gets = new ArrayList<>(rowKeys.length); for (R rowKey : rowKeys) { Get get = new Get(toBytes(rowKey)); get.readVersions(numVersionsToFetch); get.addColumn(hbColumn.familyBytes(), hbColumn.columnBytes()); gets.add(get); } Map> map = new LinkedHashMap<>(rowKeys.length, 1.0f); try (Table table = getHBaseTable()) { Result[] results = table.get(gets); for (Result result : results) { populateFieldValuesToMap(field, result, map); } } return map; } /** * Convert typed row key into a byte array * * @param rowKey Row key, as used in your code * @return Byte array corresponding to HBase row key */ public byte[] toBytes(R rowKey) { return hbObjectMapper.rowKeyToBytes(rowKey, hbTable.getCodecFlags()); } /** * Check whether a row exists or not * * @param rowKey Row key * @return true if row with given row key exists * @throws IOException When HBase call fails */ public boolean exists(R rowKey) throws IOException { try (Table table = getHBaseTable()) { return table.exists(new Get( toBytes(rowKey) )); } } /** * Check whether specified rows exist or not * * @param rowKeys Row keys * @return Array with true/false values corresponding to whether row with given row keys exist * @throws IOException When HBase call fails */ public boolean[] exists(R[] rowKeys) throws IOException { List gets = new ArrayList<>(rowKeys.length); for (R rowKey : rowKeys) { gets.add(new Get( toBytes(rowKey) )); } try (Table table = getHBaseTable()) { return table.exists(gets); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy