7.26 (금)

1. 커넥션 풀

📌 커넥션풀을 하기 위해서 HikariCP를 주로 사용한다.

config.setConnectionTimeout(1000*19);
 //해당시간이 지나면 끊고 다른 커넥션 선택
config.setMaximumPoolSize(100); 
//톰켓(WAS)의 쓰레드 개수 (최대 동접자)
config.setMinimumIdle(1); 
//처음부터 연결을 많이하면 비용문제가 생길수 있다.




2. Paging

👉 SQL에서 페이징은 데이터를 쪼개서 여러 페이지에 걸쳐 보여주는 기법으로, 대량의 데이터를 한 번에 가져오는 대신, 페이지 단위로 나누어 가져오는것.




2-1. Paging을 하는 이유?

👉 DB에서 적은양을 처리하면 네트워크 속도가 향상 및 JAVA메모리가 낮아서 서버 처리효율성이 좋아진다.




2-2. LIMIT

👉 LIMIT는 SQL에서 페이징 처리기법으로, 테이블에 저장된 데이터의 일부만 output할 수 있어서 출력속도가 월등하다.

select * from tbl_todo order by tno desc limit 0,10;
-- 1번부터 데이터가 있을때, 1번부터 10번까지 추출하기

select * from tbl_todo order by tno desc limit 10,10;
-- tno컬럼을 내림차순으로 10번부터 10개를 추출하기

📌 limit의 첫번째 구간은 skip과 같은 느낌인데, 해당 부분엔 식(5*2)를 넣을수 없고, 값 밖에 넣을수 없다.




3. 비지니스 계층과 영속계층을 나누는 이유

📌 회사의 로직이 변경되면 기존에 내용은 변경되지 않더라도 필요한 로직만 변경할 수 있도록 비지니스 계층과 영속계층(DB)를 별도로 생성한다.

👉 이는 재사용성과 관련이 깊다.




4. 수평적분할 / 수직적분할

📌 수평적 분할 : 각 사람마다 백엔드와 프론트엔드를 나누어 개발을 하기에 회사에서는 효율성이 떨어져 수평적 분할을 선호하지않는다.
📌 수직적 분할 : 프론트와 백엔드를 구분하여 개발한다.




5. TOdoList 프로젝트 페이징 처리

5-1. Pageinfo class - 페이지 기본 코드 작성

package org.example.w2.common;

import lombok.Getter;
import lombok.ToString;

@Getter
@ToString

public class Pageinfo {

    private int page; //current page

    private int start; //시작페이지
    private int end; //마지막페이지

    private boolean prev; //이전페이지가 있을지
    private boolean next; //다음페이지가 있을지


    public Pageinfo(int page, int size, int total){
        this.page = page <= 0 ? 1 : page; //현재페이지가 0이면 안되니까, url에서 이상한 행동을 하는걸 막도록
        
        end = (int)(Math.ceil(this.page/10.0) * 10);

        start = end - 9; //시작페이지는 마지막-9

        prev = start == 1 ? false : true; // 시작페이지가 1이면 이전페이지가 false

        if(end*size < total) { //전체 데이터가 마지막페이지 * 페이지당 게시물 개수보다 크면
            next = true; // 정상
        }
        else{
            next = false;
            end = (int)(Math.ceil(total/(double)size));
            //만약 데이터가 131개가 있는데 13페이지까지 밖에 없으면 안되니까, 14페이지를 만들기 위해
        }
    }
}

👆 해당코드를 분석해보자

👉 위 클래스는 게시판의 페이지가 10개씩표출될때 다음페이지로 넘기기 위해 버튼으로 만들어놓은 코드.
👉 매개변수로 현재page, 한페이지당 몇개의 게시물을 표출하는 size, 전체 데이터의 개수 total을 받아 prev, next등이 계산된다.




5-2. ListController class



@WebServlet(value = "/todo/list") //목록페이지
@Log4j2
public class TodoListController extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String pageStr = req.getParameter("page"); //url주소창에 example.com/todo/list?page=21 에 해당하는 21의 값을 가져옴

        log.info("pageStr: " + pageStr);

        int page = StringUtil.getInt(pageStr, 1);
        //페이지 이상하면 1페이로 넘어가기

        try {
            int total = TodoDAO.INSTANCE.getTotal();
            //예외처리를 해줘야하는데 던지면 오버라이딩 상속에 문제가 생김 그래서 일단 try-catch

            Pageinfo pageInfo = new Pageinfo(page, 10, total);
            //현재 / 게시물개수 / 데이터개수

            List<TodoVO> todoList = TodoDAO.INSTANCE.list(pageInfo.getPage());
            //해당페이지를 주면 todoList의 값을 준다.

            req.setAttribute("todolist", todoList);

            req.setAttribute("pageInfo", pageInfo);
            //더미데이터를 jsp로 옮기기

            req.getRequestDispatcher("/WEB-INF/todo/list.jsp").forward(req, resp); //view
            //list.jsp로 출발

        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

📌 코드의 작성순서는 해당 클래스의 모든 코드를 작성하는것이 아니지만 코드의 해석을 위주로 할것임.

👉 컨트롤러에서 jsp로 setAttribute메소드를 통해서 데이터가 이동되는데, 더미데이터를 통해 (Pageinfo pageInfo = new Pageinfo(1,10,130); / req.setAttribute("pageInfo",pageinfo); list.jsp에 데이터가 넘어가는지 확인을하는것이 중요하다.

👉 기존의 override를 통해 상속에 대한 예외를 던졌으나, 이후 추가적인 코드를 작성함에 따라 예외에 문제가 생겨 던질수없는 상황이 발발하여, 해당 클래스에서 try-catch를 사용하였다. 추가적으로 더 좋은 방법 모색이 필요함.


int total = TodoDAO.INSTANCE.getTotal();

👉 해당코드는 TodoDAO의 Enum에서 생성된 메소드인 getTotal을 사용하기 위해 변수를 생성했는데, 이 메소드는 데이터베이스에서 전체레코드의 개수를 가져오기 위함.




List<TodoVO> todoList = TodoDAO.INSTANCE.list(pageInfo.getPage());

👉 해당코드는 TodoVO라는 db table과 같은 구조를 가진 인스턴스로 구성되어있는 리스트를 해당하는 페이지에 대한 내용을 DAO -> 컨트롤러에서 가져오는 코드를 이후 setAttribute를 통해서 jsp로 이동된다.




5-3. StringUtil class

package org.example.w2.common;

public class StringUtil {

    public static int getInt(String str, int defaultValue){

        if(str == null || str.length() == 0) { //URL에서 가져온 pageStr의 값은 문자열 '21' 이므로 정수로 변경해야함.
            return defaultValue;
        }
        try {
            return Integer.parseInt(str);
        }catch (Exception e) {
            return defaultValue;
        }
    }
}

👉 해당코드는 Controller 코드와 연관이 있는데, 페이지를 선택하게 되면 url에도
?page=1 과 같이 표출이되는데, 이부분의 데이터를 가져오는것을 이용한다.

String pageStr = req.getParameter("page"); 


        int page = StringUtil.getInt(pageStr, 1);

👉 getParameter를 통해서 page의 파라미터에 해당하는 값을 추출하는데,
사용자가 12페이지를 보고있다면 example.com/todo/list?page=12 라는 url이 나오게된다.
그때 pageStr값은 문자열 '12'이므로. StringUtil의 클래스를 통해서 null 혹은 0 값이라면 default값인 1로 반환하고, 그것이 아니라면 int값으로 반환하게 된다.




5-4. list.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>


<%@include file="../includes/header.jsp"%>


<h1>todo list page</h1>

<ul>
    <c:forEach items = "${todolist}" var="todo">
        <li>
            <div>
                <div>${todo.tno}</div>
                <div>${todo.title}</div>
            </div>
        </li>
    </c:forEach>
</ul>


<ul class="pagination">

    <c:if test="${pageInfo.prev}"> <!-- 이전페이지 --> <!-- 현재 11에서 20페이지가 보인다고할때 start=11-->
        <li class="page-item"><a class="page-link" href="/todo/list?page=${pageInfo.start-1}">Previous</a></li>
    </c:if>


    <c:forEach begin="${pageInfo.start}" end="${pageInfo.end}" var="num">
        <li class="page-item ${pageInfo.page == num ? 'active':''}"><a class="page-link" href="/todo/list?page=${num}">${num}</a></li>
    </c:forEach> <!-- 현재 page와 num이 같으면 파란색으로 변경-->


    <c:if test="${pageInfo.next}"> <!-- 다음페이지 --> <!-- 현재 1에서 10페이지가 보인다고할때 end=10-->
        <li class="page-item"><a class="page-link" href="/todo/list?page=${pageInfo.end+1}">Next</a></li>
    </c:if>

</ul>


<%@include file="../includes/footer.jsp"%>

👉 JSTL 문법을 사용한 forEach문을 이용하여 items인 todolist의 객체를 활용하고, todo라는 변수(i)를 반복해서 사용한다.
todo.tno를 하게 되면 todolist에 있는 객체의 tno값을 출력하게 된다.

👉 부트스트랩을 이용하여 ul태그의 class를 pagination을 사용하고
3개의 단락으로 나누었다.

👉 prev 버튼 (11번째 페이지를 선택했을때 1~10)페이지를 누르기위함이고, pageInfo의 클래스에서 참 일경우 if문이 참이 되어 아래 li태그가 실행된다.

👉 num 버튼 페이지는 forEach의 시작점인 begin과 마지막인 end에 각 start,end를 넣고 반복되는 num변수를 생성하여 버튼을 눌렀을때 해당 url로 이동하도록 구성하였다.

👉 next 버튼도 prev와 반대되도록 구성하였다.




5-5. TodoVO Class


@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class TodoVO { //db의 레코드 한줄한줄은 todovo의 인스턴스이다. 테이블 구조가 같으니까.

    private int tno;
    private String title;
    private String writer;
    private Timestamp regDate;
    private Timestamp modDate;
    private boolean delFlag;


}

👉 tbl_table과 같은 구성을하여 db에 있는 값을 그대로 가져와 list로 만들어서 한줄씩 출력하기위해 구성에 필요한 annotaion을 사용하였다.




5-6. TodoDAO ENUM



@Log4j2
public enum TodoDAO {
    INSTANCE;

    //페이지의 total count값을 가져와야해서 db와 연결해줘야함
    public List<TodoVO> list(int page) throws Exception{ //arraylist는 클래스 list 더 추상적이니까 사용함

        int skip = (page -1) *10; //db에서의 limit

        String query = """
            select * from tbl_todo
                             where
                                 tno > 0
                               and
                                 delflag = false
                             order by tno desc
                    limit ?,10
        """;
        //limit 의 첫번째를 변수처리하기위해서 db안에서는 limit값에 값밖에 안들어가니까.
        //1페이지는 1번부터 10번까지 있으므로 limit 0 10
        //2페이지는 11번부터 20번까지 있으므로 limit 10 10
        //그러면 limit (page-1)*10 10

        @Cleanup Connection con = ConnectionUtil.INSTANCE.getDs().getConnection(); //connection pool을 사용하기 위함.
        @Cleanup PreparedStatement ps = con.prepareStatement(query);
        ps.setInt(1, skip); //첫번째 물음표의 값은 skip이야.
        @Cleanup ResultSet rs = ps.executeQuery();

        List<TodoVO> list = new ArrayList<>(); //데이터타입이 List<TodoVO> 인 ArrayList 생성

        while (rs.next()) { //re.next() 레코드 한줄씩 넘기기

            TodoVO vo = TodoVO.builder()
                    .tno(rs.getInt("tno")) //tno에다가 tno 컬럼값을 읽어서 넣자
                    .title(rs.getString("title"))
                    .writer(rs.getString("writer"))
                    .regDate(rs.getTimestamp("regdate"))
                    .modDate(rs.getTimestamp("moddate"))
                    .delFlag(rs.getBoolean("delflag"))
                    .build();

            list.add(vo); //list에 한줄씩 추가하기

        }


        return list;
    }

    public int getTotal() throws Exception {
        log.info("GetTotal");

        String query = "select count(tno) from tbl_todo where tno > 0 and delflag = false";

        @Cleanup Connection con = ConnectionUtil.INSTANCE.getDs().getConnection();
        @Cleanup PreparedStatement ps = con.prepareStatement(query);
        @Cleanup ResultSet rs = ps.executeQuery();

        rs.next(); //앞에 쓸대없는 정보는 읽지않고 한개 넘기기 tno count 85016 읽기위해서
            //rs의 count라는 한개의 행이 필요한데, 첫번째는 버리고 두번째에 그 카운트의 개수가 적혀있음.
        int total = rs.getInt(1);
            //getInt는 ResultSet에서 제공하는 메소드로 해당되는 인덱스의 값을 정수로 가져온다.


        return total;
    }
}




5-7. ConnectionUtil ENUM

package org.example.w2.common;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

public enum ConnectionUtil {

    INSTANCE;

    //결과적으로 만들어내야하는것
    private HikariDataSource ds;

    //Connection pull 하기위함.
    private ConnectionUtil() {
        HikariConfig config = new HikariConfig();
        config.setDriverClassName("org.mariadb.jdbc.Driver");
        config.setJdbcUrl("jdbc:mariadb://localhost:13306/webdb");
        config.setUsername("webdbuser");
        config.setPassword("webdbuser");
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        config.setConnectionTimeout(1000*19); //해당시간이 지나면 끊고 다른 커넥션 선택
        config.setMaximumPoolSize(20); //톰켓(WAS)의 쓰레드 개수 (최대 동접자)
        config.setMinimumIdle(1); //처음부터 연결을 많이하면 비용문제가 생길수 있다.

        ds = new HikariDataSource(config);
    }


    //필요할때마다 가져갈수있도록
    public HikariDataSource getDs(){
        return ds;
    }


}

👉 커넥션 풀을 이용하기 위해 HikariCP를 사용한다.

0개의 댓글