본문 바로가기

프로그래밍/Spring

MVC - 게시판(페이징+검색, 파일업로드) 한번에 총정리




Spring MVC - 게시판



조회, 등록, 수정, 삭제 + 검색, 페이징, 파일업로드




pom.xml & context.xml 설정


파일 업로드를 위하여 pom.xml에 업로드관련 추가


		    commons-io
		    commons-io
		    2.0.1
		
	
		
		    commons-fileupload
		    commons-fileupload
		    1.3.2
		


	
		
		
		
	


DAO에 사용될 트랜잭션 관련 context 추가

	
	
		
	

	
	


VO

public class NoticeVO {
	
	private int noticeNum;
	private String title;
	private String content;
	private String writer;
	private String regDate;

        /*getters & setters*/
}

public class NoticeFileVO {
	
	private Integer fileNum;
	private Integer noticePK;
	private String fileName;
	private String realName;
	private long size;
	
	/**
     * 파일 크기를 정형화하기.
     */
    public String size2String() {
        Integer unit = 1024;
        if (size < unit) {
            return String.format("(%d B)", size);
        }
        int exp = (int) (Math.log(size) / Math.log(unit));

        return String.format("(%.0f %s)", size / Math.pow(unit, exp), "KMGTPE".charAt(exp - 1));
    }
        /*getters & setters*/
}


페이징을 위한 도메인

public class Criteria {
	
	private Integer page;
	private Integer perPageNum;
	
	// - 생성자
	// page와 perPageNum을 default로 각각 1, 10으로 정해준다
	public Criteria() {
	
		this.page = 1;
		this.perPageNum = 15;
	}

	public Integer getPage() {
		return page;
	}

	public void setPage(Integer page) {
		// page를 1보다 작은 수로 입력되면 1로 고정
		this.page = (page < 1) ? 1 :  page; 
	}

	public Integer getPerPageNum() {
		return perPageNum;
	}

	public void setPerPageNum(Integer perPageNum) {
		// perPageNum을 10 미만 또는 100이상일 경우 10으로 고정
		this.perPageNum = (perPageNum <15 || perPageNum >=100) ? 15 : perPageNum;
	}
	
	//MyBatis
	public int getPageStart(){

		return (this.page-1) * 15 ;
	}
}

public class PageMaker {
	
	private Criteria cri; // page, perPageNum
	
	private Integer totalCount;	// 총 게시물 수
	
	private Integer startPage;	// 시작 페이지
	private Integer endPage;	// 끝 페이지
	private boolean prev;		// 이 전
	private boolean next;		// 이 후
	
	private Integer displayPageNum = 15; // 페이지네이션 보여줄 갯 수 15개로 고정
	
	// 리스트 페이지에서 단일 게시글 클릭하면 페이징,
	// 검색 정보를 가지고 URI문자열을 만들어서 조회페이지로 이동한다
	// 그러면 조회페이지에서 페이징, 검색정보를 유지하고 있기 때문에 다시 리스트 페이지로 이동할 때 원래 page와 검색 조건의 리스트
	public String makeSearch(Integer page){
			
		UriComponents uriComponents = UriComponentsBuilder.newInstance()
			.queryParam("page", page)
			.queryParam("perPageNum", cri.getPerPageNum())
			.queryParam("searchType", ((SearchCriteria)cri).getSearchType())
			.queryParam("keyword", ((SearchCriteria)cri).getKeyword())
			.build();			
		return uriComponents.toUriString();
	}// makeSearch()
	
	// 리스트페이지에서 단일 게시글 클릭하면 해당 page정보를 가지고
	// page, perPageNum 파라미터를 포함한 URI 문자열을 만들어서 조회페이지로 이동한다 
	// 그러면 조회페이지에서 page, perPageNum, bno 값을 유지하고 있기 때문에 다시 리스트 페이지로 이동할때 원래 page로 이동
	public String makeQuery(Integer page){
			
		UriComponents uriComponents = 
			UriComponentsBuilder.newInstance()
			.queryParam("page", page)
			.queryParam("perPageNum", cri.getPerPageNum())
			.build();
			
		return uriComponents.toUriString();
	}// makeQuery()	

	public Integer getTotalCount() {
		return totalCount;
	}

	// 총 게시물 수를 setting해주면 바로 다른 멤버변수들을 계산할 수 있기 때문에 이 메소드 안에서 calcData()를 호출한다.
	public void setTotalCount(Integer totalCount) {
		this.totalCount = totalCount;
		
		// 여기서 다른 계산 다 해주면 될듯
		calcData();
	}

	private void calcData(){		
		// 끝 페이지
		endPage = (int)Math.ceil((double)cri.getPage()/displayPageNum) * displayPageNum;
		// 시작 페이지
		startPage = (endPage - displayPageNum) + 1;		
		int tempEndPage = (int)Math.ceil((double)totalCount/displayPageNum);		
		// 끝 페이지 검증
		if( endPage > tempEndPage ){
			endPage = tempEndPage;
		}// if()		
		// 이전
		prev = (startPage == 1 ? false : true);
		// 이후
		next = (tempEndPage > endPage ? true : false); 		
	}// calcData()
        /*getters & setters*/
}


검색기능을 위한 도메인

public class SearchCriteria extends Criteria {
	
	private String searchType;	// 검색 타입
	private String keyword;	// 검색 키워드

        /*getters & setters*/
}



DAO

@Repository
public class NoticeDAOImpl implements NoticeDAO {
	
	@Inject
	SqlSession sqlsession;	
// 트랜잭션 활용
	@Autowired
    private DataSourceTransactionManager txManager;
	
	private static String namespace = "com.mapper.notice";	
// 등록
	@Override
	public void createNotice(NoticeVO noticeVO, List filelist, String[] fileNum) throws Exception {
		DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus status = txManager.getTransaction(def);        
        try {
        	sqlsession.insert(namespace+".insertNotice", noticeVO);
            
            if (fileNum != null) {
                HashMap fileVO = new HashMap();
                fileVO.put("fileNum", fileNum);
                sqlsession.insert(namespace+".deleteNoticeFile", fileVO);
            }
            
            for (NoticeFileVO file : filelist) {
            	file.setNoticePK(noticeVO.getNoticeNum());
                sqlsession.insert(namespace+".insertNoticeFile", file);
            }
            txManager.commit(status);
        } catch (TransactionException ex) {
            txManager.rollback(status);
            System.out.println("데이터 저장 오류: " + ex.toString());
        }			
	}	
// 상세보기
	@Override
	public NoticeVO readNotice(int noticeNum) throws Exception {
		return sqlsession.selectOne(namespace+".detailNotice", noticeNum);
	}
// 업데이트 (+ 파일업데이트)
	@Override
	public void updateNotice(NoticeVO noticeVO, List filelist, String[] fileNum) throws Exception {
		DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        TransactionStatus status = txManager.getTransaction(def);        
        try {
        	sqlsession.update(namespace+".updateNotice", noticeVO);
            
            if (fileNum != null) {
                HashMap fileVO = new HashMap();
                fileVO.put("fileNum", fileNum);
                sqlsession.insert(namespace+".deleteNoticeFile", fileVO);
            }
            
            for (NoticeFileVO file : filelist) {
            	file.setNoticePK(noticeVO.getNoticeNum());
                sqlsession.insert(namespace+".insertNoticeFile", file);
            }
            txManager.commit(status);
        } catch (TransactionException ex) {
            txManager.rollback(status);
            System.out.println("데이터 저장 오류: " + ex.toString());
        }				
	}	
// 삭제
	@Override
	public void deleteNotice(int noticeNum) throws Exception {
		sqlsession.delete(namespace+".deleteNotice", noticeNum);		
	}	
// 조회수 증가
	@Override
	public void increaseCnt(int noticeNum) throws Exception {
		sqlsession.update(namespace+".increaseCnt", noticeNum);		
	}
// 리스트(검색 + 페이징)
	@Override
	public List Noticelist(SearchCriteria cri) {
		return sqlsession.selectList(namespace+".listNotice", cri);
	}
// 페이징을 위한 카운트	
	@Override
	public Integer NoticeCount(SearchCriteria cri) {
		return sqlsession.selectOne(namespace+".NoticeCount", cri);
	}
// 파일리스트(해당 공지에 대해서)	
	@Override
	public List flist(int noticeNum) throws Exception {		
		return sqlsession.selectList(namespace+".NoticeFileList", noticeNum);
	}	
// MAXCODE 증가
	@Override
	public String getMaxCode() throws Exception{
		return sqlsession.selectOne(namespace+".maxCode");
	}
}



SERVICE

@Service
public class NoticeServiceImpl implements NoticeService {
	
	@Inject
	NoticeDAOImpl noticeDAO;
	
    @Override
    public void createNotice(NoticeVO noticeVO, List filelist, String[] fileNum) throws Exception {        
    	noticeDAO.createNotice(noticeVO, filelist, fileNum);
    }
    
    @Override
    public NoticeVO readNotice(int noticeNum) throws Exception {
        return noticeDAO.readNotice(noticeNum);
    }
    
    @Override
    public void updateNotice(NoticeVO noticeVO, List filelist, String[] fileNum) throws Exception {
    	noticeDAO.updateNotice(noticeVO, filelist, fileNum);
    }
    
    @Override
    public void deleteNotice(int noticeNum) throws Exception {
    	noticeDAO.deleteNotice(noticeNum);
    }
    
    @Override
    public void increaseCnt(int noticeNum) throws Exception {
    	noticeDAO.increaseCnt(noticeNum);
    }

    @Override
	public List Noticelist(SearchCriteria cri) {
		return noticeDAO.Noticelist(cri);
	}

	@Override
	public Integer NoticeCount(SearchCriteria cri) {
		return noticeDAO.NoticeCount(cri);
	}
	
	@Override
    public List flist(int noticeNum) throws Exception {
        return noticeDAO.flist(noticeNum);
    }
	
	@Override
	public String getMaxCode() throws Exception {
		return noticeDAO.getMaxCode();
	}
}



CONTROLLER

@Controller
@RequestMapping("/notice")
public class NoticeController {
	
	@Inject
	NoticeService noticeService;
	
	// 등록 페이지
	@RequestMapping(value="/noticeWrite.do")
	public String noticeWrite(Model model) throws Exception{
		String maxCode = noticeService.getMaxCode();
		model.addAttribute("maxCode", maxCode);	
		return "notice/noticeWrite";
	}
	
	// 수정 페이지
	@RequestMapping(value="/noticeEdit.do", method={RequestMethod.GET, RequestMethod.POST})
	public String noticeEdit(@RequestParam("noticeNum") int noticeNum, @ModelAttribute("cri") SearchCriteria cri,	Model model) throws Exception{		
		model.addAttribute("dto", noticeService.readNotice(noticeNum));
		List fileview = noticeService.flist(noticeNum);
		model.addAttribute("fileview", fileview);
		return "notice/noticeEdit";
	}
	
	// 등록
	@RequestMapping(value="/noticeInsert.do")
    public String noticeInsert(int noticeNum, NoticeVO noticeVO, HttpServletRequest request) throws Exception{		
		String[] fileNum = request.getParameterValues("fileNum");
        NoticeFileUtil nf = new NoticeFileUtil();
        nf.setConPath(request.getSession().getServletContext().getRealPath("/noticeUpload"));
        List filelist = nf.saveAllFiles(noticeVO.getUploadfile());
        noticeService.createNotice(noticeVO, filelist, fileNum);		
    	return "redirect:/notice/noticeList.do";
    }
	
	// 상세보기
	@RequestMapping(value="/noticeInfo.do", method= {RequestMethod.GET, RequestMethod.POST})
	public String noticereadGET(@RequestParam("noticeNum") int noticeNum, @ModelAttribute("cri") SearchCriteria cri, Model model) throws Exception{
		noticeService.increaseCnt(noticeNum);
		model.addAttribute("cri", cri);
		model.addAttribute("dto", noticeService.readNotice(noticeNum));		
		List fileview = noticeService.flist(noticeNum);
		model.addAttribute("fileview", fileview);        
		return "notice/noticeInfo";
	}// readGET()
	
	// 삭제
	@RequestMapping(value="/noticeDelete.do")
	public String noticedeletePOST(@RequestParam("noticeNum") int noticeNum, SearchCriteria cri) throws Exception{
		noticeService.deleteNotice(noticeNum);
		
		rttr.addAttribute("page", cri.getPage());
		rttr.addAttribute("perPageNum", cri.getPerPageNum());
		rttr.addAttribute("searchType", cri.getSearchType());
		rttr.addAttribute("keyword", cri.getKeyword());

		return "redirect:/notice/noticeList.do";
	}// deletePOST()
	
	// 수정
	@RequestMapping(value="/noticeUpdate.do", method=RequestMethod.POST)
	public String noticeupdatePOST(int noticeNum, NoticeVO noticeVO, SearchCriteria cri, RedirectAttributes rttr, HttpServletRequest request) throws Exception{
		
		String[] fileNum = request.getParameterValues("fileNum");
		NoticeFileUtil nf = new NoticeFileUtil();
		nf.setConPath(request.getSession().getServletContext().getRealPath("/noticeUpload"));
        List filelist = nf.saveAllFiles(noticeVO.getUploadfile());

        noticeService.updateNotice(noticeVO, filelist, fileNum);
		
		rttr.addAttribute("page", cri.getPage());
		rttr.addAttribute("perPageNum", cri.getPerPageNum());
		rttr.addAttribute("searchType", cri.getSearchType());
		rttr.addAttribute("keyword", cri.getKeyword());
		
		rttr.addFlashAttribute("updateMsg", "success");
		
		return "redirect:/notice/noticeInfo.do?noticeNum=" + noticeNum;
	}// updatePOST()
	
	// 조회
	@RequestMapping(value="/noticeList.do")
	public String noticelistGET(SearchCriteria cri, Model model) throws Exception{
		
		int count = noticeService.NoticeCount(cri);
		model.addAttribute("count", count);
		
		model.addAttribute("list", noticeService.Noticelist(cri));
		PageMaker pageMaker = new PageMaker();

		pageMaker.setCri(cri);
		pageMaker.setTotalCount(noticeService.NoticeCount(cri));
		
		model.addAttribute("pageMaker", pageMaker);
		
		return "notice/noticeList";
	}// listGET()
}

public class NoticeFileUtil {

private String conPath;
	
	/**
     * 파일 업로드.
     */
    public List saveAllFiles(List upfiles) {
    	String filePath = conPath;
        List filelist = new ArrayList();
        for (MultipartFile uploadfile : upfiles ) {
            if (uploadfile.getSize() == 0) {
                continue;
            }  
            String newName = getNewName();
            
            saveFile(uploadfile, filePath + "/" + newName.substring(0,4) + "/",
            		newName);
           
            NoticeFileVO filedo = new NoticeFileVO();
            filedo.setFileName(uploadfile.getOriginalFilename());
            filedo.setRealName(newName);
            filedo.setSize(uploadfile.getSize());
            filelist.add(filedo);
        }
        return filelist;
    }    
    
    /**
     * 파일 저장 경로 생성.
     */
    public void makeBasePath(String path) {
        File dir = new File(path); 
        if (!dir.exists()) {
            dir.mkdirs();
        }
    }

    /**
     * 실제 파일 저장.
     */
    public String saveFile(MultipartFile file, String basePath, String fileName){
        if (file == null || file.getName().equals("") || file.getSize() < 1) {
            return null;
        }
     
        makeBasePath(basePath);
        String serverFullPath = basePath + fileName;
  
        File file1 = new File(serverFullPath);
        try {
            file.transferTo(file1);
        } catch (IllegalStateException ex) {
            System.out.println("IllegalStateException: " + ex.toString());
        } catch (IOException ex) {
            System.out.println("IOException: " + ex.toString());
        }
        
        return serverFullPath;
    }
    
    /**
     * 날짜로 새로운 파일명 부여.
     */
    public String getNewName() {
        SimpleDateFormat ft = new SimpleDateFormat("yyyyMMddhhmmssSSS");
        return ft.format(new Date()) + (int) (Math.random() * 10);
    }
    
    public String getFileExtension(String filename) {
          Integer mid = filename.lastIndexOf(".");
          return filename.substring(mid, filename.length());
    }

    public String getRealPath(String path, String filename) {
        return path + filename.substring(0,4) + "/";
    }

    public String getConPath() {
		return conPath;
	}

	public void setConPath(String conPath) {
		this.conPath = conPath;
	}
}




SQL (MySQL)


    
    	insert into noticeboard (
    	NoticeNum,
    	NoticeType,
    	RegDate,
    	Writer,
        Title,         
        Content
        ) values(
        #{noticeNum},
        #{noticeType},
        #{regDate},
        #{writer},
        #{title},        
        #{content}
        )
    
        
	
	
    
    
    
 
    
    
        delete from noticeboard 
        where NoticeNum = #{noticeNum}
    
    
    
    
        update noticeboard set 
        	Title = #{title},
        	Content = #{content}
        where
        	NoticeNum = #{noticeNum}
    
    
    
    
        update noticeboard set
        	Cnt = Cnt + 1 
        where
        	NoticeNum = #{noticeNum}
    	


	
	insert into noticeboardfile
	(
	NoticeNum,
	FileName,
	RealName,
	Size
	) values (
	#{noticePK},
	#{fileName},
	#{realName},
	#{size}
	)
	
	
	
	
	
	
		
	delete
	from noticeboardfile
	where FileNum in (
		
			${item}
		
		)
	


	
	
		
		where Title like CONCAT("%", #{keyword} ,"%") 
		
		
		where Writer like CONCAT("%", #{keyword} ,"%")
		
		
	
	
	

	
	



JSP


파일 업로드를 위하여 enctype="multipart/form-data" 는 필수

...(생략)


페이징



검색조건

$(function(){	
	$('.disabled').on('click', function(event){
		event.preventDefault();
	});	
	/* 검색 버튼 클릭 이벤트 핸들러 */
	$('#searchBtn').on("click", function(event){		
		// 검색옵션 값 가져오고
		var searchType = $("select[name=searchType]").val();		
		// 키워드 값 가져와서
		var keyword = $("input[name=keyword]").val();		
		self.location = "/notice/noticeList.do${pageMaker.makeQuery(1)}&searchType=" + searchType + "&keyword=" + encodeURI(keyword);
	});//on()		
});