Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

뇌약간고침

[스프링] 0213 수업 아카이빙 : 게시판 글쓰기 - 첨부파일 업로드 본문

Study/Spring

[스프링] 0213 수업 아카이빙 : 게시판 글쓰기 - 첨부파일 업로드

minulbora 2024. 2. 15. 11:21

먼저 첨부파일 업로드의 개념을 명확하게 짚을 필요가 있다. 

첨부파일을 업로드한다는 것은 업로드한 첨부파일이 DB에 직접 올라가는 것이 아니라, 첨부파일 자체는 특정 디렉토리에 저장이 되고, 차후 필요에 따라 해당 첨부파일을 활용하기 위해 첨부파일의 이름, 파일명, 확장자 등의 정보가 DB에 올라가는 것이다. 

따라서 file 업로드에는 model 객체가 두 개 필요하게 된다. 

-디렉토리에 저장하기 위한 객체
-DB에 업로드하기 위한 객체

전자의 객체는 FileData.java로 다음과 같이 변수를 정의한다. 

private String name;
	// 파일명
	private String fileName;
	// 원본 파일명
	private String fileOrgName;
	// 파일 저장 경로
	private String filePath;
	// 파일 확장자
	private String fileExt;
	// 파일 크기(byte)
	private long fileSize;
	// 컨텐츠 타입
	private String contentType;

 후자의 객체는 HiBoardFile로 다음과 같다. 

private long hiBbsSeq; 		//게시물 번호
	private String userId;		//게시물 작성자 아이디
	private long hiBbsGroup; 	//게시물 그룹 번호
	private int hiBbsOrder;		//게시물 그룹 내 순서
	private int hiBbsIndent;	//게시물 그룹 내 들여쓰기
	private String hiBbsTitle;
	private String hiBbsContent;
	private int hiBbsReadCnt;
	private String regDate;
	private long hiBbsParent;	//부모 게시글의 번호
	private String userName;
	private String userEmail;
	
	private String searchType;	//검색조건(1.이름 2.제목 3.내용)
	private String searchValue;	//검색값
	private long startRow;		//시작 rowNum
	private long endRow;		//끝 rowNum
	
	private HiBoardFile hiBoardFile; //첨부파일

더불어 파일경로는 env.xml 파일에 상수 정의했기 때문에 컨트롤러에서 사용할 때에는 @Value 어노테이션을 활용한다. 

@Value("#{env['upload.save.dir']}")
	private String UPLOAD_SAVE_DIR;

 

1. 우선 글쓰기 페이지에서 첨부파일을 받기 위해서는 다음과 같은 스크립트를 활용한다. 

 <input type="file" id="hiBbsFile" name="hiBbsFile" class="form-control mb-2" placeholder="파일을 선택하세요." required />

2. dao.xml 문에는 resultMap 과 insert 문(파일 등록) select 문 (view페이지에서 보여주기 위함) 을 등록한다. 

<!-- 첨부파일 resultMap -->
<resultMap id="hiBoardFileResultMap" type="com.sist.web.model.HiBoardFile">
	<id column="HIBBS_SEQ" property="hiBbsSeq"/>
	<id column="FILE_SEQ" property="fileSeq"/>
	<result column="FILE_ORG_NAME" property="fileOrgName"/>
	<result column="FILE_NAME" property="fileName"/>
	<result column="FILE_EXT" property="fileExt"/>
	<result column="FILE_SIZE" property="fileSize"/>
	<result column="REG_DATE" property="regDate"/>
</resultMap>
<!-- 게시판 리스트  -->
<!-- 첨부파일 등록 -->
<insert id="boardFileInsert" parameterType="com.sist.web.model.HiBoardFile">
	INSERT INTO TBL_HIBOARD_FILE (
		    HIBBS_SEQ,
		    FILE_SEQ,
		    FILE_ORG_NAME,
		    FILE_NAME,
		    FILE_EXT,
		    FILE_SIZE,
		    REG_DATE) 
	VALUES (#{hiBbsSeq},
		    #{fileSeq},
		    #{fileOrgName},
		    #{fileName},
		    #{fileExt},
		    #{fileSize},
		    SYSDATE)
</insert>
<select id="boardFileSelect" parameterType="long" resultMap="hiBoardFileResultMap">
	SELECT  HIBBS_SEQ,
	        FILE_SEQ,
	        NVL(FILE_ORG_NAME,'')FILE_ORG_NAME,
	        NVL(FILE_NAME,'') FILE_NAME,
	        NVL(FILE_EXT,'') FILE_EXT,
	        NVL(FILE_SIZE,0) FILE_SIZE,
	        NVL(TO_CHAR(REG_DATE,'YYYY.MM.DD HH24:MI:SS'),'') REG_DATE
	   FROM TBL_HIBOARD_FILE
	  WHERE HIBBS_SEQ = #{value}
	    AND FILE_SEQ = 1
</select>

3. 이어 dao.java 파일에도 찾을 수 있게 메서드명과 매개변수, 리턴 타입을 잘 맞춰서 넣어준다. 

//게시물 첨부파일 등록
	public int boardFileInsert (HiBoardFile hiBoardFile);
	
	//첨부파일 가져오기
	public HiBoardFile boardFileSelect (long HiBbsSeq);

4. 서비스에 넣어준다. 

단, 컨트롤에서 서비스의 다른 메서드를 두 번 호출하는 것은 비효율적이므로, 한 서비스에서 file null 여부를 확인해서 담아줌

	//게시물 등록
	@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
	public int boardInsert (HiBoard hiBoard) throws Exception{
		//propagation=Propagation.REQUIRED : 트렌잭션이 있으면 그 트랜잭션에서 실행이 되고, 없으면
		//			현재 상태에서 새로운 트랜잭션을 실행(기본 설정)
		int cnt = 0;
		cnt = hiBoardDao.boardInsert(hiBoard);
		
		if(cnt>0 && hiBoard.getHiBoardFile()!=null) {
//			HiBoardFile hiBoardFile = hiBoard.getHiBoardFile();
//			hiBoardFile.setHiBbsSeq(hiBoard.getHiBbsSeq());
//			hiBoardFile.setFileSeq((short)1);
//			hiBoardDao.boardFileInsert(hiBoardFile);
			//위 로직과 동일한 결과
			hiBoard.getHiBoardFile().setHiBbsSeq(hiBoard.getHiBbsSeq());
			hiBoard.getHiBoardFile().setFileSeq((short)1);
			hiBoardDao.boardFileInsert(hiBoard.getHiBoardFile());
			
		}

		return cnt;
	}
    	//게시물 보기 + 조회수 증가
	public HiBoard boardView (long hiBbsSeq) {
		HiBoard hiBoard = null;
		try {
			hiBoard = hiBoardDao.boardSelect(hiBbsSeq);
			if(hiBoard != null) {
				//조회수 증가
				hiBoardDao.boardReadCntPlus(hiBbsSeq);
				//첨부파일 추가
				HiBoardFile hiBoardFile = hiBoardDao.boardFileSelect(hiBbsSeq);
				if(hiBoardFile != null) {
					hiBoard.setHiBoardFile(hiBoardFile);
				}
			}
		}
		catch(Exception e) {
			logger.error("[HiBoardService] boardView Exception",e);
		}
		return hiBoard;
	}

 위 과정에서 트랜잭션 활용. (@Transaction (propagation=propagation.required)

프로파간다... 프로파게이션... 번식하다.........\

5. 컨트롤(글쓰기)

@RequestMapping (value="/board/writeProc", method=RequestMethod.POST)
	@ResponseBody
	public Response<Object> writeProc (MultipartHttpServletRequest request, HttpServletResponse response) {
		Response<Object> ajaxRes = new Response<Object>();
		String cookieId = CookieUtil.getHexValue(request, AUTH_COOKIE_NAME);
		String hiBbsTitle = HttpUtil.get(request, "hiBbsTitle","");
		String hiBbsContent = HttpUtil.get(request, "hiBbsContent");
        //여기서 이미 지정 경로에 파일 업로드는 된다. 이제 나머지는 db관련 로직
		FileData fileData = HttpUtil.getFile(request, "hiBbsFile", UPLOAD_SAVE_DIR);
		
		//로그인 여부 확인이 계속 빠져 있는 이유 : 인터셉터가 알아서 걸러줌.
		//logger.debug("1111111111111111111111"+hiBbsTitle+","+hiBbsContent);
		if(!StringUtil.isEmpty(hiBbsTitle)&& !StringUtil.isEmpty(hiBbsContent)) {
			HiBoard hiBoard = new HiBoard();
			hiBoard.setUserId(cookieId);
			hiBoard.setHiBbsTitle(hiBbsTitle);
			hiBoard.setHiBbsContent(hiBbsContent);
			//파일이 실제로 있는지 없는지 검사
			if(fileData!=null && fileData.getFileSize()>0) {
				
				HiBoardFile hiBoardFile = new HiBoardFile();
				
				hiBoardFile.setFileName(fileData.getFileName());
				hiBoardFile.setFileOrgName(fileData.getFileOrgName());
				hiBoardFile.setFileExt(fileData.getFileExt());
				hiBoardFile.setFileSize(fileData.getFileSize());
				hiBoard.setHiBoardFile(hiBoardFile);
			}
			try {
				if(hiBoardService.boardInsert(hiBoard)>0) {
					ajaxRes.setResponse(0, "success");
				}
				else {
					ajaxRes.setResponse(500, "internal server error");
				}
			}
			catch(Exception e) {
				logger.error("[HiBoardController] writeProc Exception", e);
				ajaxRes.setResponse(500, "internal server error2");
			}
			
		}
		else {
			ajaxRes.setResponse(400, "Bad Request");
		}
		
		return ajaxRes;
	}

MultipartHttpServletRequest 를 가져다 쓰는 이유는, (근본적으로는) 아래 HttpUtil의 메서드를 활용해야 하는데 아래 메서드에는 멀티 서블렛 리퀘스트를 필요로 하기 때문이고, 더 근본적으로는 멀티 서블렛 리퀘스트는 서블렛에서 스프링이 제공하는 MultipartFile 이라는 인터페이스를 활용해서 문자와 함께 파일들도 같이 보낼 수 있음. 

https://velog.io/@rocknzero/MultipartFile-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C

 

velog

 

velog.io

 

아래는 위에서 활용한 HttpUtil의 getFile 메서드 (오버로딩 되어 있다)

public static FileData getFile(MultipartHttpServletRequest request, String name, String saveDirectory, String newFileName)
	{
		FileData data = null;
		
		logger.debug("name          : " + name);
		logger.debug("saveDirectory : " + saveDirectory);
		logger.debug("newFileName   : " + newFileName);
		
		if(!StringUtil.isEmpty(name) && !StringUtil.isEmpty(saveDirectory))
		{
			MultipartFile file = request.getFile(name);
			
			if(file != null && file.getSize() > 0)
    		{
    			try
    			{
    				if(FileUtil.createDirectory(saveDirectory))
    				{
    					data = new FileData();
        						
                	    data.setName(name);
                	    data.setFileOrgName(file.getOriginalFilename());
                	            
                	    logger.debug("org file name : " + data.getFileOrgName());
                	            
        	            String strFileExt = FileUtil.getFileExtension(data.getFileOrgName());
        	            
        	            if(!StringUtil.isEmpty(newFileName))
        	            {
            	            if(!StringUtil.isEmpty(strFileExt))
            	            {
            	            	newFileName += "." + strFileExt;
            	            	data.setFileExt(strFileExt);
            	            }
        	            }
        	            else
        	            {
        	            	newFileName = FileUtil.uniqueFileName(strFileExt);
        	            	
        	            	if(!StringUtil.isEmpty(strFileExt))
            	            {
            	            	data.setFileExt(strFileExt);
            	            }
        	            }
        	            
        	            data.setFileName(newFileName);
        	            data.setFileSize(file.getSize());
        	            
        	            String strFileFullPath = saveDirectory + FileUtil.getFileSeparator() + data.getFileName();
        	            
        	            if(FileUtil.isFile(strFileFullPath))
        	            {
        	            	logger.debug("delete file : " + strFileFullPath);
        	            	
        	            	FileUtil.deleteFile(strFileFullPath);
        	            }
        	            
        	            logger.debug("new file name : " + data.getFileName());
        	            logger.debug("file ext      : " + data.getFileExt());
        	            logger.debug("file size     : " + data.getFileSize());
        	            	
        				file.transferTo(new File(new File(saveDirectory), data.getFileName()));
        				
        				data.setFilePath(saveDirectory + FileUtil.getFileSeparator() + data.getFileName());
    				}
    				else
    				{
    					logger.error("name          : " + name);
    					logger.error("saveDirectory : " + saveDirectory);
    					logger.error("failed to create directory.");
    				}
    			}
    			catch(IllegalStateException e)
    			{
    				data = null;
    				logger.error("IllegalStateException : " + e.getMessage());
    				
    				e.printStackTrace();
    			}
    			catch(IOException e)
    			{
    				data = null;
    				logger.error("IOException : " + e.getMessage());
    				
    				e.printStackTrace();
    			}
    		}
		}
		else
		{
			logger.error("name          : " + name);
			logger.error("saveDirectory : " + saveDirectory);
		}
				
		return data;
	}

글쓰기업로드를 편리하게 넘기기 위해 (물론 dao는 따로 있음) hiBoard.java 객체에 file 주소를 담아둠 (참조형변수)

FileUtil.java 에서 자체적으로 fileName을 붙여줌... FileUtil을 활용해서 확장자명도 (파일명에서 뒤를 띁어서) 찾아줌

 

 

마지막으로 

첨부파일 다운로드

컨트롤

@RequestMapping (value="/board/download")
	public ModelAndView download (HttpServletRequest request, HttpServletResponse response) {
		ModelAndView modelAndView = null;
		
		long hiBbsSeq = HttpUtil.get(request, "hiBbsSeq", (long)0);
		
		if(hiBbsSeq>0) {
			HiBoardFile hiBoardFile = hiBoardService.boardFileSelect(hiBbsSeq);
			if(hiBoardFile != null) {
				File file = new File(UPLOAD_SAVE_DIR + FileUtil.getFileSeparator()+hiBoardFile.getFileName());
				logger.debug("==============");
				logger.debug("upload file(dir):"+UPLOAD_SAVE_DIR+", fileSeparator:"+FileUtil.getFileSeparator()+",hiBoardFile.getFileName():"+hiBoardFile.getFileName());
				logger.debug("==============");
				
				if(FileUtil.isFile(file)) {
					modelAndView = new ModelAndView();
					//setViewName 에는 클래스 이름을 넣는 것 
					//응답할 view 설정 - servlet-context.xml 에 정의한 fileDownloadView id
					modelAndView.setViewName("fileDownloadView");	
					modelAndView.addObject("file",file);
					modelAndView.addObject("fileName", hiBoardFile.getFileOrgName());
					
					return modelAndView;
				}
			}
		}
		
		return modelAndView;
	}
String com.sist.common.util.FileUtil.getFileSeparator()
Returns: String

: 자바에서 제공하는 File 에 파일 경로를설정하는데에 사용하는 용도. 
운영체제(window 등)에 따라서 구분자 '/' or '\' 을 붙여줌.