Coding History

국비 지원 IT(웹앱개발) 취업반 강의 29일차 (DB, JDBC)

BlackBirdIT 2024. 7. 16. 19:34

우선 어제 하던걸 이어서 수정, 삭제 권한 확인을 이어보자면, 중복코드가 존재한다.

public void doDelete(String cmd) {
        if(Container.session.loginedMember == null){
            System.out.println("로그인 안되어 있어.");
            return;
        }
        int id = 0;

        try {
            id = Integer.parseInt(cmd.split(" ")[2]);
        } catch (Exception e) {
            System.out.println("번호는 정수로 입력해");
            return;
        }

        boolean canAccess = articleService.canAccess(id);

        int deleteId = articleService.isExistId(id);

        if (deleteId == 0) {
            System.out.println(id + "번 게시물 없어.");
            return;
        }
        if(canAccess){
                articleService.doDelete(id);
                System.out.println(id + "번 글이 삭제되었습니다.");
        } else System.out.println("삭제 권한 없어.");

    }

    public void doModify(String cmd) {
        if(Container.session.loginedMember == null){
            System.out.println("로그인 안되어 있어.");
            return;
        }
        int id = 0;

        try {
            id = Integer.parseInt(cmd.split(" ")[2]);
        } catch (Exception e) {
            System.out.println("번호는 정수로 입력해");
            return;
        }

        boolean canAccess = articleService.canAccess(id);
        int articleId = articleService.isExistId(id);

        if (articleId == 0) {
            System.out.println(id + "번 게시물 없어.");
            return;
        }
        if (canAccess) {
            System.out.println("==수정==");
            System.out.print("새 제목 : ");
            String newTitle = Container.sc.nextLine().trim();
            System.out.print("새 내용 : ");
            String newBody = Container.sc.nextLine().trim();

            articleService.doUpdate(newTitle, newBody, id);

            System.out.println(id + "번 글이 수정되었습니다.");
        } else System.out.println("수정 권한 없어.");
    }

잘 살펴보면 로그인 여부 확인, 명령어에서 ID 추출 및 유효성 검사, 해당 ID가 존재하는지 확인, 접근 권한 확인하는 코드가 중복이여서 해당 중복코드를 줄일 메서드를 하나 선언해서 해결했다. 권환 없어. 라는 문구는 삭제에 대한 권한인지 수정에 대한 권한인지 까지 수정할 여력은 없어서 그냥 권한 없다고 표시했다.

    private Integer validateAndGetArticleId(String cmd) {
        if(Container.session.loginedMember == null){
            System.out.println("로그인 안되어 있어.");
            return null;
        }

        int id;
        try {
            id = Integer.parseInt(cmd.split(" ")[2]);
        } catch (Exception e) {
            System.out.println("번호는 정수로 입력해");
            return null;
        }

        int articleId = articleService.isExistId(id);
        if (articleId == 0) {
            System.out.println(id + "번 게시물 없어.");
            return null;
        }

        boolean canAccess = articleService.canAccess(id);
        if (!canAccess) {
            System.out.println("권한 없어.");
            return null;
        }

        return id;
    }

    public void doDelete(String cmd) {

        Integer id = validateAndGetArticleId(cmd);

        if (id == null) {
            return;
        }

        articleService.doDelete(id);
        System.out.println(id + "번 글이 삭제되었습니다.");
    }

    public void doModify(String cmd) {

        Integer id = validateAndGetArticleId(cmd);
        if (id == null) {
            return;
        }

        System.out.println("==수정==");
        System.out.print("새 제목 : ");
        String newTitle = Container.sc.nextLine().trim();
        System.out.print("새 내용 : ");
        String newBody = Container.sc.nextLine().trim();

        articleService.doUpdate(newTitle, newBody, id);
        System.out.println(id + "번 글이 수정되었습니다.");
    }

이렇게 중복코드를 제거해줬고 list에 작성자 표시와 detail에 작성자 표시가 남긴 했는데, detail 은 해놨다.

public void showDetail(String cmd) {
        int id = 0;
        try {
            id = Integer.parseInt(cmd.split(" ")[2]);
        } catch (Exception e) {
            System.out.println("번호는 정수로 입력해");
            return;
        }

        int articleId = articleService.isExistId(id);

        if (articleId == 0) {
            System.out.println(id + "번 게시물 없어.");
        } else {
            Map<String, Object> articleListMap = articleService.showDetail(id);

            Article article = new Article(articleListMap);

            System.out.println("번호 : " + article.getId());
            System.out.println("제목 : " + article.getTitle());
            System.out.println("내용 : " + article.getBody());
            System.out.println("작성자 : " + article.getName());
            System.out.println("작성시간 : " + article.getRegDate());
            System.out.println("수정시간 : " + article.getUpdateDate());
        }
    }
}

article 클래스에 name을 만들어서 작성자 이름을 가져올 수 있게 만들었고, 어제 list는 어려울 것 같다고 했는데 내가 로그인 되어있는 유저의 이름을 가져오는 방식으로(그러니까 틀려먹은 방식이다.), 쿼리를 고친다는 생각을 못했다. 근데 오늘 보니까 쿼리만 조금 고치고 list 메서드만 약간 손보면 쉽게 해결 되는 문제였다.

    public List<Map<String, Object>> showList() {
        SecSql sql = new SecSql();

        sql.append("SELECT *");
        sql.append("FROM article");
        sql.append("ORDER BY id DESC");

        return DBUtil.selectRows(Container.conn, sql);
    }

기존 list 메서드가 실행될 때 날리는 쿼리고,

아래는 수정한 쿼리다.

    public List<Map<String, Object>> showList() {
        SecSql sql = new SecSql();

        sql.append("SELECT a.*, m.`name`");
        sql.append("FROM article a");
        sql.append("INNER JOIN member m on a.memberId = m.id");
        sql.append("ORDER BY id DESC");

        return DBUtil.selectRows(Container.conn, sql);
    }

그리고 컨트롤러의 메서드는 정말 약간 수정만 하면 된다.

    public void showList() {

        System.out.println("==목록==");

        List<Article> articles = new ArrayList<>();

        List<Map<String, Object>> articleListMap = articleService.showList();

        for (Map<String, Object> articleMap : articleListMap) {
            articles.add(new Article(articleMap));
        }

        if (articles.isEmpty()) {
            System.out.println("게시글이 없습니다");
            return;
        }

        System.out.println("  번호  /   제목     /      작성자");
        for (Article article : articles) {
            System.out.printf("  %d     /   %s      /     %s\n", article.getId(), article.getTitle(),article.getName());
        }
    }

작성자를 받아오는 getName만 추가해주면 되니까. 이러면 작성자 표시까지 완벽하게 된다.

이후에는 article list에 글이 많아졌을 때를 대비해서 콘솔창에서 페이지화 하는 것을 강사님이 보여주셨다.

mySql의 Limit ?,? 의 개념을 설명하시려고 보여주신 것 같다.

주요 코드만 좀 뽑아와서 살펴보자면

    public void showList(String cmd) {
        System.out.println("==목록==");

//        List<Article> articles = articleService.getArticles(); // 전체 글 가져오기

        String[] cmdBits = cmd.split(" ");

        int page = 1;
        String searchKeyword = null;

        // 몇 페이지?
        if (cmdBits.length >= 3) {
            page = Integer.parseInt(cmdBits[2]);
        }

        // 검색어
        if (cmdBits.length >= 4) {
            searchKeyword = cmdBits[3];
        }

        // 한 페이지에 10개 씩
        int itemsInAPage = 10;

        List<Article> articles = articleService.getForPrintArticles(page, itemsInAPage, searchKeyword);

        if (articles.size() == 0) {
            System.out.println("게시글이 없습니다");
            return;
        }

        System.out.println("  번호  /  작성자  /   제목  ");
        for (Article article : articles) {
            System.out.printf("   %d     /   %s     /   %s   \n", article.getId(), article.getName(),
                    article.getTitle());
        }
    }
    public List<Article> getForPrintArticles(int page, int itemsInAPage, String searchKeyword) {
        int limitFrom = (page - 1) * itemsInAPage;
        int limitTake = itemsInAPage;

        Map<String, Object> args = new HashMap<>();
        args.put("searchKeyword", searchKeyword);
        args.put("limitTake", limitTake);
        args.put("limitFrom", limitFrom);

        return articleDao.getForPrintArticles(args);
    }
    public List<Article> getForPrintArticles(Map<String, Object> args) {
        SecSql sql = new SecSql();

        String searchKeyword = null;

        if (args.containsKey("searchKeyword")) {
            searchKeyword = (String) args.get("searchKeyword");
        }

        int limitFrom = -1;
        int limitTake = -1;

        if (args.containsKey("limitFrom")) {
            limitFrom = (int) args.get("limitFrom");
        }
        if (args.containsKey("limitTake")) {
            limitTake = (int) args.get("limitTake");
        }

        sql.append("SELECT A.*, M.name");
        sql.append("FROM article A");
        sql.append("INNER JOIN `member` M");
        sql.append("ON A.memberId = M.id");
        if (searchKeyword.length() > 0) {
            sql.append("WHERE A.title LIKE CONCAT('%', ?, '%')", searchKeyword);
        }
        sql.append("ORDER BY id DESC");
        if (limitFrom != -1) {
            sql.append("LIMIT ?, ?;", limitFrom, limitTake);
        }

        System.out.println(sql);

        List<Map<String, Object>> articleListMap = DBUtil.selectRows(Container.conn, sql);

        List<Article> articles = new ArrayList<>();

        for (Map<String, Object> articleMap : articleListMap) {
            articles.add(new Article(articleMap));
        }
        return articles;
    }

이런 코드들로 list에서 페이지로 나누고 검색을 할 수 있게 된다. 순차적으로 컨트롤러, 서비스, Dao 순이다.

내 코드에 적용시키지는 않았다.

다음으로 DDL, DML, DCL의 개념.

명령어 종류 명령어 설명
데이터 조작어 (DML : Data Manipulation Language SELECT 데이터베이스에 들어 있는 데이터를 조회하거나 검색하기 위한 명령어를 말하는 것으로 RETRIEVE 라고도 함
INSERT, UPDATE, DELETE 데이터베이스의 테이블에 들어 있는 데이터에 변형을 가하는 종류(데이터 삽입, 수정, 삭제)의 명령어들을 말함.
데이터 정의어 (DDL : Data Definition Language) CREATE, ALTER, DROP, RENAME, TRUNCATE 테이블과 같은 데이터 구조를 정의하는데 사용되는 명령어들로 (생성, 변경, 삭제, 이름변경) 데이터 구조와 관련된 명령어들을 말함.
데이터 제어어 (DCL : Data Control Language) GRANT, REVOKE 데이터베이스에 접근하고 객체들을 사용하도록 권한을 주고 회수하는 명령어들을 말함.
트랜잭션 제어어 (TCL : Transaction Control Language) COMMIT, ROLLBACK, SAVEPOINT 논리적인 작업의 단위를 묶어서 DML에 의해 조작된 결과를 작업단위(트랜잭션) 별로 제어하는 명령어를 말함.

그리고 데이터의 정규화에 대한 글을 읽었다.

데이터 정규화는 제 1 정규화, 2, 3으로 나뉘는데, 1 정규화에는 테이블의 컬럼이 원자값(Atomic Value, 하나의 값)을 갖도록 테이블을 분해하는 것이고, 제 2 정규화는 제 1 정규화를 진행한 테이블에 대해 완전 함수 종속을 만족하도록 테이블을 분해하는 것이다. 여기서 완전 함수 종속이라는 것은 기본키의 부분집합이 결정자가 되어선 안된다는 것을 의미한다.
3은 제 2 정규화를 진행한 테이블에 대해 이행적 종속을 없애도록 테이블을 분해하는 것이다. 여기서 이행적 종속이라는 것은 A -> B, B -> C가 성립할 때 A -> C가 성립되는 것을 의미한다.
출처: MangKyu's Diary:티스토리

데이터의 사용 용도에 따라 정규화를 지키지 않는 경우도 있다고 한다. 데이터 정규화는 테이블이 여러개로 나누어지기 때문에 검색 속도나, 용량이 늘어난다.

그리고 트랜젝션, 이는 어떠한 두가지 명령을 실행할 때 두가지가 한가지 처럼 움직이게끔 하는 개념이다. 예를 들어서 a가 b에게 계좌이체를 하는데 a의 돈은 빠져 나갔지만 b에게 돈이 들어오지 않았다. 이는 하나는 성공이고 하나는 실패지만 결과는 치명적이라고 할 수 있다. 이를 트랜젝션으로 묶으면 두개의 결과 값이 내가 원하는 결과 값이 아니라면 둘 다 실패가 되게끔 하는 개념이다.

START TRANSACTION;
    -- 이 블록안의 명령어들은 마치 하나의 명령어 처럼 처리됨
    -- 성공하던지, 다 실패하던지 둘중 하나가 됨.
    A의 계좌로부터 인출;
    B의 계좌로 입금;
    ROLLBACK; -- 문제가 생길시 ROLLBACK
COMMIT; --완벽하게 이행되었다면 COMMIT

일종의 안전장치라고 생각하면 이해하기 쉬운 개념인 것 같다.