Continuing from 1016

View_Detail Page

<body>
	<div>
		<table>
			<caption align="top"><b>내용보기</b></caption>
			<tr>
				<td>
					<h2><b>${dto.subject }</b></h2>
					<span style="float: right;margin-right: 20px">
						조회: ${dto.viewcount }&nbsp;&nbsp;&nbsp;
						<fmt:formatDate value="${dto.writeday }" pattern="yyyy-MM-dd HH:mm"/>
					</span><br>
					<h5><b>작성자: ${dto.writer }</b></h5>
				</td>
			</tr>
			<tr>
				<td>
					<pre>${dto.content }</pre><br>
					<!-- 이미지가 있을때만 출력 -->
					<c:if test="${dto.photo!='no' }">
						<c:forTokens var="p" items="${dto.photo }" delims=",">
							<a href="../upload/${p }"><img src="../upload/${p }" style="width: 150px;height: 150px"></a>
						</c:forTokens>
					</c:if>
				</td>
			</tr>
			<tr>
				<td align="right">
					<button type="button" class="btn btn-outline-success" onclick="location.href='writeForm'">글쓰기</button>
					<button type="button" class="btn btn-outline-info" onclick="location.href='writeForm?num=${dto.num}&regroup=${dto.regroup}&restep=${dto.restep}&relevel=${dto.relevel}&currentPage=${currentPage}'">답글</button>
					<button type="button" class="btn btn-outline-warning" onclick="location.href='updatePassForm?num=${dto.num}&currentPage=${currentPage }'">수정</button>
					<button type="button" class="btn btn-outline-danger" onclick="location.href='deletePassForm?num=${dto.num}&currentPage=${currentPage }'">삭제</button>
					<button type="button" class="btn btn-outline-success" onclick="location.href='list?currentPage=${currentPage}'">목록</button>
				</td>
			</tr>
		</table>
	</div>
</body>
  • View_Ordering Posts에서 게시물의 제목을 클릭 시 해당 게시물의 상세 정보 출력 (<a>태그의 링크 주소에 GET 방식(?변수)으로 넘긴 primary key 값 이용해 정보 출력)
  • 이미지 출력 (조건문, 데이터 분할)
    • 이미지 값의 존재 여부에 따라 이미지 출력 (조건문 : <c:if>)
    • 다중 이미지의 경우 ,(comma) 기준으로 문자열 분할 (<c:forTokens>)
  • 해당 데이터의 제어 등을 위한 버튼 (모두 onclick의 GET 방식 이용하여 데이터 전달)

Mapper_Click Event

<mapper>
	<update id="updateViewcount" parameterType="int">
		update reboard set viewcount=viewcount+1 where num=#{num}
	</update>
</mapper>
  • 상세 페이지 입장 시 조회 수 +1

for Insert

Mapper

<mapper>
	<!-- num의 max값,null일경우 0 -->
	<select id="maxNumOfReboard" resultType="int">
		select ifnull(max(num),0) from reboard
	</select>
	
	<!-- 같은 그룹중에서 step보다 큰데이터는 모두 +1 -->
	<update id="updateStepOfReboard" parameterType="HashMap">
		update reboard set restep=restep+1 where regroup=#{regroup} and restep>#{restep}
	</update>
	
	<!-- insert: 새글,답글 모두 해당 -->
	<insert id="insertOfReboard" parameterType="bdto">
		insert into reboard (writer,pass,subject,content,photo,regroup,restep,relevel,writeday)
		values (#{writer},#{pass},#{subject},#{content},#{photo},#{regroup},#{restep},#{relevel},now())
	</insert>
</mapper>
  • insert 시 g-s-l 값도 주입해야 하므로 g-s-l 값 조작
  • 뿌리 글 insert 시 g 값 제어를 위해 max(num) 호출 및 최초 작성 글의 경우 null 값 처리 위해 0 대입

DAO

@Repository
public class BoardDao implements BoardDaoInter {

	@Autowired
	private SqlSession session;

	@Override
	public void insertReboard(BoardDto dto) {
		
		int num=dto.getNum(); //String이면 null이냐 아니냐,int면 0이냐 아니냐
		int regroup=dto.getRegroup();
		int restep=dto.getRestep();
		int relevel=dto.getRelevel();
		
		if(num==0) {
			regroup=getMaxNum()+1;
			restep=0;
			relevel=0;
		}
		else {
			//같은 그룹중 잔달받은 restep보다 큰글들은 모두 +1
			this.updateRestep(regroup, restep);
			
			//전달받은 step과 level 모두 +1
			restep++;
			relevel++;
		}
		//바뀐값들을 다시 dto에 담는다
		dto.setRegroup(regroup);
		dto.setRestep(restep);
		dto.setRelevel(relevel);
		
		session.insert("insertOfReboard", dto);
	}
}
  • g-s-l 값 조작 후 DTO에 주입

Controller_Insert Form

@Controller
public class BoardWriteController {
	
	@Autowired
	BoardDaoInter dao;

	@GetMapping("/board/writeForm")
	public String writeform(Model model,@RequestParam Map<String, String> map) {
		
		//요 5개는 답글일 경우에만 넘어온다(새글일경우 안넘어옴)
		String currentPage=map.get("currentPage");
		String num=map.get("num");
		String regroup=map.get("regroup");
		String restep=map.get("restep");
		String relevel=map.get("relevel");
		
		//System.out.println(currentPage+","+num);
		
		//입력폼에 hidden으로 넣어줘야함..답글일때 대비
		model.addAttribute("currentPage", currentPage==null?"1":currentPage);
		model.addAttribute("num", num==null?"0":num);
		model.addAttribute("regroup", regroup==null?"0":regroup);
		model.addAttribute("restep", restep==null?"0":restep);
		model.addAttribute("relevel", relevel==null?"0":relevel);
		
		//0으로 넣어야 dao에서 새글로 인식
		//폼이 답글,새글 공용이므로
		
		return "reboard/writeForm";
	}
}
  • 새 데이터 생성 시 필요한 데이터 전달 및 중개
  • 초기 입장 시, 뿌리 글 작성 시 등 null 값 전달에 대비하여 null 값 처리

View_Insert Form

<body>
	<div>
		<form action="insert" method="post" enctype="multipart/form-data">
			<!-- hidden 5개 -->
			<input type="hidden" name="num"	value="${num }">
			<input type="hidden" name="currentPage"	value="${currentPage }">
			<input type="hidden" name="regroup"	value="${regroup }">
			<input type="hidden" name="restep" value="${restep }">
			<input type="hidden" name="relevel"	value="${relevel }">
			<table class="table table-bordered" style="width: 500px">
				<caption align="top"><b>
					<c:if test="${num==0 }">새글쓰기</c:if>
					<c:if test="${num!=0 }">답글쓰기</c:if>
				</b></caption>
				<tr>
					<th>작성자</th>
					<td><input type="text" name="writer" class="form-control" required="required"></td>
				</tr>
				<tr>
					<th>비밀번호</th>
					<td><input type="password" name="pass" class="form-control" required="required"></td>
				</tr>
				<tr>
					<th>제목</th>
					<td><input type="text" name="subject" class="form-control" required="required"></td>
				</tr>
				<tr>
					<th>사진</th>
					<td><input type="file" name="uimage" class="form-control" style="width: 250px" multiple="multiple"></td>
				</tr>
				<tr>
					<td colspan="2">
						<textarea name="content" required="required" class="form-control"></textarea>
					</td>
				</tr>
				<tr>
					<td colspan="2" align="center">
						<button type="submit">저장</button>
						<button type="button" onclick="location.href='list'">목록</button>
					</td>
				</tr>
			</table>
		</form>
	</div>
</body>
  • 해당 페이지로부터 Controller를 통해 받은 데이터를 hidden type의 <input>으로 <form>에 전달 (특히 g-s-l 값)
  • num 값의 여부(null 값 처리 후의 결과)에 따라 뿌리 글, 댓글 구분 (조건문) → 특히 g-s-l 값 제어에 활용

Controller_Insert Execution

@Controller
public class BoardWriteController {
	
	@Autowired
	BoardDaoInter dao;
	
	@PostMapping("/board/insert")
	public String insert(@ModelAttribute BoardDto dto,
			@RequestParam ArrayList<MultipartFile> uimage, //업로드 파일 여러개라서
			HttpSession session,
			@RequestParam int currentPage) {
		
		String path=session.getServletContext().getRealPath("/WEB-INF/photo");
		System.out.println(path);
		
		SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddHHmmss");
		
		String photo="";
		if(uimage.get(0).getOriginalFilename().equals(""))
			photo="no";
		else {
			
			for(MultipartFile f:uimage) {
				
				String fName=sdf.format(new Date())+"_"+f.getOriginalFilename();
				photo+=fName+",";
				
				try {
					f.transferTo(new File(path+"\\"+fName));
				} catch (IllegalStateException e) {
					e.printStackTrace();
				} catch (IOException e) {

					e.printStackTrace();
				}
			}
			//photo에서 마지막 컴마 제거
			photo=photo.substring(0, photo.length()-1);
		}
		//dto의 photo에 넣기
		dto.setPhoto(photo);
		
		//insert
		dao.insertReboard(dto);
		
		int num=dao.getMaxNum();
		
		return "redirect:content?num="+num+"&currentPage="+currentPage;
	}
}
  • <input type=”file”>로 넘긴 데이터는 @ModelAttribute로 DTO에 자동 입력되지 않음 → 반드시 MultipartFile로만 전달 가능 (다중 값은 Collection에 담아 전달)
  • insert 시 DB의 auto_increment 속성의 primary key인 num은 현존 num 중 가장 큰 값으로 입력 → redirect 페이지에서 출력할 데이터의 식별자로 활용 (getMaxNum())

for Authentification

View_Authentification Failure

<body>
	<script type="text/javascript">
		alert("비밀번호가 맞지 않습니다");
		hitory.back();
	</script>
</body>
  • Controller, DAO는 java이고 Mapper는 xml이므로 alert 및 history.back() 처리는 View인 jsp에서 처리 가능
  • 비밀번호 등의 인증 실패 시 표현하는 View이며 Update, Delete 등에서 모두 범용 가능

Mapper_Check Password

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="board">
	<select id="authentification" parameterType="HashMap" resultType="int">
		select count(*) from reboard where num=#{num} and pass=#{pass}
	</select>
</mapper>
  • primary key에 해당하는 password 값 대응
  • 반환 값은 0 or 1

DAO

@Repository
public class BoardDao implements BoardDaoInter {

	@Autowired
	private SqlSession session;

	@Override
	public int authentification(int num, int pass) {
		
		HashMap<String, Integer> map=new HashMap<String, Integer>();
		map.put("num", num);
		map.put("pass", pass);

		return session.selectOne("board.authentification", map);
	}
}
  • 다중 값 전달은 일반적으로 Map 이용
  • Interface로부터 Overriding

Controller_Authentification for Update

//생략..추후 설명
  • 인증 성공 여부에 따라 매핑 주소 변경

Controller_Authentification for Delete

@Controller
public class BoardDeleteController {

	@Autowired
	BoardDaoInter dao;

	@PostMapping("/board/deletePass")
	public String dpass(Model model,int num,int pass,HttpSession session) {
		
		int flag=dao.authentification(num, pass);
		
		String mapping="";
		//비번이 맞으면 수정폼으로,틀리면 passFail로
		mapping=(flag==0?"reboard/passFail":"redirect:list");
	
		//다른 삭제 기능은 생략..추후 설명
	
		return mapping;
	}
}

View_Input Password

<body>
	<div style="margin: 200px">
		<form action="deletePass" method="post">
			<input type="hidden" name="num" value="${num }">
			<input type="hidden" name="currentPage" value="${currentPage }">
			
			<div>
				<b>비밀번호를 입력해 주세요</b>
			</div>
			<div class="d-inline-flex">
				<input type="password" name="pass" class="form-control" required="required">
				<button type="submit" class="btn btn-outline-danger">수정 혹은 삭제</button>
				<button type="button" class="btn btn-outline-success" onclick="history.back()">이전</button>
			</div>
		</form>
	</div>
</body>
  • 인증을 위해 비밀번호를 입력하여 Controller에 전달
  • Update 및 Delete 범용 가능

for Update

Controller_Authentification_1

@Controller
public class BoardUpdateController {

	@Autowired
	BoardDaoInter dao;
	
	@GetMapping("/board/updatePassForm")
	public ModelAndView upassform(@RequestParam String num,@RequestParam String currentPage) {
		
		ModelAndView mv=new ModelAndView();
		mv.setViewName("reboard/updatePassForm");
		
		mv.addObject("num", num);
		mv.addObject("currentPage", currentPage);
		
		return mv;
	}
  • currentPage 데이터는 계속 중개 및 전달하여야 목록으로 돌아갈 시 활용 가능

Controller_Authentification_2

@Controller
public class BoardUpdateController {

	@Autowired
	BoardDaoInter dao;
	
	@PostMapping("/board/updatePass")
	public String upass(Model model,@RequestParam int num,int pass,int currentPage) {
		
		int flag=dao.authentification(num, pass);
		
		String mapping="";
		//비번이 맞으면 수정폼으로,틀리면 passFail로
		mapping=(flag==0?"reboard/passFail":"reboard/updateForm");
		
		BoardDto dto=dao.getData(num);
		model.addAttribute("dto", dto);
		model.addAttribute("currentPage", currentPage);
		
		return mapping;
	}
  • 인증 성공 여부에 따라 매핑 주소 변경

View_Update Form

<body>
	<div style="margin: 50px 100px">
		<form action="update" method="post" enctype="multipart/form-data">
			<!-- hidden 5개 -->
			<input type="hidden" name="num"	value="${dto.num }">
			<input type="hidden" name="currentPage"	value="${currentPage }">
			<input type="hidden" name="regroup"	value="${dto.regroup }">
			<input type="hidden" name="restep" value="${dto.restep }">
			<input type="hidden" name="relevel"	value="${dto.relevel }">
			<table>
				<caption align="top"><b>
					<c:if test="${dto.num==0 }">새글쓰기</c:if>
					<c:if test="${dto.num!=0 }">답글쓰기</c:if>
				</b></caption>
				<tr>
					<th>작성자</th>
					<td><input type="text" name="writer" class="form-control" value="${dto.writer }" readonly="readonly"></td>
				</tr>
				<tr>
					<th>비밀번호</th>
					<td><input type="password" name="pass" class="form-control" required="required" value="${dto.pass }"></td>
				</tr>
				<tr>
					<th>제목</th>
					<td><input type="text" name="subject" class="form-control" required="required" value="${dto.subject }"></td>
				</tr>
				<tr>
					<th>사진</th>
					<td><input type="file" name="modimage" class="form-control" multiple="multiple"></td>
				</tr>
				<tr>
					<td colspan="2">
						<textarea name="content" required="required" class="form-control">${dto.content }</textarea>
					</td>
				</tr>
				<tr>
					<td colspan="2" align="center">
						<button type="submit" class="btn btn-outline-info">
							<c:if test="${dto.relevel==0 }">원글 수정</c:if>
							<c:if test="${dto.relevel==1 }">답글 수정</c:if>
							<c:if test="${dto.relevel>1 }">답답글 수정</c:if>
						</button>
						<button type="button" onclick="location.href='list'">목록</button>
					</td>
				</tr>
			</table>
		</form>
	</div>
</body>
  • DTO 통해 데이터 전달 받았으므로 DTO 통해 호출
  • 초기 값 설정
  • update 시에는 g-s-l 값은 불변하므로 전달할 필요 없지만 기타 기능 위해 전달

Mapper_Update

<mapper>
	<update id="updateOfReboard" parameterType="bdto">
		update reboard set pass=#{pass},subject=#{subject},content=#{content}
		<if test="photo!='no'">
			,photo=#{photo}
		</if>
		where num=#{num}
	</update>
</mapper>
  • 변경할 데이터만 변경
  • 파일 업로드 여부에 따라 저장된 파일명 변경 여부 결정
  • DAO 생략

Controller_Update Execution

@Controller
public class BoardUpdateController {

	@Autowired
	BoardDaoInter dao;

	@PostMapping("/board/update")
	public String update(Model model,@ModelAttribute BoardDto dto,@RequestParam int currentPage,
			@RequestParam List<MultipartFile> modimage,
			HttpSession session) {
		
		String newphoto="";
		
		if(modimage.get(0).getOriginalFilename().equals(""))
			newphoto="no";
		else {
			
			String oldphoto=dao.getData(dto.getNum()).getPhoto();
			String [] oldphotoes=oldphoto.split(",");
			
			String path=session.getServletContext().getRealPath("/WEB-INF/photo");
			
			for(String o:oldphotoes) {
				
				File file=new File(path+"\\"+o);
				file.delete();
			}

			for(MultipartFile f:modimage) {
				
				SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMddHHmmss");
				String fName=sdf.format(new Date())+"_"+f.getOriginalFilename();
				
				newphoto+=fName+",";

				try {
					f.transferTo(new File(path+"\\"+fName));
				} catch (IllegalStateException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			newphoto=newphoto.substring(0, newphoto.length()-1);
		}
		dto.setPhoto(newphoto);		
		
		dao.updateData(dto);
		
		model.addAttribute("currentPage", currentPage);
		model.addAttribute("dto", dto);
		
		return "redirect:content?num="+dto.getNum()+"&currentPage="+currentPage;
	}
}
  • insert와 유사하나 실제 파일 저장 경로에서 실제 기존 파일 삭제하는 과정 추가 (다중 파일 업로드 경우를 대비하여 반복문 실행)
  • redirect 주소에 GET 방식으로 필요한 변수 전달

for Delete

Mapper_Delete Cascade

<delete id="deleteWithChildren" parameterType="HashMap">
	delete from reboard where restep
	<if test="pointValue==-1">
		>=#{restep}
	</if>
	<if test="pointValue!=-1">
		between #{restep} and #{pointValue}
	</if>
	and relevel>=#{relevel} and regroup=#{regroup}
</delete>
  • 게시물 삭제 시 해당 게시물에 첨부된 하위 게시물 전부 삭제 (g-s-l 이용)

    • g = g (같은 그룹의 게시물에만 적용)

    • l ≥ l (해당 게시물의 level보다 하위 level의 게시물까지 지정)

    • s 값은 조건(pointValue 존재 여부)에 따라 제어 방식 변경

      • s between s and pointValue (level은 하위이지만 다른 게시물에 첨부된 파일 구분)
      • s ≥ s (해당 게시물이 같은 그룹의 동일한 level 중 최후 게시물일 경우)
      <select id="nextRestep" parameterType="bdto" resultType="int">
      	select ifnull(min(restep),0) from reboard where regroup=#{regroup} and restep>#{restep} and relevel=#{relevel}
      </select>
    • pointValue는 같은 level의, 해당 s 값보다 큰 s 값 중 최소값 → 즉 삭제 대상 게시물의 하위에 속하지 가장 가까운 이후의 게시물의 s

      • pointValue의 존재 여부에 따라 Delete sql 제어문 변경
  • Delete Cascade 아닌 해당 게시물만을 삭제하고자 할 경우 아래의 Mapper 사용

<delete id="deleteOfReboard" parameterType="int">
	delete from reboard where num=#{num}
</delete>

DAO

@Repository
public class BoardDao implements BoardDaoInter {

	@Autowired
	private SqlSession session;

	@Override
	public void deleteCascade(BoardDto dto) {
		
		Map<String, Integer> map=new HashMap<String, Integer>();
		int pointValue=(getNextRestep(dto)==0?0:getNextRestep(dto));
		map.put("pointValue", getNextRestep(dto)-1);
		map.put("restep", dto.getRestep());
		map.put("relevel", dto.getRelevel());
		map.put("regroup", dto.getRegroup());
		
		session.delete("deleteChildren", map);
	}
}
  • Mapper의 getNextRestep를 이용하여 pointValue 구한 후 -1
  • 해당 게시물의 g-s-l 값 추출
  • 필요한 데이터 Map에 담아 전달
  • 해당 게시물만을 삭제할 시 적절한 Mapper와 연결하여 DAO 메서드 작성

Controller_Delete Cascade

@Controller
public class BoardDeleteController {

	@Autowired
	BoardDaoInter dao;
	
	@GetMapping("/board/deletePassForm")
	public ModelAndView dpassform(@RequestParam String num,@RequestParam String currentPage) {
		
		ModelAndView mv=new ModelAndView();
		mv.setViewName("reboard/deletePassForm");
		
		mv.addObject("num", num);
		mv.addObject("currentPage", currentPage);
		
		return mv;
	}
	
	@PostMapping("/board/deletePass")
	public String dpass(Model model,int num,int pass,HttpSession session) {
		
		int flag=dao.authentification(num, pass);
		
		String path=session.getServletContext().getRealPath("/WEB-INF/photo");
		
		BoardDto dto=dao.getData(num);
		
		String mapping="";
		//비번이 맞으면 수정폼으로,틀리면 passFail로
		mapping=(flag==0?"reboard/passFail":"redirect:list");
		
		if(mapping.equals("redirect:list")) {

			if(!dao.getData(num).getPhoto().equals("no")) {
				
				String [] oldphotoes=dao.getData(num).getPhoto().split(",");
				
				for(String o:oldphotoes) {
					
					File file=new File(path+"\\"+o);
					file.delete();
				}
			}
			dao.deleteCascade(dto);
		}
		model.addAttribute("dto", dto);
		
		return mapping;
	}
}
  • 인증에 대한 설명은 전술했으므로 생략
  • 실제 저장 경로의 파일도 삭제 (다중 파일 업로드 경우 대비하여 반복문 실행)
profile
초보개발자

0개의 댓글