Skip to main content

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!

comments powered by Disqus