[resolvido] VRaptor + Modelagem + Hibernate

Galera,

estou tentando seguir o conselho de modelagem conforme foi me sugerido no tópico:
http://www.guj.com.br/java/276418-vraptor-como-ele-comporta-com-relacionamento-n-n-com-atributos/

Então estou criando a seguinte estrutura:

MensagemContainer: vai conter a mensagem que quero enviar, o destinatario e se foi lida ou não.

@Entity
@Table(name="mensagem_container")
public class MensagemContainer {

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Integer codigo;
	
	
	@ManyToOne(cascade=CascadeType.ALL)
	private Mensagem mensagem;
	
	@Column(columnDefinition="TINYINT(1) DEFAULT 0")
	private Boolean lida = false;
	

	@Any(metaColumn = @Column(name = "tipo_autenticavel"), fetch=FetchType.EAGER,  optional = false	)
	@AnyMetaDef(idType = "integer", metaType = "string", metaValues =
	{
			@MetaValue(value = "A", targetEntity = Aluno.class),
			@MetaValue(value = "P", targetEntity = Professor.class),
			@MetaValue(value = "F", targetEntity = Funcionario.class) })
	@JoinColumn(name = "codigo_autenticavel")
	private Autenticavel destinatario;

Mensagem conterá o titulo, a mensagem, o remetente e uma lista de MensagemContainer.

	@OneToMany(cascade=CascadeType.ALL)
	private List<MensagemContainer> mensagensContainer;	

O Professor (que implementa a interface Autenticavel), vai conter tbem uma lista de MensagemContainer):

	@OneToMany
	private List<MensagemContainer> mensagensConteiners;

Esses relacionamentos e essas anotações estão corretas? Ele gerou as tabelas direitinho, mas agora estou perdido :frowning:
Vejam só eu tinha um MensagemController, onde o usuario logado poderá:
ver as mensagens que ele enviou
ver a inbox dele
escrever nova mensagem

Antes era facil, o vraptor criava para mim o Mensagem m no método save (post) e já era… Montava tudo certinho.
Agora vou ter que ter o que? Na essencia quero cadastrar Mensagem, porém tenho o MensagemContainer agora.
Por tanto teria que cadastrar o MensagemContainer.

Estou perdidinho, tão perdido que nao consigo nem expor de forma mais clara as minhas dúvidas. :frowning:
Bom, de todo jeito eu vou ter que cadastrar no banco um objeto do tipo Mensagem. Depois que eu
cadastrei essa mensagem eu tenho que cadastrar o MensagemContainer.

Como faria isso? Pensei na seguinte solução, mas nao sei se rola:

Solução 1: crio um List para representar os destinatarios na minha classe Mensagem. Essa lista não é mapeada no banco. Aí, depois que eu salvar a mensagem, dentro da logica do salvar mensagem eu faço algo parecido como:


for(Autenticavel dest : mensagem.getDestinatarios()){
    MensagemContainer mc = new MensagemContainer();
    mc.setMensagem(mensagem);
    mc.setDestinatario(dest);

    mensagemContainerDao.save(mc);
}

o que acham disso?

Dentro de mensagem, não faça isso:

@OneToMany(cascade=CascadeType.ALL) private List&lt;MensagemContainer&gt; mensagensContainer;
Faça isso:

@OneToMany(mappedBy="mensagem") private List&lt;MensagemContainer&gt; mensagensContainer;
Assim ele vai somente fazer uma referencia às MensagemContainers que essa mensagem possui. Mas quem vai incluir, apagar e alterar essa mensagem, será somente o MensagemContainer, ou seja, a mensagem não altera o container.

Na parte dos professores, alunos e funcionários, use também o mappedBy:

@OneToMany(mappedBy="destinatario") private List&lt;MensagemContainer&gt; mensagensConteiners;

Lhe confesso que nunca ouvi falar desse @Any, pelo o que eu entendi, ele vai gravar A1, P1, F1 no banco e o Hibernate irá decodificar isso, criando uma instancia para cada classe. (é isso?)
Bem interessante.

Eu não sugiro que você use o Hibernate para gerar as tabelas, ao invés disso, coloque o hbmddl como “validate”. Assim o hibernate irá verificar se o banco está de acordo e, se não estiver, lança uma HibernateException (na hora que você cria a SessionFactory) e você consegue arrumar o banco. É sempre mais interessante se você manipular o banco via script. Você vai ter mais controle. Deixar muitas coisas nas mão do hibernate vai poder te atrapalhar lá na frente. (Já vi casos onde uma simples alteração de uma entidade era motivo para dropar todo o banco e deixar com que o hibernate criasse tudo de novo.)

Uma coisa: a mensagem tem VÁRIOS mensagemconteiner que tem UM autenticável. Cada autenticável tem VÁRIOS mensagemconteiners que tem UMA mensagem.

Ou seja, faça as referencias em mensagem conteiner.

(muito provavelmente no seu form você tenha os emails das pessoas que receberão essa mensagem, correto?)

Você vai precisar salvar uma mensagemconteiner para cada autenticável, na sua action do controller:

// Controller
public void enviarMensagem(List&lt;Autenticavel&gt; destinatarios, MensagemConteiner conteiner){
	this.mensagemConteinerDAO.save(destinatarios, conteiner)
}

// DAO
public void save (List&lt;Autenticavel&gt; destinatarios, MensagemConteiner conteiner){
	try{
		this.session.getTransaction().begin();
		// itera sobre os destinatários
		for (Autenticavel destinatario : destinatarios){
			// para cada destinatário, crie um clone de conteiner (você precisa implementar Cloneable) populando cada um com um destinatário diferente
			MensagemConteiner clone = (MensagemConteiner) conteiner.clone();
			clone.setAutenticavel(destinatario);
			// seta um ID novo para esse clone também.
			this.session.save(clone);
		}
		this.session.getTransaction().commit();
	} catch (HibernateException e) {
		this.session.getTransaction().rollback();
		// configura o log, se não tiver, isso é mais importante que qualquer outra coisa.
		this.logger.error("Unable to save this MensagemContainer.", e);
	}
}

Agora na view, para conseguir popular a mensagem:

&lt;form action="${linkTo[MensagemController].enviarMensagem}" method="post"&gt;
	&lt;!-- Aqui vai receber um monte de destinatários e você deve colocar destinatarios[índice].atributo para conseguir populá-lo --&gt;
	&lt;!-- Aqui vão as mensagens então as coisas que forem pertinentes à MensagemConteiner será acessada por conteiner.atributo --&gt;
	&lt;!-- Como a classe Mensagem é um atributo de MensagemConteiner, é só você fazer conteiner.mensagem.atributo --&gt;
	&lt;textarea id="mensagem" name="conteiner.mensagem.mensagemTexto"&gt;&lt;/textarea&gt;
&lt;/form&gt;

Cara fiz as modificações que você falou nas classes Mensagem e Professor.

Eis a exception:

Caused by: org.hibernate.MappingException: Foreign key (FK5859936DF15688FE:mensagem_container [tipo_autenticavel,codigo_autenticavel])) must have same number of columns as the referenced primary key (professores [codigo])

Não entendi o pq do uso do MappedBy.
Mas gostei de tudo que você falou.
Realmente acredito que fazer o vraptor montar a Mensagem mensagem é mais fácil
que o MensagemContainer. Do jeito q esta, vou testar e te passar o resultado. Tudo bem?

O MappedBy é o seguinte:

Pense em duas entidades que se relacionam (vamos fazer com 1-1 que é mais fácil de entender) Carro e Motor.

Um carro só pode ter um motor e um motor só pode ter um carro, correto?

Mas quem de fato possui quem? O Carro que tem o Motor então, o motor precisa estar dentro de carro, e quando um carro for adicionado, eu quero que o motor também seja adicionado:

public class Carro {
	private String chassi;
	private String marca;
	private String modelo;

	@OneToOne(cascade=CascadeType.ALL)
	private Motor motor;

	// getters e setters;
}

public class Motor {
	private long serialNumber;
	private float km;
	private int litrosDeOleo;
	private float temperatura;
	
	@OneToOne(mappedBy="motor")
	private Carro carro; // Um motor TEM um carro? Estranho isso aqui, não?
	// pois o mappedBy neste caso só serve para você ter uma instancia de carro, caso você precise do "pai"
	// é só uma referencia que não implica em nada no banco.
	
	// getters e setters
}

Quanto à exception, pelo o que eu entendi aqui, é por que você tem 2 IDs na entidade mensagem_conteiner e só está referenciando (na tabela) o ID codigo (está faltando o tipo_autenticavel na referencia)

e pq ‘motor’?
nao deveria ser ‘carro’?

Nessa linha, você diz?

Por que o mappedBy referencia-se à própria entidade, ou seja, é como se ele se perguntasse: "Ok, mas eu sou mapeado por quem nessa classe que você está me atribuindo?"
Então você responde assim: “Na classe Carro, você está sendo referenciado pelo atributo motor.”

[code]
public class Carro {
// outros atributos

@OneToOne(cascade=CascadeType.ALL)  
private Motor motorXPTOX;  

// getters e setters;  

}

public class Motor {
// outros atributos

@OneToOne(mappedBy="motorXPTOX")  
private Carro carroABCDE;
  
// getters e setters  

}[/code]

Perfeito cara.
Obrigado pela paciencia de me ajudar. :slight_smile:

Pode me ajudar a conferir alguns detalhes (ok, talvez não seja detalhe)?

Tipo fiz o insert da mensagem e o insert do MensagemContainer. O que aconteceu?
Mensagem no banco: OK
MensagemContainer no banco: OK

Aí essa tabela q tbem foi gerada:
professores_mensagem_container: Nada foi inserido nela.

Sendo que: código da mensagem gerada: 14. (ok)
MensagemContainer: (código 1) ok
tipoAutenticavel 'P’
codigoAutenticavel 1
lida 0
mensagem 14

Então… Se o destinatario está na tabela MensagemContainer… pq ele gerou a tabela: professores_mensagem_container??
que loucura…

Me ajuda a entender?

Esse é o problema de deixar com que o Hibernate crie as tabelas para você. Muito provavelmente ele criou antes, para as configurações anteriores e você mexeu nelas.
O hibernate não apaga as tabelas que ele não usa. Dá um drop e ve o que ele vai criar lá para você.

Mas o restante está sendo salvo normalmente?

Por favor, veja se procede:

Para selecionar as mensagens “Recebidas” (que serão mostradas na inbox):

select m.* from mensagens, mensagem_container mc where mc.mensagem = m.mensagem AND mc.tipoAutenticavel = 'P' and mc.codigoAutenticavel   = 1 order by m.data DESC

Está certo?

Veja se isso funciona:

Por que você não usa o Criteria? Acho muito mais fácil.

cara… a consulta funcionou.
Está tudo funcional…
Agora vou ter que modificar todas minhas views pq eu tava fazendo:

mensagem.lida

agora tenho que fazer mensagemContainer.lida…
Acho que vai funcionar.

Estou terminando isto aqui no portal do professor.
Depois eu vou pro portal do aluno e testo o envio de mensagem entre um e outro
e posto o resultado aqui.

obrigado

Aeewww…

finalmente.
funcionou
100%

Rafael Guerreiro muito obrigado
vlw cara
:wink:

Então, por que você não cria uma action que recebe uma requisião AJAX que já atualiza esse valor no banco de dados?

Era exatamente esse tipo de coisa que eu estava tentando evitar, tenta ver uma forma de não repetir as lógicas. Concorda que a forma de visualizar um e-mail, de enviar e tudo mais para um professor é igual à de um aluno? E vai ser igual para o funcionário. Tenta ver uma maneira de fazer isso somente uma vez e os 3 já ficam prontos.
Assim, as futuras manutenções ficarão centralizadas e mais fáceis.

tem muita coisa que se ‘repete’ :frowning:

realmente nao repetir seria o ideal :slight_smile:
se conseguir algo te aviso
abrazz

Você já deu uma olhada no jUnit? Começa a escrever os testes unitários sobre cada simples método da sua aplicação e você vai começar a refatorá-la.