Acessando valores de anotações Java
Como todo desenvolvedor Java, eu me acostumei a utilizar anotações em declarações de classe, declarações de atributos e de métodos para incluir metadados dessas declarações e aproveitar os recursos de frameworks como o Spring. Mas, até então, nunca precisei criá-las.
Se até então nunca precisei, qual foi a situação em que identifiquei essa necessidade? Bom, foi a seguinte: realizar operações de CRUD em um sistema de armazenamento que não possui uma interface como o JPA para facilitar o uso dessas operações e mapear os atributos da classe com as colunas da tabela correspondente a essa classe. No caso, esse sistema de armazenamento é o Apache HBase, o qual armazena dados não relacionais e opera sobre o Hadoop File System.
Para exemplificar, com o JPA podemos mapear uma classe dessa forma:
@Entity
@Table(name = "cars")
public class Car {
@Column(name = "model")
private String model;
}
E, assim, para persistir o objeto no banco de dados relacional, basta utilizar o repository da entidade e seu método save:
Car car = new Car();
car.setModel("fusca");
carRepository.save(car);
Por outro lado, o client do Apache HBase não possui um modo fácil de mapear os atributos da classe para a respectiva coluna (definida por meio de uma Column Family e uma Column Qualifier). Então, para persistir um objeto, temos que passar os dados da coluna para um objeto Put e chamar o método table.put(Put put):
Car car = new Car();
car.setModel("fusca");
Table table = connection.getTable(TableName.valueOf("cars"));
Put putACar = new Put(Bytes.toBytes("0001"));
putACar.addColumn(
Bytes.toBytes("attributes"), // Column family
Bytes.toBytes("model"), // Column qualifier
Bytes.toBytes(entityObject.getModel()) // Column value
);
table.put(putACar);
table.close();
Observe que para todos os atributos de Car terei que indicar a Column Family, a Column Qualifier e chamar o getter do atributo para salvar o seu valor na tabela do HBase. Ou seja, quanto mais atributos, mais trabalhoso ficará o processo e a manutenção será nada fácil.
É neste momento que as anotações Java entram para resolver o problema. Com elas podemos salvar esses metadados da tabela do HBase (columns families e qualifiers) para cada atributo da entidade sem nos preocuparmos com a quantidade desses atributos, uma vez que os dados poderão ser persistidos de modo dinâmico.
A primeira anotação que criei mapeia o nome da tabela com a sua respectiva classe Java:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface HTable {
public String name() default "";
}
A segunda mapeia os atributos da classe para a respectiva coluna do HBase:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface HTable {
public String family() default "";
public String qualifier() default "";
}
Agora com essas anotações, podemos declarar a classe Car de forma bem parecida caso o banco de dados fosse relacional:
@HTable(name = "cars")
public class Car {
@HColumn(family = "attributes", qualifier = "model")
private String model;
}
Agora que os dados da tabela do HBase estão salvos nos elementos dessas anotações, podemos acessá-los iterando sobre os atributos da classe e chamando o método getAnnotation do objeto Field e passando como parâmetro a anotação criada (HColumn):
for (Field field : Car.class.getDeclaredFields()) {
String family = field.getAnnotation(HColumn.class).family();
String qualifier = field.getAnnotation(HColumn.class).qualifier();
}
E, então, a persistência de dados para o HBase pode ser operada da seguinte maneira:
Car car = new Car();
car.setModel("fusca");
Table table = connection.getTable(
Car.class.getAnnotation(HTable.class).name()
);
Put putACar = new Put(Bytes.toBytes("0001"));
for (Field field : Entity.class.getDeclaredFields()) {
String family = field.getAnnotation(HColumn.class).family();
String qualifier = field.getAnnotation(HColumn.class).qualifier();
putACar.addColumn(
Bytes.toBytes(family),
Bytes.toBytes(qualifier),
Bytes.toBytes(car.getValueByFieldName(field.getName())
);
}
table.put(putACar);
table.close();
Por fim, para que a inserção dos dados no objeto Put seja realmente de modo dinâmico, basta construirmos um método para obter o valor do atributo por meio do seu nome, o qual está representado pelo car.getValueByFieldName().
Até a próxima!