[Spring] I/O, 게시판 응용

2025. 3. 28. 13:33·Web

[사용자의 파일을 받아 서버에 저장, DB에 저장, 게시판 View에 출력, 삭제]

테이블 

create table macbook_banner(
bidx int unsigned auto_increment,
bname varchar(100) not null,
file_ori text null,
file_new text null,
file_url text null,
bdate timestamp not null default current_timestamp,
primary key(bidx)
);

banner_DTO.java

package spring_learning;

import org.springframework.stereotype.Repository;

import lombok.Data;

//@Getter
//@Setter
@Data	//@Data = @Getter + @Setter 한방에 적용하는 어노테이션
@Repository("banner_DTO")
//DTO 생성 후 꼭 config.xml에 추가~!
public class banner_DTO {
	//파일은 DTO에 쓰지않음!!! 절대안됨!!! I/O는 따로 핸들링함!!!
	int bidx;
	String bname, file_ori, file_new, file_url, bdate;
}
  • @Data: Getter, Setter 자동 생성 어노테이션
    • Getter, Setter, toString(), equals(), hashCode() 등 자주 사용하는 메서드 자동 생성

banner.html

입력하고 페이지 안만들어서 에러뜨는데 저장잘됨 -> 리스트로 가기

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>배너 등록 페이지</title>
</head>
<body>
<form id="frm" method="post" action="./bannerok" enctype="multipart/form-data">
이벤트 : <input type="text" name="bname"><br>
파일첨부 : <input type="file" name="bfile" ><br>
<input type="button" value="배너등록" onclick="gopage()">
</form>
</body>
<script>
var gopage = function(){
	frm.submit();
}
</script>
</html>
  • enctype="multipart/form-data": 파일 업로드 위한 필수 속성

mapper.xml 부분

<!-- 배너 파일 저장 테이블 -->
<insert id="banner_in">
insert into macbook_banner (bidx,bname,file_ori,file_new,file_url,bdate) 
values ('0',#{bname},#{file_ori},#{file_new},#{file_url},now())
</insert>

<!-- 배너 전체 리스트 출력 쿼리문 + 페이징 추가 -->
<select id="banner_all" resultType="banner_DTO" parameterType="Map">
select * from macbook_banner order by bidx desc limit #{spage},#{epage}
</select>

<!-- 
* 2중 select 못하면 새로 써야됨 
배너의 전체 데이터 개수 쿼리문 
-->
<select id="banner_total" resultType="int">
select count(*) as total from macbook_banner
</select>

<!-- 배너명 검색 쿼리문 -->
<!-- 
[mybatis에서 DB에 따른 like 사용법] 잘알아두기 !
mysql & mariaDB : like concat('%',#{search},'%')
orecle : like '%'||#{search}||'%'
mssql : like '%'+#{search}+'%'

* like 외에도 다르게 써야하는 명령어 있음 : 트리거 등등 ...
-->
<select id="banner_search" resultType="banner_DTO" parameterType="String">
select * from macbook_banner where bname like concat('%',#{search},'%') order by bidx desc
</select>

<!-- 배너 고유값으로 삭제하는 쿼리문 -->
<delete id="banner_del">
delete from macbook_banner where bidx=#{no}
</delete>
  • [검색] mybatis에서 DB에 따른 like 사용법 - 잘알아두기 !
    • mysql & mariaDB : like concat('%',#{search},'%')
    • orecle : like '%'||#{search}||'%'
    • mssql : like '%'+#{search}+'%'
  • like 외에도 다르게 써야하는 명령어 있음 : 트리거 등등 ...

banner_DAO.java

package spring_learning;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Repository;

@Repository("banner_DAO")
public class banner_DAO {

	@Resource(name = "template")
	public SqlSessionTemplate st;
	
	Integer page_ea = 5;	//한페이지에 출력할 게시물 수  

	//신규 배너 등록 메소드 
	public int new_banner(banner_DTO dto) {
		int result = this.st.insert("macbook_user.banner_in",dto);	
		return result;
	}
	
	//배너 전체 데이터 출력 + 페이징 
	public List<banner_DTO> all_banner(Integer pgno){	//Integer pgno : Controller에서 사용자가 클릭한 페이지 번호를 받는 역할 
		//limit을 사용하기 위해 Map 형태로 구성하여 Mapper로 전달 
		Map<String,Integer> data = new HashMap<String,Integer>();
		data.put("spage", this.page_ea * (pgno - 1));	//limit 첫번째 번호 (페이지 번호에 맞는 시작 게시물 번호)
		data.put("epage", this.page_ea);	//limit 두번째 번호 (출력 개수)
		
		List<banner_DTO> all = this.st.selectList("macbook_user.banner_all",data);
		return all;
	}
	
	public int banner_total() {
		int total = this.st.selectOne("macbook_user.banner_total");
		return total;
	}
	
	
	//배너명으로 검색된 데이터를 가져오는 메소드 (DAO)
	public List<banner_DTO> banner_search(String search) {
		List<banner_DTO> sel = this.st.selectList("macbook_user.banner_search",search);
		return sel;
	}
	//똑같은거 많으면 그냥 필드에 올리는게 더 좋음 List<banner_DTO> 이런거 필드에 올리기 
	
	//배너 삭제 메소드 
	public int banner_del(String no) {
		int result = this.st.delete("macbook_user.banner_del", no);
		return result;
	}
	
}

banner_controller.java

package spring_learning;

import java.io.File;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

//실무처럼 쓴 컨트롤러!! 캬~ 
@Controller
public class banner_controller {
	
	List<String> listdata = null;
	Map<String, String> mapdata = null;
	PrintWriter pw = null;
	String result = null;	//select 결과 
	int callback = 0;		//update, delete, insert 결과 
	ModelAndView mv = null;

	//Field(this. 사용)의 dto와 매개변수(그냥사용)의 dto는 다름 !!!
	@Resource(name="banner_DTO")
	banner_DTO dto;
	
	@Resource(name="banner_DAO")
	banner_DAO dao;
	
	@Resource(name="file_rename")
	m_file_rename fname;	//파일명ㅇㄹ 개발자가 원하는 형태로 변경 
	
	//배너 등록 (.do안써도됨) do쓰는 이유는 그냥 암묵적인룰? 2~3년차되면 잘 안씀 
	@PostMapping("/banner/bannerok")	//경로 잘 맞추기 
	public String bannerok(
			@ModelAttribute(name="dto") banner_DTO dto,
			MultipartFile bfile,
			HttpServletRequest req
			) throws Exception{
		//@RequestParam(name="dto", required = false) 이거는 잘못된 코드 => int + String 섞여있어서 안됨
		//=> 
		//@ModelAttribute : dto 전용 어노테이션 (근데 안써도됨 ㅋ) 부트에서 많이 쓰임 Spring에서는 딱히 .. 
		//			장점 : 1대1 매칭 => name과 DTO 자료형 변수가 같은것이 있으면 무조건 값을 setter 발동 
		
		String file_new = null;

		//날짜
		if(bfile.getSize() > 0) {
			String url = req.getServletContext().getRealPath("/upload/");
//			System.out.println(url);
			
			file_new = this.fname.rename(bfile.getOriginalFilename());
			FileCopyUtils.copy(bfile.getBytes(), new File(url + file_new));
			
			dto.setFile_url("/upload/" + file_new);		//웹디렉토리경로 및 파일명  
			dto.setFile_new(file_new);		//개발자가 원하는 방식으로 변경한 파일명 
			dto.setFile_ori(bfile.getOriginalFilename());	//사용자가 적용한 파일명 
		}
		
		this.callback = this.dao.new_banner(dto);	//this.dto와 dto는 다름 주의!!!
		System.out.println(this.callback);
		
		return null;
	}
	
	//search 검색에 관련사항은 필수조건은 아니며, 또한 null일경우 공백처리 
	@GetMapping("/banner/bannerlist")	//경로 잘 맞추기 
	public String bannerlist(Model m, 
			@RequestParam(name="search", defaultValue = "", required = false) String search,
			@RequestParam(name="pageno", defaultValue = "1", required = false) Integer pageno) {
		
		//페이징
		//페이징한다고 쿼리문 따로 만들지않고 전체출력을 꾸며서 쓰는것이 좋음 !!!!!
		
		//리스트 총개수확인 
		int total = this.dao.banner_total();
		System.out.println(total);
		
		//사용자가 클릭한 페이지 번호에 맞는 순차번호 계산값 
		int userpage = (pageno - 1) * 5;
		m.addAttribute("userpage",userpage);
		
		
		//검색 
		//search 의 값이 없을때 디폴트 "", 필수 X로 받겠다는 뜻 => 안쓰면 400! 
		List<banner_DTO> all = null;
		
		if(search.equals("")) {		//연산기호 X, equals
			System.out.println("검색어없음");
			all = this.dao.all_banner(pageno);	//사용자가 클릭한 페이지 번호값 전달 
		}else {
			all = this.dao.banner_search(search);
		}
		
		m.addAttribute("total",total);
		m.addAttribute("search",search);
		m.addAttribute("all",all);
		
		return null;
	}
	
	//리스트 삭제 
	@PostMapping("/banner/bannerdel")
	public String bannerdel(@RequestParam(name="ckdel", defaultValue = "", required = false) String ckdel, Model m) {
		this.callback = 0;
		String msg = "";
		
		if(ckdel.equals("")) {
			msg = "alert('올바른 접근이 아닙니다.'); location.href='./bannerlist';";
		}else {
			//front에서 넘어온 ckdel는 문자열형태 1,2,3
			String no[] = ckdel.split(",");
			int w = 0;
			while(w < no.length) {	//front-end에서 체크된 값만큼 반복 	
				int result = this.dao.banner_del(no[w]);
				if(result > 0) {
					this.callback++;
				}
				
				w++;
			}
			
			//-1 사용하는 이유는 반복문에 조건이 없으므로 +1이 작동될 수 있음 
			if(no.length == this.callback) {
				System.out.println(no.length);
				System.out.println(this.callback);
				msg = "alert('정상적으로 삭제되었습니다.'); location.href='./bannerlist';";				
			}else {
				System.out.println(no.length);
				System.out.println(this.callback);
				msg = "alert('비정상적인 데이터가 확인되었습니다.'); location.href='./bannerlist';";							
			}
		}
		m.addAttribute("msg", msg);
		
		return "load";
	}
	
}

 

  • FileCopyUtils.copy(): 파일 데이터를 지정한 경로에 복사
  • req.getServletContext().getRealPath(): 서버의 실제 경로를 가져옴

 

m_file_rename.java

서버 저장용 이름 짓는 Model

package spring_learning;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.stereotype.Repository;

@Repository("file_rename")
public class m_file_rename {
	
	//홍길동.jpg => 2025032755.jpg
	public String rename(String filenm) {
		
		//날짜 
		Date day = new Date();
		SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd");
		String today = sf.format(day);	//년월일

		//속성
		int com = filenm.lastIndexOf(".");
		String fnm = filenm.substring(com);
		
		//랜덤값 
		int no = (int)Math.ceil(Math.random()*1000);		//1~1000
		String makefile = today + no + fnm;		
		
		return makefile;
	}
}

bannerlist.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib prefix="cr" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>배너 리스트 페이지</title>
</head>
<body>

<!-- 
서브밋 Ajax 안씀! 위험 더블이벤트 발생 입력없는데 통신 이런거 발생 절대 X
버튼에만 Ajax 씀! 
 -->
<!-- 하나의 컨트롤러 메소드에 다해도됨 -->
<form id="sform" method="get" action="./bannerlist" onsubmit="return spage()">
<p>
배너명 검색 : <input type="text" name="search" value="${search}">
<input type="submit" value="검색">
<input type="button" value="전체목록" onclick="location.href='./bannerlist';">
</p>
</form>
	<p>전체 등록된 배너 개수 : ${total} 개</p>
	<table border="1" cellpadding="0" cellspacing="0">
		<thead>
			<tr>
				<th><input type="checkbox" id="allck" onclick="check_all(this.checked)"></th>
				<th width="80">번호</th>
				<th width="300">배너명</th>
				<th width="100">이미지</th>
				<th width="150">파일명</th>
				<th width="150">등록일</th>
			</tr>
		</thead>
		<%-- 
		배열 값을 조건문으로 jstl에 처리시 functions 이용하여 length로 검토하여 처리함 
		<cr:if test ="${fn:length(all)==0}">
		아래와 동일한 코드 
		 --%>
		<cr:if test ="${all.size()==0}">
		<tbody>
		<tr>
		<td colspan="6" align="center">검색된 데이터가 없습니다.</td>
		</tr>
		</tbody>
		</cr:if>
		
		<tbody>
		<cr:set var="ino" value="${total - userpage}"/>	<!-- 게시물 일련번호 셋팅 -->
		
		<!-- 반복문 안에는 절대 id로 같은 이름 사용 불가능 => class(ajax전송), name(form전송) 사용 -->
		<!-- 고수는 data-value 이런 맘대로 쓴 속성으로 핸들링 -->
		
			<cr:forEach var="bn" items="${all}" varStatus="idx">
				<tr height="50">
				<!-- 게시판번호 x DB에 저장된 auto_increment 값 -->
					<td><input type="checkbox" name="ckbox" value="${bn.bidx}" onclick="checkdata()"></td>
					<td align="center">
					${ino - idx.index}
					
					</td>
					<td>${bn.bname }</td>
					<td>
					<cr:if test="${bn.file_url == null}">
						NO IMAGE					
					</cr:if> 
					<cr:if test="${bn.file_url != null}">
						<img width="100" src="..${bn.file_url }">
					</cr:if>
					</td>
					<td align="center">
					<a href="../upload/${bn.file_new}" target="_blank" title="${bn.file_new}">${bn.file_ori}</a>
					</td>
					<td align="center">${fn:substring(bn.bdate,0,10)}</td>
				</tr>
			</cr:forEach>
		
			<!-- 
			이미지 엑박뜨는 이유 : 경로 다름
			
			http://localhost:8080/spring_learning/banner/bannerlist
			여기가 현주소인데 여기서 img src="bn.file_url" 이래쓰면 
			http://localhost:8080/upload/20250327311.jpeg  
			여기로옴
			
			근데 여기로 와야함 
			http://localhost:8080/spring_learning/upload/20250327311.jpeg
			
			img src="..bn.file_url"이래 쓰기 
			 -->
		</tbody>
	</table>
	<br><br>
	
	<!-- form 전송으로 선택된 값을 삭제하는 프로세서 -->
	<!-- 
	1. forEach => form => 동일한 name => post 전송 => 배열로 받음
	2. form => 하나의 hidden을 이용 => post 전송 => 자료형 한개로 받음 
	주의 : 반복문 속 form, 반복문 속 id => 절대 X
	 -->
	<form id="dform" method="post" action="./bannerdel">
	<input type="hidden" name="ckdel" value="">
	</form>
	<input type="button" value="선택삭제" onclick="check_del()">
	<br><br>
	
	<!-- pageing -->
	<table border="1" cellpadding="0" cellspacing="0">
	<tbody>
	<tr height="30">
	
	
	<!-- 
	Controller에서 데이터의 전체 갯수를 받음 
	해당 값을 한페이지당 5개씩 출력하는 구조 
	 -->
	<!-- 
	total / 5 + (1 - ((total / 5) % 1)) % 1
		- total / 5 → 몇 페이지가 필요한지 계산 (소수점 포함)
		- (total / 5) % 1 → 남은 소수점이 있는지 확인
		- 1 - ... → 소수점이 있으면 1을 더하기 위한 처리
		- % 1 → 0 또는 1로 만들기 위한 트릭
		//(1 - ((total / 5) % 1)) % 1 => 나머지가있으면 1페이지 추가 
	 -->
	<cr:set var="pageidx" value="${total / 5 + (1-((total / 5) % 1)) % 1}"/>
		<cr:forEach var="no" begin="1" end="${pageidx}" step="1">
		<td width="30" align="center" onclick="pg('${no}')">${no}</td>
		</cr:forEach>
	
	</tr>
	</tbody>
	
	</table>
	
	
	
</body>
<script>
var spage = function(){
	if(sform.search.value==""){
		alert("배너명을 입력하세요");
		return false;
	}else{
		return;	
	}
}

function pg(no){
	location.href='./bannerlist?pageno='+no;
}

//체크박스 전체선택 함수 
//getElements : name // class getElement : id
function check_all(ck){	//ck : true, false
	var ea = document.getElementsByName("ckbox");
	
	var w= 0;
	while(w < ea.length){
		ea[w].checked = ck;
		w++;
	}
	//위에거 길게쓰면 아래 코드 
	/*
	if(ck == true){	//전체선택한 경우 
		var w= 0;
		while(w < ea.length){
			ea[w].checked = true;
			w++;
		}
	}else{	//전체선택 해제한 경우 
		var w= 0;
		while(w < ea.length){
			ea[w].checked = false;
			w++;
		}
	}
	*/
}

//하나라도 체크 해제시 전체선택 체크 해제 
function checkdata(){
	
}

//선택삭제 버튼 클릭시 리스트에서 체크된 값을 확인 후 배열화하여 hidden에 값을 적용하여 Back-end에 문자열로 전달 
function check_del(){
	var ar = new Array();	//script 배열 
	
	var ob = document.getElementsByName("ckbox");
	var w = 0;
	while(w < ob.length){
		if(ob[w].checked == true){
			ar.push(ob[w].value);	
		}
		w++;
	}
	dform.ckdel.value = ar;	//배열이 자동으로 문자열로 변해서 들어감 value="9,8,7,6,5"
	
	if(confirm('해당 데이터를 삭제시 복구되지 않습니다.')){
		dform.submit();
	}
	
}
</script>
</html>

 

  • <cr:forEach>: JSTL의 반복 태그로 리스트 데이터 출력
  • fn:substring(): 문자열 자르기 함수
저작자표시 비영리 변경금지 (새창열림)
'Web' 카테고리의 다른 글
  • [Javascript] 날짜 및 시간 체크
  • [Spring] Session
  • 250328
  • [Spring] 체크박스 응용
9na0
9na0
응애
  • 9na0
    구나딩
    9na0
  • 전체
    오늘
    어제
    • 분류 전체보기 (211)
      • Web (118)
      • Java (28)
      • 데이터베이스 (14)
      • 세팅 (12)
      • 과제 (3)
      • 쪽지시험 (2)
      • 정보처리기사 (4)
      • 서버 (25)
  • 블로그 메뉴

    • 링크

      • 포폴
      • 구깃
    • 공지사항

    • 인기 글

    • 태그

      Oracle
      net4
      net2
      io_dto
      re2
      exam1_1~10
      file25_t
      spring-boot
      file24
      net3
      noticewriteok
      ab1
      macbook pro m4
      java_io1~10
      file25
      net5~10
      notice_writer
      datalist
      re_java10
      net1
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.3
    9na0
    [Spring] I/O, 게시판 응용
    상단으로

    티스토리툴바