다중파일 업로드🔎
파일을 여러개 올리기위해서는 설정이 더 필요하다. 이번에는 DB를 연결하여 게시물에 파일을 첨부해서 올리고 그 글을 읽는 기능을 구현한다.
<추가 기능>
1. 게시글 추가
2. 게시글 (목록/상세)읽기
3. 게시글 밑 사진 내의 위치정보로 지도 출력
*이전 글과 이어집니다.
https://steady-record.tistory.com/entry/Spring-Apache-Commons-IO-라이브러리1-file-입출력
파일 추가 생성🔎
패키지명(경로) | 파일명 |
com.test.file.model | |
FileDAO.java | |
FileDAOImpl.java | |
FileDTO.java | |
views/multi | |
list.jsp | |
add.jsp | |
view.jsp | |
root 폴더 | |
script.sql | |
환경 설정🔎
ojdbc6.jar 설치
src - main - webapp - WEB-INF 폴더에 lib 폴더 생성 및 ojdbc6.jar 파일 넣기
프로젝트 우클릭 - Bulid Path - Configure Build Path
ojdbc6.jar 라이브러리 추가
초기 세팅🔎
Script.sql
-- 장소 등록
create table tblPlace (
seq number primary key,
subject varchar2(500) not null,
content varchar2(1000) not null,
regdate date default sysdate not null
);
create table tblPic(
seq number primary key,
filename varchar2(300) not null,
pseq number references tblPlace(seq) not null
);
create sequence seqPlace;
create sequence seqPic;
--게시물의 등록된 첨부파일 갯수 확인용
select a.*, (select count(*) from tblPic where pseq = a.seq) as picCount from tblPlace a order by seq desc;
FileDAO.java
package com.test.file.model;
import java.util.List;
public interface FileDAO {
}
FileDAOImpl.java
package com.test.file.model;
import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class FileDAOImpl implements FileDAO{
@Autowired
private SqlSessionTemplate template;
}
PicDTO.java
package com.test.file.model;
import lombok.Data;
@Data
public class PicDTO {
private String seq;
private String filename;
private String pseq;
}
Place.java
package com.test.file.model;
import java.util.List;
import lombok.Data;
@Data
public class PlaceDTO {
private String seq;
private String subject;
private String content;
private String regdate;
private List<PicDTO> picList;
private int picCount;
}
구현 코드🔎
- 목록보기
FileController.java
@GetMapping(value = "/multi/list.do")
public String list(Model model) {
List<PlaceDTO> list = dao.list();
model.addAttribute("list", list);
return "multi/list";
}
FileDAOImpl.java
@Override
public List<PlaceDTO> list() {
return this.template.selectList("file.list");
}
file.xml
<select id="list" resultType="com.test.file.model.PlaceDTO">
select
a.*,
(select count(*) from tblPic where pseq = a.seq) as picCount
from tblPlace a
order by seq desc
</select>
list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>FileTest</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
table th:nth-child(1) {width: 50px;}
table th:nth-child(2) {width: auto;}
table th:nth-child(3) {width: 140px;}
table th:nth-child(4) {width: 180px;}
tabld td:nth-child(3) span {
float: left;
}
</style>
</head>
<body>
<!-- list.jsp -->
<h1>장소 <small>목록보기</small></h1>
<table>
<tr>
<th>번호</th>
<th>제목</th>
<th>파일</th>
<th>날짜</th>
</tr>
<c:forEach items="${list}" var="dto">
<tr>
<td>${dto.seq}</td>
<td><a href="/file/multi/view.do?seq=${dto.seq}">${dto.subject}</a></td>
<td>
<c:forEach var="i" begin="1" end="${dto.picCount}">
<span class="material-symbols-outlined"> image </span>
</c:forEach>
</td>
<td>${dto.regdate}</td>
</tr>
</c:forEach>
</table>
<div>
<button type="button" class="add" onclick="location.href='/file/multi/add.do';">등록하기</button>
</div>
<script src="https://code.jquery.com/jquery-1.12.4.js" ></script>
</body>
</html>
- 등록하기
FileController.java
//등록하기
@GetMapping(value = "/multi/add.do")
public String multiadd(Model model) {
return "multi/add";
}
//등록 처리하기
@PostMapping(value = "/multi/addok.do")
public String multiaddok(Model model, PlaceDTO dto, MultipartFile[] attach, HttpServletRequest req) {
dto.setPicList(new ArrayList<PicDTO>()); //첨부 파일 배열 추가
for (MultipartFile file : attach) {
try {
UUID uuid = UUID.randomUUID();
String filename = uuid + "_" + file.getOriginalFilename();
file.transferTo(new File(req.getRealPath("/resources/files") + "\\" + filename));
//첨부파일 1개당 PicDTO 1개 생성
PicDTO pdto = new PicDTO();
pdto.setFilename(filename);
dto.getPicList().add(pdto);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(req.getRealPath("/resources/files")); //등록된 폴더 경로 확인용
int result = dao.add(dto);
if(result > 0) {
return "redirect:/multi/list.do";
} else {
return "redirect:/multi/add.do";
}
}
등록 처리 시, 파일을 여러개 담아야할 때는 MultipartFile를 배열로 선언한다.
FileDAOImpl.java
@Override
public int add(PlaceDTO dto) {
//게시물 등록하기
int result = this.template.insert("file.add", dto);
//방금 등록한 게시물 번호 가져오기
int seq = this.template.selectOne("file.seq");
//첨부 파일 등록하기
for (PicDTO pdto: dto.getPicList()) {
pdto.setPseq(seq+"");
result += this.template.insert("file.picadd", pdto);
}
return result;
}
file.xml
<insert id="add" parameterType="com.test.file.model.PlaceDTO">
insert into tblPlace (seq, subject, content, regdate)
values (seqPlace.nextVal, #{subject}, #{content}, default)
</insert>
<select id="seq" resultType="Integer">
select max(seq) from tblPlace
</select>
<insert id="picadd" parameterType="com.test.file.model.PicDTO">
insert into tblPic (seq, filename, pseq)
values (seqPic.nextVal, #{filename}, #{pseq})
</insert>
📢첨부 파일 등록하는 다양한 스타일 중 파일선택과 드래그앤드롭을 구현해본다.
- 스타일1_파일선택
add.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>FileTest</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
</head>
<body>
<!-- add.jsp -->
<h1>장소 <small>등록하기</small></h1>
<form method="POST" action="/file/multi/addok.do" enctype="multipart/form-data">
<table class="vertical">
<tr>
<th>제목</th>
<td><input type="text" name="subject" required class="full"></td>
</tr>
<tr>
<th>내용</th>
<td><textarea name="content" required class="full"></textarea></td>
</tr>
<tr>
<th>파일</th>
<td>
<input type="file" name="attach" multiple>
<div id="attach-zone"></div>
</td>
</tr>
</table>
<div>
<button type="button" class="back" onclick = "location.href='/file/multi/list.do';">돌아가기</button>
<button type="submit" class="add">등록하기</button>
</div>
</form>
<script src="https://code.jquery.com/jquery-1.12.4.js" ></script>
<script>
</script>
</body>
</html>
- 스타일2 _Drag&Drop
add.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>FileTest</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
#attach-zone {
border: 1px solid #ccc;
background-color: #efefef;
width: 576px;
height: 150px;
overflow: auto;
padding: 0.5rem;
}
#attach-zone .item {
display : flex;
justify-content: space-between;
font-size: 14px;
margin: 5px 10px;
}
input[name=attach] {
display: none;
}
</style>
</head>
<body>
<!-- add.jsp -->
<h1>장소 <small>등록하기</small></h1>
<form method="POST" action="/file/multi/addok.do" enctype="multipart/form-data">
<table class="vertical">
<tr>
<th>제목</th>
<td><input type="text" name="subject" required class="full"></td>
</tr>
<tr>
<th>내용</th>
<td><textarea name="content" required class="full"></textarea></td>
</tr>
<tr>
<th>파일</th>
<td>
<input type="file" name="attach" multiple>
<div id="attach-zone"></div>
</td>
</tr>
</table>
<div>
<button type="button" class="back" onclick = "location.href='/file/multi/list.do';">돌아가기</button>
<button type="submit" class="add">등록하기</button>
</div>
</form>
<script src="https://code.jquery.com/jquery-1.12.4.js" ></script>
<script>
$('#attach-zone')
.on('dragenter', function(e) {
e.preventDefault();
e.stopPropagation();
})
.on('dragover', function(e){
e.preventDefault();
e.stopPropagation();
})
.on('dragleave', function(e) {
e.preventDefault();
e.stopPropagation();
})
.on('drop', function(e) {
e.preventDefault();
e.stopPropagation();
//마지막에 추가한 파일만 기억하므로 추가 첨삭을 하기 위해 누적을 해야한다. 하지만 이 작업이 매우 번거로우니, 추가 첨삭 시 이전 내용은 초기화한다.
const files = e.originalEvent.dataTransfer.files;
$(this).html('');
if (files != null && files != undefined) {
let temp = '';
for (let i=0; i<files.length; i++) {
//console.log(files[i].name);
let filename = files[i].name;
let filesize = files[i].size / 1024/ 1024; //MB로 변환
filesize = filesize < 1? filesize.toFixed(2) : filesize.toFixed(2);
temp += `
<div class="item">
<span>\${filename}</span>
<span>\${filesize}MB</span>
</div>
`;
}
$(this).append(temp);
//반드시 input type="file" 로 파일을 전달해야함. 그래서 파일선택으로 선택과 동일하게 만들어준다.
$('input[name=attach]').prop('files', files);
}
})
</script>
</body>
</html>
file 태그의 multiple 속성은 선택한 파일갯수만큼 태그가 생기는 것과 같다.
- 상세보기 (with metadata-extractor, xmpcore)
의존성 추가 - pom.xml
사진 속 메타데이터를 얻기 위해서는 metadata-extractor, xmpcore 2개의 라이브러리 설치가 필요하다.
<!-- https://mvnrepository.com/artifact/com.drewnoakes/metadata-extractor -->
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.adobe.xmp/xmpcore -->
<dependency>
<groupId>com.adobe.xmp</groupId>
<artifactId>xmpcore</artifactId>
<version>5.1.2</version>
</dependency>
FileController.java
//상세보기
@GetMapping(value = "/multi/view.do")
public String view(Model model, String seq, HttpServletRequest req) {
PlaceDTO dto = dao.get(seq);
PicDTO pdto = dto.getPicList().get(0);
if (pdto != null) {
//사진 파일 접근
File file = new File(req.getRealPath("/resources/files/" + pdto.getFilename()));
try {
Metadata metadata = ImageMetadataReader.readMetadata(file);
GpsDirectory gps = metadata.getFirstDirectoryOfType(GpsDirectory.class);
if (gps.containsTag(GpsDirectory.TAG_LATITUDE) && gps.containsTag(GpsDirectory.TAG_LONGITUDE)) {
double lat = gps.getGeoLocation().getLatitude();
double lng = gps.getGeoLocation().getLongitude();
System.out.println(lat);
System.out.println(lng);
model.addAttribute("lat", lat);
model.addAttribute("lng", lng);
}
} catch (Exception e) {
e.printStackTrace();
}
}
model.addAttribute("dto", dto);
return "multi/view";
}
GpsDirectory는 메타 정보안에서 여러 정보(위도, 경도, 조리개값 등) 중에서 위치 정보를 담는 객체이다.
FileDAOImpl.java
@Override
public PlaceDTO get(String seq) {
PlaceDTO dto = this.template.selectOne("file.get", seq);
List<PicDTO> plist = this.template.selectList("file.plist", seq);
dto.setPicList(plist);
return dto;
}
file.xml
<select id="get" parameterType="String" resultType="com.test.file.model.PlaceDTO">
select * from tblPlace where seq = #{seq}
</select>
<select id="plist" parameterType="String" resultType="com.test.file.model.PicDTO">
select * from tblPic where pseq = #{seq}
</select>
view.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>FileTest</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
table th {width: 150px;}
table tr:nth-child(1) td:nth-child(2) {width: 150px;}
table tr:nth-child(3) td {height: 100px;}
table tr:nth-child(4) img {
display: block;
border: 1px solid #ccc;
border-radius: 3px;
padding: 5px;
margin: 15px auto;
max-width: 740px;
}
#map {
width: 700px;
height: 500px;
margin: 15px auto;
border: 1px solid #ccc;
border-radius: 3px;
padding: 5px;
}
</style>
</head>
<body>
<!-- view.jsp -->
<h1>장소 <small>상세보기</small></h1>
<table>
<tr>
<th>번호</th>
<td>${dto.seq}</td>
<th>날짜</th>
<td>${dto.regdate}</td>
</tr>
<tr>
<th>제목</th>
<td colspan="3">${dto.subject}</td>
</tr>
<tr>
<th>내용</th>
<td colspan="3"><c:out value="${dto.content}"/></td>
</tr>
<tr>
<td colspan="4">
<c:forEach items="${dto.picList}" var ="pdto">
<img src="/file/resources/files/${pdto.filename}">
</c:forEach>
</td>
</tr>
<c:if test="${not empty lat}">
<tr>
<td colspan="4">
<div id="map"></div>
</td>
</tr>
</c:if>
</table>
<div>
<button type="button" class="back" onclick = "location.href='/file/multi/list.do';">돌아가기</button>
</div>
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=65374924d90b016c441e902bee01e0"></script>
<script src="https://code.jquery.com/jquery-1.12.4.js" ></script>
<script>
<c:if test="${not empty lat}">
const container = document.getElementById('map');
const options = {
center: new kakao.maps.LatLng(${lat}, ${lng}),
level: 3
};
const map = new kakao.maps.Map(container, options);
const marker = new kakao.maps.Marker({
position: new kakao.maps.LatLng(${lat}, ${lng})
})
marker.setMap(map);
</c:if>
</script>
</body>
</html>
'Spring' 카테고리의 다른 글
[Spring] JUnit를 이용한 단위테스트(JDBC, HikariCP, MyBatis) (0) | 2023.11.30 |
---|---|
[Spring] Spring AOP : @Aspect 사용하기 (0) | 2023.11.29 |
[Spring] Apache Commons IO 라이브러리(1) : file 입출력 (0) | 2023.11.29 |
[Spring] Tiles 프레임워크 : 레이아웃 프레임워크 (0) | 2023.11.29 |
[Spring] Spring MVC 프로젝트에 MyBatis 적용하기 : 코드 조각 관리 게시판 (0) | 2023.11.29 |