Spring Framework

업데이트:


Attachment Model

image

패키지, 파일 생성

image
image

게시글 등록(+ 파일 업로드)

image

  • 글쓰기 클릭 시 요청 주소
  • boardList.jsp
<a class="btn btn-success float-right" href="../${pInfo.boardType}/insert">글쓰기</a>


  • BoardController
// 게시글 등록 화면 전환용 Controller
@RequestMapping("{type}/insert")
public String insertView(@PathVariable("type") int type) {
	return "board/boardInsert" + type;
	// boardInsert1.jsp == 기존 방식
	// boardInsert2.jsp == summernote 적용 방식
}


image

  • 등록버튼 클릭 시 요청 주소
  • boardInsert.jsp
<form action="insertAction" method="post" enctype="multipart/form-data" role="form" onsubmit="return validate();">
  • Spring에서는 파일 업로드를 위한 MultipartResolver라는 인터페이스를 제공하고 있음.
  • 실제 파일 업로드를 하기 위해서는 MultipartResolver의 구현체가 필요함.

MultipartResolver 구현체를 생성하는 방법 두 가지

  • Servlet 3.0 이상 버전에서 MultipartRequest를 사용해 StandardServletMultipartResolver를 생성
  • Apache Commons FileUpload 라이브러리를 이용해 CommonsMultipartResolver를 구현

스프링에서 라이브러리 추가하는 방법

  • Maven을 이용하여 commons-fileupload 라이브러리 추가 -> mavenrepository에서 검색 후 pom.xml에 추가
  • root-context.xml 파일에 CommonsMultipartResolver를 bean으로 등록하는 코드 추가


  • pom.xml
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
	<groupId>commons-fileupload</groupId>
	<artifactId>commons-fileupload</artifactId>
	<version>1.3.3</version>
</dependency>


  • root-context.xml
<?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:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
	
    <!-- 스프링에서 사용하는 proxy를 이용한 트랜잭션 제어가 안될 경우 추가적인 트랜잭션 매니저를 추가해서 문제 해결 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<tx:annotation-driven proxy-target-class="true"/>
		
    
    <!-- 파일 업로드를 위한 CommonsMultipartResolver 객체 bean 등록 -->
    <!-- MultipartResolver를 bean으로 등록하면 multipart/form-data 형식의 요청을 받게될 경우
    		 스프링 컨테이너가 해당 bean을 제어하여 input type="file" 태그를 별도로 얻어와서 처리하여
    		 MultipartFile 객체로 반환해줌.
    		 
    		 - input type="file"에 파일 업로드 여부와 상관없이 무조건 얻어옴
    		 - 태그 하나 또는 업로드된 파일 하나 당 MultipartFile 객체 하나가 반환됨.
    		 
    		 - MultipartResolver가 파일 관련된 내용(multipart/form-data)를 다 처리하므로
    		 	  나머지 일반 텍스트 형태의 데이터를 별도의 처리 없이 사용 가능함.
     -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    	<property name="maxUploadSize" value="104857600"/>
    	<property name="maxUploadSizePerFile" value="20971520"/>
    	<property name="maxInMemorySize" value="104857600"/>
    </bean>
    <!-- 
    	maxUploadSize : 한 요청당 업로드가 허용되는 최대 용량(바이트 단위)
    	maxUploadSizePerFile : 한 파일당 업로드가 허용되는 최대 용량(바이트 단위)
    	maxInMemorySize : 파일을 디스크에 저장하지 않고 메모리에 유지하도록 허용하는 최대 용량(바이트 단위)
    										지정된 용량보다 업로드 파일이 더 큰 경우 파일로 저장함.
    										(기본값 10240byte)
			
			8bit == 1byte
			1024byte == 1KB
			1024KB == 1MB
			104857600 byte == 100MB
			20971520 byte == 20MB
     -->
</beans>


  • BoardController
// 게시글 등록(+ 파일 업로드) Controller
@RequestMapping("{type}/insertAction")
public String insertAction(@PathVariable("type") int type,
			@ModelAttribute Board board,
			@ModelAttribute("loginMember") Member loginMember,
			@RequestParam(value="images", required = false) List<MultipartFile> images,
			HttpServletRequest request,
			RedirectAttributes ra) {
	
	Map<String, Object> map = new HashMap<String, Object>();
	map.put("memberNo", loginMember.getMemberNo());
	map.put("boardTitle", board.getBoardTitle());
	map.put("boardContent", board.getBoardContent());
	map.put("categoryCode", board.getCategoryNm());
	map.put("boardType", type);
	
	String savePath = request.getSession().getServletContext().getRealPath("resources/uploadImages");
	
	int result = service.insertBoard(map, images, savePath);
	
	String url = null;
	
	if(result > 0){
		swalIcon = "success";
		swalTitle = "게시글 등록 성공";
		url = "redirect:" + result;
	}else {
		swalIcon = "error";
		swalTitle = "";
		url = "redirect:insert";
	}
	
	ra.addFlashAttribute("swalIcon", swalIcon);
	ra.addFlashAttribute("swalTitle", swalTitle);
	
	return url;
}


  • boardService
/** 게시글 등록(+ 파일 업로드) Service
* @param map
* @param savePath 
* @param images 
* @return
*/
public abstract int insertBoard(Map<String, Object> map, List<MultipartFile> images, String savePath);


  • boardServiceImpl
// 게시글 삽입 Service 구현
@Transactional(rollbackFor = Exception.class)
@Override
public int insertBoard(Map<String, Object> map, List<MultipartFile> images, String savePath) {
	int result = 0; // 최종 결과 저장 변수 선언
	
	// 1) 게시글 번호 얻어오기 -> SEQ_BNO.NEXTVAL를 통해 얻어옴
	int boardNo = dao.selectNextNo();
	
	// 2) 게시글 삽입
	if(boardNo > 0) { // 다음 게시글 번호를 얻어온 경우
		
		map.put("boardNo", boardNo); // map에 boardNo 추가
		
		// 크로스 사이트 스크립팅 방지 처리
		// 추후 summernote api 사용을 염두하여 
		// 게시판 타입별로 크로스 사이트 스크립팅 방지 처리를 선택적으로 진행
		if( (int)map.get("boardType") == 1) { // 자유게시판인 경우
			String boardTitle = (String)map.get("boardTitle");
			String boardContent = (String)map.get("boardContent");
			
			// 크로스 사이트 스크립팅 방지 처리 적용
			boardTitle = replaceParameter(boardTitle);
			boardContent = replaceParameter(boardContent);
			
			// 처리된 문자열을 다시 map에 세팅
			map.put("boardTitle", boardTitle);
			map.put("boardContent", boardContent);
			
			// 개행문자 처리 -> 화면에서 JSTL을 이용해서 처리할 예정
		}
		
		// 게시글 삽입 DAO 메소드 호출
		result = dao.insertBoard(map);
		
		// 3) 게시글 삽입 성공 시 이미지 정보 삽입
		if(result > 0) {
			
			// 이미지 정보를 Attachment객체에 저장 후 List에 추가
			List<Attachment> uploadImages = new ArrayList<Attachment>();
			
			// DB에 저장할 웹상 접근 주소(filePath)
			String filePath = "/resources/uploadImages";
			
			// for문을 이용하여 파일 정보가 담긴 images를 반복접근
			//	-> 업로드된 파일이 있을 경우에만 uploadImages 리스트에 추가
			for(int i=0; i<images.size(); i++) {
				// i == 인덱스 == fileLevel과 같은 값
				
				// 현재 접근한 images의 요소(MultipartFile)에 업로드된 파일이 있는지 확인
				if( !images.get(i).getOriginalFilename().equals("") ) {
					// 파일이 업로드 된 경우 == 업로드 된 원본 파일명이 있는 경우
					
					// 원본 파일명 변경
					String fileName = rename(images.get(i).getOriginalFilename());
					
					// Attachment 객체 생성
					Attachment at = new Attachment(filePath, fileName, i, boardNo);
					
					uploadImages.add(at); // 리스트에 추가
				}
			}
			
			if(!uploadImages.isEmpty()) { // 업로드 된 이미지가 있을 경우
				// 파일 정보 삽입 DAO 호출
				result = dao.insertAttachmentList(uploadImages);
				// result == 삽입된 행의 개수
				
				// 모든 데이터가 정상 삽입 되었을 경우 --> 서버에 파일 저장
				if(result == uploadImages.size()) {
					result = boardNo; // result에 boardNo 저장
					
					// MultipartFile.transferTo 
					//	-> MultipartFile 객체에 저장된 파일을
					//	       지정된 경로에 실제 파일의 형태로 변환하여 저장하는 메소드
					for(int i=0; i<uploadImages.size(); i++) {
						
						try {
							images.get(uploadImages.get(i).getFileLevel())
							.transferTo(new File(savePath + "/" + uploadImages.get(i).getFileName()));                
							
						}catch (Exception e) {
							e.printStackTrace();
							
							throw new InsertAttachmentFailException("파일 서버 저장 실패");
						}
					}
						
				}else { // 파일 정보를 DB에 삽입하는데 실패했을 때
					throw new InsertAttachmentFailException("파일 정보 DB 삽입 실패");
				}
			}
		}
	}
	return result;
}

// 크로스 사이트 스크립트 방지 처리 메소드
private String replaceParameter(String param) {
	String result = param;
	if(param != null) {
		result = result.replaceAll("&", "&amp;");
		result = result.replaceAll("<", "&lt;");
		result = result.replaceAll(">", "&gt;");
		result = result.replaceAll("\"", "&quot;");
	}
	
	return result;
}

// 파일명 변경 메소드
public String rename(String originFileName) {
	SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
	String date = sdf.format(new java.util.Date(System.currentTimeMillis()));
	
	int ranNum = (int)(Math.random()*100000); // 5자리 랜덤 숫자 생성
	
	String str = "_" + String.format("%05d", ranNum);
	
	String ext = originFileName.substring(originFileName.lastIndexOf("."));
	
	return date + str + ext;
}


  • BoardDAO
/** 게시글 삽입 DAO
* @param map
* @return result
*/
public int insertBoard(Map<String, Object> map) {
	return sqlSession.insert("boardMapper.insertBoard", map);
}

/** 파일 정보 삽입 DAO
* @param uploadImages
* @return result(성공한 행의 개수)
*/
public int insertAttachmentList(List<Attachment> uploadImages) {
	return sqlSession.insert("boardMapper.insertAttachmentList", uploadImages);
}


  • board-mapper.xml
<!-- 파일 정보 삽입(List 이용) -->
<insert id="insertAttachmentList" parameterType="list">
	INSERT INTO ATTACHMENT
	SELECT SEQ_FNO.NEXTVAL, A.* FROM (
	
	<foreach collection="list" item="item" separator="UNION ALL">
		SELECT #{item.filePath} FILE_PATH,
				#{item.fileName} FILE_NAME,
				#{item.fileLevel} FILE_LEVEL,
				#{item.parentBoardNo} PARENT_BOARD_NO
		FROM DUAL
	</foreach>
	)A
</insert>

태그: , ,

카테고리:

업데이트:

댓글남기기