[Resolvido] Método usando Criteria só funciona com um Controller

Opa! Olha eu aqui de novo pra bater o ponto! :smiley:
Essa aqui eu não entendi, um método usando Criteria só está funcionando em um Controller, no outro não.
O método com Criteria é este:

public Foto getAvatar(Long codImovel) {
		Criteria crit = session.createCriteria(Foto.class);	
		crit.add(Restrictions.eq("avatar", true)); // avatar é atributo boolean na classe Foto
		crit.createAlias("imovel", "codImovel");
		crit.add(Restrictions.eq("imovel.codImovel", codImovel));		
		return (Foto) crit.uniqueResult();
	}

No ImovelController ele funciona:

public void visualizar(Long codImovel) {
		result.include("imovel", imovelDao.carrega(codImovel));	
		result.include("foto", fotoDAO.getAvatar(codImovel)); // aqui retorna na boa o objeto Foto
	}

O Imovel é carregado e o avatar também.
Agora, no FotoController não funciona (fotoDAO está injetado no construtor):

public void atualizaAvatar(Long codImovel, Long idFoto) {
		Foto foto = fotoDAO.getAvatar(codImovel); // aqui é o problema
		foto.setAvatar(false); // foto está null, o getAvatar não carrega a Foto
		Foto atual = fotoDAO.carrega(idFoto);				
		atual.setAvatar(true);			
		fotoDAO.atualiza(atual, foto);
		result.include("mensagem", "Avatar selecionado com sucesso!");
		result.use(Results.logic()).redirectTo(FotoController.class).adiciona(codImovel);
	}

O getAvatar() ali não retorna nada.
Stacktrace:

Caused by: java.lang.NullPointerException
	at br.com.imobiliaria.controller.FotoController.atualizaAvatar(FotoController.java:119)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at br.com.caelum.vraptor.interceptor.ExecuteMethodInterceptor.intercept(ExecuteMethodInterceptor.java:57)
	... 50 more

Mais um detalhe, ao salvar uma foto, a tabela Foto fica com a coluna avatar “FALSE”, então pra testar isso na jsp bastaria colocar:

<c:if test="${foto.avatar eq false}">
	<p>O im&oacute;vel não possui avatar, faça upload de uma foto ou selecione-a na galeria.</p>
</c:if>

Mas não funciona, têm que ser com “null”

<c:if test="${foto.avatar eq null}">
	<p>O im&oacute;vel não possui avatar, faça upload de uma foto ou selecione-a na galeria.</p>
</c:if>

Aí o teste funciona. O.o
Tô quebrando a cabeça aqui com essas zicas mas não consegui descobrir onde está o problema.
Abraço!!!

vc tá usando o mesmo codImovel pros dois métodos?

codImovel tá vindo não nulo nos dois casos?

testar se algo é falso vc pode fazer: ${not foto.avatar}
acho que o eq não funcionaria, pois é pra comparação de strings

Beleza Lucas, isso eu posso testar, mas o método getAvatar() está vindo null só ali na hora de atualizar o avatar, é o mesmo método usado nos dois Controllers, isso que tá esquisito.

Mas tava pensando aqui, o que acha de separar avatar de foto? Crio uma classe Avatar e relaciono com @OneToOne entre Imovel e Avatar, ai no cadastro de imóvel eu crio um form de upload só pro avatar. Depois de cadastrado o imóvel o usuário cria a galeria de fotos. Acho que ficaria melhor separar as classes, Avatar e Foto, terei que criar o AvatarController e AvatarDAO tb, mais classes e mais código. O lance é resolver esse negócio de avatar que tá dando nos nervos já. :smiley:
O que acha da idéia?
Abraço!!

o método pode ser o mesmo, mas se os parâmetros forem diferentes, o resultado pode ser diferente :wink:

eu acho que vc pode mto bem fazer algo assim:

class Imovel {
    @OneToMany(mappedBy="imovel")
    List<Foto> fotos;
    @OneToOne
    Foto avatar;
}
class Foto {
    @ManyToOne
    Imovel imovel;

    @OneToOne(mappedBy="avatar")
    Imovel imovelAvatar;

    public boolean isAvatar() {
        return imovelAvatar != null;
    }
}

acho que resolve o problema

Era o que tinha, mas como o professor disse que deveria remover o List de fotos da classe Imovel pq a performance seria prejudicada eu removi, e falou tb pra tirar o avatar da classe Imovel e passar pra classe Foto (o que eu achei péssimo, dar um set false na foto antiga e um true na nova), sendo que avatar em imovel fica mais prático de manipular, é apenas uma id de foto na tabela Imovel.
Vou estudar o que fica mais correto de fazer e depois eu posto o que eu fiz.
Obrigado pela sua dica Lucas.
Abraço!

a performance não é prejudicada se vc só colocar o @OneToMany(mappedBy="…"), pq ele só vai fazer o outro select qdo vc usar a coleção…

deixar esse mapeamento na entidade ou fazer um método no dao que retorna na lista dá na mesma, talvez o mapeamento seja mais efetivo…

claro que vc não deve fazer o mapeamento se não for usar a coleção, mas como vc vai precisar dela muitas vezes deixe mapeado…

o único cuidado que vc tem que ter é que se vc for fazer um select pra listar todos os imóveis e quiser mostrar todos as fotos nessa lista, vc tem que falar pra puxar as fotos junto, senão ele vai fazer n+1 queries:

from Imovel i join fetch i.fotos where ....

Oi Lucas!
Pensei aqui sobre a melhor abordagem, e conclui que a melhor forma do imóvel ter o avatar é ter um atributo avatar do tipo Foto mapeado com @OneToOne. Pra chamar o avatar, salvar e remover é menos código, quando chamo o imóvel o avatar vêm junto automáticamente, não preciso mandar um outro result.include com getAvatar() pegando da classe Foto. Apenas coloco imovel.avatar.idFoto e o <c:url value="/avatar/${imovel.codImovel }"/>, pronto, tá lá o avatar. Se eu deixasse como atributo boolean em Foto, teria que dar um set false na foto anterior pra em seguida dar um set true na atual, fora que tava dando um problema que é o assunto tratado neste tópico. Deixar o avatar na classe Imovel não custa nada, é só um id da foto na tabela Imovel. Esta abordagem foi sugerida pelo Garcia, e até agora foi o que eu achei o mais prático.
Já tá funcionando:

O que vc acha dessa abordagem?
Abraço!! o/

bom, foi mais ou menos o que eu falei tb há 2 msgs atrás, então pra mim está bem ok =)

É eu vi a sua postagem, queria apenas a sua opinião disso em relação às boas práticas de anotações, pois vc saca do assunto . =)
Vou deixar assim que tá ótimo.
Abraço Lucas!

Só pra comprovar que o List não puxa td automáticamente se não for pedido, pelo menos foi o que eu entendi.
Coloquei o List na classe Imovel:

@OneToMany(targetEntity=Foto.class, cascade = CascadeType.REMOVE, mappedBy = "imovel", orphanRemoval=true)	
	public List<Foto> fotos; 

Ao visualizar o imóvel este é o resultado:

11:16:39,181 DEBUG [OgnlParametersProvider] Applying codImovel with [100]
11:16:39,183 DEBUG [ParanamerNameProvider] Found parameter names with paranamer for ImovelController.visualizar(Long) as [codImovel]
11:16:39,184 DEBUG [ParametersInstantiatorInterceptor] Parameter values for [DefaultResourceMethod: ImovelController.visualizarImovelController.visualizar(Long)] are [100]
11:16:39,199 DEBUG [ToInstantiateInterceptorHandler] Invoking interceptor ExecuteMethodInterceptor
11:16:39,199 DEBUG [ExecuteMethodInterceptor] Invoking ImovelController.visualizar(Long)
11:16:39,203 DEBUG [ToInstantiateInterceptorHandler] Invoking interceptor OutjectResult
11:16:39,209 DEBUG [ToInstantiateInterceptorHandler] Invoking interceptor ForwardToDefaultViewInterceptor
11:16:39,210 DEBUG [ForwardToDefaultViewInterceptor] forwarding to the dafault page for this logic
11:16:39,226 DEBUG [DefaultPageResult   ] forwarding to /WEB-INF/jsp/imovel/visualizar.jsp
11:16:39,231 DEBUG [DefaultStaticContentHandler] Deferring request to container: /imobiliaria/WEB-INF/jsp/imovel/visualizar.jsp 
Hibernate: 
    select
        imovel0_.cod_imovel as cod1_15_4_,
        imovel0_.area as area15_4_,
        imovel0_.avatar_id_foto as avatar18_15_4_,
        imovel0_.cod_categoria as cod19_15_4_,
        imovel0_.descricao as descricao15_4_,
        imovel0_.dt_inclusao as dt4_15_4_,
        imovel0_.bairro as bairro15_4_,
        imovel0_.cep as cep15_4_,
        imovel0_.cidade as cidade15_4_,
        imovel0_.complemento as compleme8_15_4_,
        imovel0_.estado as estado15_4_,
        imovel0_.logradouro as logradouro15_4_,
        imovel0_.numero as numero15_4_,
        imovel0_.pais as pais15_4_,
        imovel0_.medida as medida15_4_,
        imovel0_.cod_modalidade as cod20_15_4_,
        imovel0_.proprietario_id_proprietario as proprie21_15_4_,
        imovel0_.quartos as quartos15_4_,
        imovel0_.status as status15_4_,
        imovel0_.titulo as titulo15_4_,
        imovel0_.valor as valor15_4_,
        foto1_.id_foto as id1_17_0_,
        foto1_.descricao as descricao17_0_,
        foto1_.imovel_cod_imovel as imovel5_17_0_,
        foto1_.nome as nome17_0_,
        foto1_.url_foto as url4_17_0_,
        categoria2_.cod_categoria as cod1_20_1_,
        categoria2_.tipo_categoria as tipo2_20_1_,
        modalidade3_.cod_modalidade as cod1_19_2_,
        modalidade3_.tipo_modalidade as tipo2_19_2_,
        proprietar4_.id_proprietario as id1_18_3_,
        proprietar4_.celular as celular18_3_,
        proprietar4_.cpf as cpf18_3_,
        proprietar4_.dt_inclusao as dt4_18_3_,
        proprietar4_.email as email18_3_,
        proprietar4_.nome as nome18_3_,
        proprietar4_.telefone as telefone18_3_ 
    from
        Imovel imovel0_ 
    left outer join
        Foto foto1_ 
            on imovel0_.avatar_id_foto=foto1_.id_foto 
    left outer join
        Categoria categoria2_ 
            on imovel0_.cod_categoria=categoria2_.cod_categoria 
    left outer join
        Modalidade modalidade3_ 
            on imovel0_.cod_modalidade=modalidade3_.cod_modalidade 
    left outer join
        Proprietario proprietar4_ 
            on imovel0_.proprietario_id_proprietario=proprietar4_.id_proprietario 
    where
        imovel0_.cod_imovel=?
11:16:39,258 DEBUG [VRaptor             ] VRaptor ended the request

Trouxe o que é necessário do Imóvel, estou com 3 fotos na galeria mas só puxou o avatar como eu queria e não a galeria toda automáticamente. Para pegar os filhos automáticamente precisaria usar o FetchType.EAGER na anotação + um método InputStream ou então, dois métodos em conjunto, um para pegar as fotos do imóvel no banco, e outro para fazer o InputStream e mostrar as fotos.
Abraço!