Java实现下载BLOB字段中的文件
概述
web项目的文件下载实现;servlet接收请求,spring工具类访问数据库及简化大字段内容获取。
虽然文章的demo中是以sevlet为平台,想必在spring mvc中也有参考意义。
核心代码
响应设置和输出
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
/* 1. 设置响应内容类型 */
response.setContentType("Application/Octet-stream;charset=utf-8"); /* 2. 读取文件 */
String fileName = ... // 获取文件名
fileName = new String(file.getName().getBytes(), "ISO-8859-1");
InputStream is = ... // 获取文件流 /* 3. 将文件名加入响应头 */
String cd = "attachment; filename=${fileName}"
.replaceFirst("\\$\\{fileName\\}", fileName);
((HttpServletResponse) response).addHeader("Content-Disposition", cd); /* 4. 将内容写到指定输出流,设置响应内容长度 */
OutputStream os = response.getOutputStream();
int length = org.springframework.util.FileCopyUtils.copy(is, os);
response.setContentLength(length); // 不设置长度也可 /* 5. 关闭输出流 */
os.close();
}
Servlet
我们定义一个servlet用于接收文件下载的请求,按照上述代码实现文件下载服务。但是我们遗留了两个问题:
- 如何获取文件名
- 如何获取文件的输入流
虽然这两个问题并非难解,我们依然提供一个参考;考虑到文件可能存放在文件服务器或者数据库等多种形式,这里仅提供基于数据库的获取方案。
获取文件名和文件内容
/* 创建JdbcTemplate用以查询数据 */
org.springframework.jdbc.core.JdbcTemplate jt = new org.springframework.jdbc.core.JdbcTemplate(ds);
// 以java.sql.DataSource实例作为参数 /* 创建LobHandler用以简化Lob字段读取 */
// 在内部对象的方法中使用,需声明为final
final org.springframework.jdbc.support.lob.LobHandler lobHandler = new org.springframework.jdbc.support.lob.DefaultLobHandler(); /* 创建Map用来存放文件名和文件内容 */
final Map file = new HashMap(); /* 确保可以查询到数据记录 - 取第一条 */
jt.query(
sql,
args,
new org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor() { protected void streamData(ResultSet rs)
throws SQLException, IOException,
DataAccessException {
// 注意,没有调用过rs.next();rs初始化已指向第一条记录
String fileName = rs.getString("filename");
InputStream is = lobHandler.getBlobAsBinaryStream(rs, "filecontent");
file.put("filename", fileName);
file.put("filecontent", is);
}
});
基于spring JdbcTemplate获取文件名和文件流
上面一段代码可以用来获取存在数据库的文件名和文件内容(BLOB字段):
- JdbcTemplate用来访问数据库
依赖于数据源实例。 - LobHandler用于简化Lob字段的读取
代码只是展示了针对BLOB字段的一种用法,关于CLOB的或其他方法,请查阅API。 - 匿名内部类
我们定义了基于AbstractLobStreamingResultSetExtractor的匿名内部类,并创建了对象实例。实例的方法中所访问的实例外的变量,要求必须是final类型的,所以我们把LobHandler定义成final的。鉴于同样的原因,我们无法直接把实例内部查询到的文件名和文件内容,直接赋值给外部的变量(因为外部的必须是final的),所以我们定义了一个final Map对象,用来存放结果。
代码优化
包图

包说明:
- cn.com.hnisi.fzyw.xzfy.gz.test.servlet
集中管理servlet - cn.com.hnisi.fzyw.xzfy.gz.download.service
集中管理处理请求的service,及创建service实例的工厂 - cn.com.hnisi.fzyw.xzfy.gz.download.domain
集中管理pojo - cn.com.hnisi.baseservices.db
提供数据库访问功能
类图

数据库访问支持组件
DataSourceFactory
DataSource工厂,负责向JdbcTemplateFactory提供可用的DataSource实例。
package cn.com.hnisi.baseservices.db; import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource; import cn.com.hnisi.baseservices.config.Config;
/**
* SINOBEST 数据源工厂,用于获取DataSource.<br>
* 使用JNDI查找上下文中的DataSource.<br>
* @author lijinlong
*
*/
public class DataSourceFactory {
private static DataSourceFactory instance;
private static InitialContext context;
/** 默认的数据源的jndi名称属性的key */
private static final String DEFAULT_DATASOURCE_PROP_KEY = "DB.DATASOURCE"; private DataSourceFactory() {
super();
} /**
* 单例模式获取工厂实例.<br>
* @return
*/
public static final synchronized DataSourceFactory newInstance() {
if (instance == null)
instance = new DataSourceFactory();
return instance;
} /**
* 获取默认的DataSource.<br>
* 根据config/config.properties中DB.DATASOURCE属性值查找.<br>
* @return
*/
public DataSource getDefaultDataSource() {
String jndiName = Config.getInstance().getValue(DEFAULT_DATASOURCE_PROP_KEY);
DataSource ds = getDataSource(jndiName);
return ds;
} /**
* 根据JNDI名称,获取上下文中的DataSource.<br>
* @param jndiName
* @return
*/
public synchronized DataSource getDataSource(String jndiName) {
DataSource ds = null;
try {
if (context == null)
context = new InitialContext();
ds = (DataSource)context.lookup(jndiName);
} catch (NamingException e) {
e.printStackTrace();
}
return ds;
}
}
DataSourceFactory
JdbcTemplateFactory
JdbcTemplate工厂,负责创建可用的JdbcTemplate实例。
package cn.com.hnisi.baseservices.db; import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate;
/**
* 用于获取{@link JdbcTemplate}实例的工厂.<br>
* <ol>
* <li>使用{@link #getDefaultJT()}可以获取基于默认数据源({@link DataSourceFactory#getDefaultDataSource()})的JT实例.
* </li>
* <li>使用{@link #getJT(DataSource)}可以获取基于指定数据源的JT实例.
* </li>
* <li>使用{@link #getJT(String)}可以获取基于指定JNDI name的数据源的JT实例.
* </li>
* <li>
* 其他的可能以后会补充.
* </li>
* </ol>
* @author lijinlong
*
*/
public class JdbcTemplateFactory {
private static JdbcTemplateFactory instance; private JdbcTemplateFactory() {
super();
} public static synchronized JdbcTemplateFactory newInstance() {
if (instance == null)
instance = new JdbcTemplateFactory(); return instance;
} /**
* 使用默认的数据源,获取{@link JdbcTemplate}对象实例.<br>
* @return
*/
public JdbcTemplate getDefaultJT() {
DataSource ds = DataSourceFactory.newInstance().getDefaultDataSource();
JdbcTemplate jt = getJT(ds);
return jt;
} /**
* 使用指定的数据源,获取{@link JdbcTemplate}对象实例.<br>
* @param ds
* @return
*/
public JdbcTemplate getJT(DataSource ds) {
if (ds == null)
return null;
JdbcTemplate jt = new JdbcTemplate(ds);
return jt;
} /**
* 根据数据源的jndiName,构造{@link JdbcTemplate}对象实例.
* @param dsJndiName
* @return
*/
public JdbcTemplate getJT(String dsJndiName) {
if (dsJndiName == null || dsJndiName.length() == 0)
return null; DataSource ds = DataSourceFactory.newInstance().getDataSource(dsJndiName);
JdbcTemplate jt = getJT(ds);
return jt;
}
}
JdbcTemplateFactory
File组件
自定义pojo,存放文件的name和content。
package cn.com.hnisi.fzyw.xzfy.gz.download.domain;
import java.io.InputStream;
public class File {
private String name;
private InputStream is;
public File() {
super();
}
public File(String name, InputStream is) {
super();
this.name = name;
this.is = is;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public InputStream getIs() {
return is;
}
public void setIs(InputStream is) {
this.is = is;
}
}
File
数据库访问组件
IDownloadService
文件下载服务接口。
package cn.com.hnisi.fzyw.xzfy.gz.download.service;
import java.util.List;
import cn.com.hnisi.fzyw.xzfy.gz.download.domain.File;
public interface IDownloadService {
/**
* 读取文件.<br>
*
* @param sql
* 查询sql语句,必须包含文件名字段和文件内容字段.
* @param args
* 参数 - 确保sql查询的结果只有一条.
* @param colNameFileName
* 存放文件名的字段名,大写.
* @param colNameFileContent
* 存放文件内容的字段名,大写.
* @return
*/
public File read(final String sql, final Object[] args,
final String colNameFileName, final String colNameFileContent);
}
IDownloadService
DownloadServiceImpl
文件下载服务实现类。
package cn.com.hnisi.fzyw.xzfy.gz.download.service; import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException; import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler; import cn.com.hnisi.baseservices.db.JdbcTemplateFactory;
import cn.com.hnisi.fzyw.xzfy.gz.download.domain.File; public class DownloadServiceImpl implements IDownloadService {
/**
* SINOBEST common 文件下载实现.
*/
public File read(final String sql, final Object[] args,
final String colNameFileName, final String colNameFileContent) {
JdbcTemplate jt = JdbcTemplateFactory.newInstance().getDefaultJT();
final LobHandler lobHandler = new DefaultLobHandler();
final File file = new File();
jt.query(sql, args, new AbstractLobStreamingResultSetExtractor() { protected void streamData(ResultSet rs) throws SQLException,
IOException, DataAccessException {
file.setName(rs.getString(colNameFileName));
file.setIs(lobHandler.getBlobAsBinaryStream(rs,
colNameFileContent));
}
});
return file;
} }
DownloadServiceImpl
DownloadServiceFactory
文件下载服务工厂,创建服务组件实例。
package cn.com.hnisi.fzyw.xzfy.gz.download.service;
public class DownloadServiceFactory {
private static DownloadServiceFactory ds;
private DownloadServiceFactory() {
super();
}
public static synchronized DownloadServiceFactory newInstance() {
if (ds == null)
ds = new DownloadServiceFactory();
return ds;
}
public IDownloadService createDownloadService() {
IDownloadService ds = new DownloadServiceImpl();
return ds;
}
}
DownloadServiceFactory
Servlet
FileDownloadTestServlet
文件下载请求的servlet
package cn.com.hnisi.fzyw.xzfy.gz.test.servlet; import java.io.IOException;
import java.io.OutputStream; import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse; import org.springframework.util.FileCopyUtils; import cn.com.hnisi.fzyw.xzfy.gz.download.domain.File;
import cn.com.hnisi.fzyw.xzfy.gz.download.service.DownloadServiceFactory; public class FileDownloadTestServlet extends HttpServlet { private static final long serialVersionUID = -2168892287436159079L;
/**
* SINOBEST common 文件下载测试.
*/
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException {
/* 1. 设置响应内容类型 */
response.setContentType("Application/Octet-stream;charset=utf-8"); /* 2. 读取文件 */
String sql = "select CLMC, SQCL from V_FZYWGZ_JK_XZFYSQXX_CL where SYSTEMID=?";
String systemid = request.getParameter("systemid");
Object[] args = { systemid };
String colNameFileName = "CLMC";
String colNameFileContent = "SQCL";
File file = DownloadServiceFactory.newInstance()
.createDownloadService()
.read(sql, args, colNameFileName, colNameFileContent); /* 3. 将文件名加入响应头 */
String fileName = new String(file.getName().getBytes(), "ISO-8859-1");
((HttpServletResponse) response).addHeader("Content-Disposition",
"attachment; filename=" + fileName); /* 4. 将内容写到指定输出流,设置响应内容长度 */
OutputStream os = response.getOutputStream();
int length = FileCopyUtils.copy(file.getIs(), os);
response.setContentLength(length); /* 5. 关闭输出流 */
os.close();
}
}
FileDownloadTestServlet
Java实现下载BLOB字段中的文件的更多相关文章
- Java实现打包下载BLOB字段中的文件
概述 web项目的文件打包下载实现:servlet接收请求,spring工具类访问数据库及简化大字段内容获取,org.apache.tools.zip打包. 必要提醒:当前总结是继Java实现下载BL ...
- Java学习-040-级联删除目录中的文件、目录
之前在写应用模块,进行单元测试编码的时候,居然脑洞大开居然创建了一个 N 层的目录,到后来删除测试结果目录的时候,才发现删除不了了,提示目录过长无法删除.网上找了一些方法,也找了一些粉碎机,都没能达到 ...
- java 读写Oracle Blob字段
许久没有分享代码了,把这段时间写的一个Java操作Blob字段,有日子没写Java了,就当作笔记记录一下.1. [代码][Java]代码 跳至 [1] [全屏预览]package com.wa ...
- hadoop学习笔记(十):hdfs在命令行的基本操作命令(包括文件的上传和下载和hdfs中的文件的查看等)
hdfs命令行 ()查看帮助 hdfs dfs -help ()查看当前目录信息 hdfs dfs -ls / ()上传文件 hdfs dfs -put /本地路径 /hdfs路径 ()剪切文件 hd ...
- HttpClient使用之下载远程服务器中的文件(注意目录遍历漏洞)
参考文献: http://bbs.csdn.net/topics/390952011 http://blog.csdn.net/ljj_9/article/details/53306468 1.下载地 ...
- 用java 代码下载Samba服务器上的文件到本地目录以及上传本地文件到Samba服务器
引入: 在我们昨天架设好了Samba服务器上并且创建了一个 Samba 账户后,我们就迫不及待的想用JAVA去操作Samba服务器了,我们找到了一个框架叫 jcifs,可以高效的完成我们工作. 实践: ...
- Java将对象保存到文件中/从文件中读取对象
1.保存对象到文件中 Java语言只能将实现了Serializable接口的类的对象保存到文件中,利用如下方法即可: public static void writeObjectToFile(Obje ...
- 利用backgroundwork----递归读取网页源代码,并下载href链接中的文件
今天闲着没事,研究了一下在线更新程序版本的问题.也是工作中的需要,开始不知道如何下手,各种百度也没有找到自己想要的,因为我的需求比较简单,所以就自己琢磨了一下.讲讲我的需求吧.自己在IIs上发布了一个 ...
- ORACLE 向BLOB字段中出入图片等二进制文件,使用Oracle SQl Developer工具
使用PL/SQL也可以 create directory "image" as 'e:\'; --"image" 要带双引号,网上很多不带的,我测试时出错,并且 ...
随机推荐
- [USACO06NOV] Corn Fields
https://www.luogu.org/problem/show?pid=1879 题目描述 Farmer John has purchased a lush new rectangular pa ...
- netcore 使用log4net
1.Install Install-Package log4net 2. conifg 創建文件:log4net.config <?xml version="1.0" enc ...
- 求逆元的两种方法+求逆元的O(n)递推算法
到国庆假期都是复习阶段..所以把一些东西整理重温一下. gcd(a,p)=1,ax≡1(%p),则x为a的逆元.注意前提:gcd(a,p)=1; 方法一:拓展欧几里得 gcd(a,p)=1,ax≡1( ...
- 【51NOD】数据流中的算法
[算法]数学 [题解] 1.平均数:累加前缀和.//听说要向下取整? 2.中位数:双堆法,大于中位数存入小顶堆,小于中位数存入大顶堆,保证小顶堆内数字数量≥大顶堆,奇数则取小堆顶,偶数则取两堆顶/2. ...
- Problem L. Visual Cube(杭电多校2018年第三场+模拟)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6330 题目: 题意:给你长宽高,让你画出一个正方体. 思路:模拟即可,湘潭邀请赛热身赛原题,不过比那个 ...
- niceScroll 简单使用 及 插件API
官方网址[https://nicescroll.areaaperta.com/] 注:效果见官网右侧滚动条 jquery.nicescroll文件下载地址 引入核心文件,插件需要引入1.5.X以上版 ...
- poj 3104 Drying(二分查找)
题目链接:http://poj.org/problem?id=3104 Drying Time Limit: 2000MS Memory Limit: 65536K Total Submissio ...
- linux下删除已经不用的配置文件
使用命令 dpkg -l | grep -v ^ii 查看当前未安装或者不用了的配置文件 例如我的显示如下
- [Leetcode Week16]Insertion Sort List
Insertion Sort List 题解 原创文章,拒绝转载 题目来源:https://leetcode.com/problems/insertion-sort-list/description/ ...
- [device tree] interrupt mapping example
This is for Devicetree Specification Release 0.1 Interrupt Mapping Example p19 在講解前,先帶進一些 PCI 的基礎觀念 ...