Inicialização reflexiva de atributos final

Olá pessoal! Estou com um conflito entre a inicialização do objeto e a inicialização reflexiva de atributos final.

É o seguinte:

Estou fazendo uma espécie de DAO voltado para programadores oracle, ou seja: ele terá uma sintaxe bem similar à DML. Para conseguir a flexibilidade necessária para gerar selects complexos, precisei criar um Pojo inteligente (DbTable) que se encarrega de carregar toda a infraestrutura de mapeamento com as tabelas do banco. Os “Beans” serão filhos da DbTable e terão que declarar suas colunas como objetos TableColumn anotados.

Para assegurar que os selects desse DAO sejam feitos de forma muito similar à da DML, fiz que esses atributos fossem publicos e finais. Nota: esses atributos representam colunas e naõ dados.

Devido aos atributos de coluna serem publicos, eu os tornei final p/ evitar problemas. Porém ao fazer isso, o java me obriga a inicializar a variável (sendo assim, atribuo null nela).

O problema aparece quando vou inicializar meu “Bean”. Pela pilha de inicialização, o java chama o super do construtor (DbTable) que é encarregado de instanciar os atributos TableColumn da classe filha via reflexão. Porém ao executar o construtor da classe (Client), essa anula o TableColumn instanciado pela DbTable!

Seguem abaixo os códigos que exemplificam meu problema (Eu simplifiquei ao máximo as classes apenas para evidenciar o problema em si).

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    String name();

}
public class TableColumn {

    public static TableColumn Integer(DbTable table, String name) {
	return new TableColumn(table, name);
    }

    private final DbTable table;
    private final String name;


    private TableColumn(DbTable table, String name) {
	this.table = table;
	this.name = name;
    }
}
import java.lang.reflect.Field;

public abstract class DbTable {

    private String tableName = this.getClass().getSimpleName();
    private String alias = this.tableName;

    protected DbTable() throws Exception {
	System.out.println("Inicio de DbTable()...");
	loadColumns();
    }

    private TableColumn instancializeColumns(Field field) throws Exception {
	Column column = field.getAnnotation(Column.class);
	TableColumn tableColumn = TableColumn.Integer(this, column.name());

	field.set(this, tableColumn);

	return tableColumn;
    }

    protected void loadColumns() throws Exception {
	for (Field field : this.getClass().getFields()) {
	    if (field.getType() == TableColumn.class) {
		field.setAccessible(true);
		TableColumn column = (TableColumn) field.get(this);
		if (column == null) column = instancializeColumns(field);
		System.out.format("DbTable() Instanciou o atributo %s com [%s]\n", field.getName(), field.get(this));
	    }
	}
    }
}
public class Clients extends DbTable {
    @Column(name = "id")
    public final TableColumn id = null;

    public Clients() throws Exception {
	super();
	System.out.format("Clients() Instanciou o atributo %s com [%s]\n", "id", this.id);

    }
}
public class Main {
    public static void main(String[] args) throws Exception {
	Clients c = new Clients();
	System.out.println("ID após o termino da inicialização: " + c.id);
    }

}

Saída:

Não consegui descobrir como modificar um atributo para final via reflexão, se alguém souber como fazer, seria muito útil.

Transformar os objetos TableColumn em static também naõ ajudará, pois isso causará problemas para referenciar as colunas do select com as tabelas envolvidas.

Alguém poderia me ajudar? Até com idéias mirabolantes de magia negra?

Vc já pensou em usar JPA?

Parece que vc tá reinventando a roda.

Olá!

Já pensei em JPA sim, só q encontrei alguns problemas nessa abordagem… talvez por inexperiência minha no assunto…

Por exemplo:
Tenho meu genericDao com beans anotados. Se quero os nomes dos clientes de RG X, Y e Z, faço 3 pesquisas (queryBySample) criando um Bean e setando o RG para cada um dos valores e recebo 3 beans que possuem todos os atributos do bean populados.

Em outras palavras:
O q deveria ser isso:
select name from clients where rg in(X,Y,Z)

Se transforma nisso:
select id, name, address, age, is_active, …, created_at from clients where rg = X
select id, name, address, age, is_active, …, created_at from clients where rg = Y
select id, name, address, age, is_active, …, created_at from clients where rg = Z

3 requisições de todos os campos ao banco de dado. Em tabelas pequenas, tudo bem. Mas e para sistemas que possuem tabelas de 150 campos? E se o desempenho for critico? Entende a origem do meu problema?

Minha idéia é criar algo assim:
Client client = new Client();
select(client.name).from(client).where(client.rg.in(X,Y,Z))

E com isso, o meu framework montaria um único select e retornaria os objetos que eu preciso manipular.

Claro, se já existir algo capaz de fazer isso disponível, eu tratarei de ver na mesma hora! :smiley:

E ai, ninguém tem nenhuma idéia p/ tentar driblar o problema? :frowning:

Eu acho que hibernate ainda te resolve isso.

Session s = HibernateUtil.openSession();//Ou uma session de qualquer lugar
Criteria crit = s.createCriteria(Client.class);
Disjunction dis = new Disjunction();//Nao lembro direito como inicia esse objeto mas voce procura.
dis.add(Restrictions.eq("rg",X));
dis.add(Restrictions.eq("rg",Y));
dis.add(Restrictions.eq("rg",Z));
crit.add(dis);
List<Client> list = crit.list();

resolve mesmo

na verdade da pra fazer melhor com hibernate

Session s = HibernateUtil.openSession();//Ou uma session de qualquer lugar Criteria crit = s.createCriteria(Client.class); crit.add(Restrictions.in("rg",new Object[] {X,Y,Z})); List&lt;Client&gt; list = crit.list();

com JPA tambem da pra fazer

JPA QL = “c From Client as c where c.rg in(X,Y,Z)”;

se vc só quer o nome = “c.name From Client as c where c.rg in(X,Y,Z)”;

não tente reiventar a roda, que da muitaaaaaaaa dor de cabeça ^^ … pessoal do hibernate ta nessa a nos já

Interessante, essas dicas resolvem mesmo o problema da clausula "IN". Muito bom!

Só faltam alguns outros problemas, vou listar aqui para vcs verem:

[list]Trazer apenas algumas colunas da tabela ao invez da linha toda - Tem lugares q eu só preciso de 2 ou 3 campos e não de 150… e cá entre nós, fazer um beam p/ representar cada "visão" da mesma tabela (assim traria apenas as colunas q estao no bean) é algo um tanto qto estúpido e sujo p/ se fazer num código… eu vejo isso como retrabalho desnecessário, aumento potencial de pontos de bug e dificuldade no entendimento posterior[/list]

[list]Caso eu queira fazer um count ou chamar alguma outra função de banco na lista de colunas do select - O q eu vi até agora é q isso deve ser feito por fora, no melhor estilo RAW JDBC[/list]

[list]Fazer agrupamentos "group by" ou "having by" com componentes nativos - novamente sem recorer ao velho RAW JDBC[/list]

[list]Criar joins com tabelas diferentes e obter algo como uma lista dos objetos utilizados no select - Por ex: se eu fizer um join para obter 2 colunas de cliente e 2 colunas de produto, eu receberia 2 objetos por linha retornada contendo os dados das minhas colunas[/list]

[list]Além disso eu tenho fortes limitações cerebrais e motoras que me bloqueiam sempre que eu vejo que o select que eu quero executar possui apenas 15 singelas linhas bem identadas e de fácil entendimento se transformam em um monstro amorfo de cerca de 30 linhas. - Claro q exagerei, na maioria dos casos normais de tabela<->bean isso não ocorre. Esses problemas costumam aparecer em relatórios e aplicações de análise, que precisam cruzar dados das mais diferentes formas[/list]

Eu não quero provocar guerra ou discussões fervorozas em defesa do hibernate, JPA ou sei lá o q. Apenas estou espondo os problemas que já presenciei qdo decidi me aventurar pelos mares do hibernate e da JPA.

Por isso decidi fazer um framework de persistência voltado à programação de scripts (mesma utilizada pelos bancos de dados relacionais padrões de mercado) ao invés de fazê-lo voltado à programação orientada a objetos.

Aliás, q mal há em criar uma roda com câmara e pneu quando tudo que se tem são rodas de madeira e metal? :wink:

[quote=barenko]Interessante, essas dicas resolvem mesmo o problema da clausula "IN". Muito bom!

Só faltam alguns outros problemas, vou listar aqui para vcs verem:

[list]Trazer apenas algumas colunas da tabela ao invez da linha toda - Tem lugares q eu só preciso de 2 ou 3 campos e não de 150… e cá entre nós, fazer um beam p/ representar cada "visão" da mesma tabela (assim traria apenas as colunas q estao no bean) é algo um tanto qto estúpido e sujo p/ se fazer num código… eu vejo isso como retrabalho desnecessário, aumento potencial de pontos de bug e dificuldade no entendimento posterior[/list]
[/quote]

O Hibernate tem o esquema de apenas carregar o dado do atributo quando voce fizer um getAlgo no objeto. Então acho que mesmo usando duas colunas voce pode carregar o objeto inteiro sem se preocupar.

PS: Claro se voce não mudar essa função para o modo EAGER ou então não pegar da Session com o comando get(Seriable id)

Criteria c = session.createCriteria(Client.class);
c.setProjection(Projections.rowCount);
System.out.println(c.uniqueObject());

Quanto a do group by e havin by e a outra questão não sei te responder.

Fiquei curioso qto a como funciona isso…

Por ex:

Eu tenho um bean (B) com 3 atributos (a1, a2, a3).
Configuro o Hibernate para Fetch.SELECT (ou o LAZY depreciado).
Até ai, ok… a partir de agora, corrija-me se eu estiver errado:

Eu quero obter apenas o a1. Ao efetuar um select com o hibernate, eu passo o bean e ele faz um equivalente à:

select b.a1, b.a2, b.a3 from b

Pode ser que o hibernate não carregue as colunas no bean (para otimizar desempenho), mas os valores estarão em seu cache interno. Se eu precisar do conteúdo do a1 fazendo algo como :

b.getA1()

O hibernate usará o mock que ele me retornou para obter o valor do campo a1 que estava no cache. Não é isso?

Se for isso, utilizar ou não Lazy implicará no mesmo problema: trazer tudo da linha do banco no select…

Se voce fazer o getClass do objeto do Hibernate (Que provavelmente é encapsulado como um seu) voce vai ser que este extends o seu.
O hibernate cria novas classes em execução e nos getters ele coloca código para quando for acessado o atributo ele faça o select no banco.

Vou ver se eu entendi:

Bean: B
Atributos b1, b2, b3,… bn

[quote=Mark_Ameba ‘levemente modificado’][code]

  1. Session s = HibernateUtil.openSession();//Ou uma session de qualquer lugar
  2. Criteria crit = s.createCriteria(Client.class);
  3. crit.add(Restrictions.in(“b3”,new Object[] {X}));
  4. List list = crit.list();
    [/code][/quote]
    Isso deve recuperar o objeto com b3 = X.
    Por trás o hibernate faz o q? um select p/ recuperar apenas os ids?
select b1 from b where b3 in (X)

Blz, se for isso, vai ficar bem rápido para obter as referências reais dos objetos (id’s)

Ai, vc disse q o hibernate faria um select por atributo. Então se eu fizer isso:

System.out.println(b.get(bn))

O hibernate, por trás, fará isso?

select bn from b where id = b1

Então, se eu fizer isso:

System.out.format("%s %s %s %s %s",b.get(b4),b.get(b5),b.get(b6),b.get(b7),b.get(b8))

O hibernate fará isso?

select b4 from b where id = b1 select b5 from b where id = b1 select b6 from b where id = b1 select b7 from b where id = b1 select b8 from b where id = b1
Deduzi isso pensando na pilha de chamadas do java. Em lazy é isso mesmo q ocorre?

vc pode escolher que campos trazer no hibernate, mais isso vai te custar ter q popular o objeto java, o que é uma chatisse…

objetos com 150 atributos são uma bizzarrice do ponto de vista OO, não importa se na tua tabela tem 150 atributos, em Java, vc pode modelar para ser varios objetos com varios atributos…

é possivel ainda agrupos N atributos de uma mesma tabela em objetos @Embedabble, esses objetos funciona mais ou menos assim: e neste caso tornas esses embeddable groups lazy

@Table(name="USERS")
...
public class User implements Serializable {
                                                    
    @Id
    @Column(name="USER_ID", nullable=false)
    protected Long userId;
                            
    ...
    @Embedded
    protected Address address;
    ...
}
...

mesmo assim é possivel separar uma mesma tabela em N objetos java, com relacionamentos 1-1, para não puxar a linha toda de uma vez

Não, não é isso que acontece, o Hibernate não deixa você selecionar os campos que ele vai trazer, ele sempre vai buscar todas as colunas, o que pode ser lazy loaded são as associações.

Usando hibernate você faria assim - http://docs.jboss.org/hibernate/stable/core/reference/en/html/querysql.html#d0e13696

Mas tabelas com 150 colunas são um lindo sinal de que o banco de dados está com sérios problemas :slight_smile:

quote++

agree

É algo parecido com isso mesmo.

Mas o comando get da Session já retorna com o select completo, para ter esse efeito voce precisa carregar com o load.

Pra voce ver.

No hibernate.properties coloque a opção para mostrar o SQL
acho que é hibernate.show_sql=true da uma pesquisada.

Depois faça um código como abaixo e rode pra ver como isso funciona.

Session s = //de algum lugar
System.out.println(s.get(Client.class,id));
Client c = s.load(Client.class,id2);
System.out.println(c.getAlgo());
System.out.println("...");
System.out.println(c.getOutroAlgo());