7.29 (์›”) ์˜ค์ „

1. DB๊ฐ์ฒด ์ง€์—ญ๋ณ€์ˆ˜์„ ์–ธ

๐Ÿ‘‰ close ์˜ ์˜๋ฏธ๋Š” ์›๊ฒฉ์— ํ†ต๋ณด๋ฅผ ํ•˜๋Š” ์˜๋ฏธ๋กœ GC๊ฐ€ ์ œ ์—ญํ• ์„ ํ•ด๋„ ๋œ๋‹ค๋Š” ๋ง์ด๋‹ค.

๐Ÿ‘‰ ๋งŒ์•ฝ์— Connection ๊ฐ์ฒด๋ฅผ ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜๋กœ ํ• ๋‹นํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ํ•œ ์‚ฌ๋žŒ์ด 1๋ฒˆ ์ž‘์—…์„ ํ•˜๊ณ  ์žˆ์„๋•Œ ๋‘ ๋ฒˆ์งธ ์‚ฌ๋žŒ์ด ์ค‘๊ฐ„์— 2๋ฒˆ์ž‘์—…์„ ํ•˜๋ ค๊ณ  ๋“ค์–ด์˜จ๋‹ค๋ฉด, ์ดํ›„ close๋ฅผ ํ• ๋•Œ๋Š” 1๋ฒˆ์ž‘์—…์ด ์•„๋‹ˆ๊ณ  2๋ฒˆ์ž‘์—…์ด close๊ฐ€ ๋˜๋ฏ€๋กœ GC๊ฐ€ ์ œ ์—ญํ• ์„ ํ•˜์ง€ ๋ชปํ•œ๋‹ค.

๐Ÿ‘‰ ๊ณ ๋กœ, Connection, PreparedStatement, ResultSet๊ณผ ๊ฐ™์€ DB์™€ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฐ์ฒด๋“ค์€ ์ง€์—ญ๋ณ€์ˆ˜๋กœ ์„ ์–ธํ•ด์•ผํ•œ๋‹ค




2. Thread safe๋ž€?

๐Ÿ‘‰ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ํ™˜๊ฒฝ์—์„œ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ์ ‘๊ทผํ•˜๋”๋ผ๋„ ํ”„๋กœ๊ทธ๋žจ์ด ์˜๋„ํ•œ ๋Œ€๋กœ ์ •ํ™•ํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.

๐Ÿ‘‰ Thread safe ๊ธฐ๋ฒ•

๐Ÿ‘‰ ๋™๊ธฐํ™” ๋งค์ปค๋‹ˆ์ฆ˜ : ๋™๊ธฐํ™”๋ฅผ ํ†ตํ•ด ํ•œ ์Šค๋ ˆ๋“œ๊ฐ€ ์ ‘๊ทผ์ค‘์ธ ์ž์›์— ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋„๋ก lock ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™๊ธฐํ™”๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค

๐Ÿ‘‰ ๋ถˆ๋ณ€๊ฐ์ฒด : ๊ฐ์ฒด์˜ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋„๋ก ์ง€์—ญ๋ณ€์ˆ˜๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฉ”์„œ๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ์—๋งŒ ์กด์žฌํ•œ๋‹ค.




3. Auto commit / Transcation

๐Ÿ‘‰ ๊ธฐ์กด์— 100๊ฐœ์˜ db๊ฐ€์žˆ์„๋•Œ, a์‚ฌ์šฉ์ž๊ฐ€ 30๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ db์— ๋„ฃ๊ฒŒ ๋˜๋ฉด ์„ธ์…˜์ž„์‹œ ๋ณด๊ด€์†Œ์— ๋“ค์–ด๊ฐ€ ์žˆ๋‹ค๊ฐ€ commit์„ ํ•˜๊ฒŒ๋˜๋ฉด ์ตœ์ข…์ ์œผ๋กœ 130๊ฐœ๊ฐ€ ๋œ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ๋์„๋•Œ b์‚ฌ์šฉ์ž๊ฐ€ select๋ฅผ ํ•˜๊ฒŒ ๋˜๋ฉด 130๊ฐœ๋กœ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

๐Ÿ‘‰ JDBC๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ auto-commit์ด๊ธฐ ๋•Œ๋ฌธ์— ์ž๋™์œผ๋กœ ๋ฐ˜์˜๋œ๋‹ค.

๐Ÿ‘‰ ๋ฐ์ดํ„ฐ๋ฅผ insertํ•œ ํ›„์— last-insert id ๋ฅผ ๋ฌผ์–ด๋ณด๋ฉด ๋งˆ์ง€๋ง‰์œผ๋กœ ์ด ์„ธ์…˜์—์„œ ๋งŒ๋“ค์–ด์ง„ auto incresement ์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค. ๋งŒ์•ฝ insertํ•˜๊ณ  ์—ฐ๊ฒฐ์„ ๋Š์€ ๋‹ค์Œ ์ƒˆ๋กœ ์—ฐ๊ฒฐํ•œ ์ดํ›„์— last-linsert id ๋ฅผ ๋ฌผ์–ด๋ณด๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๐Ÿ‘‰ ์—ฌ๋Ÿฌ๋ฒˆ์˜ sql์„ ์‹คํ–‰ํ•ด์•ผ ํ•  ๋•Œ๋Š” con.setAutocommit(false)๋ฅผ ํ•ด๋†“๊ณ 
์ž‘์—…์ด ๋๋‚œ ์ดํ›„์— con.commit( )์„ ํ•ด์ฃผ๋Š”๊ฑธ ๊ถŒ์žฅํ•œ๋‹ค. ์ถ”๊ฐ€์ ์œผ๋กœ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋ฉด rollback์„ ์ง„ํ–‰ํ•œ๋‹ค.

๐Ÿ‘‰ ์ด๋Ÿฌํ•œ ์—ฌ๋Ÿฌ๊ฐœ์˜ ์ž‘์—…์ด ํ•˜๋‚˜์˜ ๋‹จ์œ„ ์ž‘์—…์ด ๋˜๋Š”๊ฒƒ์„ Transcation์ด๋ผ๊ณ  ํ•œ๋‹ค.




4. Optional ์ด๋ž€

๐Ÿ‘‰ null์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ƒˆ๋กœ์šด ๋ฐฉ๋ฒ•

๐Ÿ‘‰ Optional์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•œ ๊ฒฐ๊ณผ๊ฐ€ null์ผ ๋•Œ์˜ ์ฒ˜๋ฆฌ๋ฅผ ๊ฐ„์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, get(Integer tno) ๋ฉ”์„œ๋“œ์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•œ ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด Optional.empty()๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ํ˜ธ์ถœ์ž์—๊ฒŒ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Œ์„ ๋ช…ํ™•ํžˆ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ‘‰ TodoRegisterController์˜ Builder : ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์ƒˆ๋กœ์šด TodoVO ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ œ์ถœํ•œ ํผ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋ฉฐ, ๊ฐ ํ•„๋“œ๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
๐Ÿ‘‰ TodoDAO์˜ Builder: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ TodoVO ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ปฌ๋Ÿผ๋“ค์„ TodoVO ๊ฐ์ฒด์˜ ๊ฐ ์†์„ฑ์— ๋งคํ•‘ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.




5. Oracle rownum์ด๋ž€?

๐Ÿ‘‰ ํŠน์ • ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ์ง‘ํ•ฉ์—์„œ ๊ฐ ํ–‰์˜ ์ˆœ๋ฒˆ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ€์ƒ์ปฌ๋Ÿผ.

๐Ÿ‘‰ rownum์€ ์ •๋ ฌ ์—†์ด ๋ฐ˜ํ™˜๋œ ์ˆœ์„œ๋Œ€๋กœ ํ–‰์— ํ• ๋‹น๋œ๋‹ค.

๐Ÿ‘‰ ์˜ค๋ฆ„์ฐจ์ˆœ ์ •๋ ฌํ›„ rownum ๋ถ€์—ฌ

๐Ÿ‘‰๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌํ›„ rownum๋ถ€์—ฌ




5-1. Where ROWNUM = 1 ์˜ ์˜๋ฏธ?

๐Ÿ‘‰ ํ•ด๋‹น ์ฝ”๋“œ๋Š” ๊ฒฐ๊ณผ ์ง‘ํ•ฉ์—์„œ ์ฒซ ๋ฒˆ์งธ ํ–‰์„ ์ฐพ๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

๐Ÿ‘‰ ROWNUM์€ ๊ฒฐ๊ณผ ์ง‘ํ•ฉ์ด ์ตœ์ข…์ ์œผ๋กœ ์ •๋ ฌ ๋ฐ ํ•„ํ„ฐ๋ง ๋œ ์ดํ›„ ํ• ๋‹น๋˜๋ฏ€๋กœ ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์˜๋„ํ•œ ๋Œ€๋กœ ์ฒซ ๋ฒˆ์งธ ํ–‰์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.




5-2. Oracle Paging

๐Ÿ‘‰ db ์—์„œ rownum์ด 10๊ณผ 20์‚ฌ์ด์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์ถœ๋ ฅํ•˜๊ณ  ์‹ถ์„๋•Œ

select ROWNUM rn, bno, title
        from tbl_board
        where rn <= 20 and rn > 10
        order by bno desc

๐Ÿ‘‰ ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฒฐ๊ณผ๊ฐ’์ด ๋‚˜์˜ค์ง€ ์•Š๋Š” ์ด์œ ๋Š” from ์ ˆ ๋’ท ๋ถ€๋ถ„์€ ๋ฐ์ดํ„ฐ ์ง‘ํ•ฉ์„ ์˜๋ฏธ

select
*
from
(
select ROWNUM rn, bno, title
from tbl_board
where rownum <= 20
order by bno desc
)
where rn > 10
;




5-3. oracle์—์„œ paging์„ ํ• ๋•Œ where ์กฐ๊ฑด์— rownum <= 20 and rownum >10 ์„ํ•˜๋ฉด ๊ฐ’์ด ์•ˆ๋‚˜์˜ค๋Š” ์ด์œ ?

๐Ÿ‘‰ from ๋’ค์— ์˜ค๋Š” ๊ฐ’๋“ค์€ ์ง‘ํ•ฉ์œผ๋กœ ๋‚˜์˜ค๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. where์ ˆ์„ ๋ดค์„๋•Œ rownum์ด 10 ๋ณด๋‹ค ํฐ ๊ฐ’์ผ๋•Œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š”๋ฐ ํ•ด๋‹น ํ…Œ์ด๋ธ”์„ rownum์ •๋ ฌ๋กœ ๋ณด์•˜์„๋•Œ ์ฒซ๋ฒˆ์งธ๋Š” rownum์ด 1๋ฒˆ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ํ•ด๋‹น ๊ฐ’์€ where์กฐ๊ฑด์— ๋งž์ง€์•Š์•„์„œ ์ถœ๋ ฅ์ด ์•ˆ๋˜๊ณ  ๋„˜๊ธด๋‹ค๊ณ  ์น˜๋ฉด ๋‹ค์Œ ์€ 2๋ฒˆ์ด ์•„๋‹ˆ๊ณ  ๋˜ 1๋ฒˆ๊ฐ’์ด ๋˜๋Š”๊ฒƒ ์ด๋ฏ€๋กœ where์ ˆ์— ๋งž๋Š” rownum๊ฐ’์€ ๋‚˜์˜ค์ง€ ์•Š์•„ ๊ฒฐ๊ณผ ๊ฐ’์ด ์•ˆ๋‚˜์˜ค๋Š”๊ฒƒ์ด๋‹ค.




5-4. ์ธ๋ผ์ธ๋ทฐ

๐Ÿ‘‰ oracle์—์„œ ํŽ˜์ด์นญ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•

select
*
from
(
select ROWNUM rn, bno, title
from tbl_board
where rownum <= 20
order by bno desc
)
where rn > 10
;




5-5. RowID ๋ž€?

๐Ÿ‘‰ Oracle db์—์„œ ๊ฐ ํ–‰์„ ์‹๋ณ„ํ•˜๋Š” ์œ ์ผํ•œ ๊ฐ’.

๐Ÿ‘‰ โ€˜AAARyโ€ฆโ€™๊ฐ’์ด RowID๋กœ ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์›ํ•˜๋Š” ROW์˜ ๊ฐ’์„ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.




6.๋กœ๊ทธ

  • ๋กœ๊ทธ๋ ˆ๋ฒจ / Appender / Layout

  • ๋กœ๊ทธ๋ ˆ๋ฒจ

    ๐Ÿ‘‰ ๋กœ๊ทธ๋ ˆ๋ฒจ์˜ ์ข…๋ฅ˜๋Š” trace, debug, info, warining, error, fatal
    ๊ฐœ๋ฐœ์— ๋”ฐ๋ผ์„œ ์–ด๋–ค ๋ ˆ๋ฒจ๊นŒ์ง€ ์ถœ๋ ฅ์ด ๋˜๋Š”์ง€ ์ ์šฉํ•˜์—ฌ
    ์‹ฌ๊ฐํ•œ ์—๋Ÿฌ๋งŒ ๋”ฐ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

  • Appender๋Š” ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•˜๋Š”๊ฒƒ.

    ๐Ÿ‘‰ ConsoleAppender ๋Š” ์ฝ˜์†”์— ๊ธฐ ๋ก์ด ๋˜๋Š”๊ฒƒ.

    ๐Ÿ‘‰ DailyRollingFileAppender ๋Š” ํŒŒ์ผ์— ๊ธฐ๋ก์ด ๋˜๋Š”๊ฒƒ.

  • Layout

    ๐Ÿ‘‰ ํฌ๋ฉงํŒ… ์—ญํ• , ๋ช‡๋ฒˆ์งธ ๋ผ์ธ์—์„œ log๋ฅผ ๋‚จ๊ฒผ๋Š”์ง€ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•จ




7. TodoList ๋งˆ๋ฌด๋ฆฌ

7-0. TodoListController

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();
        }
    }
}




7-1. TodoRegisterController


@WebServlet(value = "/todo/register")
@Log4j2
public class TodoRegisterController extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        log.info("todo register doGet");
        req.getRequestDispatcher("/WEB-INF/todo/register.jsp").forward(req, resp);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        //step1 - ๋ธŒ๋ผ์šฐ์ €์— ์ „๋‹ฌํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘
        log.info("todo register doPost");

        String title = req.getParameter("title");
        String writer = req.getParameter("writer");

        log.info("title :" + title);
        log.info("writer :" + writer);

        //step2 - ๋ฐ์ดํ„ฐ ๊ฐ€๊ณตํ•ด์„œ VO, DTO ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ
        TodoVO vo = TodoVO.builder()
                .title(title)
                .writer(writer)
                .build();


        log.info("todoVO: " + vo);


        //step3 - service, DAO์—๊ฒŒ ์ฒ˜๋ฆฌ๋ฅผ ๋ถ€ํƒ
        try {
            Integer tno = TodoDAO.INSTANCE.insert(vo);
            //step4 - ๊ฒฐ๊ณผ ์ „์†ก Redirect
            resp.sendRedirect("/todo/list?tno=" + tno);
            // ์ž˜ ์ฒ˜๋ฆฌํ•˜๋ฉด listํ™”๋ฉด์œผ๋กœ redirect

        } catch (Exception e) {
            //step4 - ๊ฒฐ๊ณผ ์ „์†ก insert ์˜ˆ์™ธ ๋ฐœ์ƒ์‹œ Redirect
            resp.sendRedirect("/todo/register?error=input");
            //์ž˜๋ชป ์ฒ˜๋ฆฌํ•˜๋ฉด
        }




    }
}




7-2. TodoReadControler


@WebServlet(value = {"/todo/get","/todo/edit"})
@Log4j2
public class TodoReadController extends HttpServlet {


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        log.info("doGet");

        String uri = req.getRequestURI(); //ํ˜„์žฌ ๋ธŒ๋ผ์šฐ์ €์˜ uri์ฐฝ์ด ์–ด๋–ค๊ฑด์ง€ ์•Œ์•„๋ณด๋Š” ๋ฐฉ๋ฒ•

        // /todo/get ์ผ๋•Œ ๋งˆ์ง€๋ง‰ ์Šฌ๋ž˜์‰ฌ๋ฅผ ๋Š์–ด๋‚ด๋ฉด get ํ˜น์€ edit ์„ ๊ฐ€์ ธ์˜จ๋‹ค.
        String jspName = uri.substring(uri.lastIndexOf('/') + 1);

        String tnoStr = req.getParameter("tno"); //์ •๋ณด๊ฐ€์ ธ์˜ค๋Š”๊ฑฐ ๋ฌธ์ž์—ด๋กœ ๊ฐ€์ ธ์˜ค๋‹ˆ๊นŒ ๋ฐ‘์—์„œ ์ˆซ์ž๋กœ ๋ฐ”๊ฟ”์ค˜์•ผํ•จ

        Integer tno = StringUtil.getInt(tnoStr,-1); //์˜ค๋ฅ˜๋‚˜๋ฉด -1๋กœ ๋ฐ˜ํ™˜ํ•˜๊ธฐ

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

        try {
            Optional<TodoVO> result = TodoDAO.INSTANCE.get(tno);

            TodoVO vo = result.orElseThrow(); //์—†์œผ๋ฉด ์˜ˆ์™ธ๊ฐ€ ์ฒ˜๋ฆฌ๋Š”๊ฒƒ, ์—†์œผ๋ฉด ์•„๋ž˜๋กœ ๋น ์ง€๊ฒŒ๋จ ๋‚ด๋ถ€์— ์˜ˆ์™ธ๊ธฐ๋Šฅ์ด ๋‚ดํฌ๋˜์–ด์žˆ๋Š” ๋ฉ”์†Œ๋“œ

            req.setAttribute("todo", vo); //vo์˜ ๊ฐ’์„ jsp์—์„œ todo๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ• ๊ฑฐ์•ผ.

            req.getRequestDispatcher("/WEB-INF/todo/"+jspName+".jsp").forward(req, resp);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }



    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String uri = req.getRequestURI(); //ํ˜„์žฌ ๋ธŒ๋ผ์šฐ์ €์˜ uri์ฐฝ์ด ์–ด๋–ค๊ฑด์ง€ ์•Œ์•„๋ณด๋Š” ๋ฐฉ๋ฒ•

        String job = uri.substring(uri.lastIndexOf('/') + 1);

        if(job.equals("get")){ //get์œผ๋กœ ๋“ค์–ด์˜ค๊ฒŒ ๋œ๋‹ค๋ฉด list๋กœ ๋ณด๋‚ด๋ฒ„๋ฆฌ๊ธฐ
            resp.sendRedirect("/todo/list");
            return;
        }

        String tnoStr = req.getParameter("tno");

        Integer tno = StringUtil.getInt(tnoStr,-1);

        String title = req.getParameter("title");
        String writer = req.getParameter("writer");

        TodoVO vo = TodoVO.builder()
                .tno(tno)
                .title(title)
                .writer(writer)
                .build();

        try {
            boolean result = TodoDAO.INSTANCE.update(vo);
            resp.sendRedirect("/todo/get?tno="+tno);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}




7-3. TodoRemoveController


//์œ„ ์ฝ”๋“œ์—์„œ @WebServlet(value="/todo/remove") ์–ด๋…ธํ…Œ์ด์…˜์€ /todo/remove ๊ฒฝ๋กœ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ 
//์ด ์„œ๋ธ”๋ฆฟ ํด๋ž˜์Šค(TodoRemoveController)๊ฐ€ ์ฒ˜๋ฆฌํ•˜๊ฒ ๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋ƒ„.

@WebServlet(value= "/todo/remove")
@Log4j2
public class TodoRemoveController extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        log.info("doPost");

        String tnoStr = req.getParameter("tno");

        Integer tno = StringUtil.getInt(tnoStr,-1);

        //์‚ญ์ œ๊ฐ€ ๋˜๋ฉด true or false๊ฐ€ ๋œ๋‹ค๋Š” ๊ฐ€์ •
        boolean result = false;

        try {
            result = TodoDAO.INSTANCE.delete(tno);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        //์‚ญ์ œ๊ฐ€ ๋˜๋ฉด ๋ฆฌ์ŠคํŠธ ํ™”๋ฉด
        //redirectํ• ๋•Œ๋Š” get๋ฐฉ์‹๋ฐ–์— ๋ชป์“ฐ๋‹ˆ๊นŒ ์ฟผ๋ฆฌ์ŠคํŠธ๋ง ์จ์ค˜์•ผํ•จ
        resp.sendRedirect("/todo/list?result="+result);

    }
}




7-4. TodoDAO

7-4-1. insert DAO

@Log4j2
public enum TodoDAO {
    INSTANCE;

    public Integer insert(TodoVO todoVO) throws Exception {
        //insert update delete๋Š” ๋Œ€๊ฒŒ integer ํ˜น์€ boolean์„ ์‚ฌ์šฉํ•œ๋‹ค.
        //return ๊ฐ’์œผ๋กœ ๋Œ€๋ถ€๋ถ„ tno์™€ ๊ฐ™์€ ์ ‘์ˆ˜๋ฅผ ์ „๋‹ฌํ•จ.

        String sql = "insert into tbl_todo (title, writer) values (?,?)";

        @Cleanup Connection con = ConnectionUtil.INSTANCE.getDs().getConnection();

        @Cleanup PreparedStatement ps = con.prepareStatement(sql);
        ps.setString(1, todoVO.getTitle());
        ps.setString(2, todoVO.getWriter());
		//๋งค๊ฐœ๋ณ€์ˆ˜๋กœ todoVO๋ฅผ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— todoVO์˜ get๊ฐ’์„ ์‚ฌ์šฉํ•œ๋‹ค.
        
        
        int count = ps.executeUpdate(); 
		//์—…๋ฐ์ดํŠธ๋ฅผ ์‹คํ–‰ํ•˜์—ฌ count๋กœ ๋ฐ›๋Š” ์ด์œ ๋Š” dml์€ count๋กœ ๋ฐ›์•„์•ผํ•จ.
        
        if(count != 1){ //count๊ฐ’์ด 1์ด ์•„๋‹ˆ๋ฉด ์ž˜๋ชป๋œ๊ฒƒ์ด๋‹ˆ๊นŒ.
            throw new Exception("Abnormal insertion");
          
        }

        ps.close(); //ps๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉํ•ด์•ผํ•˜๋ฏ€๋กœ ์ฒซ๋ฒˆ์งธ ps๋ฅผ ๋‹ซ๊ณ 
        ps = con.prepareStatement("select LAST_INSERT_ID()"); //๋‘๋ฒˆ์งธ ps๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉํ•œ๋‹ค.
        //๋งˆ์ง€๋ง‰์— ๋„ฃ์€ tno๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค.
        

        @Cleanup ResultSet rs = ps.executeQuery();

        rs.next(); 
        Integer tno = rs.getInt(1); 

        log.info("TNO:..." +tno);

        con.commit(); //ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹
        con.setAutoCommit(true); //ํŠธ๋žœ์žญ์…˜ ์˜คํ† ์ปค๋ฐ‹

        return tno;
    }
    
    




7-4-2. list DAO

    //ํŽ˜์ด์ง€์˜ 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();
        //ResultSet ์ž๋ฐ”์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ๊ฐ€์ ธ์˜จ ๊ฒฐ๊ณผ๋ฅผ ์ง‘ํ•ฉ์œผ๋กœ ๋‚˜ํƒ€๋ƒ„. 
		//dml์ผ ๊ฒฝ์šฐ์—๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ.


        List<TodoVO> list = new ArrayList<>(); //๋ฐ์ดํ„ฐํƒ€์ž…์ด List<TodoVO> ์ธ ArrayList ์ƒ์„ฑ
        //๋ฆฌ์ŠคํŠธ ํ™”๋ฉด์—์„œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ Todo ํ•ญ๋ชฉ์„ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉ

        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;
    }
   
   




7-4-3. TOTAL DAO

    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;
    }
   




7-4-4. GET DAO

    public Optional<TodoVO> get(Integer tno) throws Exception {

        final String query = """
                 select tno, title, writer, regdate, moddate, delflag
                 from 
                     tbl_todo
                 where tno = ?
                 """;
        @Cleanup Connection con = ConnectionUtil.INSTANCE.getDs().getConnection();
        @Cleanup PreparedStatement ps = con.prepareStatement(query);
        ps.setInt(1, tno);
        @Cleanup ResultSet rs = ps.executeQuery();

        if(!rs.next()){ //์›๋ž˜ ์ฒซ๋ฒˆ์งธ๋Š” next๋กœ ๋„˜๊ธฐ๋Š”๋ฐ, ๊ทธ ์ฒซ๋ฒˆ์งธ๊ฐ€ ์—†๋‹ค๋ฉด.
            return Optional.empty();
        }

        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();

        return Optional.of(vo);
    }
   




7-4-5. DELETE DAO


    public boolean delete(Integer tno) throws Exception {
        String sql = "update tbl_todo set moddate = now() , delflag = true where tno = ?";


        @Cleanup Connection con = ConnectionUtil.INSTANCE.getDs().getConnection();
        @Cleanup PreparedStatement ps = con.prepareStatement(sql);
        ps.setInt(1, tno);

        int count = ps.executeUpdate(); //dml์€ ๋ช‡๊ฑด์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ int๋กœ ์ถœ๋ ฅ๋จ

        return count == 1; //count๊ฐ€ 1์ด๋ฉด true
    }
   




7-4-6. UPDATE DAO


    public boolean update(TodoVO todoVO) throws Exception {
        String sql = """
                update tbl_todo
                    set 
                        title = ?,
                        writer = ?,
                        moddate = now()
                where tno = ?
                """;
        @Cleanup Connection con = ConnectionUtil.INSTANCE.getDs().getConnection();
        @Cleanup PreparedStatement ps = con.prepareStatement(sql);

        ps.setString(1, todoVO.getTitle());
        ps.setString(2, todoVO.getWriter());
        ps.setInt(3, todoVO.getTno());

        int count = ps.executeUpdate();

        return count == 1 ? true : false;

    }
}




7-5. edit.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@include file="../includes/header.jsp"%>



<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<h1>Edit Page</h1>

${todo}


<form action = "/todo/edit" method="post">
    <div>
        <label>TNO</label>
        <input type="text" name="tno" value="${todo.tno}" readonly> <!-- ์ˆ˜์ •ํ• ๊ฒŒ ์•„๋‹ˆ๋‹ˆ๊นŒ readonly-->
    </div>
    <div>
        <label>TITLE</label>
        <input type="text" name="title" value="${todo.title}">
    </div>
    <div>
        <label>WRITER</label>
        <input type="text" name="writer" value="${todo.writer}">
    </div>
    <div>
        <button>MODIFY</button>
    </div>
</form>

<form action = "/todo/remove" method="post">
    <input type="hidden" name="tno" value="${todo.tno}">
    <button>REMOVE</button>
</form>

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




7-6. get.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@include file="../includes/header.jsp"%>



<%@ page contentType="text/html;charset=UTF-8" language="java" %>

${todo}

<%--<a href="/todo/edit?tno=${todo.tno}">Modify/Delete</a>--%>
<br>
<form action ="/todo/edit?tno=${todo.tno}" method="get">
    <input type="hidden" name="tno" value="${todo.tno}">
    <button type="submit" name="tno" class="btn btn-warning" onclick="submitForm()">Modify/Deletery</button>
</form>

<br><br>


<script>
    function submitForm() {
        document.getElementById("editForm").submit();
    }
</script>

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




7-7. list.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@include file="../includes/header.jsp"%>

<style>
    .msg {
        width: 200px;
        height: 100px;
        background-color: lightblue;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        display: none; /* Initially hidden */
        opacity: 0; /* Initially transparent */
        animation: showHide 3s forwards; /* 3 seconds animation */
    }

    @keyframes showHide {
        0% {
            display: block; /* Make it visible */
            opacity: 0;
        }
        10% {
            opacity: 1;
        }
        90% {
            opacity: 1;
        }
        100% {
            opacity: 0;
            display: none; /* Hide it again */
        }
    }
</style>

<h1>todo list page</h1>

<a href ="/todo/register">Todo Register</a>

<c:if test="${param.tno != null || param.result}"> <!--๋“ฑ๋ก์„ ํ•œ ์ดํ›„์— url์— ๋œจ๋Š” tno๋ฅผ ์ด์šฉํ•˜๊ธฐ-->
    <div class="msg">
        <h1>์ฒ˜๋ฆฌ๊ฒฐ๊ณผ ์ž…๋‹ˆ๋‹ค.</h1>
    </div>
</c:if>

<ul>
    <c:forEach items = "${todolist}" var="todo">
        <li>
            <div>
                <div>${todo.tno}</div>
                <div>
                     <a href="/todo/get?tno=${todo.tno}"> ${todo.title} </a>
                </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>

<script>
    window.onload = function() {
        document.querySelector('.msg').style.display = 'block';
    };
</script>

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




7-8. register.jsp


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

<div class="card" style="margin: 3em">


<p>Todo Register Page</p>

    <div class="card-body">

        <form action="/todo/register" method="post">
        <div class="mb-3">
            <label class="form-label">Title</label>
            <input type="text" name="title" class="form-control" placeholder="name@example.com">
        </div>
        <div class="mb-3">
            <label class="form-label">Writer</label>
            <input type="text" name="writer" class="form-control" placeholder="user">
        </div>
            <div class="mb-3">
                <button type="submit" class="btn btn-primary">SAVE</button>
            </div>
        </form>
    </div>
</div>

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

0๊ฐœ์˜ ๋Œ“๊ธ€