Spring MVC: Edição de Produtos e Tipos de Retorno do Controlador

Cenário de Negócio

O fluxo operacional para edição de um produto segue estas etapas:

  1. O usuário acessa a listagem de produtos disponíveis
  2. Ao clicar em um item, a página de edição é carregada com os dados recuperados do banco
  3. Após alterar as informações, o usuário confirma o envio do formulário

Mapeamento de Consulta no MyBatis

O arquivo de mapeamento define as consultas SQL utilizadas pela camada de persistência:

<mapper namespace="com.app.mapper.ProductMapper">
  <sql id="ColunasPrincipais">
    id, nome, valor, imagem, data_cadastro
  </sql>

  <sql id="ColunaTextoLongo">
    descricao
  </sql>

  <resultMap id="MapaResultadoBase" type="com.app.model.Produto">
    <id column="id" property="identificador" jdbcType="INTEGER"/>
    <result column="nome" property="nome" jdbcType="VARCHAR"/>
    <result column="valor" property="valor" jdbcType="REAL"/>
    <result column="imagem" property="imagem" jdbcType="VARCHAR"/>
    <result column="data_cadastro" property="dataCadastro" jdbcType="TIMESTAMP"/>
  </resultMap>

  <resultMap id="MapaResultadoCompleto" type="com.app.model.Produto" extends="MapaResultadoBase">
    <result column="descricao" property="descricao" jdbcType="LONGVARCHAR"/>
  </resultMap>

  <select id="buscarPorId" resultMap="MapaResultadoCompleto" parameterType="java.lang.Integer">
    SELECT <include refid="ColunasPrincipais"/>, <include refid="ColunaTextoLongo"/>
    FROM produtos
    WHERE id = #{codigo,jdbcType=INTEGER}
  </select>

  <update id="atualizarProdutoCompleto" parameterType="com.app.model.ProdutoDetalhado">
    UPDATE produtos
    SET nome = #{nome,jdbcType=VARCHAR},
        valor = #{valor,jdbcType=REAL},
        imagem = #{imagem,jdbcType=VARCHAR},
        data_cadastro = #{dataCadastro,jdbcType=TIMESTAMP},
        descricao = #{descricao,jdbcType=LONGVARCHAR}
    WHERE id = #{identificador,jdbcType=INTEGER}
  </update>
</mapper>

Interface e Implementação do Serviço

public interface ProdutoService {

    List<ProdutoDetalhado> listarProdutos(FiltroProduto filtro) throws Exception;

    ProdutoDetalhado buscarProdutoPorId(Integer codigo) throws Exception;

    void salvarAlteracoes(Integer codigo, ProdutoDetalhado produtoAtualizado) throws Exception;
}
package com.app.service.impl;

import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import com.app.mapper.ProdutoMapper;
import com.app.mapper.ProdutoMapperCustom;
import com.app.model.Produto;
import com.app.model.ProdutoDetalhado;
import com.app.model.FiltroProduto;
import com.app.service.ProdutoService;

public class ProdutoServiceImpl implements ProdutoService {

    @Autowired
    private ProdutoMapperCustom mapperCustom;

    @Autowired
    private ProdutoMapper mapperPadrao;

    @Override
    public List<ProdutoDetalhado> listarProdutos(FiltroProduto filtro) throws Exception {
        return mapperCustom.consultarProdutos(filtro);
    }

    @Override
    public ProdutoDetalhado buscarProdutoPorId(Integer codigo) throws Exception {
        Produto registro = mapperPadrao.buscarPorId(codigo);
        ProdutoDetalhado resultado = new ProdutoDetalhado();
        BeanUtils.copyProperties(registro, resultado);
        return resultado;
    }

    @Override
    public void salvarAlteracoes(Integer codigo, ProdutoDetalhado produtoAtualizado) throws Exception {
        if (codigo == null) {
            throw new IllegalArgumentException("O identificador do produto é obrigatório");
        }
        produtoAtualizado.setIdentificador(codigo);
        mapperPadrao.atualizarProdutoCompleto(produtoAtualizado);
    }
}

Camada do Controlador

@Controller
@RequestMapping("/produtos")
public class ProdutoController {

    @Autowired
    private ProdutoService servicoProduto;

    @RequestMapping("/listar")
    public ModelAndView listarProdutos() throws Exception {
        List<ProdutoDetalhado> lista = servicoProduto.listarProdutos(null);
        ModelAndView mv = new ModelAndView();
        mv.addObject("listaProdutos", lista);
        mv.setViewName("produtos/listagem");
        return mv;
    }

    @RequestMapping(value = "/editar", method = {RequestMethod.GET, RequestMethod.POST})
    public String exibirEdicao(Model modelo) throws Exception {
        ProdutoDetalhado produto = servicoProduto.buscarProdutoPorId(1);
        modelo.addAttribute("produtoSelecionado", produto);
        return "produtos/formularioEdicao";
    }

    @RequestMapping("/confirmarEdicao")
    public String processarEdicao(HttpServletRequest requisicao) throws Exception {
        return "redirect:listar.action";
    }
}

Aspectos Importantes do @RequestMapping

Mapeamento de URLs

Esta anotação associa métodos do controlador a caminhos específicos da aplicação, permitindo que o Spring MVC direcione requisições HTTP ao handler correto.

Restrição por Namespace

Ao colocar @RequestMapping("/produtos") na classe, todos os métodos internos herdam esse prefixo. Por exemplo, o método mapeado como "/editar" responde efetivamente à URL /produtos/editar.

Filtragem por Método HTTP

É possível restringir quais verbos HTTP acionam determinado endpoint:

@RequestMapping(value = "/editar", method = {RequestMethod.POST, RequestMethod.GET})

Tipos de Retorno dos Métodos do Controladro

ModelAndView

Encapsula tanto os dados (modelo) quanto a identificação da view. É a abordagem clássica quando se deseja controlar ambos os aspectos em um único objeto de retorno.

String (Nome Lógico da View)

O valor retornado representa o nome lógico da página. O Spring resolve o caminho real combinando prefixo + nome lógico + sufixo configurados no ViewResolver.

Quando se usa Model como parâmetro do método, os atributos adicionados ficam disponíveis na view:

modelo.addAttribute("produtoSelecionado", produto);

Redirecionamento (redirect)

O prefixo redirect: instrui o navegador a fazer uma nova requisição para o endereço indicaod. A URL visível na barra de endereços muda e o escopo original da requisição é perdido — os dados não são transferidos automaticamente. Caso seja necessário enviar parâmetros, eles devem ser incluídos diretamente na URL de destino:

return "redirect:listar.action?status=sucesso";

Internamente, o comportamento equivale a chamar response.sendRedirect(). Como uma requisição completamente nova é disparada, o request original não é compartilhado.

Encaminhamento (forward)

O prefixo forward: realiza o encaminhamento no lado do servidor. A URL exibida no navegador permanece inalterada e o objeto request original é preservado, permitindo acesso a todos os atributos já definidos.

return "forward:listar.action";

Esse mecanismo corresponde ao uso de RequestDispatcher.forward(), onde nenhuma nova requisição é criada.

Retorno void

Quando o método não retorna valor, o controlador manipula a resposta diretamente pelos objetos HttpServletRequest e HttpServletResponse injetados como parâmetros.

Encaminhamento via request:

requisicao.getRequestDispatcher("/WEB-INF/jsp/produtos/listagem.jsp")
           .forward(requisicao, resposta);

Redirecionamento via response:

resposta.sendRedirect("/produtos/listar.action");

Escrita direta de conteúdo JSON:

resposta.setCharacterEncoding("UTF-8");
resposta.setContentType("application/json;charset=UTF-8");
resposta.getWriter().write("{\"mensagem\":\"Operacao realizada com sucesso\"}");

Tags: Spring MVC @RequestMapping MyBatis ModelAndView controller

Publicado em 7-19 06:19