728x90
인텔리제이 -> SpringMybatisMini 프로젝트
🔔 TODO : 회원 게시판
1) 로그인 한 경우에만 글쓰기 버튼이 보이게
2)
🖐 새로 배운 것
autofocus="autofocus"
// 폼 이동시 이 것을 설정해준 쪽으로 화면이 바로 이동한다.
margin : 상(top) 우(right) 하(bottom) 좌(left)
//margin 한번에 주기
select * from bootmember order by num desc limit 0,2;
//bootmember 테이블을 조회하는데, 0번부터 2번까지 만 조회한다
//10개의 글이 있으면 2개만 조회 된다
select ifnull(Max(num),0) from memboard
- 기본적으로 조회하면 max(num)은 null 이 나오는데, 이것을 자바에서 처리해주기 귀찮으닌까 sql쿼리에서 ifnull 을 사용해서 default 값을 0으로 한다.
-> oracle에서는 MVL을 사용
Model -> MemboardService / MemboardRepository
View -> memboard
Controller -> MemBoardController
Mapper -> memboardsql
#1 멤버 게시판을 만들고 데이터를 처리하기 위해선 데이터를 보내줄 폼을 작성했다
addform.jsp (게시글 등록폼)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.7.0.js"></script>
<title>글 쓰기</title>
</head>
<body>
<div style="margin-left: 330px;">
<form action="insert" method="post" enctype="multipart/form-data">
<table class="table table-bordered" style="width:500px;">
<caption align="top"><b>회원전용 글쓰기</b></caption>
<tr>
<th>제목</th>
<td><input type="text" name="subject" class="form-control" required="required" autofocus="autofocus"></td>
</tr>
<tr>
<th>파일업로드</th>
<td><input type="file" name="upload" class="form-control"></td>
</tr>
<tr>
<td colspan="2">
<textarea style="width:490px; height: 200px;" required="required" class="form-control" name="content"></textarea>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<button type="submit" class="btn btn-outline-dark">등록</button>
<button type="button" class="btn btn-outline-dark" onclick="location.href='list'">목록</button>
<button type="button" class="btn btn-outline-dark" onclick="location.href='content1'">글 목록</button>
</td>
</tr>
</div>
</table>
</form>
</body>
</html>
content.jsp ( 내가 쓴 글 보는 페이지 )
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<link href="https://fonts.googleapis.com/css2?family=VT323&display=swap" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.7.0.js"></script>
<title>Insert title here</title>
</head>
<body>
<div style="margin : 50px 150px;">
<table class="table table-bordered" style="width:800px;">
<tr>
<td>
<h4><b>${dto.subject}</b>
<span style="font-size:0.7em; color:gray; float: right">
🔔 조회수 : ${dto.readcount}
🕰 작성일 : <fmt:formatDate value="${dto.writeday}" pattern="yyyy-MM-dd HH:mm:ss"/>
</span>
</h4>
<span>작성자 : ${dto.name} (${dto.myid})</span>
<c:if test="${dto.uploadfile!= 'no'}">
<span style="float:right">
<a href="download?clip=${dto.uploadfile}">
<i class="bi bi-arrow-down-circle"> </i><b>${dto.uploadfile}</b>
</a>
</span>
</c:if>
</td>
</tr>
<tr>
<td>
<c:if test="${bupload ==true}">
<img src="../savefile/${dto.uploadfile}" style="width:350px; height:400px;">
</c:if>
<br><br>
<pre>
${dto.content}
</pre>
</td>
</tr>
<c:if test="${sessionScope.loginok!=null}">
<tr>
<td>
<button type="button" class="btn btn-outline-dark" onclick="location.href='form'" style="width:100px;">글작성</button>
<button type="button" class="btn btn-outline-dark" onclick="location.href='list'" style="width:100px;">목록</button>
<c:if test="${sessionScope.loginok!=null and sessionScope.myid ==dto.myid}">
<button type="button" class="btn btn-outline-dark" onclick="location.href='update?num=${dto.num}'" style="width:100px;">수정</button>
<button type="button" class="btn btn-outline-dark" onclick="location.href='delete?num=${dto.num}'" style="width:100px;">삭제</button>
</c:if>
</td>
</tr>
</c:if>
</table>
</div>
</body>
</html>
#2 다음으로는 이제 이 데이터를 처리해줄 로직입니다.
MemBoardMapperInter.java
import java.util.HashMap;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import boot.data.dto.MemBoardDto;
@Mapper
public interface MemBoardMapperInter {
public int getTotalCount();
public void updateReadcount(String num);
public void insertBoard(MemBoardDto memBoardDto);
public MemBoardDto getData(String num);
public int getMaxNum();
public List<MemBoardDto> getList(HashMap<String,Integer> map);
}
- Mybatis에서 sql문을 처리해줄 로직을 모아둔 곳 입니다
- 나중에 이 로직을 복사해서 진짜로 처리해줄, Repository에 모아둘 예정 입니다.
- getTotalCount() --> 글의 개수를 구하기 위한 로직
- updateReadCount(String num) --> num값에 따른 dto들이 클릭 되었을 때 마다 조회수를 올리기 위한 로직
- insertBoard(MemboardDto dto) --> DB에 저장할 로직
- getData(String num) --> num값에 따른 dto값을 가져오기 위한 로직 ex) 2번 num의 값들을 다 불러온다
- getMaxNum() --> 이 메소드를 사용하는 이유는 내가 글을 쓴 이후 detailpage 즉 내가 쓴글을 보기위해서는 쓴 글에 대한 num값이 필요하다. 그러므로 getMaxNum()을 사용해서 제일 최근에 올라온 num값이 detailpage로 넘겨주는num이다
- 위 글은 나중에 확실하게 이해해서 다시 쓰도록 하겠습니다.
- getList(Hash<String,Integer> map) --> 리스트 출력 및 페이징 처리하기 위한 로직
다음으로는 이 메소드들을 sql쿼리로 처리해주기 위한 Mapper 이다
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="boot.data.mapper.MemBoardMapperInter">
<select id="getTotalCount" resultType="int">
select count(*) from memboard
</select>
<update id="updateReadcount" parameterType="String">
update memboard set readcount=readcount+1 where num=#{num}
</update>
<insert id="insertBoard" parameterType="MemBoardDto">
insert into memboard(myid,name,subject,content,uploadfile,writeday) values (#{myid},#{name},#{subject},#{content},#{uploadfile},now())
</insert>
<select id="getData" resultType="MemboardDto" parameterType="String">
select * from memboard where num=#{num}
</select>
<select id="getMaxNum" resultType="int">
select ifnull(Max(num),0) from memboard
</select>
<select id="getList" parameterType="HashMap" resultType="MemboardDto">
select * from memboard order by num desc limit #{start},#{perpage}
</select>
</mapper>
- 위 MemBoardMapperInter클래스에 있는 메소드들 이름이랑 mapper에서 처리해줄 id랑 name이 무조건 같아야 한다. 그렇게 처리를 해놨다
- resultType, ParameterType 이 동일한 이유는 Dto를 생성해준곳에서 @Alias(MemBoardDto) 를 해주었기 때문에,이 어노테이션에 의해 MemBoardDto로 한다 (원래 alias는 별명으로 더 줄여서 하는데, 저는 그냥 똑같이 함)
#3 다음으로는 위 메소드들을 처리해주긴 위한 로직 입니다.
위 Mapper 메소드들을 그대로 복사해와서 메소드를 모아둘 인터페이스를 생성했다
MemboardServiceRepository.java
package boot.data.service;
import java.util.HashMap;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import boot.data.dto.MemBoardDto;
@Mapper
@Repository
public interface MemboardServiceRepo {
public int getTotalCount();
public void updateReadcount(String num);
public void insertBoard(MemBoardDto memBoardDto);
public MemBoardDto getData(String num);
public int getMaxNum();
public List<MemBoardDto> getList(int start, int perpage);
}
- 여기서 궁금한게 Mapper와 repository 를 둘다 왜 선언했는지가 궁금 할 수도 있다
- https://pamyferret.tistory.com/69 이 글을 보고 확실이 이해할 수 있었습니다 (궁금하신 분은 읽어보세요)
- 결론만 말하면 Repository라는 박스안에, 작은 박스중 하나가 Mapper라는 것을 알수 있었다.
- https://pamyferret.tistory.com/69 이 글을 보고 확실이 이해할 수 있었습니다 (궁금하신 분은 읽어보세요)
- 그리고 이상한게 하나 있는데, Mapper에서는 List 매개변수를 HashMap<> 으로 처리하였지만, 여기서는 Hash를 넣지않고 직접적인 start, perpage 라는 int 변수를 넣었다. 왜 일까?
- 제 생각으로는 Mapper에서는 sql문을 처리해주기 위한 간접적인 메소드 생성하는 곳이고, repository는 이제 직접적으로 메소드를 처리해주어야기 때문에 확실한 값 들을 매개변수로 주었다고 생각이 듭니다..!
다음으로는 이 메소드들을 진짜로 implements 받아 구현해줄 메소드들을 보겠습니다
MemboardService.java
package boot.data.service;
import java.util.HashMap;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import boot.data.dto.MemBoardDto;
import boot.data.mapper.MemBoardMapperInter;
@Service
public class MemboardService implements MemboardServiceRepo{
@Autowired
MemBoardMapperInter memBoardMapperInter;
@Override
public int getTotalCount() {
return memBoardMapperInter.getTotalCount();
}
@Override
public void updateReadcount(String num) {
memBoardMapperInter.updateReadcount(num);
}
@Override
public void insertBoard(MemBoardDto memBoardDto) {
memBoardMapperInter.insertBoard(memBoardDto);
}
@Override
public MemBoardDto getData(String num) {
return memBoardMapperInter.getData(num);
}
@Override
public int getMaxNum() {
return memBoardMapperInter.getMaxNum();
}
@Override
public List<MemBoardDto> getList(int start, int perpage) {
HashMap<String,Integer> map = new HashMap<>();
map.put("start",start);
map.put("perpage",perpage);
return memBoardMapperInter.getList(map);
}
}
- List를 출력 할 떄 HashMap으로 담아서 페이징을 시켜줄 것이다
- map을 제네릭 타입이 <String,Integer>인 이유는 <key,value> 이기 때문에 key는 보통 String 값이 들어가서 나중에 꺼내서 쓸때 문자열을 호출해서 사용할 테고, 우리는 페이징을 해줘서 페이징 값은 숫자로 처리해줘야 하기 때문에, Integer를 넣었다.
- 그리고 왜 int 가 아닌 Integer를 넣었는지 궁금 할 수 도 있다.
- 이 것에 대한 해답을 원하면 제네릭 타입에 대하여 공부해야 한다 ✔
- Java에서 제네릭 클래스나 인터페이스를 사용할 때, 기본 데이터 타입(예: int, double)은 허용되지 않습니다. 대신, 해당 기본 데이터 타입에 해당하는 래퍼 클래스를 사용해야 합니다.
- 그리고 왜 int 가 아닌 Integer를 넣었는지 궁금 할 수 도 있다.
최종적으로 data를 처리하고 반환해줄 controller를 보겠습니다
@PostMapping("/insert")
public String insert(@ModelAttribute MemBoardDto dto , HttpSession httpSession) {
String path = httpSession.getServletContext().getRealPath("/savefile");
SimpleDateFormat sdf= new SimpleDateFormat("yyyyMMddHHmmss");
//System.out.println(path);
//업로드 할게 없으면은
if(dto.getUpload().getOriginalFilename().equals("")) {
dto.setUploadfile("no");
} else { //업로드 한 경우
String uploadFile = sdf.format(new Date()) + dto.getUpload().getOriginalFilename();
dto.setUploadfile(uploadFile);
try {
dto.getUpload().transferTo(new File(path+"\\"+uploadFile));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//아이디를 insert안하고 세션을 통해서 얻오는 방법
String myid = (String)httpSession.getAttribute("myid");
//세션에서 저장한 아이디를 dto에 저장시킨다.
dto.setMyid(myid);
//이름을 insert안하고 서비스를 통해서 얻는 방법
String name = memberService.getName(myid);
dto.setName(name);
memboardService.insertBoard(dto);
return "redirect:content?num="+memboardService.getMaxNum(); //num을 통해서 자신이 쓴 글에 목록으로 들어가진다.
}
@GetMapping("/content")
public ModelAndView content(@RequestParam String num , @RequestParam(defaultValue = "1") int currentPage) {
ModelAndView modelAndView = new ModelAndView();
memboardService.updateReadcount(num); //위 글 누르면은. 조회수가 올라가게 끔
MemBoardDto dto =memboardService.getData(num);
modelAndView.addObject("dto",dto);
//업로드 파일의 확장자 얻기
int dotLoc = dto.getUploadfile().lastIndexOf('.');
String ext = dto.getUploadfile().substring(dotLoc+1); //다음글자부터 끝까지 추출
System.out.println(dotLoc + ext);
if(ext.equalsIgnoreCase("jpg") || ext.equalsIgnoreCase("gif") || ext.equalsIgnoreCase("png") || ext.equalsIgnoreCase("jpeg")) {
modelAndView.addObject("bupload",true);
} else {
modelAndView.addObject("bupload",false);
}
modelAndView.addObject("currentPage",currentPage);
modelAndView.setViewName("/memboard/content");
return modelAndView;
}
- insert 할 때 파일을 담아서 보내줘야한다. 그래서 form에서 enctype="multipart-formdata" 를 해줬다
- 위 insert 로직에서 파일을 담아줄 path 변수를 만들어 둔다.
- 그리고 업로드 했을 때 조건이랑, 업로드 안했을 때 조건을 지정해 준다.
- 그리고 세션에서 저장된 id랑 name을 dto에 담아서 insert를 해준다.
- 왜 이렇게 하냐? 물론 dto에도 db에 저장된 레코드 들이 있을 것이다. 하지만 현재 세션에 저장되어 로그인된 아이디와 현재 로그인 한 사람을 이름을 가져와서, dto에 담아서 데이터를 넘겨주면은 나중에 form에서 myid랑 name을 비교하는 로직을 짤 수가 있어서 넘긴다고 생각한다.
- 그리고 return 하는 주소를 적을 때는 content중에서, 제일 높은 숫자를 redirect를 해줍니다.
- 그래서 getMaxNum() 메소드를 만들어 준 것 이다.
- 다음은 content에 내용을 띄어줄 list를 작성하는 메소드 이다.
- updateReadCount 미리 만들어둔 메소드를 통해, 새로고침 및 누군가 내 글을 눌러서 보면은 조회수가 올라가게한다
- 그리고 num값에 따른 dto들을 modelandview 에 담아서 form으로 보내준다.
- 업로드 파일의 확장자 얻기 이 로직은 그냥 이런게 있다는 것을 알아두고 이 로직을 작성하게 되면은 내가 파일을 업로드를 하면은 그 업로드된 파일을 다시 내가 다운받을 수 있다
- 위 로직은 아래 코드에서 더 자세하게 알 수 있다.
DowonloadController.java
package boot.data.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
public class DownloadController {
//외부서버의 파일을 내 컴퓨터로 다운로드하는소스
@GetMapping("/memboard/download")
public void download(HttpServletRequest request,
HttpServletResponse response,
@RequestParam String clip)
{
String path=request.getSession().getServletContext().getRealPath("/savefile");
File file=new File(path+"\\"+clip);
System.out.println("파일 경로:"+file);
setHeaderType(response, request, file);
try {
transport(new FileInputStream(file),
response.getOutputStream(), file);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void setHeaderType(HttpServletResponse response,
HttpServletRequest request,
File file)
{
String mime = request.getSession().getServletContext().getMimeType(file.toString());
if(mime != null)
mime = "application/octet-stream";
response.setContentType(mime);
response.setHeader("Content-Disposition",
"attachment;filename=" + toEng(file.getName()));
response.setHeader("Content-Length", "" + file.length());
}
private void transport(InputStream in, OutputStream out, File file)
throws IOException
{
BufferedInputStream bin = null;
BufferedOutputStream bos = null;
try{
bin = new BufferedInputStream(in);
bos = new BufferedOutputStream(out);
byte[] buf=new byte[(int)file.length()];
int read=0;
while((read = bin.read(buf)) != -1)
{
bos.write(buf, 0, read); //객체, 시작(offset), 길이
}
}catch(Exception e){
System.out.println("transport error : " + e);
}finally{
bos.close();
bin.close();
}
}
//////////////////////////////////////////////////////////////
public String toEng(String str)
{
String tmp=null;
try{
tmp = new String(str.getBytes("utf-8"), "8859_1");
}catch(Exception e){}
return tmp;
}
}
이상 입니다.
728x90