使用 JDBC 上传和下载文件到 SQL 服务器
本文将通过一个示例,在 Spring MVC 应用程序中使用 JDBC 将文件上传和下载到 SQL Server。
引言
本文将通过一个示例,在 Spring MVC 应用程序中使用 JDBC 将文件上传和下载到 SQL Server。
背景
JDBC 和 Java 本身一样成熟,并且之后在此基础上构建了许多替代方案。它仍然是访问数据库最重要的途径,仅仅因为它最为基础。当其他方法不能满足您的需求时,JDBC 永远在那里。本文将通过一个示例,在 Spring MVC 应用程序中使用 JDBC 将文件上传和下载到 SQL Server。示例使用了 jQuery 文件上传插件 将文件上传到服务器。我在 Firefox 和 Chrome 上测试了该应用程序。由于 jQuery 文件上传插件 需要 Web 浏览器支持 HTML5,并且我没有较新版本的 IE,因此我没有在 Internet Explorer 上测试此应用程序。
数据库
我在测试中使用的 SQL Server 是 SQL Server 2014 Express。但如果您使用的是 SQL Server 的早期版本,我相信该示例也应该适用。如果您拥有服务器的管理权限,可以执行以下脚本来创建测试数据库。
USE master
GO
-- Create the [Experiment] database
IF EXISTS (SELECT name FROM sys.databases WHERE name = N'Experiment')
BEGIN
ALTER DATABASE Experiment SET SINGLE_USER WITH ROLLBACK IMMEDIATE
END
GO
DROP DATABASE Experiment
GO
CREATE DATABASE [Experiment]
GO
-- Create the FileLoader login to the server
IF EXISTS (SELECT * FROM sys.server_principals WHERE name = N'FileLoader')
DROP LOGIN [FileLoader]
GO
EXEC sp_addlogin @loginame = 'FileLoader', @passwd = 'Pwd123456';
GO
USE Experiment
GO
-- Grant FileLoader the access to the Experiment database
sp_grantdbaccess 'FileLoader'
GO
-- Create the ID table
CREATE TABLE [dbo].[TABLE_ID](
[TableName] [varchar](255) NOT NULL,
[Current_ID] [int] NOT NULL,
CONSTRAINT [PK_TABLE_ID] PRIMARY KEY CLUSTERED ([TableName] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
INSERT INTO TABLE_ID VALUES ('TABLE_FILES', 0)
GO
-- Create the file table
CREATE TABLE [dbo].[TABLE_FILES](
[ID] [int] NOT NULL,
[Name] [varchar](255) NOT NULL,
[ContentType] [varchar](255) NOT NULL,
[Size] [bigint] NOT NULL,
[Data] [varbinary](max) NOT NULL,
[UploadTime] [datetime] NOT NULL,
CONSTRAINT [PK_TABLE_FILES] PRIMARY KEY NONCLUSTERED ([ID] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
-- Create the stored procedures
CREATE PROCEDURE [dbo].[File_Insert]
@name VARCHAR(255),
@contentType VARCHAR(255),
@size BIGINT,
@data VARBINARY(max),
@fileId INT OUTPUT
AS
BEGIN
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
-- Get an Id for the file
BEGIN TRANSACTION
BEGIN TRY
SELECT @fileId = Current_ID + 1 FROM [TABLE_ID] WITH(XLOCK, ROWLOCK)
WHERE [TableName] = 'TABLE_FILES'
UPDATE [TABLE_ID] SET Current_ID = @fileId WHERE [TableName] = 'TABLE_FILES'
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
RAISERROR ('Unable to get an Id for the file', 16, 1)
RETURN
END CATCH
IF @@TRANCOUNT > 0
COMMIT TRANSACTION;
-- Insert the file to the table
INSERT INTO [TABLE_FILES] VALUES(@fileId, @name, @contentType, @size, @data, GETDATE())
END
GO
CREATE PROCEDURE [dbo].[File_Delete]
@fileId INT
AS
BEGIN
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
DELETE FROM [TABLE_FILES] WHERE ID = @fileId
END
GO
CREATE PROCEDURE [dbo].[File_Select]
@fileId INT
AS
BEGIN
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT ID, Name, ContentType, Size, Data
FROM [TABLE_FILES] WHERE ID = @fileId
END
GO
CREATE PROCEDURE [dbo].[File_List]
AS
BEGIN
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT ID, Name, ContentType, Size FROM [TABLE_FILES]
ORDER BY UploadTime DESC
END
GO
-- Grant FileLoader the execution permissons to the SPs
GRANT EXECUTE ON OBJECT::[dbo].[File_Insert] TO FileLoader
GRANT EXECUTE ON OBJECT::[dbo].[File_Delete] TO FileLoader
GRANT EXECUTE ON OBJECT::[dbo].[File_Select] TO FileLoader
GRANT EXECUTE ON OBJECT::[dbo].[File_List] TO FileLoader
-- Check the initial data
SELECT * FROM TABLE_ID
SELECT * FROM TABLE_FILES
GO
-- Bring the database on-line
ALTER DATABASE [Experiment] SET MULTI_USER
GO
如果脚本成功运行,您的服务器上应该会出现一个名为 [Experiment] 的数据库。
[Experiment] 数据库应该包含以下组件
- 表 [Table_files] 和 [Table_id]。[Table_files] 将用于保存上传的文件,而 [Table_id] 将用于生成文件的 ID;
- 存储过程 [File_Delete]、[File_Insert]、[File_List] 和 [File_Select];
- 数据库用户 [FileLoader],密码为 "Pwd123456",已被授予执行存储过程的权限。我们将使用此数据库用户从我们的 Spring MVC 应用程序访问数据库。
[File_Insert] 存储过程将用于将文件保存到数据库。让我们详细了解一下。
CREATE PROCEDURE [dbo].[File_Insert]
@name VARCHAR(255),
@contentType VARCHAR(255),
@size BIGINT,
@data VARBINARY(max),
@fileId INT OUTPUT
AS
BEGIN
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
-- Get an Id for the file
BEGIN TRANSACTION
BEGIN TRY
SELECT @fileId = Current_ID + 1 FROM [TABLE_ID] WITH(XLOCK, ROWLOCK)
WHERE [TableName] = 'TABLE_FILES'
UPDATE [TABLE_ID] SET Current_ID = @fileId WHERE [TableName] = 'TABLE_FILES'
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
RAISERROR ('Unable to get an Id for the file', 16, 1)
RETURN
END CATCH
IF @@TRANCOUNT > 0
COMMIT TRANSACTION;
-- Insert the file to the table
INSERT INTO [TABLE_FILES] VALUES(@fileId, @name, @contentType, @size, @data, GETDATE())
END
- 您可能会注意到,获取文件 ID 和保存文件是在两个不同的事务中进行的。通过这样做,我们可以在获取完文件 ID 后快速释放对 [Table_id] 的锁定,从而避免其他并发操作因向 [Table_files] 插入数据而被阻塞;
- 在读取 [Table_id] 中的文件 ID 时,我们使用了 查询提示 "XLOCK"。这是为了避免当其他并发进程同时调用存储过程上传其他文件时可能发生的死锁。
如果您现在向数据库发出以下命令,您可以检查这两个表中的数据。
-- Check the initial data
SELECT * FROM TABLE_ID
SELECT * FROM TABLE_FILES
GO
MVC 应用程序
附件中的 Spring MVC 应用程序是一个 Maven 项目。以下是其 "pom.xml" 文件。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.song.example</groupId>
<artifactId>JDBCExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<spring.version>4.0.7.RELEASE</spring.version>
<jackson.version>2.4.3</jackson.version>
<tomcat.version>7.0.55</tomcat.version>
</properties>
<dependencies>
<!-- Minimal dependencies for Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Multi-part file support -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- Jackson dependencies -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Sevlet jars for compilation, provided by Tomcat -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>true</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
- "org.springframework" 的依赖项是创建 Spring MVC 应用程序所需的最小依赖集;
- "commons-fileupload" 的依赖项用于使 MVC 应用程序能够处理上传的文件;
- "com.fasterxml.jackson.core" 的依赖项用于帮助应用程序执行 JSON 操作。
处理此项目的最简单方法是使用 IDE。如果您不知道如何将项目导入 Eclipse,可以参考这篇 文章。以下是 Eclipse 项目资源管理器中的项目视图。
Web 应用程序配置在 "web.xml" 和 "mvc-dispatcher-servlet.xml" 文件中。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>Spring Web Example</display-name>
<welcome-file-list>
<welcome-file>mvc/mainpage</welcome-file>
</welcome-file-list>
<filter>
<filter-name>nocachefilter</filter-name>
<filter-class>com.song.web.filter.NocacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>nocachefilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/mvc/*</url-pattern>
</servlet-mapping>
<env-entry>
<env-entry-name>DBConnectionInformation</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>
jdbc:sqlserver://;DatabaseName=Experiment|FileLoader|Pwd123456
</env-entry-value>
</env-entry>
<context-param>
<param-name>BaseUrl</param-name>
<param-value>
https://:8080/JDBCExample
</param-value>
</context-param>
</web-app>
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<context:component-scan base-package="com.song.web.controller" />
<mvc:annotation-driven />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver" >
<property name="prefix"><value>/views/</value></property>
</bean>
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1048576"/>
</bean>
</beans>
- 连接数据库的信息配置在 "DBConnectionInformation" 条目中;
- "BaseUrl" "context-param" 是 Web 应用程序的基 URL。如果您想将应用程序部署到 Tomcat 服务器,则需要修改它以匹配 Web 服务器的域和端口;
- "multipartResolver" 设置了上传文件的最大大小。在我的示例中,设置为 1 兆字节。
数据库访问类
"com.song.database" 包中的类用于连接数据库以保存和检索文件。"DBConnector" 类实现如下,以获取到数据库的 "Connection"。
package com.song.database;
import java.sql.Connection;
import java.sql.DriverManager;
import javax.naming.InitialContext;
public class DBConnector {
private final static class ConnInformation {
public String DBUrl;
public String UserName;
public String Password;
private ConnInformation(String dBUrl,
String userName, String password) {
DBUrl = dBUrl;
UserName = userName;
Password = password;
}
private static ConnInformation instance = null;
private static synchronized ConnInformation getInstance() throws Throwable{
if (instance == null) {
try {
InitialContext initialContext = new javax.naming.InitialContext();
String contextString = (String) initialContext
.lookup("java:comp/env/DBConnectionInformation");
String[] info = contextString.split("\\|");
instance = new ConnInformation(info[0], info[1], info[2]);
} catch (Throwable e) {
instance = null;
throw e;
}
}
return instance;
}
}
public static Connection getAConnection(boolean autocommit) throws Throwable {
ConnInformation connInfo = ConnInformation.getInstance();
Connection connection = null;
try {
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
connection = DriverManager
.getConnection(connInfo.DBUrl, connInfo.UserName, connInfo.Password);
connection.setAutoCommit(autocommit);
} catch (Throwable e) {throw e;}
return connection;
}
public static Connection getAConnection() throws Throwable {
return getAConnection(true);
}
}
- 静态类 "ConnInformation" 存储了从 "web.xml" 文件连接数据库所需的信息;
- 重载方法 "getAConnection" 使用 Microsoft SQL Server JDBC 驱动程序 返回到数据库的连接;
连接对象上 "setAutoCommit" 方法的名称有些令人困惑。当设置为 false 时,它会向数据库发出 "SET IMPLICIT_TRANSACTIONS ON" 命令。从数据库的角度来看,如果 "IMPLICIT_TRANSACTIONS" 为 "ON",并且连接当前不在事务中,几乎任何 SQL 查询都会启动一个事务,包括简单的 select 查询。这可能会导致一些复杂性。
- 如果您从未打算启动事务而只是向数据库发出 select 查询,那么如果您在连接对象上将 "setAutoCommit" 设置为 false,就会启动一个事务。您需要记住提交或回滚这个不必要的事务。否则,您的 select 查询可能会阻塞其他人的数据库查询,直到您的会话超时并且数据库服务器选择回滚您的事务;
- 根据 ACID 原则,单条语句查询始终是一个事务。如果您在连接对象上将 "setAutoCommit" 设置为 false,即使您只是将单行插入到单个表中,您也需要记住提交它,否则无法保证您的数据会保存到数据库中。
由于 JDBC 不实现事务,事务机制是在数据库服务器中实现的。我认为控制事务的最佳位置是实现事务的地方。在此示例中,我使用存储过程来保存和删除数据库中的文件。事务在存储过程中进行控制,因此在本示例中,我始终将 "setAutoCommit" 设置为 true。基于 "DBConnector" 类,"DBOperation" 类调用存储过程来处理文件。
package com.song.database;
import java.io.InputStream;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import com.song.model.File;
public class DBOperation {
public static File saveAFile(String name, String contentType,
int size, InputStream stream) throws Throwable {
int fileId = 0;
String sql = "{call File_Insert(?,?,?,?,?)}";
try (Connection con = DBConnector.getAConnection()) {
try (CallableStatement stmt = con.prepareCall(sql)) {
stmt.registerOutParameter(5, java.sql.Types.INTEGER);
stmt.setString(1, name);
stmt.setString(2, contentType);
stmt.setInt(3, size);
stmt.setBinaryStream(4, stream);
stmt.execute();
fileId = stmt.getInt(5);
}
}
return new File(fileId, name, contentType, size);
}
public static void deleteAFile(int fileId) throws Throwable {
String sql = "{call File_Delete(?)}";
try (Connection con = DBConnector.getAConnection()) {
try (CallableStatement stmt = con.prepareCall(sql)) {
stmt.setInt(1, fileId);
stmt.execute();
}
}
}
public static File selectAFile(int fileId) throws Throwable {
File file = null;
String sql = "{call File_Select(?)}";
try (Connection con = DBConnector.getAConnection()) {
try (CallableStatement stmt = con.prepareCall(sql)) {
stmt.setInt(1, fileId);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
fileId = rs.getInt("ID");
String name = rs.getString("Name");
String contentType = rs.getString("ContentType");
int size = rs.getInt("Size");
byte[] data = rs.getBytes("Data");
file = new File(fileId, name, contentType, size);
file.setData(data);
}
}
}
return file;
}
public static List<File> ListAllFile() throws Throwable {
List<File> files = new ArrayList<File>();
String sql = "{call File_List}";
try (Connection con = DBConnector.getAConnection()) {
try (CallableStatement stmt = con.prepareCall(sql)) {
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
int fileId = rs.getInt("ID");
String name = rs.getString("Name");
String contentType = rs.getString("ContentType");
int size = rs.getInt("Size");
File file = new File(fileId, name, contentType, size);
files.add(file);
}
}
}
return files;
}
}
MVC 模型类
"com.song.model" 和 "com.song.model.rest" 包中的类是 MVC 应用程序的模型。"RestResponseStatusCode"、"RestResponseStatus" 和 "RestResponse" 枚举/类代表一个 REST 响应。
package com.song.model.rest;
public enum RestResponseStatusCode { SUCCESS, FAIL }
package com.song.model.rest;
public final class RestResponseStatus {
public RestResponseStatusCode code;
public String msg;
private RestResponseStatus(RestResponseStatusCode code, String msg) {
this.code = code;
this.msg = msg;
}
public static RestResponseStatus success(String msg) {
msg = (msg == null)? "": msg.trim();
if (msg.length() == 0) {
msg = "The operation is successful";
}
return new RestResponseStatus(RestResponseStatusCode.SUCCESS, msg);
}
public static RestResponseStatus fail(String msg) {
msg = (msg == null)? "": msg.trim();
if (msg.length() == 0) {
msg = "Error occured while doing the operation";
}
return new RestResponseStatus(RestResponseStatusCode.FAIL, msg);
}
}
package com.song.model.rest;
public final class RestResponse {
public RestResponseStatus status;
public Object payload;
}
"RestResponse" 类中的 "payload" 对象可以是任何对象。当然,在示例中,它是 "File" 类。
package com.song.model;
public final class File {
public int fileId;
public String name;
public String contentType;
public int size;
public byte[] data;
public File(int fileId, String name, String contentType, int size) {
this.fileId = fileId;
this.name = name;
this.contentType = contentType;
this.size = size;
}
public void setData(byte[] data) {
this.data = data;
}
}
MVC 控制器
"com.song.web.controller" 包中的 "FileController" 类是应用程序的控制器。
package com.song.web.controller;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import com.song.database.DBOperation;
import com.song.model.File;
import com.song.model.rest.RestResponse;
import com.song.model.rest.RestResponseStatus;
@Controller
public class FileController {
@RequestMapping(value = "/mainpage",
method = RequestMethod.GET)
public ModelAndView mainpage() {
RestResponse response = new RestResponse();
List<File> files = null;
try {
files = DBOperation.ListAllFile();
}catch(Throwable e) {
response.status = RestResponseStatus
.fail("Failed to load the existing files");
}
if (response.status == null) {
response.status = RestResponseStatus.success(null);
response.payload = files;
}
ModelAndView modelView = new ModelAndView();
modelView.addObject("data", response);
modelView.setViewName("homepage.jsp");
return modelView;
}
@RequestMapping(value = "/api/uploadfile",
method = RequestMethod.POST)
public ModelAndView uploadfile(MultipartHttpServletRequest request)
throws Throwable {
MultipartFile multipartFile = request.getFile("uploadFile");
int size = (int) multipartFile.getSize();
String contentType = multipartFile.getContentType();
String name = multipartFile.getOriginalFilename();
InputStream stream = multipartFile.getInputStream();
final File file = DBOperation.saveAFile(name, contentType, size, stream);
List<File> files = new ArrayList<File>();
files.add(file);
ModelAndView modelView = new ModelAndView();
modelView.addObject("files", files);
modelView.setViewName("files.jsp");
return modelView;
}
@ResponseBody
@RequestMapping(value = "/api/deletefile",
method = RequestMethod.POST)
public RestResponse deletefile(int fileId) throws Throwable {
DBOperation.deleteAFile(fileId);
RestResponse response = new RestResponse();
response.status = RestResponseStatus.success(null);
return response;
}
@RequestMapping(value = "/api/downloadfile",
method = RequestMethod.GET)
public void downloadfile(@RequestParam("fileId") int fileId,
HttpServletResponse response) throws Throwable {
File file = DBOperation.selectAFile(fileId);
response.setContentType(file.contentType);
response.addHeader("Content-Disposition",
"attachment; filename=\"" + file.name + "\"");
response.setContentLength(file.size);
response.getOutputStream().write(file.data);
response.flushBuffer();
}
}
- "mainpage" 方法加载应用程序的主 Web 页面;
- "uploadfile" 方法接收从 Web 浏览器上传的文件并将它们保存到数据库;
- "deletefile" 方法删除一个文件;
- "downloadfile" 方法将文件数据发送到 Web 浏览器进行下载。
MVC 视图
"homepage.jsp" 文件是应用程序的主页。
<%@page import="com.song.model.rest.RestResponseStatusCode"%> <%@page import="com.song.model.rest.RestResponseStatus"%> <%@page import="java.util.List"%> <%@page import="com.song.database.DBOperation"%> <%@page import="com.song.model.File"%> <%@page import="com.song.model.rest.RestResponse"%> <%@page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%String baseUrl = getServletContext().getInitParameter("BaseUrl");%> <%RestResponse data = (RestResponse)request.getAttribute("data");%> <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title>jQuery File Upload Example</title> <link rel="shortcut icon" href="<%=baseUrl%>/styles/favicon.png" /> <link rel="stylesheet" type="text/css" href="<%=baseUrl%>/external/jquery-ui-1.11.2/jquery-ui.min.css" /> <link rel="stylesheet" type="text/css" href="<%=baseUrl%>/styles/site.css" /> <script src="<%=baseUrl%>/external/jquery/jquery-2.1.3.min.js"></script> <script src="<%=baseUrl%>/external/jquery-ui-1.11.2/jquery-ui.min.js"></script> <script src="<%=baseUrl%>/external/jquery-file-upload-9.8.1/jquery.iframe-transport.js"></script> <script src="<%=baseUrl%>/external/jquery-file-upload-9.8.1/jquery.fileupload.js"></script> <script src="<%=baseUrl%>/scripts/file-processor.js"></script> <script> $(function () { fileProcessor.initiate('<%=baseUrl%>', $('#fileupload'), $('table>tbody')); }); </script> </head> <body> <% RestResponseStatus status = data.status; if (status.code == RestResponseStatusCode.SUCCESS) { @SuppressWarnings("unchecked") List<File> files = (List<File>)data.payload; request.setAttribute("files", files); } %> <div> <input id="fileupload" type="file" name="uploadFile" data-url="<%=baseUrl%>/mvc/api/uploadfile" multiple /> <div style="margin-top: 5px"> <%if (status.code == RestResponseStatusCode.FAIL) { %> <div>Failed to get the list of the files from the database</div> <%} %> <table> <tbody> <%if (status.code == RestResponseStatusCode.SUCCESS) { %> <jsp:include page="files.jsp" /> <%} %> </tbody> </table> </div> </div> </body> </html>
- 为了使用 jQuery 文件上传插件,我们需要添加指向 jQuery、jQuery UI 和 jQuery 文件上传 JavaScript 文件的链接;
- 在我自己的测试中,我发现 "jquery.iframe-transport.js" 在现代浏览器中执行文件上传时实际上不需要。根据 文档,如果浏览器不支持 XHR,则使用它。
已上传文件的列表使用 "files.jsp" 文件在 Web 页面上显示。
<%@page import="java.util.List"%> <%@page import="com.song.model.File"%> <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <% @SuppressWarnings("unchecked") List<File> files = (List<File>)request.getAttribute("files"); %> <%for (File file: files) { %> <tr> <td> <label class="linklabel" onclick="return fileProcessor.downloadfile(<%=file.fileId%>)"> <%=file.name%> </label> </td> <td><%=file.size%></td> <td> <label class="linklabel" onclick="return fileProcessor.deletefile(this, <%=file.fileId%>)"> Delete </label> </td> </tr> <%} %>
执行文件上传的 JavaScript 实现在 "file-processor.js" 文件中。
var fileProcessor = function() {
var context = {
baseurl: null,
file: null,
tbody: null
};
return {
initiate: function(baseurl, $file, $tbody) {
context.baseurl = baseurl;
context.file = $file;
context.tbody = $tbody;
context.file.fileupload({
dataType: 'text',
add: function(e, data) {
var size = parseInt(data.files[0].size);
var name = data.files[0].name;
var $td = $('<td colspan="3"></td>');
var $tr = $('<tr>');
$tr.append($td).prependTo(context.tbody);
// Stop uploading if larger than 1 megabytes
if (size > 1048576) {
var text = name + ' is larger than 1 Megabytes';
$td.html(text);
var $lblCancel = $('<label style="margin-left: 10px" class="linklabel">')
.html('Cancel');
$lblCancel.click(function() {
$(this).closest('tr').remove();
}).appendTo($td);
return;
}
// Upload the file
$td.html('Uploading ' + name + ', ' + size + ' bytes');
data.context = $td;
data.submit();
},
progress: function(e, data) {
// The progress data is only for the upload, not including the
// time to save into the database, so it is not a valid number to show
// data.context.html(parseInt(data.loaded / data.total * 100, 10)
// + '% - completed');
},
fail: function(e, data) {
var name = data.files[0].name;
data.context.html('Failed to upload ' + name);
var $lblCancel = $('<label style="margin-left: 10px" class="linklabel">')
.html('Cancel');
$lblCancel.click(function() {
$(this).closest('tr').remove();
});
data.context.append($lblCancel);
},
done: function(e, data) {
data.context.parent().html($(data.jqXHR.responseText).html());
},
// Global events
progressall: function(e, data) {},
start: function() {},
stop: function() {}
});
},
downloadfile: function(id) {
var url = context.baseurl + '/mvc/api/downloadfile?fileId=' + id;
var hiddenIFrameID = 'hiddenDownloader';
var iframe = document.getElementById(hiddenIFrameID);
if (iframe === null) {
iframe = document.createElement('iframe');
iframe.id = hiddenIFrameID;
iframe.style.display = 'none';
document.body.appendChild(iframe);
}
iframe.contentWindow.location.replace(url);
},
deletefile: function(ctl, id) {
var url = context.baseurl + '/mvc/api/deletefile';
var ajax = $.ajax({
cache: false, type: "POST", data: {fileId: id}, url: url});
ajax.done(function() {
$(ctl).closest('tr').remove();
}).fail(function() {
alert('Error occured while deleting the file');
});
}
};
}();
JDBC 驱动程序
编译应用程序不需要 JDBC 驱动程序,但如果我们要运行应用程序,就需要它。由于我们使用的是 SQL Server,我们可以访问 Microsoft 网站 下载驱动程序。如果您使用 Tomcat 运行 Web 应用程序,可以将下载的 jar 文件放在 Tomcat 的 "lib" 文件夹中。
运行应用程序
您可以执行 "mvn clean install" 来构建应用程序并将其部署到 Web 服务器,或直接在 Eclipse 中运行它。要在 Eclipse 中运行应用程序,您可以参考此 链接。您需要确保 Web 服务器已准备好 JDBC 驱动程序。您还需要检查 "web.xml" 文件以查看 "BaseUrl" 是否已正确配置。当然,您的 SQL Server 需要正在运行,并且您已成功创建了测试数据库。
当应用程序首次启动时,它会在浏览器中显示文件上传按钮。您可以单击它来上传文件。您可以尝试批量上传多个文件。
您可以单击 "Delete" 链接来删除上传的文件。您也可以单击文件名将文件下载到您的浏览器。以下是我上传到服务器并下载回浏览器的金鱼图片。
关注点
- 本文介绍了一个在 Spring MVC 应用程序中使用 JDBC 将文件上传和下载到 SQL Server 的示例;
- JDBC 是一个成熟的主题,但您可能需要不断地重新审视它,如果其他方法失败,JDBC 应该始终有效;
- 该示例使用了 jQuery 文件上传 插件将文件上传到服务器,该插件允许以 Ajax 的方式上传多个文件,而无需重新加载 Web 页面;
- 我希望您喜欢我的文章,希望本文能以某种方式帮助您。
历史
第一次修订 - 2015 年 2 月 3 日。