Este post vai apresentar como implementar um upload e download de arquivos usando Servlets, JSP, JSTL, EL e o padrão MVC.
Para este exemplo, vou criar um projeto web dinâmico no Eclipse. Eu não vou especificar um banco de dados, o código deve funcionar com qualquer banco relacional.
Para implementar o exemplo precisamos de quatro bibliotecas adicionais, sendo duas da Apache:
Upload
A página de envio deve conter um formulário com os campos necessários e mais um campo de input type file para o upload. No exemplo a seguir, vou mostrar o formulário com apenas dois campos: input type="file" e outro input type="text".
A servlet que vai receber os dados deste formulário deve conter o código:
Para verificar se um FileItem é um campo do comum formulário ou um campo (input) do tipo file, podemos usar o método: fileItem.isFormField().
Não se esqueça de mapear a sua servlet (UploadFileServlet)! Pode ser mapeada usando anotações (API Servlet 3.x) ou com o bom e velho deployment descriptor (web.xml). Segue o trecho do web.xml correspondente:
No código acima a classe da servlet se encontra no pacote controller.user.file , altere para o caminho correto dentro da sua aplicação.
A servlet UploadFileServlet faz uso da classe JavaBean FileBean, com o código:
Para armazenar o código no banco de dados temos que ter uma tabela que corresponda ao arquivo. Essa tabela deve conter um varchar para o nome, outro para a descrição, um campo blob para o arquivo (bytes) e um campo id para identificar cada arquivo adicionado. Devemos setar esse último campo como autoincrement (MySQL).
O código da DAO que armazena os dados, lista e busca o JavaBean (FileBean) no banco de dados é:
Os métodos da DAO fazem chamada a um método da classe ConnectionFactory que apenas cria e retorna uma conexão ao banco de dados. Daqui já podemos executar pra testar!
Download
Para o download vou mostrar um trecho de código de um JSP (list.jsp) que lista os arquivos do banco de dados e ao clicar sobre um arquivo o download é iniciado. Este código faz uso do JSTL e da EL. Se for uma imagem ele mostra um thumbnail ao invés do botão de download.
A servlet que busca os arquivos na base de dados tem o código:
A classe da servlet responsável pelo download tem o código:
Elas também devem ser mapeadas, tal como no exemplo a seguir:
Acho que é isso. Se tiver algum erro, melhoria ou dificuldade na implementação é só avisar!
Happy coding!
[]`s
Para este exemplo, vou criar um projeto web dinâmico no Eclipse. Eu não vou especificar um banco de dados, o código deve funcionar com qualquer banco relacional.
Para implementar o exemplo precisamos de quatro bibliotecas adicionais, sendo duas da Apache:
E para usar o JSTL precisamos de mais duas bibliotecas adicionais, a API e a implementação. Ambos podem ser encontrados no link:
Baixe todas as bibliotecas e adicione na pasta lib dentro de WebContent. A pasta lib também deve conter o conector para o banco de dados utilizado.Upload
A página de envio deve conter um formulário com os campos necessários e mais um campo de input type file para o upload. No exemplo a seguir, vou mostrar o formulário com apenas dois campos: input type="file" e outro input type="text".
<form action="upload.do" enctype="multipart/form-data" method="post">
<label for="desc">Descrição:</label>
<input type="text" name="desc" id="desc"><br/>
<label for="file">Arquivo:</label>
<input type="file" name="file" id="file"><br/>
<input type="submit" value="Enviar"/>
</form>
A servlet que vai receber os dados deste formulário deve conter o código:
public class UploadFileServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
DiskFileItemFactory dfif = new DiskFileItemFactory();
ServletFileUpload sfu = new ServletFileUpload(dfif);
if (!ServletFileUpload.isMultipartContent(req)) {
// tratar erro
}
try {
List items = sfu.parseRequest(req);
//a posicao 0 corresponde ao primeiro campo input do formulario (descricao)
FileItem descFI = (FileItem) items.get(0);
String desc = descFI.getString();
//a posicao 1 corresponde ao segundo campo input do formulario (arquivo)
FileItem fileFI = (FileItem) items.get(1);
byte[] bytes = read(fileFI);
//Não é o File do Java. É um JavaBean apresentado a seguir
FileBean filebean = new FileBean();
filebean.setDescription(fileFI.getDesc());
filebean.setName(fileFI.getName());
filebean.setData(bytes);
FileDao fdao = new FileDaoImpl();
fdao.add(filebean);
} catch (FileUploadException e) {
// tratar erro
}
resp.sendRedirect("list.jsp");
}
private byte[] read(FileItem fi) throws IOException{
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream is = fi.getInputStream();
int read = 0;
final byte[] b = new byte[1024];
while ((read = is.read(b)) != -1) {
out.write(b, 0, read);
}
return out.toByteArray();
}
}
Para verificar se um FileItem é um campo do comum formulário ou um campo (input) do tipo file, podemos usar o método: fileItem.isFormField().
Não se esqueça de mapear a sua servlet (UploadFileServlet)! Pode ser mapeada usando anotações (API Servlet 3.x) ou com o bom e velho deployment descriptor (web.xml). Segue o trecho do web.xml correspondente:
<servlet>
<servlet-name>UploadFileServlet</servlet-name>
<servlet-class>controller.user.file.UploadFileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadFileServlet</servlet-name>
<url-pattern>/upload.do</url-pattern>
</servlet-mapping>
No código acima a classe da servlet se encontra no pacote controller.user.file , altere para o caminho correto dentro da sua aplicação.
A servlet UploadFileServlet faz uso da classe JavaBean FileBean, com o código:
public class FileBean {
private String description;
private String name;
private Long id;
private byte[] data;
public FileBean() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
}
Para armazenar o código no banco de dados temos que ter uma tabela que corresponda ao arquivo. Essa tabela deve conter um varchar para o nome, outro para a descrição, um campo blob para o arquivo (bytes) e um campo id para identificar cada arquivo adicionado. Devemos setar esse último campo como autoincrement (MySQL).
O código da DAO que armazena os dados, lista e busca o JavaBean (FileBean) no banco de dados é:
public boolean add(FileBean filebean) {
int added = 0;
try {
Connection con = ConnectionFactory.createConnection();
if (con != null) {
String sql = "INSERT INTO file (name, description, data) VALUES (?, ?, ?)";
PreparedStatement st = con.prepareStatement(sql);
st.setString(1, filebean.getName());
st.setString(2, filebean.getDescription());
st.setBytes(3, filebean.getData());
added = st.executeUpdate();
st.close();
con.close();
}
} catch (SQLException e) {
//tratar erro
}
return added > 0;
}
public List list() {
List files = new ArrayList();
try {
Connection con = ConnectionFactory.createConnection();
if (con != null) {
String sql = "SELECT * FROM file";
Statement st = con.createStatement();
ResultSet rs = st.execute(sql);
while(rs.next()){
FileBean filebean = createFileBean(rs);
files.add(filebean);
}
st.close();
con.close();
}
} catch (SQLException e) {
//tratar erro
}
return files;
}
public FileBean getFile(long id) {
FileBean filebean = null;
try {
Connection con = ConnectionFactory.createConnection();
if (con != null) {
String sql = "SELECT * FROM file WHERE id=?";
PreparedStatement st = con.prepareStatement(sql);
st.setLong(1, id);
ResultSet rs = st.executeQuery();
if (rs.next()) {
filebean = createFileBean(rs);
}
rs.close();
st.close();
con.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
return file;
}
private FileBean createFileBean(ResultSet rs) throws SQLException {
FileBean filebean = new FileBean();
filebean.setData(rs.getBytes("data"));
filebean.setId(rs.getLong("id"));
filebean.setName(rs.getString("name"));
filebean.setDescription(rs.getString("description"));
return filebean;
}
Os métodos da DAO fazem chamada a um método da classe ConnectionFactory que apenas cria e retorna uma conexão ao banco de dados. Daqui já podemos executar pra testar!
Download
Para o download vou mostrar um trecho de código de um JSP (list.jsp) que lista os arquivos do banco de dados e ao clicar sobre um arquivo o download é iniciado. Este código faz uso do JSTL e da EL. Se for uma imagem ele mostra um thumbnail ao invés do botão de download.
<table>
<thead>
<tr>
<th align="center"><b>Nome</b></th>
<th align="center"><b>Descrição</b></th>
<th align="center"><b>Arquivo</b></th>
</tr>
</thead>
<tbody>
<c:forEach var="file" items="${items}">
<c:set value="${fn:endsWith(file.name, 'jpg') or fn:endsWith(file.name, 'png')}" var="isImage"/>
<tr>
<td>${file.name}</td>
<td>${file.description}</td>
<c:if test="${isImage}">
<td><img width="50" height="50" src="download.do?id=${file.id}"/></td>
</c:if>
<c:if test="${not isImage}">
<td><a href="download.do?id=${file.id}">Baixar</a></td>
</c:if>
</tr>
</c:forEach>
</tbody>
</table>
A servlet que busca os arquivos na base de dados tem o código:
public class ListFilesServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
FileDao dao = new FileDaoImpl();
List<FileBean> files = dao.list();
req.setAttribute("items", dao.list());
RequestDispatcher rd = pReq.getRequestDispatcher("list.jsp");
rd.forward(req, resp);
}
}
A classe da servlet responsável pelo download tem o código:
public class DownloadFileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String idStr = req.getParameter("id");
if(idStr == null){
//redirect to home or other
}
FileDao dao = new FileDaoImpl();
Long id = Long.parseLong(idStr);
FileBean filebean = dao.getFile(id);
if (file != null) {
ServletContext context = getServletConfig().getServletContext();
String mimetype = context.getMimeType(filebean.getName());
resp.setContentLength(
filebean.getData().length);
resp.setContentType(mimetype);//resp.setContentType("image/jpeg");
resp.setHeader("Content-Disposition", "attachment; filename=\"" + filebean.getName() + "\"");
OutputStream out = resp.getOutputStream();
InputStream in = new ByteArrayInputStream(filebean.getData());
byte[] buffer = new byte[4096];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
in.close();
out.flush();
}
}
}
Elas também devem ser mapeadas, tal como no exemplo a seguir:
<servlet>
<servlet-name>DownloadFileServlet</servlet-name>
<servlet-class>controller.user.file.DownloadFileServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DownloadFileServlet</servlet-name>
<url-pattern>/download.do</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>ListFilesServlet</servlet-name>
<servlet-class>controller.user.file.ListFilesServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ListFilesServlet</servlet-name>
<url-pattern>/list.do</url-pattern>
</servlet-mapping>
Acho que é isso. Se tiver algum erro, melhoria ou dificuldade na implementação é só avisar!
Happy coding!
[]`s
Olá muito boa sua explicação
ResponderExcluirPorém estou com um problema, eu preciso usar no meu form um tipo hidden. E usando esse enctype eu não to conseguindo.
Sabe algum jeito de resolver isso ?
Desde já grato
Marcos Fabris
Marcos, fiz um teste aqui e verifiquei que funciona normalmente como se fosse um campo do tipo "text", como no exemplo da descrição. Acho que o problema deve ser outro.
Excluir[]s
Olá Anderson, muito boa sua explicação, só não consegui entender como fazer o upload de arquivos grandes, como crio um buffer? teria algum material?
ResponderExcluirMuito obrigado
Alterei o código do exemplo para possibilitar o upload de arquivos de qualquer tamanho.
Excluir[]s
Muito obrigado Anderson :)
ResponderExcluirHelder Weber Gayer
Consegui salvar a imagem no entanto não consigo a recuperar, no meu projeto tenho que usar jsp puro, não posso colocar jstl.
ResponderExcluirVocê pode me ajudar?
Olá Douglas,
ExcluirPara fazer o download ou exibir a imagem sem o JSTL e EL é só trocar o código para um código com scripts (scriptlets, expressões, declarações JSP) correspondente. Por exemplo: a tag c:forEach deve ser trocada por um <% for(File file: request.getAttribute("items")) ... %>. Já a tag c:if deve ser trocada por um <% if( ... ) %> e assim por diante.
[]s
Olá Anderson,
ResponderExcluirGostaria de uma dica se possivel, nosso sistema tem a missão de fazer upload de muitas imagens, e tambem ter uma campo de busca de imagens, essa busca de imagens seria assim, primeiro colocariamos o numero que faz referencia a um determinado registro e este registro iria puxar e exibir a imagem para o usuario, kara, são muitos registros e muitas imagens, muitas um numero que iria aumentando gradativamente todos os dias, e este servidor com as imagens seria acessado por varias maquinas, kara, vc tem alguma dica onde posso pesquisar algo do tipo, e qual processo de upload nao iria pesar o sistema? uma dica, por favor? Agradecido
Olá Marcos,
ExcluirNão sei se entendi direito, mas o upload que eu mostrei vai enviar a imagem para o servidor e vai guardar no BD. É claro que depois de um tempo, o BD vai aumentando sim. Existe a possibilidade de enviar várias imagens de uma vez através do atributo multiple da tag input. Só que você tem que ler corretamente do lado servidor.
Não entendi corretamente a parte do sistema com a busca de imagens. Preciso de mais informações, são muitos detalhes.
Um abraço.