1. DB
1. table 제작
create table gallery(
gidx int(7) not null auto_increment,
gwriter char(100) not null,
gsubject varchar(200) not null,
gtext text not null,
gorifile text null, //사용자가 업로드한 파일명
gfile text null, //저장 시 랜덤함수를 이용하여 저장된 파일명
gindate timestamp not null default current_timestamp,
primary key(gidx)
);
2. MVC
2-0. 게시판 제작 전 고려할 점
① 출력되는 페이지는 제일 먼저 리스트가 출력되어야함
② 파일 저장 후에 db에 저장되어야함 -> 파일 저장이 된 것만 DB에 저장되도록 해야함
③ 모든 게시판에 대한 리스트, 상세보기 => 무조건 get 으로 처리
④ 모든 게시판의 글쓰기 => 무조건 post
⑤ 필요한 기능
⒜ 게시판 글 쓰기 (첨부파일도 o)
⒝ 게시판 리스트 출력되는 페이지
⒞ 게시판 상세보기 페이지 -> 1. 삭제(실제 웹 디렉토리에서도 삭제) 2. 수정
2-1. Module
# 1. DAO 제작 (gallery_dao.java)
-> 주의 사항 1. ) 경우에 따라 배열 갯수가 달라지므로 하나의 배열이 아니라
제목을 클릭 했을 때 보여지는 viewlist 배열 따로, 상세페이지 클릭 했을 때
보여지는 list 배열 따로 만들어야함
(메소드 하나로 조건문(받은 인자값에따라 다른 조건을 설정)을 활용해서
작성할 수 도 있음)
-> 주의 사항 2. ) 모든 값이 필수값 이라면 removeall 사용하는 것에 문제가 없으나 ,
모든 값이 필수값이 아니라면 오류발생 할 수 있음
- 해당 프로젝트에서는 file 관련 데이터는 필수값이 아님
@Getter @Setter public class gallery_dao { int gidx; String gwriter, gsubject, gtext; String gorifile, gfile; String gindate; //상세 보기 public ArrayList<Object> views(){ ArrayList<Object> al = new ArrayList<Object>(); al.add(getGidx()); al.add(getGwriter()); al.add(getGsubject()); al.add(getGtext()); al.add(getGorifile()); // al.add(getGfile()); // al.add(getGindate()); return al; } //메소드 하나로 조건문(받은 인자값에따라 다른 조건을 설정)을 활용해서 작성할 수 도 있음 //리스트 출력 public ArrayList<Object> list(){ ArrayList<Object> al = new ArrayList<Object>(); al.add(getGidx()); al.add(getGwriter()); al.add(getGsubject()); al.add(getGtext()); al.add(getGorifile()); // al.add(getGfile()); // al.add(getGindate()); //입력되는 데이터에 null 이 있을 수 도 없을 수도 있기 떄문에 //null이 나올 경우를 대비해서 al.removeAll(Arrays.asList("",null)); return al; } }
# 2. 글 저장 모듈 ( file_save.java )
-> 모듈 기능
⑴ rename 메소드 : file명이 개발자에 맞게 변경
- 파일명을 랜덤메소드를 이용하여 생성
⑵ datafile_save 메소드 : 파일이 웹 디렉토리 경로, Database에 저장
① String.join("구분할 문자",적용될 배열) : 클래스 배열을 문자열 형태로 변환해주는 메소드
② 모듈 하나에서 DB 저장, 웹디렉토리에 저장 둘 다 하기 때문에
basicdatasource 가져와야함/* * 모듈 기능 * 1.file명이 개발자에 맞게 변경 * 2.파일이 웹 디렉토리 경로에 저장 * 3. Database 에 해당 컬럼에 경로가 저장 */ public class file_save { Connection con = null; PreparedStatement ps = null; String result = ""; //결과값 (Y:성공, N:실패) //직접 호출 받지 않고 이름만 바꿈 //파일명을 랜덤메소드를 이용하여 생성 public String rename() { Date day = new Date(); SimpleDateFormat sf = new SimpleDateFormat("yyyymmdd"); String today = sf.format(day); int no = (int)Math.ceil(Math.random()*1000); String datacode = today + no ; return datacode; } //파일 저장 및 데이터베이스에 insert =>동시에 같이 하므로 basicdatasource 가져와야함 public String datafile_save(BasicDataSource dataSource, gallery_dao dao, MultipartFile files[], HttpServletRequest req) throws Exception{ this.con = dataSource.getConnection(); String sql = ""; String ori_name=""; //사용자가 업로드한 파일명 String new_name=""; //개발자가 업로드된 파일명을 다른 이름으로 변경 //System.out.println(files.length); //파일 첨부 안해도 1 찍히기 때문에 이걸로 조건문 걸면 안됨 if(files[0].getSize()>0) {//첨부파일이 있을 경우 //여러개의 파일명을 하나의 컬럼에 저장하기 위한 배열 ArrayList<String> al = new ArrayList<String>(); //파일 원본명이 담기는 배열 ArrayList<String> al2 = new ArrayList<String>(); //파일 사본명이 담기는 배열 //Module 에서 웹 경로를 로드하여 저장시킴 String url = req.getServletContext().getRealPath("/upload/"); int w=0; while(w<files.length) { al.add(files[w].getOriginalFilename()); //사본 명을 제작하는 코드 int com = files[w].getOriginalFilename().indexOf(".");//속성명만 뽑기위해 .위치를 파악 String wd = files[w].getOriginalFilename().substring(com); String refilename = this.rename() + wd; al2.add(refilename); FileCopyUtils.copy(files[w].getBytes(), new File(url + refilename)); w++; } ori_name = String.join(",", al); new_name = String.join(",", al2); sql = "insert into gallery (gidx,gwriter,gsubject,gtext,gorifile,gfile,gindate) " + "values ('0',?,?,?,?,?,now())"; }else { //첨부파일 없을 경우 sql = "insert into gallery (gidx,gwriter,gsubject,gtext,gindate) " + "values ('0',?,?,?,now())"; } try { this.ps = this.con.prepareStatement(sql); this.ps.setString(1, dao.getGwriter()); this.ps.setString(2, dao.getGsubject()); this.ps.setString(3, dao.getGtext()); if(ori_name!="") { this.ps.setString(4, ori_name); this.ps.setString(5, new_name); } this.ps.executeUpdate(); this.result = "Y"; } catch (Exception e) { //e.printStackTrace(); this.result = "N"; }finally { this.ps.close(); this.con.close(); } return this.result; } }
# 3. 갤러리 게시판 리스트 모듈 gallery_select.java
-> 모듈 기능
⑴ all_lists 메소드 : 메소드 자료형을 클래스배열로 설정해서 결과값을 배열로 리턴
① select 로 DB 에서 데이터 긁어오기
② 긁어온 데이터를 2차 배열에 담아서 리턴해줌
//갤러리 리스트 출력 public class gallery_select { Connection con = null; PreparedStatement ps = null; ResultSet rs = null; public ArrayList<ArrayList<Object>> all_lists(BasicDataSource dataSource,gallery_dao dao) throws Exception{ ArrayList<ArrayList<Object>> all = new ArrayList<ArrayList<Object>>(); try { this.con = dataSource.getConnection(); String sql = "select gidx, gwriter, gsubject, gindate, gfile from gallery order by gidx desc"; this.ps = this.con.prepareStatement(sql); this.rs = ps.executeQuery(); while(this.rs.next()) {//select 해준만큼 set 해야함 //dao에 맞춰서 순서대로 가져와야함 dao.setGidx(Integer.parseInt(this.rs.getString(1))); dao.setGwriter(this.rs.getString(2)); dao.setGsubject(this.rs.getString(3)); dao.setGfile(this.rs.getString(4)); dao.setGindate(this.rs.getString(5)); all.add(dao.list()); }; } catch (Exception e) { System.out.println("에러"); e.printStackTrace(); }finally { try { this.rs.close(); this.ps.close(); this.con.close(); } catch (Exception e2) { System.exit(0); } } return all; } }
# 4. 갤러리 게시판 상세보기 모듈 gallery_select.java
-> 모듈 기능
⑴ one_list 메소드
① Controller에 있는 static 변수형을 활용하여 select함
② 리스트 출력 시 order by desc 때려놨기 때문에 찾을 때도 동일하게
해주면 거꾸로 읽어올필요가 없어 속도가 빠름
③ 코드 간소화 위해 새로 배열을 만들지 않고 dao.views()로 바로 가져옴
④ dao 불러오는 방식의 차이에서 getter, setter 사용 여부가 갈림
-> 읽기 전용, setter만 사용할 때 (dao 가 아닌 vo가 됨) -> getter, setter 둘다 사용해야 할 때 (new 로 다시 로드해줘야함)public class gallery_select { Connection con = null; PreparedStatement ps = null; ResultSet rs = null; //Controller 에 있는 변수 모듈에서 출력하는 방법 //1. shop_main2 sm = new shop_main2(); shop_main2 sm; // 2. //프로세서가 이미 읽어서 로드가 된 상황 gallery_dao dao = new gallery_dao(); //setter, getter 모두 사용 //gallery_dao dao; //: 읽기 전용, getter 만 작동함 //갤러리 상세보기 페이지(Controller에 있는 static 변수형을 활용하여 select함) public ArrayList<Object> one_list(BasicDataSource dataSource){ //System.out.println(sm.gidx); try { this.con = dataSource.getConnection(); //리스트 출력 시 order by desc 때려놨기 때문에 찾을 때도 동일하게 해주면 거꾸로 읽어올필요가 없어 속도가 빠름 String sql = "select * from gallery where gidx=? order by gidx desc"; this.ps = this.con.prepareStatement(sql); this.ps.setString(1, String.valueOf(sm.gidx)); this.rs = this.ps.executeQuery(); this.rs.next(); //가져올 때 dao 기준으로 가져오기 this.dao.setGidx(Integer.parseInt(this.rs.getString(1))); this.dao.setGwriter(this.rs.getString(2)); this.dao.setGsubject(this.rs.getString(3)); this.dao.setGtext(this.rs.getString(4)); this.dao.setGorifile(this.rs.getString(5)); this.dao.setGfile(this.rs.getString(6)); this.dao.setGindate(this.rs.getString(7)); this.rs.close(); this.ps.close(); this.con.close(); } catch (Exception e) { e.printStackTrace(); System.out.println("오류발생"); } //배열을 새로 생성한 후 받아서 리턴해도 됨 //굳이 만들 필요 없이 dao 에서 바로 보낼 수도 있음 return dao.views(); } }
# 5. 갤러리 게시판 게시글 삭제 모듈 gallery_delete.java
-> deleteok 메소드
⑴ 파일 삭제
- 첨부 파일 유무로 조건을 나눠서 파일 삭제 진행
- 웹 디렉토리 경로 가져올 때 유의
⑵ DB 삭제public class gallery_delete { Connection con = null; PreparedStatement ps = null; ResultSet rs = null; PrintWriter pw = null; String result=""; shop_main2 sm; //게시물 삭제 public String deleteok(BasicDataSource dataSource, HttpServletRequest req) throws Exception{ try { this.con = dataSource.getConnection(); //파일 삭제 //웹에 저장된 이름을 가져와야함 (오리지널 필요없음) String query = "select gfile from gallery where gidx=? order by gidx desc"; this.ps = this.con.prepareStatement(query); this.ps.setInt(1, sm.gidx); this.rs = this.ps.executeQuery(); this.rs.next(); //첨부파일 있을 경우 삭제하는 조건문 if(this.rs.getString(1)!=null) {//파일이 실제할 경우 String filename[] = this.rs.getString(1).split(","); // , 이외 |등 쓸 경우 //|형태로 써야함 String url = req.getServletContext().getRealPath("./upload/"); int w=0; while(w < filename.length) { File f = new File(url+filename[w]); f.delete();//파일삭제를 실행하는 메소드 w++; } } //DB 삭제 String sql = "delete from gallery where gidx=? order by gidx desc"; //order by 사용 이유 : 최신글 부터 찾게하여 속도 향상을 위한것 this.ps = this.con.prepareStatement(sql); this.ps.setInt(1, sm.gidx); this.ps.executeUpdate(); this.result = "Y"; } catch (Exception e) { this.result = "N"; }finally { this.ps.close(); } return this.result; } }
2-2. View
# 1. 갤러리 게시판 (글쓰기) gallery_write.jsp
-> 첨부파일 값 받아 넘길 때 name 값은 dao에서 사용하는 이름을 사용하지 않음
<title>갤러리 게시판(글쓰기)</title> </head> <body> <form id="frm" method="post" action="./galleryok.do" enctype="multipart/form-data"> 작성자 : <input type="text" name="gwriter"><br> 제목 : <input type="text" name="gsubject"><br> 내용 : <textarea rows="30" cols="100"name="gtext"></textarea> <!-- 첨부파일 만 dao에서 사용하는 이름을 사용하지 않음 --> <!-- dao 이름을 쓰면 안됨, 못받음 --> 첨부파일 : <input type="file" name="files" multiple="multiple"><br> <input type="button" value="등록" onclick="gallery()"> </form> </body> <script> function gallery() { frm.submit(); } </script> </html>
# 2. 갤러리 게시판 리스트 출력 gallery.jsp
① substring : javascript 글자 자르는 문법 (DB에는 시,분,초 까지 저장되어있으나 날짜만 나오면 되므로)
0~10글자 까지 자름
② JSTL if 문
-> null 을 사용한 이유는 Databases에 가져오는 값이 null로 입력되어있음,
컬럼에 null이 아닌 비어있는 값일 경우 ''로 표기함
③ split을 사용하기 위해서는 functions 엔진을 로드해야함
-> functions 엔진: contains , split , startWith , join(배열) , length ,indexOf
-> split(데이터 값,'분리할 문자'); => 원시배열로 구성함
④ 상세페이지로 이동하는 function은 text에 a태그 붙이기보다, td 나 div에 onclick 하는 편이 좋음
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="cp" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <!-- split을 사용하기 위해서는 functions 엔진을 로드해야함 --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>갤러리 리스트 출력</title> </head> <body> <p>갤러리 리스트</p> <table border="1" cellpading="0" cellspacing="0"> <thead> <tr> <th width="150">썸네일</th> <th width="150">제목</th> <th width="150">글쓴이</th> <th width="150">등록일</th> </tr> </thead> <tbody align="center"> <cp:forEach var="cpdata" items="${all_lists}" varStatus="status"> <tr> <!-- null 을 사용한 이유는 Databases에 가져오는 값이 null로 입력되어있음 컬럼에 null이 아닌 비어있는 값일 경우 ''로 표기함 --> <cp:if test="${cpdata.get(4)==null}"> <td> NO IMAGE </td> </cp:if> <!-- fn : contains, split, startWith, join(배열), length,indexOf split(데이터 값,'분리할 문자'); => 원시배열로 구성함 --> <cp:if test="${cpdata.get(4)!=null}"> <cp:set var="imgs" value="${fn:split(cpdata.get(4),',')}"></cp:set> <td> <img src="./upload/${imgs[0]}" width="100" height="100"> </td> </cp:if> <!-- 상세페이지로 이동 --> <td align="left" onclick="gallery_view('${cpdata.get(0)}')">${cpdata.get(2)}</td> <td>${cpdata.get(1)}</td> <td>${cpdata.get(3).substring(0,10)}</td> </tr> </cp:forEach> </tbody> </table> </body> <script> function gallery_view(no) { //상세 페이지로 이동 location.href="./gallery_view.do?gidx="+no; } </script> </html>
# 3. 갤러리 게시판 상세보기 gallery_view.jsp
-> jstl 코어 로드
⑴ 사용자가 업로드한 파일명도 유지하면서 개발자가 변경한 파일명으로 출력
-> forEach에 items사용불가
⑵ 클래스 배열에 fn:split을 사용할 경우 원시배열로 바뀜 : get을 못쓰고 []로 출력해야함
⑶ javascript 변수로 jstl 값의 one_list.get(0) (==gidx) 값을 받아서 핸들링<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="cp" 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> 작성자 : ${one_list.get(1)}<br> 제목 :${one_list.get(2)}<br> 내용 :${one_list.get(3)} <br> <!-- 사용자가 업로드한 파일명도 유지하면서 개발자가 변경한 파일명으로 출력을 할 경우 forEach에 items사용불가 --> <!-- 클래스 배열에 fn:split을 사용할 경우 원시배열로 바뀜 : get을 못쓰고 []로 출력해야함 --> <cp:set var="imgs" value="${fn:split(one_list.get(4),',')}"></cp:set> <cp:set var="imgs2" value="${fn:split(one_list.get(5),',')}"></cp:set> <cp:set var="ea" value="${fn:length(imgs) }"></cp:set><!-- 원시배열크기, 굳이 필요는 없음 --> <cp:forEach var="no" begin="0" end="${ea-1}"> <!-- target="_blank" : 새로운 창으로 띄움 --> 첨부파일 :<a href="./upload/${imgs2[no]}" target="_blank">${imgs[no]}</a><br> <!-- 작성자가 올린 파일명 --> </cp:forEach> <br><br> <input type="button" value="게시물 삭제" onclick="del_gallery"> </body> <script> function del_gallery() { //javascript 변수는 jstl, jsp 변수를 모두 받을 수 있음 var gidx="${one_list.get(0)}"; if(confirm("해당 게시물 삭제시 복구 되지 않습니다. 삭제하시겠습니까?")){ location.href='gallery_del.do?gidx='+gidx; } } </script> </html>
2-3. Controller
# 1. 갤러리 게시판 (글 저장) - galleryok.do
-> 파일이 저장 된 후에 DB에 저장시켜야함
-> 모듈을 하나만 생성해서 제작함
@Controller public class shop_main2 { //데이터 베이스 사용위해 작성 @Autowired BasicDataSource dataSource; PrintWriter pw = null; @PostMapping("/galleryok.do") public void galleryok(@ModelAttribute("ga") gallery_dao dao, @RequestParam("files") MultipartFile files[], HttpServletResponse res, HttpServletRequest req) throws Exception{ //배열 기준으로 첫번째 값만 확인하여 I/O 실행 유무를 나눔 //1. 모듈을 하나만 이용하는 방법 //2. 별도의 모듈을 이용하는 방법 //수업에서는 1번 시현 String result = new file_save().datafile_save(dataSource, dao, files ,req); res.setContentType("text/html;charset=utf-8"); this.pw = res.getWriter(); if(result=="Y") { this.pw.write("<script>" + "alert('정상적으로 게시물이 등록되었습니다.');" + "location.href='./gallery.do';" + "</script>"); }else { this.pw.write("<script>" + "alert('데이터 오류로 인하여 등록되지 않았습니다.');" + "history.go(-1);" + "</script>"); } this.pw.close(); } }
# 2. 갤러리 게시판 (리스트) 출력 - gallery.do
-> 모듈에서 던진 2차배열을 JSTL 로 찍어줌
-> JSTL 로 찍기 위해 Model 사용
@Controller public class shop_main2 { //데이터 베이스 사용위해 작성 @Autowired BasicDataSource dataSource; //갤러리 게시판 리스트 @GetMapping("/gallery.do") public String gallery_list(Model m,gallery_dao dao) throws Exception { ArrayList<ArrayList<Object>> all_lists = new gallery_select().all_lists(dataSource); m.addAttribute("all_lists",all_lists); return null; } }
# 3. 갤러리 게시판 상세보기 Controller
-> gallery_view 메소드
-> gallery.jsp 에서 get으로 데이터 받아와서 모듈로 넘긴 후 모듈에서 클래스배열로 데이터를 받아
jstl 형식으로 gallery_view.jsp에 넘겨줌@Controller public class shop_main2 { @Autowired BasicDataSource dataSource; PrintWriter pw = null; public static int gidx; //갤러리 게시판 상세보기 @GetMapping("gallery_view.do") //권고사항은 integer : integer는 null도 사용하기 때문 public String gallery_view(Model m, @RequestParam(defaultValue = "0",required = false) Integer gidx) { this.gidx = gidx; //모듈 불러오기 ArrayList<Object> one_list = new gallery_select().one_list(dataSource); m.addAttribute("one_list",one_list); return null; } }
# 4. 게시물 삭제 shop_main2.java
-> gallery_del 메소드
① 모듈로 gidx , req 만 넘겨줌 (모듈에서 삭제 처리)@Controller public class shop_main2 { @Autowired BasicDataSource dataSource; PrintWriter pw = null; public static int gidx; //갤러리 게시판 삭제 @GetMapping("gallery_del.do") public void gallery_del (@RequestParam(required = true) int gidx, HttpServletResponse res, HttpServletRequest req) throws Exception { res.setContentType("text/html;charset=utf-8"); this.pw=res.getWriter(); try { this.gidx = gidx; String callback = new gallery_delete().deleteok(dataSource,req); if(callback=="Y") { this.pw.write("<script>" + "alert('정상적으로 게시물이 삭제 되었습니다.');" + "location.href='./gallery.do';" + "</script>"); }else { this.pw.write("<script>" + "alert('데이터 오류로 인하여 게시물이 삭제되지 않았습니다.');" + "location.href='./gallery.do';" + "</script>"); } } catch (ClassCastException ce) { this.pw.write("<script>" + "alert('올바른 접근 방법이 아닙니다.');" + "location.href='./gallery.do';" + "</script>"); }catch (Exception e) { e.printStackTrace(); System.out.println(e); } this.pw.close(); } }
'Spring' 카테고리의 다른 글
JSTL 문법 (1) | 2024.07.15 |
---|---|
Exception 예외처리 (0) | 2024.07.12 |
Spring - I/O 파일 업로드, 저장 (0) | 2024.07.11 |
Spring - 쿠폰 생성 프로세서 (0) | 2024.07.10 |
legacy project 생성 (0) | 2024.07.09 |