카테고리 없음

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;
	}

앞으로 DAO(Model) 는 라이브러리 방식으로 바꿀것이며 mybatis 라이브러리를 사용할 예정이다.

폼 전송 기능을 하는 <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;
	}