Aplicar lógica do método update em um service
03/01/2012 01:44
1
Olá, pessoal!

Estou com a seguinte dúvida:
Como preciso fazer algumas validações importantes antes de persistir os objetos de uma certa entidade no banco, resolvi migrar as regras de negócio dos métodos "save" e "update" do meu controlador para um service.
Neste link tem um exemplo que explica exatamente isso:
http://www.grailsbrasil.com.br/links/show/16

Seguindo o exemplo no link, o método save funciona perfeitamente. No entanto, quando aplico a regra para o método update, recebo a seguinte msg de erro:

Error 500: Error processing GroovyPageView: Error executing tag <g:form>: Error executing tag <g:render>: Error evaluating expression [usuarioInstance?.centroDeCusto] on line [30]: could not initialize proxy - no Session at C:/projetosGrails/ativos/grails-app/views/usuario/edit.gsp:97 at C:/projetosGrails/ativos/grails-app/views/usuario/edit.gsp:197
Servlet: grails
URI: /ativos/grails/usuario/update.dispatch
Exception Message: could not initialize proxy - no Session
Caused by: Error processing GroovyPageView: Error executing tag <g:form>: Error executing tag <g:render>: Error evaluating expression [usuarioInstance?.centroDeCusto] on line [30]: could not initialize proxy - no Session at C:/projetosGrails/ativos/grails-app/views/usuario/edit.gsp:97 at C:/projetosGrails/ativos/grails-app/views/usuario/edit.gsp:197
Class: edit.gsp
At Line: [197]

Abaixo segue o código domeu service:

class ValidationUsuarioException extends RuntimeException{
Object invalidObject

ValidationUsuarioException(String message, Object invalidObject){
super(message)
this.invalidObject = invalidObject
}
}

class UsuarioService {

def authenticateService

static transactional = true

Usuario updateUsuario(Map params){

def usuarioInstance = Usuario.get(params.id)

usuarioInstance.alteradoPor = authenticateService?.userDomain()

if(params.empresaAddId){
def empresa = Empresa.get(params.empresaAddId)
usuarioInstance.empresa = empresa
}

if(params.centroDeCustoAddId){

def centroDeCusto = CentroDeCusto.get(params.centroDeCustoAddId)
def empresa = Empresa.get(params.empresaAddId)
def centroDeCustoCorreto = CentroDeCusto.findByNomeAndEmpresa(centroDeCusto.nome, empresa)

if(centroDeCustoCorreto){
usuarioInstance.centroDeCusto = centroDeCusto
}else{
throw new ValidationUsuarioException("O centro de custo ${centroDeCusto} n\u00E3o pertence a ${empresa}", usuarioInstance)
}

}

if(params.cargoAddId){

def cargo = Cargo.get(params.cargoAddId)
def empresa = Empresa.get(params.empresaAddId)
def cargoCorreto = Cargo.findByNomeAndEmpresa(cargo.nome, empresa)

if(cargoCorreto){
usuarioInstance.cargo = cargo
}else{
throw new ValidationUsuarioException("O cargo ${cargo} n\u00E3o pertence a ${empresa}", usuarioInstance)
}

}

if (usuarioInstance) {
if (params.version) {
def version = params.version.toLong()
if (usuarioInstance.version > version) {

usuarioInstance.errors.rejectValue("version", "default.optimistic.locking.failure", [message(code: 'usuario.label', default: 'Usuario')] as Object[], "Another user has updated this Usuario while you were editing")
return usuarioInstance
}
}
usuarioInstance.properties = params
if (!usuarioInstance.hasErrors() && usuarioInstance.save()) {
return usuarioInstance
}else {
throw new ValidationUsuarioException("O usu\u00E1rio n\u00E3o foi salvo", usuarioInstance)

}

}else {
throw new NullPointerException()
}
}
}


Agradeço à quem puder me ajudar! Inclusive, considero este um tema muito importante mas pouco explorado na comunidade.

Obrigado!
Tags: service, lógica de negócio, servlet, update, exception


1
Oi Carlos,

o problema é o seguinte: a sua classe de domínio está desanexada a uma sessão. Consequentemente, quando você vai acessar uma propriedade lazy, você topa com este erro.

Solucionar este problema é razoávelmente simples. Chame o método attach da sua classe de domínio. Ele irá reanexar seu objeto à sessão corrente, evitando assim o problema.

Isto normalmente ocorre quando um objeto "dura demais", ou seja, quando você fecha uma sessão (o Grails fecha as sessões automaticamente pra você quando é enviado o domínio para a view e em outros momentos também), mas o objeto ainda existe.

A este respeito, eu inclusive recomendo um livro muito bom, chamado "Grails Persistence with GORM and GSQL", do Robert Fischer, pois neste são tratados os principais problemas com os quais topamos normalmente. Da uma olhada neste link.

Outra técnica importante é você fazer o merge da sua instância com uma sessão. O que ocorre: toda alteração que você faz em uma entidade, o Hibernate agenda para ser persistida no banco de dados. No entanto, com seu objeto desanexado, pode ser que outro usuário tenha alterado o banco de dados e, consequentemente, você precise do seu objeto com o estado mais atualizado possível.

Neste caso, você deve executar um código como o abaixo:


seu_objeto.merge()


Este método obtém a sessão corrente do contexto e atualiza os atributos do seu objeto. Mas não simplesmente execute o método merge, porque ele sempre te retorna um novo objeto, que é a versão do seu corrente atualizada.

Então, execute algo como

seu_objeto = seu_objeto.merge()


0
Olá, Henrique.

Compreendi sua explicação. Mas em que local devo chamar esses métodos? Imediatamente antes da Exception? Pois um controlador recupera essas informações e envia para a view.

O interessante é que o erro aparece apenas quando chamo a exceção. Quando as validações são satisfeitas, o objeto é persistido normalmente.

Muito obrigado pelas explicações e pela dica do livro. Vou encomendar :-)
03/01/2012 11:53


1
Oi Carlos,

pois é: acaba que cada caso é único, então não há muito como jogar uma solução universal pro problema. Normalmente, se você está lidando com objetos cacheados, é uma boa usar esta opção do merge, attach.

Quando enviamos algo para a view, e na view você precisa acessar alguma propriedade, é uma prática interessante também você acessar esta propriedade antes desta ser retornada no model (e consequentemente pra view).


0
Então, Henrique. Fiz dessa forma no meu service:

if(cargoCorreto){
usuarioInstance.cargo = cargo
}else{
usuarioInstance.attach()
usuarioInstance = usuarioInstance.merge()
throw new ValidationUsuarioException("O cargo ${cargo} n\u00E3o pertence a ${empresa}", usuarioInstance)
}

O meu controlador que recebe resultado do service está assim:

def update = {

try{
def usuarioInstance = usuarioService.updateUsuario(params)
flash.message = "${message(code: 'default.updated.message', args: [message(code: 'usuario.label', default: 'Usuario'), usuarioInstance.id])}"
redirect(action: "show", id: usuarioInstance.id)
}catch (ValidationUsuarioException ex){
flash.message = "${ex?.message}"
render(view: "edit", model: [usuarioInstance: ex.invalidObject])
}
}

O erro continua aparecendo :-(
Muito estranho. Já estou sem opções. Não aceito ter que migrar essa lógica para o meu controlador simplesmente por não conseguir aplicar em um service.

Pergunta: Tem uma forma de garantir essa integridade entre os objetos sem utilizar uma lógica de serviço? Usando uma validação customizada com o validator, por exemplo? É possível fazer validações nesse nível?

Obrigado!
03/01/2012 14:29


1
Oi Carlos,

saca só: se você já fez o merge, não precisa anexar de volta à sessão, pois o objeto mesclado já está associado.

O que você tem de lembrar é o seguinte: por default, todo método de um service é encapsulado em uma transação. Provavelmente seu controlador está em uma transação e seu serviço em outra.


0
Oi, Henrique!

Me perdoe a insistência, mas escrevi o seguinte código no "catch" da minha action update:

}catch (ValidationUsuarioException ex){
println "xxxxxxxxxxxxxxxxx"
def instancia = ex.invalidObject.getProperties()
println instancia

flash.message = "${ex?.message}"
render(view: "edit", model: [usuarioInstance: ex.invalidObject])
}

Ao executar a action, não recebi nada na console. Quando pus esse mesmo código no "try-catch" da action save, ele escreveu na consoe as propriedades do objeto, vindas do service. Ou seja, realmente alguma coisa está se perdendo aí. Mas só para o mátodo "update" e não para o "save".

Sabe porq isso acontece?
03/01/2012 16:29


1
Oi Carlos,

To aqui pra te ajudar, então sem este papo de "estou insistindo" ok? :D

Bom, vamos lá. Isto só ocorre no update porque provavelmente o objeto está relacionado a outra sessão do Hibernate que já fechou. Como está inválida, a gente topa com este erro.

Mesmo após o merge continua o mesmo problema?


0
Henrique,

Muito obrigado por sua ajuda! Aparentemente, foi resolvido!

O que fiz foi colocar o .attach() no controlador, ao invés de por no service. Meu bloco "try-catch" ficou dessa forma:

}catch (ValidationUsuarioException ex){
ex.invalidObject = ex.invalidObject.attach()
flash.message = "${ex?.message}"
render(view: "edit", model: [usuarioInstance: ex.invalidObject])
}


Talvez pelo fato do método update ser acessado com uma requisição via POST?

Legal esse tipo de discussão... São dúvidas que surgem constantemente e nos fazem aprender cada vez mais sobre grails. Parabéns por manter o fórum. Pela sua iniciativa e dedicação. Mais uma vez me ajudou muito! E, se não fosse esse espaço, muitas das dúvidas que tive um dia estariam sem respostas.

Forte abraço!
03/01/2012 17:49


0
Oi Carlos,

neste caso, acho que foi mais a questão de haverem duas sessões diferentes: uma no seu serviço e outra no seu controlador. Ai eu acho que rolou foi mais ou menos isto mesmo (parece!).

Também achei massa essa discussão. Foi doido porque a gente trocou um mundo de informações sobre um problema bem comum.

E sabe de uma coisa? Isto ainda vai render um post no meu blog! :D


0
Ansioso para ver! :-)

Abraços!
03/01/2012 19:44



Ainda não faz parte da comunidade???

Para se registrar, clique aqui.


Aprenda Groovy e Grails com a Formação itexto!

Newsletter Semana Groovy

Assinar

Envie seu link!


Livro de Grails


/dev/All

Os melhores blogs de TI (e em português) em um único lugar!

 
Creative Commons
RSS Grails Brasil é mantido por itexto Consultoria.
Em caso de problemas contacte Henrique Lobo Weissmann (Kico) por e-mail: kico@itexto.com.br
Todo o conteúdo presente neste site adota o Creative Commons como licença padrão.
Ver: 4.14.0
itexto