Solucionado (ver solução)
Solucionado
(ver solução)
12
respostas

Estou com dúvida no curso de Desenvolvendo aplicações Web com Spring MVC 4, no exercício do capítulo Injeção de dependências.

Fiz as alterações para usar Injeção de Dependências, a primeira vista, ok!

Exemplo do problema.

Vou até a listagem de contas. Na primeira vez, ok. Ele lista sem problemas. Se tentar recarregar a listagem de contas ele me lança uma exception dizendo que a conexão está fechada.

java.lang.RuntimeException: java.sql.SQLException: Connection is closed.

Converti meu projeto pra MySQL. Mas com HSQLDB também ocorria o problema.

ConnectionFactory

package br.com.caelum.contas;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;

public class ConnectionFactory {

    public Connection getConnection() throws SQLException {
        System.out.println("conectando ...");

        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new SQLException(e);
        }

        return DriverManager.getConnection("jdbc:mysql://localhost:3306/estudos_java","root", "root");
    }

}

ContaDao

package br.com.caelum.contas.dao;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import br.com.caelum.contas.ConnectionFactory;
import br.com.caelum.contas.modelo.Conta;
import br.com.caelum.contas.modelo.TipoDaConta;


@Repository
public class ContaDAO {
    private Connection connection;

    @Autowired
    public ContaDAO(DataSource ds) {
        try {
            this.connection = ds.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void adiciona(Conta conta) {
        String sql = "insert into contas (descricao, paga, valor, tipo) values (?,?,?,?)";
        PreparedStatement stmt;
        try {
            stmt = connection.prepareStatement(sql);
            stmt.setString(1, conta.getDescricao());
            stmt.setBoolean(2, conta.isPaga());
            stmt.setDouble(3, conta.getValor());
            stmt.setString(4, conta.getTipo().name());
            stmt.execute();
            connection.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

    }

    public void remove(Conta conta) {

        if (conta.getId() == null) {
            throw new IllegalStateException("Id da conta naoo deve ser nula.");
        }

        String sql = "delete from contas where id = ?";
        PreparedStatement stmt;
        try {
            stmt = connection.prepareStatement(sql);
            stmt.setLong(1, conta.getId());
            stmt.execute();

            connection.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void altera(Conta conta) {
        String sql = "update contas set descricao = ?, paga = ?, dataPagamento = ?, tipo = ?, valor = ? where id = ?";
        PreparedStatement stmt;
        try {
            stmt = connection.prepareStatement(sql);
            stmt.setString(1, conta.getDescricao());
            stmt.setBoolean(2, conta.isPaga());
            stmt.setDate(3, conta.getDataPagamento() != null ? new Date(conta
                    .getDataPagamento().getTimeInMillis()) : null);
            stmt.setString(4, conta.getTipo().name());
            stmt.setDouble(5, conta.getValor());
            stmt.setLong(6, conta.getId());
            stmt.execute();

            connection.close();

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public List<Conta> lista() {
        try {
            List<Conta> contas = new ArrayList<Conta>();
            PreparedStatement stmt = this.connection
                    .prepareStatement("select * from contas");

            ResultSet rs = stmt.executeQuery();

            while (rs.next()) {
                // adiciona a conta na lista
                contas.add(populaConta(rs));
            }

            rs.close();
            stmt.close();
            connection.close();

            return contas;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public Conta buscaPorId(Long id) {

        if (id == null) {
            throw new IllegalStateException("Id da conta nao deve ser nula.");
        }

        try {
            PreparedStatement stmt = this.connection
                    .prepareStatement("select * from contas where id = ?");
            stmt.setLong(1, id);
            ResultSet rs = stmt.executeQuery();

            if (rs.next()) {
                connection.close();
                return populaConta(rs);
            }

            rs.close();
            stmt.close();

            connection.close();
            return null;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void paga(Long id) {

        if (id == null) {
            throw new IllegalStateException("Id da conta nao deve ser nula.");
        }

        String sql = "update contas set paga = ?, dataPagamento = ? where id = ?";
        PreparedStatement stmt;
        try {
            stmt = connection.prepareStatement(sql);
            stmt.setBoolean(1, true);
            stmt.setDate(2, new Date(Calendar.getInstance().getTimeInMillis()));
            stmt.setLong(3, id);
            stmt.execute();

            connection.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private Conta populaConta(ResultSet rs) throws SQLException {
        Conta conta = new Conta();

        conta.setId(rs.getLong("id"));
        conta.setDescricao(rs.getString("descricao"));
        conta.setPaga(rs.getBoolean("paga"));
        conta.setValor(rs.getDouble("valor"));

        Date data = rs.getDate("dataPagamento");
        if (data != null) {
            Calendar dataPagamento = Calendar.getInstance();
            dataPagamento.setTime(data);
            conta.setDataPagamento(dataPagamento);
        }

        conta.setTipo(Enum.valueOf(TipoDaConta.class, rs.getString("tipo")));

        return conta;
    }
}

Conta

package br.com.caelum.contas;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;

public class ConnectionFactory {

    public Connection getConnection() throws SQLException {
        System.out.println("conectando ...");

        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new SQLException(e);
        }

        return DriverManager.getConnection("jdbc:mysql://localhost:3306/estudos_java","root", "root");
    }

}

Conta

package br.com.caelum.contas.modelo;

import java.util.Calendar;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat;

public class Conta {

    private Long id;
    //@NotEmpty(message= "{conta.descricao.vazia}")    
    //@Size(min = 5, max = 500, message="{conta.descricao.size}")    
//    @NotNull(message="{conta.descricao.vazia}") 
    @NotEmpty(message="{conta.descricao.vazia}")
    @Size(min=3, message="{conta.formulario.descricao}")
    private String descricao;

    private boolean paga;

    private double valor;

    @DateTimeFormat(pattern="dd/MM/yyyy")
    private Calendar dataPagamento;

    private TipoDaConta tipo;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getDescricao() {
        return descricao;
    }

    public void setDescricao(String descricao) {
        this.descricao = descricao;
    }

    public boolean isPaga() {
        return paga;
    }

    public void setPaga(boolean paga) {
        this.paga = paga;
    }

    public Calendar getDataPagamento() {
        return dataPagamento;
    }

    public void setDataPagamento(Calendar dataPagamento) {
        this.dataPagamento = dataPagamento;
    }

    public TipoDaConta getTipo() {
        return tipo;
    }

    public void setTipo(TipoDaConta tipo) {
        this.tipo = tipo;
    }

    public double getValor() {
        return valor;
    }    

    public void setValor(double valor) {
        this.valor = valor;
    }

}

ContaController

package br.com.caelum.contas.controller;

import java.util.List;

import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import br.com.caelum.contas.dao.ContaDAO;
import br.com.caelum.contas.modelo.Conta;

@Controller
public class ContaController {

    private ContaDAO contaDao;

    @Autowired
    public ContaController(ContaDAO contaDao) {
        this.contaDao = contaDao;
    }

    @RequestMapping("/formulario-adiciona-conta")
    public String formulario() {
        return "conta/formulario";
    }

    @RequestMapping("/adiciona-conta")
    public String adicionaConta(@Valid Conta conta, BindingResult result) {

        if (!result.hasFieldErrors("descricao")) {
            contaDao.adiciona(conta);
        }
        return "conta/formulario";
        // Importante, se feito o redirect, a mensagem
        // de erro não é exibida.
        // return "redirect:/formulario-adiciona-conta";
    }

    @RequestMapping("/remove-conta")
    public String removeConta(Conta conta) {
        contaDao.remove(conta);

        return "redirect:lista-contas";
    }

    @RequestMapping("/paga-conta")
    public void finaliza(Long id, HttpServletResponse response) {
        contaDao.paga(id);
        response.setStatus(200);
    }

    @RequestMapping("/mostra-conta")
    public String mostra(Long id, Model model) {
        model.addAttribute("conta", contaDao.buscaPorId(id));
        return "conta/mostra-conta";
    }

    @RequestMapping("/altera-conta")
    public String altera(Conta conta) {
        contaDao.altera(conta);
        return "redirect:lista-contas";
    }

    @RequestMapping("/lista-contas")
    public ModelAndView listaConta() {
        List<Conta> contas = contaDao.lista();

        ModelAndView mv = new ModelAndView("conta/lista-contas");
        mv.addObject("contas", contas);

        return mv;
    }

    /**
     * Versão alternativa utilizando a classe MODEL ao invés da classe
     * ModelAndView
     */
    // @RequestMapping("/lista-conta")
    // public String lista(Model mv) {
    // ContaDao dao = new ContaDao();
    // List<Conta> contas = dao.lista();
    //
    // mv.addAttribute("contas", contas);
    // return "conta/lista";
    // }

}package br.com.caelum.contas.controller;

import java.util.List;

@Controller
public class ContaController {

    private ContaDAO contaDao;

    @Autowired
    public ContaController(ContaDAO contaDao) {
        this.contaDao = contaDao;
    }

    @RequestMapping("/formulario-adiciona-conta")
    public String formulario() {
        return "conta/formulario";
    }

    @RequestMapping("/adiciona-conta")
    public String adicionaConta(@Valid Conta conta, BindingResult result) {

        if (!result.hasFieldErrors("descricao")) {
            contaDao.adiciona(conta);
        }
        return "conta/formulario";
        // Importante, se feito o redirect, a mensagem
        // de erro não é exibida.
        // return "redirect:/formulario-adiciona-conta";
    }

    @RequestMapping("/remove-conta")
    public String removeConta(Conta conta) {
        contaDao.remove(conta);

        return "redirect:lista-contas";
    }

    @RequestMapping("/paga-conta")
    public void finaliza(Long id, HttpServletResponse response) {
        contaDao.paga(id);
        response.setStatus(200);
    }

    @RequestMapping("/mostra-conta")
    public String mostra(Long id, Model model) {
        model.addAttribute("conta", contaDao.buscaPorId(id));
        return "conta/mostra-conta";
    }

    @RequestMapping("/altera-conta")
    public String altera(Conta conta) {
        contaDao.altera(conta);
        return "redirect:lista-contas";
    }

    @RequestMapping("/lista-contas")
    public ModelAndView listaConta() {
        List<Conta> contas = contaDao.lista();

        ModelAndView mv = new ModelAndView("conta/lista-contas");
        mv.addObject("contas", contas);

        return mv;
    }

    /**
     * Versão alternativa utilizando a classe MODEL ao invés da classe
     * ModelAndView
     */
    // @RequestMapping("/lista-conta")
    // public String lista(Model mv) {
    // ContaDao dao = new ContaDao();
    // List<Conta> contas = dao.lista();
    //
    // mv.addAttribute("contas", contas);
    // return "conta/lista";
    // }

}
package br.com.caelum.contas.controller;

import java.util.List;

@Controller
public class ContaController {

    private ContaDAO contaDao;

    @Autowired
    public ContaController(ContaDAO contaDao) {
        this.contaDao = contaDao;
    }

    @RequestMapping("/formulario-adiciona-conta")
    public String formulario() {
        return "conta/formulario";
    }

    @RequestMapping("/adiciona-conta")
    public String adicionaConta(@Valid Conta conta, BindingResult result) {

        if (!result.hasFieldErrors("descricao")) {
            contaDao.adiciona(conta);
        }
        return "conta/formulario";
        // Importante, se feito o redirect, a mensagem
        // de erro não é exibida.
        // return "redirect:/formulario-adiciona-conta";
    }

    @RequestMapping("/remove-conta")
    public String removeConta(Conta conta) {
        contaDao.remove(conta);

        return "redirect:lista-contas";
    }

    @RequestMapping("/paga-conta")
    public void finaliza(Long id, HttpServletResponse response) {
        contaDao.paga(id);
        response.setStatus(200);
    }

    @RequestMapping("/mostra-conta")
    public String mostra(Long id, Model model) {
        model.addAttribute("conta", contaDao.buscaPorId(id));
        return "conta/mostra-conta";
    }

    @RequestMapping("/altera-conta")
    public String altera(Conta conta) {
        contaDao.altera(conta);
        return "redirect:lista-contas";
    }

    @RequestMapping("/lista-contas")
    public ModelAndView listaConta() {
        List<Conta> contas = contaDao.lista();

        ModelAndView mv = new ModelAndView("conta/lista-contas");
        mv.addObject("contas", contas);

        return mv;
    }

    /**
     * Versão alternativa utilizando a classe MODEL ao invés da classe
     * ModelAndView
     */
    // @RequestMapping("/lista-conta")
    // public String lista(Model mv) {
    // ContaDao dao = new ContaDao();
    // List<Conta> contas = dao.lista();
    //
    // mv.addAttribute("contas", contas);
    // return "conta/lista";
    // }

}
12 respostas

spring-context

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc 
                        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
                        http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/context 
                        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="br.com.caelum.contas" />
    <mvc:annotation-driven />
    <mvc:default-servlet-handler />
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="conversionService"
        class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="br.com.caelum.contas.StringToEnumConverterFactory" />
            </list>
        </property>
    </bean>
    <mvc:interceptors>
        <bean class="br.com.caelum.contas.interceptor.AutorizadorInterceptor" />
    </mvc:interceptors>


    <bean id="mysqlDataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/estudos_java" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>


</beans>

Você tem que instanciar a classe ConnectionFactory, para pegar uma um objeto do tipo Connection.

Você pode criar um bean dessa classe no arquivo spring-context.

Em nenhum lugar do controlador e vi você criar uma conexão com o banco.

Eu crio uma Connection como atributo da classe e instancio ela no construtor. Se não houvesse conexão com banco não teria como fazer transações com o mesmo. E eu consigo, uma vez, depois a conexão é fechada.

@Repository
public class ContaDAO {
    private Connection connection;

    @Autowired
    public ContaDAO(DataSource ds) {
        try {
            this.connection = ds.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
   <bean id="mysqlDataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/estudos_java" />
        <property name="username" value="root" />
        <property name="password" value="root" />
    </bean>

Mas você não está usando o Bean.

Exemplo:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="jdbc:hsqldb:mem:."/>
    <property name="username" value="sa"/>
    <property name="password" value="sa1234"/>
    </bean>

ApplicationContext ac = new ClassPathXmlApplicationContext("context.xml", Main.class); DataSource dataSource = (DataSource) ac.getBean("dataSource");

enter code here

Coloca isso em seu controlador. quando for instanciar o seu DAO.

ApplicationContext ac = new ClassPathXmlApplicationContext("context.xml", Main.class); DataSource dataSource = (DataSource) ac.getBean("dataSource");

Mas quando usamos a anotação @Autowired não estamos dizendo ao Spring para ele ja ustilizar o Datasource definido no spring-context???

Veja só, você está usando isso errado, já trabalhei com JSF, com isso vou mostrar como fizemos isso.

Essa é só a parte do controlador. Como você pode notar o Bean do DAO é instanciado no controlador e esse bean tem que ser colocado no context.xml, atravês da linha

@Controller("controladorClienteGrupo")
@Scope("session")
public class ControladorClienteGrupo extends ControladorDefault {
    // Recurso injetado pelo Spring
    @Autowired
    @Qualifier("clienteGrupoDao")    
    private IClienteGrupoDAO clienteGrupoDao;

No seu projeto você está misturando as coisas, você está passando a conexão no construtor do DAO. Ao meu ver isso não pode ser dessa forma, pois você está usando JDBC, pois a cada chamada de um método do DAO, você tem a obrigação de pegar uma conexão nova e fechar após usar, com isso para mim você tem que usar o BEAN de conexão dentro do DAO e não passar a conexão via construtor. Vou postar outro post, da forma que eu faria.

Faz dessa forma:

@Controller
public class ContaController {

    @Autowired
    private ContaDAO contaDao;

Na sua Classe ContaDao, retira do construtor dela o parâmetro Connection, o construtor DAO, não pode ter parâmetros. Deixa para criar a conexão dentro do DAO, usa a Factory, pois se você deixar na sessão e está sessão perder essa conexão, a sessão vai quebrar.

Em meu comentário que coloquei o código do Controlador, não saiu a linha do meu xml, com isso ai vai linha:

context.xml

 <bean id="clienteGrupoDao" class="dao.impl.ClienteGrupoDAOImpl" />

Controlador

@Controller("controladorClienteGrupo")
@Scope("session")
public class ControladorClienteGrupo extends ControladorDefault {
    // Recurso injetado pelo Spring
    @Autowired
    @Qualifier("clienteGrupoDao")    
    private IClienteGrupoDAO clienteGrupoDao;

Entendi seu ponto, Alexsandro. Porém eu segui exatamente o que estava no curso. Nos DAOs as conexões estão sendo fechadas explicitamente, estou dando um close nelas, ACHO que é isso. E segundo a explicação, o Spring ficaria a cargo do ciclo da conexão.

Segue um trecho da explicação dada nesse capitulo. Obrigado pela ajuda.

"O Spring vai criar todos e administrar o ciclo da vida deles. Com a mysqlDataSource definida, podemos injetar ela na ContaDao para recuperar a conexão JDBC. Para isso não podemos esquecer do @Autowired também"

@Repository
public class ContaDao {
  private Connection connection;

  @Autowired
  public ContaDao(DataSource ds) {
    try {
      this.connection = ds.getConnection();
    } catch(SQLException e) {
      throw new RuntimeException(e);
    }
  }

  // DAO continua ..

}

Ola, Lucas. Eu tive esse mesmo erro. Ele acontece porque ao listar as contas esse metodo fecha a conexao "connection.close();". Posteriormente ele vai usar a conexao e ela esta fechada.

Para resolver isso no inicio recupere novamente a conexao com this.connection = dataSource.getConnection();. Nao se preocupe porque nada é criado novamente.

Esse é um tipico erro onde mudar a implementacao das suas classes pode causar comportamentos indesejados. No caso a implementacao da conexao foi mudada quando voce passou a criar usando o DataSource da biblioteca dbcp do apache.

solução!

Verificando melhor, na realidade nao foi por causa da mudanca na implementacao. A causa principal do erro foi que antes a conexao era sempre criado toda vez que o DAO era criado. Por isso que nao dava erro. Quando o DAO passou a ser criado uma unica vez a conexao era fechada e depois nao era recuperada.