[RESOLVIDO] VRaptor + Merge JPA

Olá pessoal,

Quero atualizar um objeto no banco de dados com JPA. Para isso no meu formulário (de edição) o VRatpor cria o objeto com os valores dos inputs:

Bom, com isso, o VRaptor vai criar um cliente e setar o nome com o valor digitado.
Quando vou atualizar no banco de dados, outros campos da tabela, como por exemplo dataCadastro são setados como NULL, pois este objeto que o VRaptor criou não tem data.

O cliente que o VRaptor criou é detached, e quando faço o merge com o EntityManager, ele “sobrescreve” os campos da tabela.
Ainda não descobri uma forma de nao deixar o JPA sobrescrever os campos que não estou atualizando no form.

Contornei da seguinte forma:

  • buscando o cliente no banco pelo id
  • ia chamando setter por setter do cliente attached com os valores do cliente detached

Mas se eu tiver uns 20 atributos pra atualizar, teria que ficar “setando” cada um.
Contudo acho que essa não é uma boa forma.

Espero ter sido claro, e desde já agradeço a atenção.

Tive o mesmo problema em relação a data de cadastro, e como esse tipo de data não precisa ser atualizada, setei esta anotação no atributo:

@Column(name="data_inclusao", updatable=false)

Mas se a sua data de cadastro o usuário pode alterar, ai acho que vai ter que colocar um input hidden na jsp com a data pra ser enviada com o resto dos dados. Tb não sei outra forma de fazer.
[]s

Olá leandro,

esse é o jeito mais seguro de atualizar uma entidade… Do lado do Controller não tem como vc saber
se o parâmetro veio do seu formulário ou se foi algum usuário malicioso que passou o parâmetro.

se vc vai atualizar poucos campos faça como vc já está fazendo. Se vc for atualizar muitos campos, faça
o contrário:

  • busque o cliente pelo id
  • copie para o detached os campos que vc não quer atualizar
  • faça merge no detached

o que vc pode fazer também é uma classe que copia os atributos que não são nulos ou vazios da classe.
Tem um jeito bem fácil, usando o Mirror que já é uma dependência da sua aplicação:

public static <T> void copiaCampos(final T formulario, final T banco) {
	List<Field> camposAtualizaveis = new Mirror().on(formulario).reflectAll().fieldsMatching(new Matcher<Field>() {
		public boolean accepts(Field field) {
			return new Mirror().on(formulario).get().field(field) != null;
		}
	});
	for (Field field : camposAtualizaveis) {
		Object valor = new Mirror().on(formulario).get().field(field);
		new Mirror().on(banco).set().field(field).withValue(valor);
	}
}

vc pode separar isso em uma classe utilitária qqer da sua aplicação… vc pode modificar esse método acima pra
receber o nome dos campos que vc quer atualizar também (no field() vc pode passar o nome do campo)

Achei interessante a dica do Guevara pra informar que meu campo não pode ser atualizavel. Com isso os campos “atualizaveis” são os que vêm do meu formulario, e os outros eu indico na anotação.
De qq forma, todos os campos que vêm no formulario eu faço as devidas validações, e com isso talvez não precise ficar setando de atached pra detached e vice-versa.

Vou criar um utilitário com o Mirror tbm, parece ser bem interessante.
Grato pela atenção de todos.

[quote=Lucas Cavalcanti][quote=leandronsp]
Contornei da seguinte forma:

  • buscando o cliente no banco pelo id
  • ia chamando setter por setter do cliente attached com os valores do cliente detached

Mas se eu tiver uns 20 atributos pra atualizar, teria que ficar “setando” cada um.
Contudo acho que essa não é uma boa forma.
[/quote]

Olá leandro,

esse é o jeito mais seguro de atualizar uma entidade… Do lado do Controller não tem como vc saber
se o parâmetro veio do seu formulário ou se foi algum usuário malicioso que passou o parâmetro.

se vc vai atualizar poucos campos faça como vc já está fazendo. Se vc for atualizar muitos campos, faça
o contrário:

  • busque o cliente pelo id
  • copie para o detached os campos que vc não quer atualizar
  • faça merge no detached

o que vc pode fazer também é uma classe que copia os atributos que não são nulos ou vazios da classe.
Tem um jeito bem fácil, usando o Mirror que já é uma dependência da sua aplicação:

public static <T> void copiaCampos(final T formulario, final T banco) {
	List<Field> camposAtualizaveis = new Mirror().on(formulario).reflectAll().fieldsMatching(new Matcher<Field>() {
		public boolean accepts(Field field) {
			return new Mirror().on(formulario).get().field(field) != null;
		}
	});
	for (Field field : camposAtualizaveis) {
		Object valor = new Mirror().on(formulario).get().field(field);
		new Mirror().on(banco).set().field(field).withValue(valor);
	}
}

vc pode separar isso em uma classe utilitária qqer da sua aplicação… vc pode modificar esse método acima pra
receber o nome dos campos que vc quer atualizar também (no field() vc pode passar o nome do campo)[/quote]

Gostei da alternativa, infelizmente não funciona mais:/ E por se tratar de uma postagem de 5 anos atrás, gostaria de saber se já foi implementado pelo vraptor ou foi introduzido no JPA uma outra solução para o caso.

Grato

o que não está funcionando?

ainda não tem nada muito mágico pra fazer isso, mas agora dá pra usar os lambdas do java 8 pra isso ficar mais fácil.

[quote=Lucas Cavalcanti]o que não está funcionando?

ainda não tem nada muito mágico pra fazer isso, mas agora dá pra usar os lambdas do java 8 pra isso ficar mais fácil.[/quote]

Essa implementação:

public static <T> void copiaCampos(final T formulario, final T banco) { List<Field> camposAtualizaveis = new Mirror().on(formulario).reflectAll().fieldsMatching(new Matcher<Field>() { public boolean accepts(Field field) { return new Mirror().on(formulario).get().field(field) != null; } }); for (Field field : camposAtualizaveis) { Object valor = new Mirror().on(formulario).get().field(field); new Mirror().on(banco).set().field(field).withValue(valor); } }

Quando fui usar o método acima, reflectAll() apresentou a seguinte msg:

  • The method reflectAll() is undefined for the type AccessorsController

acho que vc tem que fazer algo do tipo:

new Mirror().on(formulario.getClass()).reflectAll()....

[quote=Lucas Cavalcanti]acho que vc tem que fazer algo do tipo:

new Mirror().on(formulario.getClass()).reflectAll().... [/quote]

\o/ Deu certo, desculpa a burrice aqui… vlw! Cara, isso é bruxaria e das mais belas, vou estudar reflections… Muito massa _