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 하는 편이 좋음