본문 바로가기

Spring

Spring - I/O + DB gallaryboard 제작

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