카테고리 없음
Board,Attach(spring.ver),Spring:이미지로컬다운로드,session생명주기셋팅_210618(금)
docc
2021. 7. 26. 23:00
1. 이미지로컬 다운로드
>> 게시물컨트롤
@RequestMapping(value="board", method=RequestMethod.POST)
public ModelAndView 게시물등록하다(@ModelAttribute Board board, HttpSession session) {//세션필요하면 그냥 매개값으로 Spring 이 알아서 준다.
// 요청
ModelAndView mv = new ModelAndView();
//다른값들은 알아서 들어가지만 회원의 번호는 session 통해서 가져와 넣어놓고 DB에 save 해야한다.
int 로그인한회원번호 = (Integer)session.getAttribute("no");
Member 작성한회원 = new Member();
작성한회원.setNo(로그인한회원번호);
board.setWriter(작성한회원);
boardDAO.save(board);
// 경로지정
mv.addObject("title", board.getTitle());
mv.setViewName("board/게시물등록결과통보");
return mv;
}
폼 전송 기능을 하는 <input type="submit"> 과 <button> 은 기능적으로 동일하다. 기본적으로 button 요소는 type 속성을 명시하지 않으면 submit 기능을 수행한다. 즉 폼에서 이를 대체하기 위한 목적으로는 안성맞춤이다.
Board 와 Attach 가 서로 참조하면 서로의 정보를 확인할 수 있으니
서로 객체를 멤버변수로 놓는다.(1 대 다관계의 서로참조 )
public class Attach {
private int no;
private String name;
private long size;
private Board board;
//...
}
public class Board {
// 일반속성 = 원시속성
private int no;
private String title;
private String contents;
private Date wdate;
private long views;
private Member writer;
private List<MultipartFile> attachFiles;
private List<Attach> attachs;
// 중략...
public List<Attach> getAttachs() {//내부의 attachFiles 의 값을 가져와 set하고 get까지 하는 역할
if(attachFiles!=null && this.attachs==null) {
this.attachs = new ArrayList<Attach>();
for(MultipartFile multipartFile: attachFiles) {//배열로 된걸 하나씩 꺼내기
Attach attach = new Attach();
//multipartFile 을 통해 파일의 이름을 가져올 수 있다.
attach.setName(multipartFile.getOriginalFilename());
attach.setSize(multipartFile.getSize());
//서로를 참조하는 관계가 되었다.
attach.setBoard(this);
attachs.add(attach);
}
}
return attachs;
}
public void setAttachs(List<Attach> attachs) {//DB에 있는 값을 받는 역할
for(Attach attach: attachs) {
attach.setBoard(this);//AttachDAO 에서 하는 것이 아니라 Board 객체안에서 한다.
}
this.attachs = attachs;
}
}
board 에는 번호를 넣지 않아 board 에서 getNo() 할 수 없다.(DB에서는 저장하면서 번호를 넣는다.)
그러면 attach 테이블에 insert 시 이름, 사이즈 는 있으나 fk 인 board 의 no 이 없기 때문에 insert 불가능하다.
밑의 방법대로 해보자.
>> BoardDAO (IBoardDAO 에서 save() 메서드 반환값을 void -> int )
//게시물 저장하기.
public int save(Board board) {
Connection DB연결관리자 = null;
PreparedStatement ps = null;
try {
DB연결관리자 = ConnectionUtil.getConnection();
// insert 할 때 미리 키값을 가져올거라고 말해줌.
ps = DB연결관리자.prepareStatement("insert into board(title,contents,writer) values(?,?,?)",Statement.RETURN_GENERATED_KEYS);
ps.setString(1, board.getTitle());
ps.setString(2, board.getContents());
ps.setInt(3, board.getWriter().getNo());
ps.executeUpdate();
// 방금 executeUpdate() 한 튜플의 primary 키를 가져온다.
// rs.getInt("컬럼명"); 이렇게하면 키값을 불러올수없다.
// 절대로 rs.getInt(1); 이렇게 해야한다.
ResultSet rs = ps.getGeneratedKeys();
if(rs.next()){
board.setNo(rs.getInt(1));
}
rs.close();
ps.close();
DB연결관리자.close();
// 위는 BoardDAO 의 커넥션. attachDAO의 메서드에도 Connectino 을 쓰니 위에꺼 닫힌 후에 해야함!
for(Attach attach : board.getAttachs()) {
attachDAO.save(attach);
}
} catch (Exception e) {
e.printStackTrace();
}
return board.getNo();
}
>> AttachDAO
@Override
public void save(Attach 첨부) {
Connection DB연결관리자 = null;
PreparedStatement ps = null;
try {
DB연결관리자 = ConnectionUtil.getConnection();
ps = DB연결관리자.prepareStatement("insert into attach(name,size,board_no) values(?,?,?)");
ps.setString(1, 첨부.getName());
ps.setLong(2, 첨부.getSize());
ps.setInt(3, 첨부.getBoard().getNo());
ps.executeUpdate();
ps.close();
DB연결관리자.close();
} catch (Exception e) {
e.printStackTrace();
}
}
1. 게시물 DB저장 | ||
2. 게시물번호가져옴 ( DAO 의 save() 메서드를 저장후 게시물 번호를 가져오도록 바꿀 것이다.) Statement 의 getGeneratedKeys() 로 방금 실행한 sql 문의 튜플의 테이블을 가져올 수 있다. 단, Statement.RETURN_GENERATED_KEYS 를 실행할 sql 문에 입력해줘야 준비해서 가져온다. 테이블을 가져오니까 ResultSet으로 받아서 하던대로 getInt() 하면 된다. |
||
3. 게시물번호로 폴더만들기 1) 경로 생성 경로로 폴더생성시 없으면 만들고 있으면 알아서 안 만든다. 하지만 폴더와 파일(뒤에확장자있으면 파일로인식)을 한번에 만들지는 못한다. (마지막에 오는 파일 또는 폴더만 만들기 때문.) 그래서 폴더, 파일따로 만든다. |
||
4. 첨부파일 이름 , 사이즈를 저장 1) 파일 생성 new File(경로 + "파일명.txt") 는 경로에 파일명.txt 라는 이름만 있는 빈 파일이 생성된 것이다. 2) 생성된 파일에 multipartFile 이 가져온 파일을 적기. multipartFile.transferTo(file); |
||
5. BoardDAO 에서 save() 하며 AttachDAO 의 save() 를 호출해 같이 저장한다. |
서버의 경로개념 폴더 저장경로는 이클립스 내에 upload/board 로 만들면 되는데 이 폴더에 업로드시 실제로는 서버인 tomcat 이 돌 때 이클립스 내에 만든것을 복사해서 어딘가에 경로를 두고 거기에 파일이 업로드 된다. ( --> 파일 업로드는 서버를 통해 들어오고 있으니까 Tomcat 서버가 복사해간 프로젝트 아래에 폴더명에 맞게 저장한다. ) 그래서 eclipse 내에 만든 upload/board 폴더에는 파일이 없는 것이다. 이 Tomcat 서버가 복사해간 폴더 경로를 알아내기 위해 getRealPath("/upload/board"); 사용한다. |
>> 게시물컨트롤
@RequestMapping(value="board", method=RequestMethod.POST)
public ModelAndView 게시물등록하다(@ModelAttribute Board board, HttpSession session) {//세션필요하면 그냥 매개값으로 Spring 이 알아서 준다.
// 1,2. 저장하고 게시물번호 가져오기
Member writer = new Member();
// 저장시 작성자에 작성자 번호를 셋팅하고, 셋팅된 작성자를 board 에 넣기.
writer.setNo((int)session.getAttribute("no"));
board.setWriter(writer);
int 새게시물번호 = boardDAO.save(board);
// 첨부파일이 있을 때 저장하기.
if(board.getAttachFiles() != null) {
// 3. 새게시물번호로 폴더 만들기
System.out.println(session.getServletContext().getRealPath("/upload/board"));
String boardPath = session.getServletContext().getRealPath("/upload/board");
String 새게시물번호경로 = boardPath + "//" + 새게시물번호;
File folder = new File(새게시물번호경로);
folder.mkdir();
// 4. 폴더에 첨부파일 저장.
for(MultipartFile multipartFile: board.getAttachFiles()) {
//
File file = new File(새게시물번호경로 + "//" + multipartFile.getOriginalFilename());
// 위의 파일에다가
try {
multipartFile.transferTo(file);//input 등 io 가 할 일을 혼자 다해줌.
} catch (Exception e) {
e.printStackTrace();
}
}
}
//요청
//경로지정
ModelAndView mv=new ModelAndView();
mv.addObject("title", board.getTitle());
mv.setViewName("board/게시물등록결과통보");
return mv;
}
이미지 로컬 저장하기!!! ( DB 저장은 아래에서 다시) | ||
1. board 객체에 자동 매핑되어 List<MultipartFile> 에 파일들이 들어간다. ( 어제자꺼 확인해보기) | ||
2. 그 board 의 List<MultipartFile> 을 가져와 for 문으로 MultipartFile 을 하나씩 가져온다. | ||
3. 넣을 폴더를 만든다. File 클래스를 이용해 생성시 폴더명을 넣고 mkdir() 하면됨 | ||
4. File클래스로 이번엔 file 을 생성을 위한 정보를 넣는다. (폴더, 파일명(꺼내온 multipartFile 을 통해 얻기)) | ||
5. multipartFile가 가지고 있는 파일을 transferTo() 메서드로 정보를 넣은 file의 경로와 이름대로 저장한다. | ||
File 하는 일 1) 폴더 만들기 2) multipartFile 이 로컬에 업로드 할 때의 폴더+파일명 을 제공한다. multipartFile 하는 일 1) 폴더 사이즈, 이름 제공 (업로드시 이름 필요, DB에 사이즈 , 이름 필요) 2) file 에게서 어디에(폴더) 어떤이름 으로 저장할 지 얻은 정보대로 파일을 로컬에 저장한다. |
2. Session 생성시간 늘리기
1. 생성하는 컨트롤에서 session 객체로 설정
s(초) 단위
session.setMaxInactiveInterval(60*30);
//session이 존재하는 시간. 즉, 로그인 유지시간이다.
2. web.xml 에서 설정
분단위
<web-app>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
3. 회원가입 프로필 등록시 size 제한하기
바이트 단위
<script>
function 그림파일읽어출력하기(event){
var 그림파일 = event.target.files[0];
if(size > 2230202){
alert("용량초과!");
return;
}
if(!그림파일.type.match('image.*')){
alert("그림파일을 선택해주세요.");
return;
}
var 파일리더 = new FileReader();
파일리더.onload = function(event2){
var myimg = document.querySelector("#myimg");
myimg.src = event2.currentTarget.result;
}
파일리더.readAsDataURL(그림파일);
}
document.querySelector("#profile").addEventListener("change",그림파일읽어출력하기);
</script>
게시물등록 view
<body>
<form action="board" method="post" enctype="multipart/form-data">
제목
<input type="text" name="title" />
<br> 내용
<textarea name="contents"></textarea>
<br> 첨부
<br>
<ul id="attachlist"></ul>
<button onclick="첨부요소추가하다()" type="button">첨부추가</button>
<input type="submit" value="등록하기"/>
</form>
</body>
<script type="text/javascript">
function 첨부요소추가하다(e) {
// ul 을 가져온다.
var 첨부리스트 = document.querySelector("#attachlist");
// ul 아래 자식요소(li)가 1개 이상은 있는데,
// 마지막 li의 첫번째 자식인 input.file 에 값이 없을 때
if (첨부리스트.childNodes.length > 0
&& 첨부리스트.childNodes[첨부리스트.childNodes.length - 1].childNodes[0].value == "") {
return 0;
}
// ul 아래 들어갈 li, input.file, input.button 을 만든다.
var li = document.createElement("li");
var fileInput = document.createElement("input");
fileInput.type = "file";
var btnDelete = document.createElement("input");
btnDelete.type = "button";
btnDelete.value = "삭제";
// containerLi 라는 속성을 직접 만들어 li 객체를 기억한다.
// 과거의 li를 지목해서 가져올려면 어딘가에 저장해야 하기 때문이다.
btnDelete.containerLi = li;
btnDelete.addEventListener("click", function(event) {
// 현재 클릭 이벤트가 발생한 btnDelete 의 containerLi속성에 저장한 li를 가져온다.
var li=event.target.containerLi;
// 저장한 li 정보의 부모인 ul 을 가져온다.
var ul=li.parentNode;
// ul 의 자식요소인 li 를 지운다.
ul.removeChild(li);
})
// li 아래에 input.file, input.button 을 넣는다.
li.appendChild(fileInput);
li.appendChild(btnDelete);
// ul 아래에 위의 li 를 넣는다.
첨부리스트.appendChild(li);
if(첨부리스트.childNodes != null){
// 이 부분 따로 변수로 만들지 않고 name에 바로 넣으면 오류난다.
var i = 첨부리스트.childNodes.length - 1;
// li 가 아니라 input.file 에다가 name 넣어야 함(근데 왜 li는 name속성이 undefinded뜨고 안되지..?)
var fileInput = 첨부리스트.childNodes[i].childNodes[0];
fileInput.name = "attachFiles[" + i + "]";
}
}
1. Board, Attach VO 설정
>> Attach<VO>
public class Attach {
private int no;
private String name;
private long size;
private Board board;
//getter, setter자리..
}
>> Board<VO>
public class Board {
private int no;
private String title;
private String contents;
private Date wdate;
private long views;
private Member writer;
private List<MultipartFile> attachFiles;
private List<Attach> attachs;
public List<MultipartFile> getAttachFiles() {
return attachFiles;
}
public void setAttachFiles(List<MultipartFile> attachFiles) {
this.attachFiles = attachFiles;
}
public List<Attach> getAttachs() {
if(attachFiles != null && this.attachs == null) {
attachs = new ArrayList<Attach>();
for(MultipartFile multi : attachFiles) {
Attach attach = new Attach();
attach.setName(multi.getName());
attach.setSize(multi.getSize());
attach.setBoard(this);
attachs.add(attach);
}
}
return attachs;
}
public void setAttachs(List<Attach> attachs) {
for(Attach attach : attachs) {
attach.setBoard(this);
}
this.attachs = attachs;
}
// 중략..
}
1. Board 와 Attach 는 서로 참조관계이다 ( DB 상에서는 Attach 만 Board의 PK 를 참조했다.) Board 는 DB에는 존재하지 않는 Attach 참조변수를 List 형으로 참조한다. |
||
2. getAttachs() 2-1 : form에서 오는 File 들은 MultipartFile 형식으로 오기 때문에 getAttachs 에 그 MultipartFile 이 get했을 Attach 의 이름,크기 등의 정보를 넘긴다. 2-2 : Attach 는 DB 상에서도 VO 상에서도 Board 를 참조하고 있으며 board 객체를 set하기 위해 setBoard(this) 로 참조변수를 채운다. |
||
3. board 에 다시 DB의 정보를 set하여 view 로 가져가기 위해 setAttachs() 에도 setBoard(this) 로 참조변수를 채운다. | ||
getAttachs : Board 로 form 의 모든 정보를 얻어와 Attach 객체에게 getAttachs()로 List<Attach> 를 넣어주며 DB에 insert 한다. setAttachs : DB 에서 view 로 가져올 때 DB에서 주는 Attach의 정보들을 setAttachs() 하여 가져간다. 정리할 AttachDAO 의 메서드로 넣거나 가져온다. |
2. BoardDAO, AttachDAO 설정
1) save()
>> AttachDAO
public void save(Attach 첨부) {
Connection DB연결관리자 = null;
PreparedStatement ps = null;
try {
DB연결관리자 = ConnectionUtil.getConnection();
ps = DB연결관리자.prepareStatement("insert into attach(name,size,board_no) values(?,?,?)");
ps.setString(1, 첨부.getName());
ps.setLong(2, 첨부.getSize());
ps.setInt(3, 첨부.getBoard().getNo());
ps.executeUpdate();
ps.close();
DB연결관리자.close();
} catch (Exception e) {
e.printStackTrace();
}
}
>> BoardDAO
//게시물 저장하기.
public int save(Board board) {
Connection DB연결관리자 = null;
PreparedStatement ps = null;
try {
DB연결관리자 = ConnectionUtil.getConnection();
// insert 할 때 미리 키값을 가져올거라고 말해줌.
ps = DB연결관리자.prepareStatement("insert into board(title,contents,writer) values(?,?,?)",Statement.RETURN_GENERATED_KEYS);
ps.setString(1, board.getTitle());
ps.setString(2, board.getContents());
ps.setInt(3, board.getWriter().getNo());
ps.executeUpdate();
// 방금 executeUpdate() 한 튜플의 primary 키를 가져온다.
// rs.getInt("컬럼명"); 이렇게하면 키값을 불러올수없다.
// 절대로 rs.getInt(1); 이렇게 해야한다.
ResultSet rs = ps.getGeneratedKeys();
if(rs.next()){
board.setNo(rs.getInt(1));
}
rs.close();
ps.close();
DB연결관리자.close();
// 위는 BoardDAO 의 커넥션. attachDAO의 메서드에도 Connectino 을 쓰니 위에꺼 닫힌 후에 해야함!
for(Attach attach : board.getAttachs()) {
attachDAO.save(attach);
}
} catch (Exception e) {
e.printStackTrace();
}
return board.getNo();
}
1. AttachDAO : 처음에 BoardDAO save() 처럼 간단히 하면 된다. | ||
2. BoardDAO : 여기서 AttachDAO 의 save() 를 호출해 board 와 attach 를 한번에 insert 해준다. 2-1 : form 에서 온 board 의 정보를 get 하여 DB에 insert 해준다 이때 Statement.RETURN_GENERATED_KEYS 를 sql 문에 선언해줘야 board 의 일련번호를 가져온다. (attach는 board 일련번호를 FK 를 가지고 있기 때문에 일련번호를 따로 가져와야 한다.//form에서는 일련번호가 오지 않기 때문에) 2-2 : 가져온 일련번호를 board 에 넣고 Board 의 getAttachs() 메서드가 참조변수 board 를 set 한 attach를 꺼내기 때문에 AttachDAO 의 save() 메서드에서 set 한 board 를 get하여 board 에 넣은 일련번호를 DB에 insert 해준다. |
2) selectByNo()
>> AttachDAO
@Override
public List<Attach> selectByBoardNo(int 게시물번호) {
ArrayList<Attach> attachs = null;
Connection connection = null;
Statement 명령전달자 = null;
try {
connection = ConnectionUtil.getConnection();
명령전달자 = connection.createStatement();
String 수집SQL = String.format("select * from attach where board_no = %d",게시물번호);
ResultSet 수집된표관리자 = 명령전달자.executeQuery(수집SQL);
if(수집된표관리자.next()) {
int no = 수집된표관리자.getInt("no");
String name = 수집된표관리자.getString("name");
long size = 수집된표관리자.getLong("size");
Attach attach = new Attach();
attach.setNo(no);
attach.setName(name);
attach.setSize(size);
if(attachs == null) {
attachs = new ArrayList<Attach>();
}
attachs.add(attach);
}
수집된표관리자.close();
명령전달자.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
return attachs;
}
>> BoardDAO
// 게시물의 번호로 게시물의 상세내역 보기(+ 조회수 증가여부)
public Board findByNo(int no, boolean addView) {
Board 찾은게시물 = null;
try {
Connection DB연결관리자 = ConnectionUtil.getConnection();
Statement 명령전달자 = DB연결관리자.createStatement();
// 조회수증가
if(addView) {
명령전달자.executeUpdate(String.format("update board set views = views + 1 where no = %d", no));
}
// 선택한 게시물 가져오기
String 수집SQL = String.format("select * from board where no = %d",no);
ResultSet 수집된표관리자 = 명령전달자.executeQuery(수집SQL);
if(수집된표관리자.next()) {
int 게시물번호 = 수집된표관리자.getInt("no");
String 제목 = 수집된표관리자.getString("title");
String 내용 = 수집된표관리자.getString("contents");
java.sql.Date 작성일 = 수집된표관리자.getDate("wdate");
int 조회수 = 수집된표관리자.getInt("views");
// 게시물의 작성자번호를 통해 member을 찾아달라고 MemberDAO에게 부탁. (작성자번호는 게시판번호(no) 이 아님!!)
int 작성자번호 = 수집된표관리자.getInt("writer");
Member member = memberDAO.findByNo(작성자번호);
// Attach들도 가져와 넣기 - board 변수만 빠져있음
List<Attach> attachs = attachDAO.selectByBoardNo(게시물번호);
찾은게시물 = new Board();
찾은게시물.setNo(no);
찾은게시물.setTitle(제목);
찾은게시물.setContents(내용);
찾은게시물.setWdate(작성일);
찾은게시물.setViews(조회수);
찾은게시물.setWriter(member);
찾은게시물.setAttachs(attachs);// 여기서 이 찾은게시물이 Attach 의 참조변수인 board에 들어감
}
수집된표관리자.close();
명령전달자.close();
DB연결관리자.close();
} catch (Exception e) {
e.printStackTrace();
}
return 찾은게시물;
}
전제 : board 상세보기 즉, board 를 하나씩 꺼내올 때만 이미지 파일을 꺼내온다. | ||
1. AttachDAO : DB 에서 Board 전체를 가져오는 메서드와 똑같은 구조다. (List<Attach>가 반환값) 주의할 점 : 참조하는건 DB 상에서는 board 테이블의 일련번호이지만 java 에서는 board 객체 자체이다. |
||
2. BoardDAO : 가져온 board 테이블에서 일련번호를 통해 AttachDAO 의 selectByBoardNO() 을 실행하여 List<Attach> 를 가져온다. 이 리스트를 setAttachs(). setAttachs() 하면 현재 board를 set하도록 VO의 메서드를 바꿨기 때문에 AttachDAO 의 메서드에서 초기화하지 않은 board 참조변수를 초기화 해준다. 다른 변수도 똑같이 set 해주고 이 셋팅된 board를 return 하면 끝. |
게시물컨트롤
@RequestMapping(value="board", method=RequestMethod.POST)
public ModelAndView 게시물등록하다(@ModelAttribute Board board, HttpSession session) {//세션필요하면 그냥 매개값으로 Spring 이 알아서 준다.
// 1,2. 저장하고 게시물번호 가져오기
Member writer = new Member();
// 저장시 작성자에 작성자 번호를 셋팅하고, 셋팅된 작성자를 board 에 넣기.
writer.setNo((int)session.getAttribute("no"));
board.setWriter(writer);
int 새게시물번호 = boardDAO.save(board);
// 첨부파일이 있을 때 저장하기.
if(board.getAttachFiles() != null) {
// 3. 새게시물번호로 폴더 만들기
System.out.println(session.getServletContext().getRealPath("/upload/board"));
String boardPath = session.getServletContext().getRealPath("/upload/board");
String 새게시물번호경로 = boardPath + "//" + 새게시물번호;
File folder = new File(새게시물번호경로);
folder.mkdir();
// 4. 폴더에 첨부파일 저장.
for(MultipartFile multipartFile: board.getAttachFiles()) {
//
File file = new File(새게시물번호경로 + "//" + multipartFile.getOriginalFilename());
// 위의 파일에다가
try {
multipartFile.transferTo(file);//input 등 io 가 할 일을 혼자 다해줌.
} catch (Exception e) {
e.printStackTrace();
}
}
}
//요청
//경로지정
ModelAndView mv=new ModelAndView();
mv.addObject("title", board.getTitle());
mv.setViewName("board/게시물등록결과통보");
return mv;
}