[Spring Boot] File Service (게시판에 사진 업로드하기)

yihyun·2024년 9월 11일

Spring Boot

목록 보기
11/33

💾 File Service

Upload & Download

File Upload 를 위해서는 commons-fileupload 와 commons-io 라는 라이브러리가 필요 하다.

하지만 Boot 에서는 기본으로 위 라이브러리를 가지고 있기 때문에 Application.properties 에서 아래 설정만 추가 해 주면 된다.
→ 설정을 안해줄 경우 에러가 발생한다.

1개 파일의 용량 제한 (보통 5MB 정도로 잡는다.)

  • spring.servlet.multipart.max-file-size=50MB

여러 개 파일을 올릴 경우 총 용량 제한

  • spring.servlet.multipart.max-request-size=500MB

저장될 파일 위치

  • spring.servlet.multipart.location=C:/upload
spring.servlet.multipart.max-file-size=50MB
spring.servlet.multipart.max-request-size=500MB
spring.servlet.multipart.location=C:/upload

✨ 파일 등록하기

🔽 1) view

위 설정을 완료했다면 일단 file을 올려주기 위해 html 문서에 file 업로드를 위한 input 태그를 작성해보자

<form action="" method="POST" enctype="multipart/form-data">
  <table>
    <tr>
      <th>이미지</th>
      <td><input type="file" name="files" multiple="multiple"/></td>
    </tr>
  </table>
</form>

multiple : 파일을 복수개 올릴 때 사용한다.
enctype="multipart/form": 파일을 전송시 필수로 설정해야한다.


🔽 2) controller

@RequestMapping(value="/write") 
	public String write(MultipartFile[] files, Model model , @RequestParam Map<String,String> param) {
		logger.info("file coutn : " + files.length);
		boardService.write(param, files);
		return "redirect:/list";
	}

input type="file"multiple 이 설정되어 있다면 배열 형태로 받아줘야 한다.
MultipartFile 을 선언해주고 파라미터의 이름은 input 태그 속 name 과 동일하게 맞춰줘야 한다.

🔽 3) service (nio 사용)

	public void write(Map<String, String> param, MultipartFile[] files) {
	
    	// 3) inset와 동시에 방금 생성한 key값 가져오기
		BoardDTO dto = new BoardDTO();
		dto.setUser_name(param.get("user_name"));
		dto.setSubject(param.get("subject"));
		dto.setContent(param.get("content"));
		
        // 3)
		if(boardDAO.write(dto)>0) {
			int idx = dto.getIdx();
			
            // 1)
			for (MultipartFile file : files) {
				try {
            	    // 2) 파일 이름 지정
					String fileName = file.getOriginalFilename();
					String ext = fileName.substring(fileName.lastIndexOf("."));
                    String newFileName = UUID.randomUUID().toString() + ext;
					
					
                    // 1) 배열로 담아온 값을 추출해 입력한다.
					byte[] arr = file.getBytes();
					Path path = Paths.get("C:/upload/"+newFileName);
					Files.write(path, arr);
					boardDAO.fileWrite(idx,fileName, newFileName);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

1) 배열로 담아온 값을 추출해 입력한다.

  • 배열에 담아온 값을 바이트로 추출 → .getBytes()
  • 저장 경로 지정 → Path, Paths.get()
  • 파일쓰기 → Files.write()
  • (다 썼으면)저장 정보 DB에 insert → DB와 연결된 메서드 실행

2) 파일 이름 지정

  • 기존 파일명 가져오기 → getOriginalFilename()
  • 확장자명 분리하기 → .substring(fileName.lastIndexOf("."))
    substirng() 을 활용해 특정 인덱스 부터 문자열을 출력한다.
    .lastIndexOf(".") : .이 있는 곳의 맨 뒤에 있는 인덱스를 가져온다.
  • 유일한 이름 부여하기 → UUID.randemUUID().toString() + ext(확장자명을 담은 변수)
    ▷ UUID : 유일한 이름을 부여한다.
    .toString() : UUID 는 UUID 객체를 반환하기 때문에 문자로 변환해야 한다.
    ext : UUID 는 확장자 명까지 바꿔버리기 때문에 뒤에 파일의 기존 확장자를 붙여준다.

❌ 파일을 저장할 때 만약 동일한 파일명이 저장될 경우 덮어쓰기가 되므로, 유일한 이름을 부여해줘야 한다. (확장자는 기존 파일명에서 떼와야 한다)

3) inset와 동시에 방금 생성한 key값 가져오기

  • DTO 객체를 생성한다.
  • DTO private 변수에 파라미터로 받아온 값을 넣어 저장해준다. → dto.setUser_name(param.get("user_name"));
  • ※ mybatis 에 설정해준다. → 아래 mapper 설명 참고
  • insert 가 실행된 후 key 값 가져오기 → if 문으로 걸러주고, DTO에 저장된 값을 get으로 가져온다.
    ※ insert 가 실행된 후 DTO에 저장된 값을 사용할 수 있으므로, if문으로 완료 여부를 판단하고, DTO에 저장된 값을 가져온다.

❗ 주의사항

  • 파라미터가 DTO 여야 한다.
  • mybatis 에서 설정을 해줘야 한다.
  • insert 한 후에 DTO에서 꺼내 써야 한다.

🔽 4) DAO

@Mapper
public interface BoardDAO {
	List<BoardDTO> list();
}

→ Mapper 과 연결시켜 줄 추상메서드 생성

🔽 5) mapper

<mapper namespace="kr.co.gudi.dao.BoardDAO">
	<insert id="write" 
		useGeneratedKeys="true"
		keyColumn="idx"
		keyProperty="idx"
	
	parameterType="kr.co.gudi.dto.BoardDTO">
		INSERT INTO bbs(user_name,subject,content)
			VALUES(#{user_name},#{subject},#{content})
	</insert>
  
  	<insert id="filewrite" parameterType="map">
		INSERT INTO files(idx, ori_filename, new_filename)
			VALUES(#{param1}, #{param2}, #{param3})
	</insert>
  	
</mapper>
  • useGeneratedKeys="true" : key generate 설정을사용할건지
  • keyColumn="idx" : 가져올 PK 컬럼 이름
  • keyProperty="idx" : 저장할 DTO 변수 명

📌 generate key
너무 많이 사용하기 때문에 무조건 제공해주는 기능

여기까지 설정을 해주면 이제 파일을 등록하고 그 파일을 DB에 넣어 저장할 수 있다.

✨ 등록된 파일 출력하기

✅ 1) view

<table>
	<c:if test="${files.size() > 0}">
      <tr>
        <th>이미지</th>
        <td>
          <c:forEach items="${files}" var="file">
            <img width="500" alt="${file.ori_filename}" src="/photo/${file.new_filename}"><br/>
          </c:forEach>
      </tr>
  </c:if>
</table>

→ file이 있을 경우에만 보여주기 위해 size 체크
→ 복수개의 파일을 업로드할 수 있기 때문에 c:forEach 사용
→ 파일이 있으면 photo에서 new_filename 경로로 파일을 가져오도록 지정
❌ 현재 서버에는 photo라는 경로가 없기 때문에 server.xml을 추가 설정해줘야 한다.

<Context docBase="C:/upload" path="/photo"/>

✅ 2) controller

@RequestMapping(value="/detail")
public String detail(String idx ,Model model) {
	boardService.detail(idx, model);
	return "detail";
}

→ 보내줘야 하는 값이 글과 이미지 2개 인데 return은 2개를 보낼 수 없기 때문에 service 에게 Model 을 넘겨줘서 service에서 값을 전달할 수 있게 한다.

✅ 3) service

@Transactional
public void detail(String idx, Model model) {
	boardDAO.uphit(idx);
		
	BoardDTO bbs = boardDAO.detail(idx);
	List<FileDTO> files = boardDAO.files(idx);
		
	model.addAttribute("info", bbs);
	model.addAttribute("files", files);
}
  • 하나의 단위로 묶여야 하기 때문에 @Transactional 선언
  • 게시물 조회 시 조회수를 높여주기 위한 .uphit() 메서드 실행
  • 글을 가져오기 위한 .detail() 메서드 실행 → PK 값을 전달해 특정한 글만 볼 수 있도록 진행
    여러 값을 받아올 수 있기 때문에 List로 선언
  • 파일을 가져오기 위한 .files() 메서드 실행 → 특정 글에 해당하는 파일을 가져오기 위해 PK값 전달
  • model로 받아온 값을 view에 전달한다.

✅ 4) DAO

@Mapper
public interface BoardDAO {
	BoardDTO detail(String idx);
    List<FileDTO> files(String idx);
}

✅ 5) mapper

<select id="detail" resultType="kr.co.gudi.dto.BoardDTO">
	SELECT * FROM bbs WHERE idx = #{param1}
</select>
<select id="files" resultType="kr.co.gudi.dto.FileDTO">
	SELECT * FROM files WHERE idx = #{param1}
</select>

<update id="uphit">
	UPDATE bbs set bHit = bHit + 1 where idx=#{param1}
</update>
profile
개발자가 되어보자

0개의 댓글