<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1073741824" /> <!-- 1GB -->
</bean>
<form action="addAction.do" method="post" enctype="multipart/form-data">
<input type="file" name="studentPhoto" />
<input type="submit" value="上傳" />
</form>
關鍵點:enctype="multipart/form-data",input name 需與 Form 對象屬性名一致。
StudentForm)public class StudentForm extends GeneralForm {
private String studentName;
private int studentSex;
private String studentBirthday;
private MultipartFile studentPhoto; // 對應文件上傳欄位
// getter/setter 省略
}
CREATE TABLE PHOTO_TBL (
PHOTO_ID VARCHAR(100) PRIMARY KEY,
PHOTO_DATA LONGBLOB, -- 存儲文件二進制數據
FILE_NAME VARCHAR(100) -- 原始文件名
);
PhotoEntity)public class PhotoEntity {
private String photoId;
private byte[] photoData;
private String fileName;
// getter/setter
}
PhotoMapper)@Repository
@Transactional
public interface PhotoMapper {
void createPhoto(PhotoEntity entity);
int deletePhotoByPhotoId(String photoId);
int updatePhotoData(@Param("photoId") String photoId, @Param("photoData") byte[] photoData);
PhotoEntity getPhotoEntityByPhotoId(String photoId);
}
PhotoMapper.xml)<resultMap type="PhotoEntity" id="photoResultMap">
<id property="photoId" column="PHOTO_ID" jdbcType="VARCHAR" />
<result property="photoData" column="PHOTO_DATA"
javaType="byte[]" jdbcType="BLOB"
typeHandler="org.apache.ibatis.type.BlobTypeHandler" />
<result property="fileName" column="FILE_NAME" jdbcType="VARCHAR" />
</resultMap>
<insert id="createPhoto" parameterType="PhotoEntity">
<selectKey keyProperty="photoId" resultType="String" order="BEFORE">
SELECT UUID() -- 或自定義序列生成策略
</selectKey>
INSERT INTO PHOTO_TBL (PHOTO_ID, PHOTO_DATA, FILE_NAME)
VALUES (
#{photoId, jdbcType=VARCHAR},
#{photoData, javaType=byte[], jdbcType=BLOB, typeHandler=org.apache.ibatis.type.BlobTypeHandler},
#{fileName, jdbcType=VARCHAR}
)
</insert>
<select id="getPhotoEntityByPhotoId" resultMap="photoResultMap">
SELECT PHOTO_ID, PHOTO_DATA, FILE_NAME FROM PHOTO_TBL WHERE PHOTO_ID = #{photoId, jdbcType=VARCHAR}
</select>
關鍵點:BLOB 類型必須指定 typeHandler="org.apache.ibatis.type.BlobTypeHandler",否則讀寫異常。
@RequestMapping("addAction.do")
public String uploadToServer(StudentForm form) {
MultipartFile file = form.getStudentPhoto();
String filename = file.getOriginalFilename();
File target = new File("d:/temp_upload_file/", filename);
// 衝突處理:刪除同名舊文件
if (target.exists()) target.delete();
try (InputStream is = file.getInputStream();
FileOutputStream fos = new FileOutputStream(target)) {
byte[] buffer = new byte[8192];
int count;
while ((count = is.read(buffer)) > 0) {
fos.write(buffer, 0, count);
}
} catch (IOException e) { e.printStackTrace(); }
return "success";
}
@RequestMapping("addAction.do")
public String uploadToDb(StudentForm form) {
MultipartFile file = form.getStudentPhoto();
byte[] data = file.getBytes(); // 簡化讀取
PhotoEntity entity = new PhotoEntity();
entity.setPhotoData(data);
entity.setFileName(file.getOriginalFilename());
photoMapper.createPhoto(entity);
return "success";
}
@RequestMapping("downPhotoById")
public void download(String id, HttpServletResponse response) {
PhotoEntity entity = photoMapper.getPhotoEntityByPhotoId(id);
byte[] data = entity.getPhotoData();
String filename = entity.getFileName() != null ? entity.getFileName() : "photo.png";
response.reset();
response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(filename, "UTF-8") + "\"");
response.addHeader("Content-Length", String.valueOf(data.length));
response.setContentType("application/octet-stream;charset=UTF-8");
try (OutputStream os = new BufferedOutputStream(response.getOutputStream())) {
os.write(data);
os.flush();
} catch (IOException e) { e.printStackTrace(); }
}
前端調用:<a href="downPhotoById.do?id=8000001">下載照片</a>
@RequestMapping("getPhotoById")
public void showImage(String id, HttpServletResponse response) {
PhotoEntity entity = photoMapper.getPhotoEntityByPhotoId(id);
byte[] data = entity.getPhotoData();
response.setContentType("image/jpeg");
response.setCharacterEncoding("UTF-8");
try (InputStream in = new ByteArrayInputStream(data);
OutputStream out = response.getOutputStream()) {
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
} catch (IOException e) { e.printStackTrace(); }
}
前端調用:<img src="getPhotoById.do?id=8000001" />
@RequestMapping("getPhotoById")
public void showScaledImage(String id, int width, int height, HttpServletResponse response) {
PhotoEntity entity = photoMapper.getPhotoEntityByPhotoId(id);
byte[] data = entity.getPhotoData();
if (width > 0 && height > 0) {
data = scaleImage(data, width, height);
}
// 輸出邏輯同 showImage...
}
public static byte[] scaleImage(byte[] data, int targetW, int targetH) throws IOException {
BufferedImage src = ImageIO.read(new ByteArrayInputStream(data));
double scale = Math.min((double) targetW / src.getWidth(), (double) targetH / src.getHeight());
int newW = (int) (src.getWidth() * scale);
int newH = (int) (src.getHeight() * scale);
BufferedImage dest = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_RGB);
dest.getGraphics().drawImage(src.getScaledInstance(newW, newH, Image.SCALE_SMOOTH), 0, 0, null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(dest, "jpeg", baos);
return baos.toByteArray();
}
前端調用:<img src="getPhotoById.do?id=8000001&width=300&height=300" />
| 存儲方式 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 數據庫 BLOB | 事務一致性強、備份簡單、權限隨數據庫 | 數據庫膨脹快、讀寫性能較差、併發壓力大 | 小文件、強一致性要求、文檔管理系統 |
| 服務器磁盤 | 性能高、不佔用數據庫資源、易於 CDN 分發 | 事務管理複雜、備份需單獨處理、多節點同步難 | 圖片/視頻等大文件、高併發讀取、靜態資源 |
| 對象存儲 (OSS/S3) | 無限擴展、高可用、內置 CDN、生命周期管理 | 引入外部依賴、成本較高、延遲略高 | 生產環境推薦、海量文件、分佈式架構 |
本文完整演示了 SpringMVC + MyBatis 文件上傳下載全流程,涵蓋磁盤存儲、數據庫 BLOB 存儲、圖片流式輸出、等比縮放等實戰技巧。生產環境建議採用對象存儲 + 數據庫存元數據架構,利用簽名直傳減輕應用服務器壓力,MyBatis 僅負責元數據 CRUD,實現職責分離與高性能。