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" 要带双引号,网上很多不带的,我测试时出错,并且 ...
随机推荐
- linux查看内存cpu占用
linux查看内存cpu占用top 命令 按q退出 可以添加额外选项选择按进程或按用户查看如: top -u gitu PID:进程idPR:进程的优先级别,越小越优先被执行NInice:值VIRT ...
- 「6月雅礼集训 2017 Day7」电报
[题目大意] 有n个岛屿,第i个岛屿有有向发射站到第$p_i$个岛屿,改变到任意其他岛屿需要花费$c_i$的代价,求使得所有岛屿直接或间接联通的最小代价. $1 \leq n \leq 10^5, 1 ...
- NodeJS 微信公共号开发 - 响应微信发送的Token验证(山东数漫江湖)
背景 使用 NodeJS 进行微信公共号开发,首先需要响应微信发送的Token验证,官方文档 填写服务器配置 登录微信公共平台,在开发下的基本配置打开该页面. 依次填写接口的 URL.自定义的 Tok ...
- 微信小程序提示框
一.wx.showToast 如上图所示,showToast会显示一个弹窗,在指定的时间之后消失.中间的图标默认只有加载中和成功两种,也可以用image参数自定义图标 wx.showToast({ t ...
- Vue 双向绑定原理
Vue.js最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统. 一.访问器属性:Object.defineProperty ECMAScript 262v5带来的新东西,FF把它归入为jav ...
- js_数组去重效率对比
学习javascript已经快两年了,也不知道到了什么程度了. 说说我对javascript的理解,在电脑的世界里,只有数据. 数组,对象,字符串.对这些数据进行操作就可以完成很多业务逻辑,和页面的交 ...
- python基础===继承和多继承
继承 class A: def spam(self): print("A.SPAM") def add(self, x,y): return x+y class B(A): def ...
- java===java基础学习(12)---方法的重写和重载
覆盖 / 重写(Override) 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变.即外壳不变,核心重写! 重写的好处在于子类可以根据需要,定义特定于自己的行为. 也 ...
- BZOJ 4516: [Sdoi2016]生成魔咒——后缀数组、并查集
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4516 题意 一开始串为空,每次往串后面加一个字符,求本质不同的子串的个数,可以离线.即长度为 ...
- [caffe error] undefined reference to `inflateValidate@ZLIB_1.2.9'
undefined reference to `inflateValidate@ZLIB_1.2.9' Makefile.config添加一行LINKFLAGS := -Wl,-rpath,$(HOM ...