Back-End/Spring

์Šคํ”„๋ง์˜ ์ •์„ Ch. 04 MyBatis๋กœ ๊ฒŒ์‹œํŒ ๋งŒ๋“ค๊ธฐ

์ฑ”๐Ÿป 2024. 1. 25. 12:05
๐Ÿ˜

01. MyBatis์˜ ์†Œ๊ฐœ์™€ ์„ค์ •

1. MyBatis๋ž€?

  • SQL Mapping Framework - Easy & Simple

    ์ž๋ฐ” ์ฝ”๋“œ์™€ SQL์„ ๋งคํ•‘ํ•œ๋‹ค.

  • ์ž๋ฐ” ์ฝ”๋“œ๋กœ๋ถ€ํ„ฐ SQL๋ฌธ์„ ๋ถ„๋ฆฌํ•ด์„œ ๊ด€๋ฆฌ
  • ๋งค๊ฐœ๋ณ€์ˆ˜ ์„ค์ •๊ณผ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ์ฝ์–ด์˜ค๋Š” ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐ

    setString(), setInt() | getString(), getInt()

  • ์ž‘์„ฑํ•  ์ฝ”๋“œ๊ฐ€ ์ค„์–ด์„œ ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ & ์œ ์ง€ ๋ณด์ˆ˜ ํŽธ๋ฆฌ

    E = mc2c^2๏ปฟ

    Error = more code

    ์ฝ”๋“œ๊ฐ€ ๋งŽ์„ ์ˆ˜๋ก ์—๋Ÿฌ โ†‘

์Šคํ”„๋ง์—์„œ MyBatis๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์•„๋ž˜ 2๊ฐœ์˜ ๋ชจ๋“ˆ์ด ํ•„์š”ํ•˜๋‹ค.

<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.5.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>2.0.7</version>
</dependency>

2. SqlSessionFactoryBean๊ณผ SqlSessionTemplate

  • SqlSessionFactory - SqlSession์„ ์ƒ์„ฑํ•ด์„œ ์ œ๊ณต

    SqlSession - SQL ๋ช…๋ น์„ ์ˆ˜ํ–‰ํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ๋ฉ”์„œ๋“œ ์ œ๊ณต

  • SqlSessionFactoryBean - SqlSessionFactory๋ฅผ Spring์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋นˆ

    SqlSessionTemplate - SQL ๋ช…๋ น์„ ์ˆ˜ํ–‰ํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ๋ฉ”์„œ๋“œ ์ œ๊ณต.

root-context.xml

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/> <!-- mybatis ์„ค์ •ํŒŒ์ผ -->
		<property name="mapperLocations" value="classpath:mapper/*Mapper.xml"/>
</bean>

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg ref="sqlSessionFactory"/>
</bean>

๐Ÿ’กํฌ์ธํŠธ

๋ฉ€ํ‹ฐ ์“ฐ๋ ˆ๋“œ์— ์•ˆ์ „ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๋Ÿฌ Dao๊ฐ€ SqlSessionTemplate๋ฅผ ๊ณต์œ ํ•ด๋„ ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค.

3. SqlSession์˜ ์ฃผ์š” ๋ฉ”์„œ๋“œ

๋ฉ”์„œ๋“œ์„ค๋ช…๋น„๊ณ 
int insert(String statement)
int insert(String statement, Object parameter)
insert๋ฌธ์„ ์‹คํ–‰ํ•˜๊ณ , insert๋œ ํ–‰์˜
๊ฐฏ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜
int๋Š” DB์— ์˜ํ–ฅ์„ ์ค€ row์˜ ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
param์€ ์˜ˆ๋ฅผ ๋“ค์–ด User๊ฐ์ฒด๋‚˜ Map๋„ ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
int delete(String statement)
int delete(String statement, Object parameter)
delete๋ฌธ์„ ์‹คํ–‰ํ•˜๊ณ , delete๋œ ํ–‰์˜
๊ฐฏ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜
int update(String statement)
int update(String statement, Object parameter)
update๋ฌธ์„ ์‹คํ–‰ํ•˜๊ณ , update๋œ ํ–‰์˜
๊ฐฏ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜
T selectOne(String statement)
T selectOne(String statement, Object parameter)
ํ•˜๋‚˜์˜ ํ–‰์„ ๋ฐ˜ํ™˜ํ•˜๋Š” select์— ์‚ฌ์šฉ
paramter๋กœ SQL์— binding๋  ๊ฐ’ ์ œ๊ณต
ํ•œ ํ–‰ ์กฐํšŒ(1๊ฐœ ์กฐํšŒ)
List<E> selectList(String statement)
List<E> selectList(String statement, Object parameter)
์—ฌ๋Ÿฌ ํ–‰์„ ๋ฐ˜ํ™˜ํ•˜๋Š” select์— ์‚ฌ์šฉ
paramter๋กœ SQL์— binding๋  ๊ฐ’ ์ œ๊ณต
n๊ฐœ ์กฐํšŒ(์–˜ ๋งŽ์ด ์”€)
Map<K,V> selectMap(String statement, String keyCol)
Map<K,V> selectMap(String statement, String keyCol, Object parameter)
์—ฌ๋Ÿฌ ํ–‰์„ ๋ฐ˜ํ™˜ํ•˜๋Š” select์— ์‚ฌ์šฉ
keyCol์— Map์˜ Key๋กœ ์‚ฌ์šฉํ•  ์ปฌ๋Ÿผ
์ง€์ •
n๊ฐœ ์กฐํšŒ

4. Mapper XML์˜ ์ž‘์„ฑ

  • getter๋ฅผ ํ†ตํ•ด์„œ ๊ฐ’์„ ๋„ฃ์–ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— DTO์— getter๋Š” ํ•„์ˆ˜๋‹ค.
  • parameterType : ์ž…๋ ฅ

    resultType : ์ถœ๋ ฅ

๐Ÿ’กinsert๋Š” ๋ฐ˜ํ™˜ํƒ€์ž…์ด ํ•ญ์ƒ int๋ผ resultType์ด ์—†๋‹ค.

+) Mapper ์ธํ„ฐํŽ˜์ด์Šค? ์„ค์ •์ด๋ผ๋Š” ๊ฒŒ ์žˆ๋‹ค๋Š” ๊ฒƒ๋งŒ ์•Œ์•„๋‘๊ธฐ

5. <typeAliases>์œผ๋กœ ์ด๋ฆ„ ์งง๊ฒŒ ํ•˜๊ธฐ

mybatis โ€“ MyBatis 3 | Configuration
https://mybatis.org/mybatis-3/configuration.html#typeAliases

typeAliases : ๋ณ„๋ช…

02. MyBatis๋กœ DAO ์ž‘์„ฑํ•˜๊ธฐ

1. BoardDao์˜ ์ž‘์„ฑ

  1. DB ํ…Œ์ด๋ธ” ์ž‘์„ฑ
  1. Mapper XML & DTO ์ž‘์„ฑ
  1. DAO ์ธํ„ฐํŽ˜์ด์Šค ์ž‘์„ฑ
  1. DAO ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„ & ํ…Œ์ŠคํŠธ

2. DTO๋ž€? - Data Transfer Object

  • ๊ณ„์ธต๊ฐ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ  ๋ฐ›๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” ๊ฐ์ฒด

๊ฒŒ์‹œํŒ์—์„œ ๊ธ€์„ ์ฝ์„ ๋•Œ๋Š” board ํ…Œ์ด๋ธ”์— ์žˆ๋Š” Data๋ฅผ BoardDto ๊ฐ์ฒด์— ๋‹ด์•„์„œ ๊ฐ€์ ธ์™€์•ผ ํ•˜๊ณ ,

๊ฒŒ์‹œํŒ์—์„œ ๊ธ€์„ ์“ธ ๋•Œ๋Š” DTO๋ผ๋Š” ๊ฐ์ฒด์— ๊ฐ’์„ ์ฑ„์›Œ์„œ DB์˜ board ํ…Œ์ด๋ธ”์— ์ €์žฅํ•œ๋‹ค.

๊ทธ๋ž˜์„œ BoardDTO๋Š” board ํ…Œ์ด๋ธ”์— ๋งž๊ฒŒ ์ •์˜ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญํ•œ data๋ฅผ DTO์— ๋‹ด์•„์„œ Service ๊ณ„์ธต์œผ๋กœ, ๋˜ Repository ๊ณ„์ธต(DAO)์œผ๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค. ๊ณ„์ธต์„ ๋ถ„๋ฆฌํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ ๊ณ„์ธต๊ฐ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•  ๋ฌด์–ธ๊ฐ€๊ฐ€ ์žˆ์–ด์•ผ ํ•˜๋Š”๋ฐ ๊ทธ๊ฒŒ DTO๋‹ค.

๋˜ ์กฐํšŒ๋ฅผ ํ•˜๋ฉด ์กฐํšŒํ•œ ํ…Œ์ดํ„ฐ๋ฅผ DTO์— ๋‹ด์•„์„œ ๊ฐ ๊ณ„์ธต์„ ๊ฑฐ์ณ์„œ ์‘๋‹ตํ•˜๊ฒŒ ๋˜์–ด์žˆ๋‹ค.

โ†’ data๋ฅผ ์ „์†กํ•  ๋•Œ ์“ฐ๋Š” ๊ฐ์ฒด์ž„

MVC์—์„œ๋Š” Model

3๊ณ„์ธต์—์„œ๋Š” DTO

์˜ˆ์™ธ์ฒ˜๋ฆฌ

  • Repository(DAO) ๊ณ„์ธต์—์„œ๋Š” ์˜ˆ์™ธ์ฒ˜๋ฆฌ โŒ

    ์ „์— ๋ฐฐ์šด ๊ฒƒ๊ณผ ๊ฐ™์ด @Service ๊ณ„์ธต์—์„œ try-catch๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— DAO์—์„œ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜์—์„œ๋Š” ์ปค๋ฐ‹ํ•ด์•ผ ํ• ์ง€ ๋กค๋ฐฑํ•ด์•ผ ํ• ์ง€ ์•Œ ์ˆ˜๊ฐ€ ์—†๋‹ค. ๊ทธ๋ž˜์„œ Repository ๊ณ„์ธต์—์„œ๋Š” ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š๋Š”๋‹ค.

  • ๋ณดํŽธ์ ์œผ๋กœ Service ๊ณ„์ธต์—์„œ ์˜ˆ์™ธ์ฒ˜๋ฆฌ.

    ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ๋ฅผ try-catch๋กœ ํ•˜๋‹ˆ๊นŒ ํ•„์š”ํ•˜๋‹ค.(์ฒ˜๋ฆฌ ์•ˆ ํ•˜๊ณ  ์ปจํŠธ๋กค๋Ÿฌ๋กœ ๋„˜๊ธฐ๋Š” ์„ ํƒ์ง€๋„ ์žˆ์Œ)

<์˜ˆ์™ธ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•>

  1. Service์—์„œ ์ฒ˜๋ฆฌ
  1. Controller์—์„œ ์ฒ˜๋ฆฌ
  1. ์•„๋‹ˆ๋ฉด ๋‘˜ ๋‹ค(์˜ˆ์™ธ ๋˜๋˜์ง€๊ธฐ)

์–ด๋–ค ๋ฐฉ๋ฒ•์œผ๋กœ ์˜ˆ์™ธ์ฒ˜๋ฆฌํ• ์ง€ ์œ„ 3๊ฐ€์ง€์—์„œ ์Šค์Šค๋กœ ๊ณ ๋ฏผํ•ด๋ด์•ผ ํ•œ๋‹ค.(์–ด๋ ค์šฐ๋ฉด ์ปจํŠธ๋กค๋Ÿฌ๋กœ ๋„˜๊ธฐ๋ฉด ๋จ. ์˜ˆ์™ธ ์ข…๋ฅ˜์— ๋”ฐ๋ผ์„œ ์•Œ์•„์„œ ์ฒ˜๋ฆฌํ•˜๋„๋ก.)

+) VO(Value Object)๋Š” ์ž˜๋ชป๋œ ์šฉ์–ด๋‹ค. DTO๊ฐ€ ๋งž์Œ.

์‹ค์Šต

getter, setter๊ฐ€ ์žˆ์–ด์•ผ MyBatis๊ฐ€ ๊ฐ’์„ ์ž๋™์œผ๋กœ ์ฝ์–ด์˜ค๊ณ  ์ฑ„์›Œ์ค€๋‹ค. ๊ทธ๋ž˜์„œ DTO์—์„œ getter, setter๋Š” ํ•„์ˆ˜.

๋งŒ์•ฝ BoardDTO์— bno๋ผ๋Š” iv์— getter๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด MyBatis๊ฐ€ ๊ฐ’์„ ์ฝ์–ด์˜ค์ง€ ๋ชปํ•œ๋‹ค.

<mapper namespace="com.fastcampus.ch4.dao.BoardMapper">
    <!-- mybatis-config.xml์—์„œ alias๋ฅผ ์ค˜์„œ BoardDTO ํŒจํ‚ค์ง€๋ช… ์ƒ๋žต ๊ฐ€๋Šฅ -->
    <select id="select" parameterType="int" resultType="BoardDTO">
        select bno,
               title,
               content,
               writer,
               view_cnt,
               comment_cnt,
               reg_date,
               up_date
          from board
         where bno = #{bno};
    </select>
</mapper>

<typeAliases>
    <typeAlias alias="BoardDto" type="com.fastcampus.ch4.domain.BoardDto"/>
</typeAliases>

sql๋ฌธ์„ ํ˜ธ์ถœํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด๊ธฐ!

  1. table ๋งŒ๋“ค๊ณ 
    CREATE TABLE `board` (
      `bno` int NOT NULL AUTO_INCREMENT,
      `title` varchar(100) NOT NULL,
      `content` text NOT NULL,
      `writer` varchar(30) NOT NULL,
      `view_cnt` int DEFAULT '0',
      `comment_cnt` int DEFAULT '0',
      `reg_date` datetime DEFAULT CURRENT_TIMESTAMP,
      `up_date` datetime DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY (`bno`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3
  1. Sql mapper์—์„œ sql ์ž‘์„ฑ
    <mapper namespace="com.fastcampus.ch4.dao.BoardMapper">
        <!-- mybatis-config.xml์—์„œ alias๋ฅผ ์ค˜์„œ ์‹ค์ œ ํƒ€์ž… ๋Œ€์‹ ์— BoardDTO๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
            id๋Š” select์ง€๋งŒ ์‹ค์ œ ์ด๋ฆ„์€ ์•ž์— namespace๊ฐ€ ๋ถ™์–ด์•ผ ํ•œ๋‹ค.(ํด๋ž˜์Šค ์•ž์— ํŒจํ‚ค์ง€ ์ด๋ฆ„ ๋ถ™๋“ฏ์ด)
        -->
        <select id="select" parameterType="int" resultType="BoardDTO">
            select bno,
                   title,
                   content,
                   writer,
                   view_cnt,
                   comment_cnt,
                   reg_date,
                   up_date
            from board
            where bno = #{bno};
        </select>
    </mapper>
  1. DAO ์ž‘์„ฑ

    ๐Ÿšจย ์ฃผ์˜ํ•  ์ 

    namespace ๋งˆ์ง€๋ง‰์— โ€˜.โ€™ ๋ถ™์—ฌ์•ผ ํ•˜๋Š” ๊ฑฐ ์žŠ์ง€ ๋ง๊ธฐ

    public class BoardDao {
        @Autowired
        SqlSession session;
    
        String namespace = "com.fastcampus.ch4.dao.BoardMapper."; // ๋’ค์— ์  ํ•˜๋‚˜ ๋ถ™์—ฌ์•ผ ํ•˜๋Š” ๊ฑฐ ์žŠ์ง€๋ง๊ธฐ
    
        /*
        * ๋ฉ”์„œ๋“œ๋ช… : select
        * ๋งค๊ฐœ๋ณ€์ˆ˜ : int
        * ๋ฐ˜ํ™˜ํƒ€์ž… : BoardDTO
        * */
        public BoardDTO select(int bno) throws Exception { // ์˜ˆ์™ธ๋ฅผ service๋กœ ๋˜์ ธ์ค˜์•ผ ํ•ด์„œ ์˜ˆ์™ธ ์„ ์–ธ.
            // session ๊ฐ์ฒด๋ฅผ ์ง€์šฉํ•ด์„œ one row ๊ฐ€์ง€๊ณ  ์˜จ๋‹ค.
            return session.selectOne(namespace + "select", bno);
        }
    }
  1. ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ถ”์ถœ

    ์ธํ„ฐํŽ˜์ด์Šค๋กœ ํ•ด๋†“์œผ๋ฉด ๋‹ค์ค‘์— DAO๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ Service์ชฝ์— ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๋„๋ก, ํด๋ž˜์Šค๊ฐ€ ์•„๋‹ˆ๋ผ ์ธํ„ฐํŽ˜์ด์Šค ํƒ€์ž…์„ ์ด์šฉํ•ด์„œ ์ฃผ์ž…๋ฐ›๋Š” ๊ฒƒ.

    Refacter > Extract Interface


๋‹ค์Œ. BoardDao๋ฅผ ํ…Œ์ŠคํŠธ ํ•ด๋ณด์ž.

  1. BoardDaoImpl ์šฐํด๋ฆญ > Go to > Test

    Junit4๋กœ ์ฒดํฌํ•˜๊ณ  ์ƒ์„ฑ

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/root-context.xml"})
public class BoardDaoImplTest {
    @Autowired
    BoardDao boardDao;

    @Test
    public void select() throws Exception {
        // boardDao๊ฐ€ ์ฃผ์ž…์ด ์ž˜ ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
        assertTrue(boardDao != null);
        System.out.println("boardDao = " + boardDao);

        BoardDto boardDto = boardDao.select(1);
        System.out.println("boardDto = " + boardDto);

        assertTrue(boardDto.getBno().equals(1));
    }
}

3. #{}์™€ ${}์˜ ์ฐจ์ด

#{} โ†’ PreparedStatement ์‚ฌ์šฉ

pstmt๋ฅผ ์“ฐ๋Š” ๊ฒŒ ์ข‹์Œ

์žฅ์ 

  1. ์„ฑ๋Šฅ : sql๋ฌธ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  1. ๋ณด์•ˆ : sql injection ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค.

    ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์„ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.

<insert id="insert" parameterType="BoardDto">
    INSERT INTO board
        (title, content, writer)
    VALUES
        (#{title}, #{content}, #{writer})
</insert>

โ†’

String sql = "INSERT INTO board"
				+ "(title, content, writer)"
				+ "VALUES"
				+ "(?, ?, ?)";

${} โ†’ Statement ์‚ฌ์šฉ

title, content, writer๊ฐ€ ๋ญ๋ƒ์— ๋”ฐ๋ผ sql๋ฌธ์ด ๋‹ค ๋ฐ”๋€๋‹ค. โ†’ ์„ฑ๋Šฅ์— ์ข‹์ง€ ์•Š๋‹ค.

but ์ œ์•ฝ์ด ๋” ์ ์Œ

๋ฌธ์ž์—ด ๋ณ€ํ™˜ ์—†์ด ๊ฐ’์„ ์ง์ ‘ SQL์— ์‚ฝ์ž…ํ•œ๋‹ค.

<insert id="insert" parameterType="BoardDto">
    INSERT INTO board
        (title, content, writer)
    VALUES
        ('${title}', '${content}', '${writer}')
</insert>

โ†’

String sql = "INSERT INTO board"
				+ "(title, content, writer)"
				+ "VALUES"
				+ " ('"+title+"','"+content+"','"+writer+"')";

4. XML์˜ ํŠน์ˆ˜ ๋ฌธ์ž ์ฒ˜๋ฆฌ - CDATA

  • XML ๋‚ด์˜ ํŠน์ˆ˜ ๋ฌธ์ž (<, >, &, โ€ฆ)๋Š” &lt; &gt;๋กœ ๋ณ€ํ™˜ ํ•„์š”
  • ๋˜๋Š” ํŠน์ˆ˜๋ฌธ์ž๊ฐ€ ํฌํ•จ๋œ ์ฟผ๋ฆฌ๋ฅผ <![CDATA[sql๋ฌธ ]]>๋กœ ๊ฐ์Œ“ใ„ด๋‹ค.

    CDATA = Character Data. ์ด ์•ˆ์—๋Š” ์ „๋ถ€ ๋ฌธ์ž data๋ผ๋Š” ๋œป์ด๋‹ค.

<update id="update" parameterType="BoardDto">
    UPDATE board
    SET   title = #{title}
      , content = #{content}
      , up_date = now()
    WHERE bno = #{bno} and bno &lt &gt 0
</update>

ํƒœ๊ทธ๋กœ ์˜คํ•ดํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— HTML ์—”ํ‹ฐํ‹ฐ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

<update id="update" parameterType="BoardDto">
		<![CDATA[  
		    UPDATE board
		    SET   title = #{title}
		      , content = #{content}
		      , up_date = now()
		    WHERE bno = #{bno} and bno <> 0
		]]>
</update>

๊ฐ€๋…์„ฑ๋ฉด์—์„œ ์ด๊ฑฐ ์“ฐ๋Š” ใ„ฑ ใ…” ์ข‹์Œ

03~04. ๊ฒŒ์‹œํŒ ๋ชฉ๋ก ๋งŒ๋“ค๊ธฐ์™€ ํŽ˜์ด์ง• - TDD

1. ๊ฒŒ์‹œ๋ฌผ ๋ชฉ๋ก ํŽ˜์ด์ง•

ํŽ˜์ด์ง•์€ ์šฉ์–ด๊ฐ€ ํ—ท๊ฐˆ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ์šฉ์–ด๋ฅผ ์ ์–ด๋‘๊ณ  ์‹œ์ž‘ํ•˜๊ธฐ

ํ™”๋ฉด์„ ๋งŒ๋“ค๊ธฐ ์ „์— TDD๋กœ ๋‚ด๊ฐ€ ์›ํ•˜๋Š”๋Œ€๋กœ ํŽ˜์ด์ง•๋˜๋Š”์ง€ Test ํ•ด๋ด์•ผ ํ•œ๋‹ค. โ†’ ํŽ˜์ด์ง•์ด TDDํ•˜๊ธฐ์— ๊ฐ€์žฅ ์ ํ•ฉ

2. LIMIT [offset ,] row_count

TABLE์— ๋“ค์–ด์žˆ๋Š” Data๋ฅผ ํŽ˜์ด์ง€ ๋ณ„๋„๋กœ ๊ฐ€์ ธ์˜ค๋ ค๋ฉด SELECT๋ฌธ์— LIMIT ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

offset : ๋งจ ์ฒ˜์Œ๋ถ€ํ„ฐ ์–ผ๋งˆ๋งŒํผ ๋–จ์–ด์ ธ์žˆ๋Š”๊ฐ€

row_count : ์ฝ์–ด์˜ฌ row์˜ ์ˆ˜

ex) LIMIT 0, 10 : ์ฒ˜์Œ๋ถ€ํ„ฐ 10๊ฐœ ๊ฐ€์ ธ์˜จ๋‹ค.

์ž๋ฐ”์˜ ์ •์„ 3์žฅ ์—ฐ์Šต๋ฌธ์ œ์—์„œ ๋‚˜์˜จ๋‹ค?

์‹ค์Šต1 - ํŽ˜์ด์ง•(PageHandler ๋งŒ๋“ค๊ณ  TDD)

TDD๋กœ ํŽ˜์ด์ง•ํ•˜๋Š” ๊ฑธ ๊ณ„์‚ฐํ•ด๋ณด์ž.

beginPage ๊ตฌํ•˜๋Š” ๊ณต์‹

PageHandler.java

public class PageHandler {
    int totalCnt; // ์ด ๊ฒŒ์‹œ๋ฌผ ๊ฐฏ์ˆ˜
    int pageSize; // ํ•œ ํŽ˜์ด์ง€์˜ ํฌ๊ธฐ
    int naviSize = 10; // ํŽ˜์ด์ง€ ๋‚ด๋น„๊ฒŒ์ด์…˜์˜ ํฌ๊ธฐ
    int totalPage; // ์ „์ฒด ํŽ˜์ด์ง€์˜ ๊ฐฏ์ˆ˜
    int page; // ํ˜„์žฌ ํŽ˜์ด์ง€
    int beginPage; // ๋‚ด๋น„๊ฒŒ์ด์…˜์˜ ์ฒซ ๋ฒˆ์งธ ํŽ˜์ด์ง€
    int endPage; // ๋‚ด๋น„๊ฒŒ์ด์…˜์˜ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€
    boolean showPrev; // ์ด์ „ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” ๋งํฌ๋ฅผ ๋ณด์—ฌ์ค„ ๊ฒƒ์ธ์ง€์˜ ์—ฌ๋ถ€
    boolean showNext; // ๋‹ค์Œ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” ๋งํฌ๋ฅผ ๋ณด์—ฌ์ค„ ๊ฒƒ์ธ์ง€์˜ ์—ฌ๋ถ€

    public PageHandler(int totalCnt, int page) {
        this(totalCnt, page, 10);
    }

    // ํŽ˜์ด์ง• ๊ณ„์‚ฐํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ๊ฒŒ 3๊ฐ€์ง€
    public PageHandler(int totalPage, int page, int pageSize) {
        this.totalPage = totalPage;
        this.page = page;
        this.pageSize = pageSize;

        totalPage = (int) Math.ceil(totalCnt / pageSize); // ๋‚จ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ฌ๋ฆผ์ฒ˜๋ฆฌ
        beginPage = page / naviSize * naviSize + 1;
        endPage = Math.min(beginPage + naviSize, totalPage); // endPage๊ฐ€ totalPage๋ณด๋‹ค ์ž‘์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—
        showPrev = beginPage != 1; // ์‹œ์ž‘ ํŽ˜์ด์ง€๊ฐ€ 1์ผ ๋•Œ๋งŒ ์•ˆ ๋‚˜์˜ค๋ฉด ๋œ๋‹ค.
        showNext = endPage != totalPage; // ๋‹ค์Œ์œผ๋กœ ๊ฐˆ๊ฒŒ ์—†์œผ๋‹ˆ๊นŒ ๋ณด์—ฌ์ฃผ์ง€์•Š์Œ
    }

    // page nav๋ฅผ printํ•˜๋Š” ๋ฉ”์„œ๋“œ
    void print() {
        System.out.println("page = " + page);

        System.out.println(showPrev ? "[PREV] " : "");
        for (int i = beginPage; i <= endPage; i++) {
            System.out.println(i + " ");
        }
        System.out.println(showNext ? " [NEXT]" : "");
    }

    @Override
    public String toString() {
        return "PageHandler{" +
                "totalCnt=" + totalCnt +
                ", pageSize=" + pageSize +
                ", naviSize=" + naviSize +
                ", totalPage=" + totalPage +
                ", page=" + page +
                ", beginPage=" + beginPage +
                ", endPage=" + endPage +
                ", showPrev=" + showPrev +
                ", showNext=" + showNext +
                '}';
    }
}

ํ…Œ์ŠคํŠธ1

์šฐ๋ฆฌ๊ฐ€ ์ž‘์„ฑํ•œ๋Œ€๋กœ ์ž˜ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž

@Test
  public void test() {
      PageHandler ph = new PageHandler(250, 1);
      ph.print();
      System.out.println("ph = " + ph);

      assertTrue(ph.beginPage == 1);
      assertTrue(ph.endPage == 10);

}

ํ…Œ์ŠคํŠธ2

์–ด๋–ป๊ฒŒ ๋‚˜์˜ฌ์ง€ ์˜ˆ์ƒ ๊ฒฐ๊ณผ๋ฅผ ๋จผ์ € ๊ทธ๋ฆผ์œผ๋กœ ๊ทธ๋ ค๋ณด๊ธฐ

@Test
public void test2() {
    PageHandler ph = new PageHandler(250, 11);
    ph.print();
    System.out.println("ph = " + ph);

    assertTrue(ph.beginPage == 11);
    assertTrue(ph.endPage == 20);
}

ํ…Œ์ŠคํŠธ3

์—ฌ๊ธฐ์„œ ์‹ค์ˆ˜. totalPage ๊ฒŒ์‚ฐ์ด ์ž˜๋ชป๋จ. ์ด๋ž˜์„œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค.

ํŽ˜์ด์ง• ํ…Œ์ŠคํŠธ ์‹œ์—๋Š” ๋งจ ์•ž๊ณผ ๋งจ ๋, ๊ฒฝ๊ณ„๊ฐ€ ๋˜๋Š” ์ง€์ ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ž˜ ํ•ด์•ผ ํ•œ๋‹ค.

before ==> totalPage = (int) Math.ceil(totalCnt / pageSize); // ๋‚จ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ฌ๋ฆผ์ฒ˜๋ฆฌ

after ==> totalPage = (int) Math.ceil(totalCnt / (double) pageSize); // ๋‚จ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ฌ๋ฆผ์ฒ˜๋ฆฌ

@Test
public void test3() {
    PageHandler ph = new PageHandler(255, 25);
    ph.print();
    System.out.println("ph = " + ph);

    assertTrue(ph.beginPage == 21);
    assertTrue(ph.endPage == 26);
}

๊ทธ๋ฆผ์„ ๊ทธ๋ ค์„œ ๊ฒฐ๊ณผ๋ฅผ ์˜ˆ์ƒํ•ด๋ณด๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ ค์„œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚ด๊ฐ€ ์˜ˆ์ƒํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ์ง€ ๊ผญ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์•„์•ผ ํ•œ๋‹ค.

โ—โ—โ—โ—โ—๊ทธ๋ฆผ ๊ทธ๋ฆฌ๋Š” ๊ฑฐ ์ค‘์š”!!!!!!!!! ๊ฐ•์˜ ์•ˆ ๋ณด๊ณ  ๊ทธ๋ฆด ์ˆ˜ ์žˆ์–ด์•ผ ํ•จโ—โ—โ—โ—โ—

์‹ค์Šต2 - ํŽ˜์ด์ง•(PageHandler ์ˆ˜์ •)

PageHandler.java์— getter, setter ์ถ”๊ฐ€

  • PageHandler.java
    package com.fastcampus.ch4.domain;
    
    import java.net.SocketTimeoutException;
    
    public class PageHandler {
        private int totalCnt; // ์ด ๊ฒŒ์‹œ๋ฌผ ๊ฐฏ์ˆ˜
        private int pageSize; // ํ•œ ํŽ˜์ด์ง€์˜ ํฌ๊ธฐ
        private int naviSize = 10; // ํŽ˜์ด์ง€ ๋‚ด๋น„๊ฒŒ์ด์…˜์˜ ํฌ๊ธฐ
        private int totalPage; // ์ „์ฒด ํŽ˜์ด์ง€์˜ ๊ฐฏ์ˆ˜
        private int page; // ํ˜„์žฌ ํŽ˜์ด์ง€
        private int beginPage; // ๋‚ด๋น„๊ฒŒ์ด์…˜์˜ ์ฒซ ๋ฒˆ์งธ ํŽ˜์ด์ง€
        private int endPage; // ๋‚ด๋น„๊ฒŒ์ด์…˜์˜ ๋งˆ์ง€๋ง‰ ํŽ˜์ด์ง€
        private boolean showPrev; // ์ด์ „ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” ๋งํฌ๋ฅผ ๋ณด์—ฌ์ค„ ๊ฒƒ์ธ์ง€์˜ ์—ฌ๋ถ€
        private boolean showNext; // ๋‹ค์Œ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” ๋งํฌ๋ฅผ ๋ณด์—ฌ์ค„ ๊ฒƒ์ธ์ง€์˜ ์—ฌ๋ถ€
    
        public PageHandler(int totalCnt, int page) {
            this(totalCnt, page, 10);
        }
    
        // ํŽ˜์ด์ง• ๊ณ„์‚ฐํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ๊ฒŒ 3๊ฐ€์ง€
        public PageHandler(int totalCnt, int page, int pageSize) {
            this.totalCnt = totalCnt;
            this.page = page;
            this.pageSize = pageSize;
    
            totalPage = (int) Math.ceil(totalCnt / (double) pageSize); // ๋‚จ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ฌ๋ฆผ์ฒ˜๋ฆฌ
            beginPage = (page - 1) / naviSize * naviSize + 1;
            endPage = Math.min(beginPage + naviSize - 1, totalPage); // endPage๊ฐ€ totalPage๋ณด๋‹ค ์ž‘์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—
            showPrev = beginPage != 1; // ์‹œ์ž‘ ํŽ˜์ด์ง€๊ฐ€ 1์ผ ๋•Œ๋งŒ ์•ˆ ๋‚˜์˜ค๋ฉด ๋œ๋‹ค.
            showNext = endPage != totalPage; // ๋‹ค์Œ์œผ๋กœ ๊ฐˆ๊ฒŒ ์—†์œผ๋‹ˆ๊นŒ ๋ณด์—ฌ์ฃผ์ง€์•Š์Œ
        }
    
        // page nav๋ฅผ printํ•˜๋Š” ๋ฉ”์„œ๋“œ
        public void print() {
            System.out.println("page = " + page);
    
            System.out.print(showPrev ? "[PREV] " : "");
            for (int i = beginPage; i <= endPage; i++) {
                System.out.print(i + " ");
            }
            System.out.println(showNext ? " [NEXT]" : "");
        }
    
        public int getTotalCnt() {
            return totalCnt;
        }
    
        public void setTotalCnt(int totalCnt) {
            this.totalCnt = totalCnt;
        }
    
        public int getPageSize() {
            return pageSize;
        }
    
        public void setPageSize(int pageSize) {
            this.pageSize = pageSize;
        }
    
        public int getNaviSize() {
            return naviSize;
        }
    
        public void setNaviSize(int naviSize) {
            this.naviSize = naviSize;
        }
    
        public int getTotalPage() {
            return totalPage;
        }
    
        public void setTotalPage(int totalPage) {
            this.totalPage = totalPage;
        }
    
        public int getPage() {
            return page;
        }
    
        public void setPage(int page) {
            this.page = page;
        }
    
        public int getBeginPage() {
            return beginPage;
        }
    
        public void setBeginPage(int beginPage) {
            this.beginPage = beginPage;
        }
    
        public int getEndPage() {
            return endPage;
        }
    
        public void setEndPage(int endPage) {
            this.endPage = endPage;
        }
    
        public boolean isShowPrev() {
            return showPrev;
        }
    
        public void setShowPrev(boolean showPrev) {
            this.showPrev = showPrev;
        }
    
        public boolean isShowNext() {
            return showNext;
        }
    
        public void setShowNext(boolean showNext) {
            this.showNext = showNext;
        }
    
        @Override
        public String toString() {
            return "PageHandler{" +
                    "totalCnt=" + totalCnt +
                    ", pageSize=" + pageSize +
                    ", naviSize=" + naviSize +
                    ", totalPage=" + totalPage +
                    ", page=" + page +
                    ", beginPage=" + beginPage +
                    ", endPage=" + endPage +
                    ", showPrev=" + showPrev +
                    ", showNext=" + showNext +
                    '}';
        }
    }
  • BoardDao.java
    package com.fastcampus.ch4.dao;
    
    import com.fastcampus.ch4.domain.*;
    
    import java.util.*;
    
    public interface BoardDao {
        BoardDto select(Integer bno) throws Exception;
        int delete(Integer bno, String writer) throws Exception;
        int insert(BoardDto dto) throws Exception;
        int update(BoardDto dto) throws Exception;
        int increaseViewCnt(Integer bno) throws Exception;
    
        List<BoardDto> selectPage(Map map) throws Exception;
        List<BoardDto> selectAll() throws Exception;
        int deleteAll() throws Exception;
        int count() throws Exception;
    
    //    int searchResultCnt(SearchCondition sc) throws Exception;
    //    List<BoardDto> searchSelectPage(SearchCondition sc) throws Exception;
    }
  • BoardDaoImpl.java
    package com.fastcampus.ch4.dao;
    
    import com.fastcampus.ch4.domain.*;
    import org.apache.ibatis.session.*;
    import org.springframework.beans.factory.annotation.*;
    import org.springframework.stereotype.*;
    
    import java.util.*;
    
    @Repository
    public class BoardDaoImpl implements BoardDao {
        @Autowired
        private SqlSession session;
        private static String namespace = "com.fastcampus.ch4.dao.BoardMapper.";
    
        @Override
        public int count() throws Exception {
            return session.selectOne(namespace+"count");
        } // T selectOne(String statement)
    
        @Override
        public int deleteAll() {
            return session.delete(namespace+"deleteAll");
        } // int delete(String statement)
    
        @Override
        public int delete(Integer bno, String writer) throws Exception {
            Map map = new HashMap();
            map.put("bno", bno);
            map.put("writer", writer);
            return session.delete(namespace+"delete", map);
        } // int delete(String statement, Object parameter)
    
        @Override
        public int insert(BoardDto dto) throws Exception {
            return session.insert(namespace+"insert", dto);
        } // int insert(String statement, Object parameter)
    
        @Override
        public List<BoardDto> selectAll() throws Exception {
            return session.selectList(namespace+"selectAll");
        } // List<E> selectList(String statement)
    
        @Override
        public BoardDto select(Integer bno) throws Exception {
            return session.selectOne(namespace + "select", bno);
        } // T selectOne(String statement, Object parameter)
    
        @Override
        public List<BoardDto> selectPage(Map map) throws Exception {
            return session.selectList(namespace+"selectPage", map);
        } // List<E> selectList(String statement, Object parameter)
    
        @Override
        public int update(BoardDto dto) throws Exception {
            return session.update(namespace+"update", dto);
        } // int update(String statement, Object parameter)
    
        @Override
        public int increaseViewCnt(Integer bno) throws Exception {
            return session.update(namespace+"increaseViewCnt", bno);
        } // int update(String statement, Object parameter)
    
    //    @Override
    //    public int searchResultCnt(SearchCondition sc) throws Exception {
    //        System.out.println("sc in searchResultCnt() = " + sc);
    //        System.out.println("session = " + session);
    //        return session.selectOne(namespace+"searchResultCnt", sc);
    //    } // T selectOne(String statement, Object parameter)
    //
    //    @Override
    //    public List<BoardDto> searchSelectPage(SearchCondition sc) throws Exception {
    //        return session.selectList(namespace+"searchSelectPage", sc);
    //    } // List<E> selectList(String statement, Object parameter)
    }
  • BoardDaoImplTest.java
    package com.fastcampus.ch4.dao;
    
    import com.fastcampus.ch4.domain.*;
    import org.junit.*;
    import org.junit.runner.*;
    import org.springframework.beans.factory.annotation.*;
    import org.springframework.test.context.*;
    import org.springframework.test.context.junit4.*;
    
    import java.util.*;
    
    import static org.junit.Assert.*;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/root-context.xml"})
    public class BoardDaoImplTest {
        @Autowired
        private BoardDao boardDao;
    
        @Test
        public void countTest() throws Exception {
            boardDao.deleteAll();
            assertTrue(boardDao.count() == 0);
    
            BoardDto boardDto = new BoardDto("no title", "no content", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
            assertTrue(boardDao.count() == 1);
    
            assertTrue(boardDao.insert(boardDto) == 1);
            assertTrue(boardDao.count() == 2);
        }
    
        @Test
        public void deleteAllTest() throws Exception {
            boardDao.deleteAll();
            assertTrue(boardDao.count() == 0);
    
            BoardDto boardDto = new BoardDto("no title", "no content", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
            assertTrue(boardDao.deleteAll() == 1);
            assertTrue(boardDao.count() == 0);
    
            boardDto = new BoardDto("no title", "no content", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
            assertTrue(boardDao.insert(boardDto) == 1);
            assertTrue(boardDao.deleteAll() == 2);
            assertTrue(boardDao.count() == 0);
        }
    
        @Test
        public void deleteTest() throws Exception {
            boardDao.deleteAll();
            assertTrue(boardDao.count() == 0);
    
            BoardDto boardDto = new BoardDto("no title", "no content", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
            Integer bno = boardDao.selectAll().get(0).getBno();
            assertTrue(boardDao.delete(bno, boardDto.getWriter()) == 1);
            assertTrue(boardDao.count() == 0);
    
            assertTrue(boardDao.insert(boardDto) == 1);
            bno = boardDao.selectAll().get(0).getBno();
            assertTrue(boardDao.delete(bno, boardDto.getWriter() + "222") == 0);
            assertTrue(boardDao.count() == 1);
    
            assertTrue(boardDao.delete(bno, boardDto.getWriter()) == 1);
            assertTrue(boardDao.count() == 0);
    
            assertTrue(boardDao.insert(boardDto) == 1);
            bno = boardDao.selectAll().get(0).getBno();
            assertTrue(boardDao.delete(bno + 1, boardDto.getWriter()) == 0);
            assertTrue(boardDao.count() == 1);
        }
    
        @Test
        public void insertTest() throws Exception {
            boardDao.deleteAll();
            BoardDto boardDto = new BoardDto("no title", "no content", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
    
            boardDto = new BoardDto("no title", "no content", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
            assertTrue(boardDao.count() == 2);
    
            boardDao.deleteAll();
            boardDto = new BoardDto("no title", "no content", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
            assertTrue(boardDao.count() == 1);
        }
    
        @Test
        public void selectAllTest() throws Exception {
            boardDao.deleteAll();
            assertTrue(boardDao.count() == 0);
    
            List<BoardDto> list = boardDao.selectAll();
            assertTrue(list.size() == 0);
    
            BoardDto boardDto = new BoardDto("no title", "no content", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
    
            list = boardDao.selectAll();
            assertTrue(list.size() == 1);
    
            assertTrue(boardDao.insert(boardDto) == 1);
            list = boardDao.selectAll();
            assertTrue(list.size() == 2);
        }
    
        @Test
        public void selectTest() throws Exception {
            boardDao.deleteAll();
            assertTrue(boardDao.count() == 0);
    
            BoardDto boardDto = new BoardDto("no title", "no content", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
    
            Integer bno = boardDao.selectAll().get(0).getBno();
            boardDto.setBno(bno);
            BoardDto boardDto2 = boardDao.select(bno);
            assertTrue(boardDto.equals(boardDto2));
        }
    
        @Test
        public void selectPageTest() throws Exception {
            boardDao.deleteAll();
    
            for (int i = 1; i <= 10; i++) {
                BoardDto boardDto = new BoardDto("" + i, "no content" + i, "asdf");
                boardDao.insert(boardDto);
            }
    
            Map map = new HashMap();
            map.put("offset", 0);
            map.put("pageSize", 3);
    
            List<BoardDto> list = boardDao.selectPage(map);
            assertTrue(list.get(0).getTitle().equals("10"));
            assertTrue(list.get(1).getTitle().equals("9"));
            assertTrue(list.get(2).getTitle().equals("8"));
    
            map = new HashMap();
            map.put("offset", 0);
            map.put("pageSize", 1);
    
            list = boardDao.selectPage(map);
            assertTrue(list.get(0).getTitle().equals("10"));
    
            map = new HashMap();
            map.put("offset", 7);
            map.put("pageSize", 3);
    
            list = boardDao.selectPage(map);
            assertTrue(list.get(0).getTitle().equals("3"));
            assertTrue(list.get(1).getTitle().equals("2"));
            assertTrue(list.get(2).getTitle().equals("1"));
        }
    
        @Test
        public void updateTest() throws Exception {
            boardDao.deleteAll();
            BoardDto boardDto = new BoardDto("no title", "no content", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
    
            Integer bno = boardDao.selectAll().get(0).getBno();
            System.out.println("bno = " + bno);
            boardDto.setBno(bno);
            boardDto.setTitle("yes title");
            assertTrue(boardDao.update(boardDto) == 1);
    
            BoardDto boardDto2 = boardDao.select(bno);
            assertTrue(boardDto.equals(boardDto2));
        }
    
        @Test
        public void increaseViewCntTest() throws Exception {
            boardDao.deleteAll();
            assertTrue(boardDao.count() == 0);
    
            BoardDto boardDto = new BoardDto("no title", "no content", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
            assertTrue(boardDao.count() == 1);
    
            Integer bno = boardDao.selectAll().get(0).getBno();
            assertTrue(boardDao.increaseViewCnt(bno) == 1);
    
            boardDto = boardDao.select(bno);
            assertTrue(boardDto != null);
            assertTrue(boardDto.getView_cnt() == 1);
    
            assertTrue(boardDao.increaseViewCnt(bno) == 1);
            boardDto = boardDao.select(bno);
            assertTrue(boardDto != null);
            assertTrue(boardDto.getView_cnt() == 2);
        }
    }

์ด์ œ Service๋ฅผ ๋งŒ๋“ค ์ฐจ๋ก€. BoardService๋Š” BoardDaoImpl์— ์žˆ๋Š” ๊ฑธ ๋‹ค ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.

  • BoardServiceImpl.java
    package com.fastcampus.ch4.service;
    
    import com.fastcampus.ch4.dao.*;
    import com.fastcampus.ch4.domain.*;
    import org.springframework.beans.factory.annotation.*;
    import org.springframework.stereotype.*;
    
    import java.util.*;
    
    @Service
    public class BoardServiceImpl implements BoardService {
        @Autowired
        BoardDao boardDao;
    
        @Override
        public int getCount() throws Exception {
            return boardDao.count();
        }
    
        @Override
        public int remove(Integer bno, String writer) throws Exception { // ์‚ญ์ œ ์‹œ ๊ฒŒ์‹œ๋ฌผ ๋ฒˆํ˜ธ์™€ ์ž‘์„ฑ์ž ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ
            return boardDao.delete(bno, writer);
        }
    
        @Override
        // Tx์ฒ˜๋ฆฌํ• ๊ฒŒ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์™ธ๋ฅผ ๋‹ค ์ปจํŠธ๋กค๋Ÿฌ์—๊ฒŒ ๋˜์ง Tx ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค๋ฉด try-catch๋กœ ๋ฌถ์–ด์•ผ ํ•จ
        public int write(BoardDto boardDto) throws Exception {
            return boardDao.insert(boardDto);
        }
    
        @Override
        public List<BoardDto> getList() throws Exception {
            return boardDao.selectAll();
        }
    
        @Override
        public BoardDto read(Integer bno) throws Exception {
            BoardDto boardDto = boardDao.select(bno);
            boardDao.increaseViewCnt(bno);
    
            return boardDto;
        }
    
        @Override
        public List<BoardDto> getPage(Map map) throws Exception {
            return boardDao.selectPage(map);
        }
    
        @Override
        public int modify(BoardDto boardDto) throws Exception {
            return boardDao.update(boardDto);
        }
    
    //    @Override
    //    public int getSearchResultCnt(SearchCondition sc) throws Exception {
    //        return boardDao.searchResultCnt(sc);
    //    }
    //
    //    @Override
    //    public List<BoardDto> getSearchResultPage(SearchCondition sc) throws Exception {
    //        return boardDao.searchSelectPage(sc);
    //    }
    }
  • BoardService.java
    package com.fastcampus.ch4.service;
    
    import com.fastcampus.ch4.domain.BoardDto;
    
    import java.util.List;
    import java.util.Map;
    
    public interface BoardService {
        int getCount() throws Exception;
    
        int remove(Integer bno, String writer) throws Exception;
    
        int write(BoardDto boardDto) throws Exception;
    
        List<BoardDto> getList() throws Exception;
    
        BoardDto read(Integer bno) throws Exception;
    
        List<BoardDto> getPage(Map map) throws Exception;
    
        int modify(BoardDto boardDto) throws Exception;
    
    //    int getSearchResultCnt(SearchCondition sc) throws Exception;
    //
    //    List<BoardDto> getSearchResultPage(SearchCondition sc) throws Exception;
    }
  • boardMapper.xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.fastcampus.ch4.dao.BoardMapper">
        <select id="count" resultType="int">
            SELECT count(*) FROM board
        </select>
    
        <delete id="deleteAll">
            DELETE FROM board
        </delete>
    
        <delete id="delete" parameterType="map">
            DELETE FROM board WHERE bno = #{bno} and writer = #{writer}
        </delete>
    
        <insert id="insert" parameterType="BoardDto">
            INSERT INTO board
                (title, content, writer)
            VALUES
                (#{title}, #{content}, #{writer})
        </insert>
    
        <select id="selectAll" resultType="BoardDto">
            SELECT bno, title, content, writer, view_cnt, comment_cnt, reg_date
            FROM board
            ORDER BY reg_date DESC, bno DESC
        </select>
    
        <sql id="selectFromBoard">
            SELECT bno, title, content, writer, view_cnt, comment_cnt, reg_date
            FROM board
        </sql>
    
        <select id="select" parameterType="int" resultType="BoardDto">
            <include refid="selectFromBoard"/>
            WHERE bno = #{bno}
        </select>
    
    <!--    <select id="selectPage" parameterType="map" resultType="BoardDto">-->
    <!--        <include refid="selectFromBoard"/>-->
    <!--        ORDER BY reg_date DESC, bno DESC-->
    <!--        LIMIT #{offset}, #{pageSize}-->
    <!--    </select>-->
    
        <select id="selectPage" parameterType="map" resultType="BoardDto">
            SELECT bno, title, content, writer, view_cnt, comment_cnt, reg_date
            FROM board
            ORDER BY reg_date DESC, bno DESC
            LIMIT #{offset}, #{pageSize}
        </select>
    
        <update id="update" parameterType="BoardDto">
            UPDATE board
            SET   title = #{title}
              , content = #{content}
              , up_date = now()
            WHERE bno = #{bno}
        </update>
    
        <update id="updateCommentCnt" parameterType="map">
            UPDATE board
            SET   comment_cnt = comment_cnt + #{cnt}
            WHERE bno = #{bno}
        </update>
    
        <update id="increaseViewCnt" parameterType="int">
            UPDATE board
            SET   view_cnt = view_cnt + 1
            WHERE bno = #{bno}
        </update>
    
    </mapper>
  • BoardController.java
    package com.fastcampus.ch4.controller;
    
    import com.fastcampus.ch4.domain.*;
    import com.fastcampus.ch4.service.*;
    import org.springframework.beans.factory.annotation.*;
    import org.springframework.stereotype.*;
    import org.springframework.ui.*;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.servlet.mvc.support.*;
    
    import javax.servlet.http.*;
    import java.time.*;
    import java.util.*;
    
    @Controller
    @RequestMapping("/board")
    public class BoardController {
        @Autowired
        BoardService boardService;
    
        @GetMapping("/list")
        public String list(int page, int pageSize, Model m, HttpServletRequest request) {
            if (!loginCheck(request))
                return "redirect:/login/login?toURL=" + request.getRequestURL();  // ๋กœ๊ทธ์ธ์„ ์•ˆํ–ˆ์œผ๋ฉด ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™
    
            try {
                Map map = new HashMap();
                map.put("offset", (page - 1) * pageSize);
                map.put("pageSize", pageSize);
    
                List<BoardDto> list = boardService.getPage(map);
                m.addAttribute("list", list);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return "boardList"; // ๋กœ๊ทธ์ธ์„ ํ•œ ์ƒํƒœ์ด๋ฉด, ๊ฒŒ์‹œํŒ ํ™”๋ฉด์œผ๋กœ ์ด๋™
        }
    
        private boolean loginCheck(HttpServletRequest request) {
            // 1. ์„ธ์…˜์„ ์–ป์–ด์„œ
            HttpSession session = request.getSession();
            // 2. ์„ธ์…˜์— id๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ, ์žˆ์œผ๋ฉด true๋ฅผ ๋ฐ˜ํ™˜
            return session.getAttribute("id") != null;
        }
    }
  • boardList.jsp
    <%@ page language="java" contentType="text/html; charset=UTF-8" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>fastcampus</title>
        <link rel="stylesheet" href="<c:url value='/css/menu.css'/>">
    </head>
    <body>
    <div id="menu">
        <ul>
            <li id="logo">fastcampus</li>
            <li><a href="<c:url value='/'/>">Home</a></li>
            <li><a href="<c:url value='/board/list'/>">Board</a></li>
            <li><a href="<c:url value='/login/login'/>">login</a></li>
            <li><a href="<c:url value='/register/add'/>">Sign in</a></li>
            <li><a href=""><i class="fas fa-search small"></i></a></li>
        </ul>
    </div>
    <div style="text-align:center">
        <table border="1">
            <tr>
                <th>๋ฒˆํ˜ธ</th>
                <th>์ œ๋ชฉ</th>
                <th>์ด๋ฆ„</th>
                <th>๋“ฑ๋ก์ผ</th>
                <th>์กฐํšŒ์ˆ˜</th>
            </tr>
            <c:forEach var="board" items="${list}">
                <tr>
                    <td>${board.bno}</td>
                    <td>${board.title}</td>
                    <td>${board.writer}</td>
                    <td>${board.reg_date}</td>
                    <td>${board.view_cnt}</td>
                </tr>
            </c:forEach>
        </table>
        <br>
        <div>
            <c:if test="${ph.showPrev}">
                <a href="<c:url value='/board/list?page=${ph.beginPage - 1}&pageSize=${ph.pageSize} '/>">&lt;</a>
            </c:if>
            <c:forEach var="i" begin="${ph.beginPage}" end="${ph.endPage}">
                <a href="<c:url value='/board/list?page=${i}&pageSize=${ph.pageSize} '/>">${i}</a>
            </c:forEach>
            <c:if test="${ph.showNext}">
                <a href="<c:url value='/board/list?page=${ph.endPage + 1}&pageSize=${ph.pageSize} '/>">&gt;</a>
            </c:if>
        </div>
    </div>
    </body>
    </html>

๐Ÿ’กย ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ Integer๋กœํ•˜๋ฉด ๊ฐ’์ด ์•ˆ ๋“ค์–ด์™”์„ ๋•Œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋‘˜์ด ์–ด๋–ค ์ฐจ์ด์ธ์ง€ ์ž˜ ์•Œ์•„๋‘๊ธฐ

//    public String list(int page, int pageSize, Model m, HttpServletRequest request) {
    public String list(Integer page, Integer pageSize, Model m, HttpServletRequest request) { // page ๊ฐ’์ด ์•ˆ ๋“ค์–ด์™”์„ ๋•Œ ์—๋Ÿฌ ๋ฐœ์ƒ X

select * from board
order by reg_date desc, bno desc
limit 0, 10; # 0๋ถ€ํ„ฐ 10๊ฐœ ๊ฐ€์ ธ์˜ค๋ผ๋Š” ๋œป

select * from board
order by reg_date desc, bno desc
limit 10, 10; # 2ํŽ˜์ด์ง€

pageHandler ์‚ฌ์šฉํ•ด์„œ view navigation ๋ณด์—ฌ์ฃผ๊ธฐ

์ž˜ ์•ˆ ๋‚˜์˜ค๋Š” ๊ฑฐ ์žˆ์œผ๋ฉด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑํ•ด๋ณด๊ธฐโ€ฆ

์ด ์ผ€์ด์Šค ํ†ต๊ณผ ์•ˆ ๋˜์—ˆ์—ˆ์Œ

@Test
public void test3() {
    PageHandler ph = new PageHandler(255, 25);
    ph.print();
    System.out.println("ph = " + ph);
    assertTrue(ph.getBeginPage() == 21);
    assertTrue(ph.getEndPage() == 26);
}
before -> beginPage = (page - 1) / naviSize * naviSize + 1; // beginPage ๊ณ„์‚ฐ์ด ์ž˜๋ชป๋์—ˆ์Œ
after ->  beginPage = page/ naviSize * naviSize + 1;

05~06. ๊ฒŒ์‹œํŒ ์ฝ๊ธฐ, ์“ฐ๊ธฐ, ์‚ญ์ œ, ์ˆ˜์ • ๊ธฐ๋Šฅ ๊ตฌํ˜„

1. ๊ธฐ๋Šฅ๋ณ„ URI ์ •์˜

  1. ๊ฐ ๊ธฐ๋Šฅ๋ณ„๋กœ URI๋ฅผ ๋จผ์ € ์ •ํ•œ๋‹ค.
  1. ๊ทธ ๋‹ค์Œ์—๋Š” ๋ฉ”์„œ๋“œ์™€ ์„ค๋ช…์„ TABLE๋กœ ์ •๋ฆฌํ•œ๋‹ค.

URL๊ณผ URI์˜ ์ฐจ์ด

URL : ์ „์ฒด ๊ฒฝ๋กœ

URI : URL ์ผ๋ถ€

์ž‘์—…URIHTTP๋ฉ”์„œ๋“œ์„ค๋ช…
์ฝ๊ธฐ/board/read?bno=๋ฒˆํ˜ธGET์ง€์ •๋œ ๋ฒˆํ˜ธ์˜ ๊ฒŒ์‹œ๋ฌผ์„ ๋ณด์—ฌ์ค€๋‹ค.
์‚ญ์ œ/board/removePOST๊ฒŒ์‹œ๋ฌผ์„ ์‚ญ์ œํ•œ๋‹ค.
์“ฐ๊ธฐ/board/writeGET๊ฒŒ์‹œ๋ฌผ์„ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•œ ํ™”๋ฉด์„ ๋ณด์—ฌ์ค€๋‹ค.
/board/writePOST์ž‘์„ฑํ•œ ๊ฒŒ์‹œ๋ฌผ์„ ์ €์žฅํ•œ๋‹ค.
์ˆ˜์ •/board/modify?bno=๋ฒˆํ˜ธGET๊ฒŒ์‹œ๋ฌผ์„ ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•ด ์ฝ์–ด์˜จ๋‹ค.
/board/modifyPOST์ˆ˜์ •๋œ ๊ฒŒ์‹œ๋ฌผ์„ ์ €์žฅํ•œ๋‹ค.

์‚ญ์ œ โ†’ ์ฝ๊ธฐ โ†’ ์“ฐ๊ธฐ โ†’ ์ˆ˜์ • ์ˆœ์„œ๋กœ ์‰ฌ์›€

2. ๊ฒŒ์‹œ๋ฌผ ์ฝ๊ธฐ ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„

boardList์—์„œ title ํด๋ฆญ ์‹œ ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ์„ ์ฝ๋Š” ์š”์ฒญ(/board/read?bno=533)์ด GET์œผ๋กœ ๊ฐ€๊ณ  ๊ทธ๊ฑธ BoardController๊ฐ€ ๋ฐ›์•„์„œ boardService.read(bno)๋ฅผ ํ˜ธ์ถœํ•ด์„œ DB์—์„œ boardDto๋ฅผ ๋ฐ›์•„๋‹ค๊ฐ€ ๊ทธ ๋‚ด์šฉ์„ board.jsp์— ๋ฟŒ๋ ค์ค˜์•ผ ํ•œ๋‹ค.

์•„๋ž˜์ฒ˜๋Ÿผ ํ”„๋กœ์ ํŠธํ•  ๋•Œ ์ง์ ‘ ๋‹ค flow๋ฅผ ๊ทธ๋ ค๋ด์•ผ ํ•œ๋‹ค.(DFD?๊ฐ€ ๋ญ”์ง€ ์ฐพ์•„๋ณด๊ธฐ)

[ ์‹ค์Šต ]

BoardController.java

Model์— ๋‹ด์„ ๋•Œ ์ด๋ฆ„ ์ƒ๋žต ์‹œ ํƒ€์ž…์˜ ์ฒซ ๊ธ€์ž๋ฅผ ์†Œ๋ฌธ์ž๋กœ ํ•œ ๋ฌธ์ž์—ด์ด ํ‚ค๊ฐ€ ๋œ๋‹ค.

public String read(Integer bno, Model m) {
    try {
        BoardDto boardDto = boardService.read(bno);
				// m.addAttribute("boardDto", boardDto); // ์•„๋ž˜ ๋ฌธ์žฅ๊ณผ ๋™์ผ
        m.addAttribute(boardDto); // ์ด๋ฆ„ ์ƒ๋žต ์‹œ ํƒ€์ž…์˜ ์ฒซ ๊ธ€์ž๊ฐ€ ์†Œ๋ฌธ์ž๋กœ ๋ฐ”๋€๊ฒŒ key๊ฐ€๋œ๋‹ค.
    } catch (Exception e) {
        e.printStackTrace();
    }

    return "board";
}

board.jsp

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

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>fastcampus</title>
    <link rel="stylesheet" href="<c:url value='/css/menu.css'/>">
</head>
<body>
<div id="menu">
    <ul>
        <li id="logo">fastcampus</li>
        <li><a href="<c:url value='/'/>">Home</a></li>
        <li><a href="<c:url value='/board/list'/>">Board</a></li>
        <li><a href="<c:url value='/login/login'/>">login</a></li>
        <li><a href="<c:url value='/register/add'/>">Sign in</a></li>
        <li><a href=""><i class="fas fa-search small"></i></a></li>
    </ul>
</div>
<div style="text-align:center">
    <h2>๊ฒŒ์‹œ๋ฌผ ์ฝ๊ธฐ</h2>
    <form action="" id="form">
        <input type="text" name="bno" value="${boardDto.bno}" readonly/>
        <input type="text" name="title" value="${boardDto.title}" readonly>
        <textarea name="content" id="" cols="30" rows="10" readonly>${boardDto.content}</textarea>
        <button type="button" id="writeBtn" class="btn">๋“ฑ๋ก</button>
        <button type="button" id="modifyBtn" class="btn">์ˆ˜์ •</button>
        <button type="button" id="removeBtn" class="btn">์‚ญ์ œ</button>
        <button type="button" id="listBtn" class="btn">๋ชฉ๋ก</button>
    </form>
</div>
</body>
</html>

๊ฒŒ์‹œ๋ฌผ ์ฝ๊ธฐ์™€ ์ˆ˜์ •์„ ๊ฐ™์€ board.jsp๋ฅผ ์“ธ ์˜ˆ์ •์ด๋‹ค.

[ ๊ฒฐ๊ณผ ํ™”๋ฉด ]

๋ชฉ๋ก ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋ฉด boardList.jsp๋กœ ์ด๋™ํ•˜๋Š” ๊ธฐ๋Šฅ ๊ตฌํ˜„

๊ฒŒ์‹œ๋ฌผ ๋ฆฌ์ŠคํŠธ์—์„œ ์ œ๋ชฉ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ ค์„œ ์ƒ์„ธํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ ์ƒํƒœ์ผ ๋•Œ, ์—ฌ๊ธฐ์„œ ๋ชฉ๋ก ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์•„๋ฌด ๋ชฉ๋ก ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ๋‚ด๊ฐ€ ๋ณด๊ณ  ์žˆ์—ˆ๋˜ ํŽ˜์ด์ง€๋กœ ๊ฐ€์•ผ ํ•œ๋‹ค.

How?

boardList.jsp์—์„œ ์ฒซ ์š”์ฒญ ์‹œ BoardController๊ฐ€ page=3&pageSize=10์„ ๋ฐ›์•„์„œ ๊ทธ๊ฑธ board.jsp์— ๋„˜๊ฒจ์ค˜์•ผ ํ•œ๋‹ค. ๊ทธ๋ž˜์•ผ ๋ฒ„ํŠผ ๋งํฌ์— ๊ทธ ๊ฐ’์ด ๋„˜์–ด๊ฐˆ ์ˆ˜ ์žˆ์Œ

[ ์‹ค์Šต ]

BoardController.java

page์™€ pageSize๋ฅผ boardController์˜ list ๋ฉ”์„œ๋“œ๊ฐ€ ์ค˜์•ผ ํ•œ๋‹ค.

@GetMapping("/list")
//    public String list(int page, int pageSize, Model m, HttpServletRequest request) {
public String list(Integer page, Integer pageSize, Model m, HttpServletRequest request) { // page ๊ฐ’์ด ์•ˆ ๋“ค์–ด์™”์„ ๋•Œ ์—๋Ÿฌ ๋ฐœ์ƒ X
    if (!loginCheck(request))
        return "redirect:/login/login?toURL=" + request.getRequestURL();  // ๋กœ๊ทธ์ธ์„ ์•ˆํ–ˆ์œผ๋ฉด ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™

    if (page == null) page = 1;
    if (pageSize == null) pageSize = 10;

    try {
        int totalCnt = boardService.getCount();
        PageHandler pageHandler = new PageHandler(totalCnt, page, pageSize);

        Map map = new HashMap();
        map.put("offset", (page - 1) * pageSize);
        map.put("pageSize", pageSize);

        List<BoardDto> list = boardService.getPage(map);
        m.addAttribute("list", list);
        m.addAttribute("ph", pageHandler);
        m.addAttribute("page", page);
        m.addAttribute("pageSize", pageSize);
    } catch (Exception e) {
        e.printStackTrace();
    }

    return "boardList"; // ๋กœ๊ทธ์ธ์„ ํ•œ ์ƒํƒœ์ด๋ฉด, ๊ฒŒ์‹œํŒ ํ™”๋ฉด์œผ๋กœ ์ด๋™
}

/board/read

 @GetMapping("/read")
public String read(Integer bno, Integer page, Integer pageSize, Model m) {
    try {
        BoardDto boardDto = boardService.read(bno);
        m.addAttribute(boardDto); // ์ด๋ฆ„ ์ƒ๋žต ์‹œ ํƒ€์ž…์˜ ์ฒซ ๊ธ€์ž๊ฐ€ ์†Œ๋ฌธ์ž๋กœ ๋ฐ”๋€๊ฒŒ key๊ฐ€๋œ๋‹ค.
        m.addAttribute("page", page);
        m.addAttribute("pageSize", pageSize);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "board";
}

board.jsp์— jquery ์ถ”๊ฐ€

  • ์›๋ณธ
    <%@ page language="java" contentType="text/html; charset=UTF-8" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>fastcampus</title>
        <link rel="stylesheet" href="<c:url value='/css/menu.css'/>">
        <script src="https://code.jquery.com/jquery-1.11.3.js"></script>
    </head>
    <body>
    <div id="menu">
        <ul>
            <li id="logo">fastcampus</li>
            <li><a href="<c:url value='/'/>">Home</a></li>
            <li><a href="<c:url value='/board/list'/>">Board</a></li>
            <li><a href="<c:url value='/login/login'/>">login</a></li>
            <li><a href="<c:url value='/register/add'/>">Sign in</a></li>
            <li><a href=""><i class="fas fa-search small"></i></a></li>
        </ul>
    </div>
    <div style="text-align:center">
        <h2>๊ฒŒ์‹œ๋ฌผ ์ฝ๊ธฐ</h2>
        <form action="" id="form">
            <input type="text" name="bno" value="${boardDto.bno}" readonly/>
            <input type="text" name="title" value="${boardDto.title}" readonly>
            <textarea name="content" id="" cols="30" rows="10" readonly>${boardDto.content}</textarea>
            <button type="button" id="writeBtn" class="btn">๋“ฑ๋ก</button>
            <button type="button" id="modifyBtn" class="btn">์ˆ˜์ •</button>
            <button type="button" id="removeBtn" class="btn">์‚ญ์ œ</button>
            <button type="button" id="listBtn" class="btn">๋ชฉ๋ก</button>
        </form>
    </div>
    <script>
        $(document).ready(function(){ // main() js window.onload๋ž‘ ๋น„์Šทํ•œ ๊ฑฐ?
            $('#listBtn').on("click", function() {
                location.href = "<c:url value='/board/list'/>?page=${page}&pageSize=${pageSize}";
            });
        });
    </script>
    </body>
    </html>

๋ธŒ๋ผ์šฐ์ €๊ฐ€ html๋ฌธ์„œ๋ฅผ ์ญ‰ ์ฝ๊ณ  dom์„ ๋‹ค ๊ตฌ์„ฑํ•˜๊ณ  ๋‚˜๋ฉด document.ready์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒ

<script>
    $(document).ready(function(){ // main() js window.onload๋ž‘ ๋น„์Šทํ•œ ๊ฑฐ?
        $('#listBtn').on("click", function() {
            location.href = "<c:url value='/board/list'/>?page=${page}&pageSize=${pageSize}";
        });
    });
</script>

3. ๊ฒŒ์‹œ๋ฌผ ์‚ญ์ œ ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„

model์— ๋‹ด์•„์ฃผ๋ฉด redirect์‹œ ๋’ค์— ์ž๋™์œผ๋กœ ๋ถ™๋Š”๋‹ค.

BoardController.java

@PostMapping("/remove")
public String remove(Integer bno, Integer page, Integer pageSize, Model m, HttpSession session) {
    String writer = (String) session.getAttribute("id");

    try {
        boardService.remove(bno, writer);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

    m.addAttribute("page", page);
    m.addAttribute("pageSize", pageSize);

    return "redirect:/board/list";
}

board.jsp

remove ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ๋“ฑ๋ก

  • ์ „์ฒด
    <%@ page language="java" contentType="text/html; charset=UTF-8" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>fastcampus</title>
        <link rel="stylesheet" href="<c:url value='/css/menu.css'/>">
        <script src="https://code.jquery.com/jquery-1.11.3.js"></script>
    </head>
    <body>
    <div id="menu">
        <ul>
            <li id="logo">fastcampus</li>
            <li><a href="<c:url value='/'/>">Home</a></li>
            <li><a href="<c:url value='/board/list'/>">Board</a></li>
            <li><a href="<c:url value='/login/login'/>">login</a></li>
            <li><a href="<c:url value='/register/add'/>">Sign in</a></li>
            <li><a href=""><i class="fas fa-search small"></i></a></li>
        </ul>
    </div>
    <div style="text-align:center">
        <h2>๊ฒŒ์‹œ๋ฌผ ์ฝ๊ธฐ</h2>
        <form action="" id="form">
            <input type="text" name="bno" value="${boardDto.bno}" readonly/>
            <input type="text" name="title" value="${boardDto.title}" readonly>
            <textarea name="content" id="" cols="30" rows="10" readonly>${boardDto.content}</textarea>
            <button type="button" id="writeBtn" class="btn">๋“ฑ๋ก</button>
            <button type="button" id="modifyBtn" class="btn">์ˆ˜์ •</button>
            <button type="button" id="removeBtn" class="btn">์‚ญ์ œ</button>
            <button type="button" id="listBtn" class="btn">๋ชฉ๋ก</button>
        </form>
    </div>
    <script>
        $(document).ready(function(){ // main() js window.onload๋ž‘ ๋น„์Šทํ•œ ๊ฑฐ?
            $('#listBtn').on("click", function() {
                location.href = "<c:url value='/board/list'/>?page=${page}&pageSize=${pageSize}";
            });
    
            $('#removeBtn').on("click", function() {
                if(!confirm("์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?")) return;
    
                let form = $('#form');
                form.attr("action", "<c:url value='/board/remove'/>?page=${page}&pageSize=${pageSize}")
                form.attr("method", "post");
                form.submit();
            });
        });
    </script>
    </body>
    </html>
$('#removeBtn').on("click", function() {
    if(!confirm("์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?")) return;
    
    let form = $('#form');
    form.attr("action", "<c:url value='/board/remove'/>?page=${page}&pageSize=${pageSize}")
    form.attr("method", "post");
    form.submit();
});

์‚ญ์ œ๋˜๋ฉด ์‚ญ์ œ๋˜์—ˆ๋‹ค๊ณ  ๋ฉ”์‹œ์ง€ ๋„์šฐ๊ธฐ + ์‚ญ์ œ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์•ˆ ๋˜๋ฉด ์˜ˆ์™ธ

BoardController.java

@PostMapping("/remove")
public String remove(Integer bno, Integer page, Integer pageSize, Model m, HttpSession session) {
    String writer = (String) session.getAttribute("id");

    try {
        m.addAttribute("page", page);
        m.addAttribute("pageSize", pageSize);

        int rowCnt = boardService.remove(bno, writer);

        if (rowCnt != 1) {
            throw new Exception("board remove error");
        }
        m.addAttribute("msg", "DEL_OK");

    } catch (Exception e) {
        e.printStackTrace();
        m.addAttribute("msg", "DEL_ERR");
    }

    return "redirect:/board/list";
}

boardList.jsp

  • ์ „์ฒด ์ฝ”๋“œ
    <%@ page language="java" contentType="text/html; charset=UTF-8" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>fastcampus</title>
        <link rel="stylesheet" href="<c:url value='/css/menu.css'/>">
    </head>
    <body>
    <div id="menu">
        <ul>
            <li id="logo">fastcampus</li>
            <li><a href="<c:url value='/'/>">Home</a></li>
            <li><a href="<c:url value='/board/list'/>">boardDto</a></li>
            <li><a href="<c:url value='/login/login'/>">login</a></li>
            <li><a href="<c:url value='/register/add'/>">Sign in</a></li>
            <li><a href=""><i class="fas fa-search small"></i></a></li>
        </ul>
    </div>
    <div style="text-align:center">
        <table border="1">
            <tr>
                <th>๋ฒˆํ˜ธ</th>
                <th>์ œ๋ชฉ</th>
                <th>์ด๋ฆ„</th>
                <th>๋“ฑ๋ก์ผ</th>
                <th>์กฐํšŒ์ˆ˜</th>
            </tr>
            <c:forEach var="boardDto" items="${list}">
                <tr>
                    <td>${boardDto.bno}</td>
                    <td><a href="<c:url value='/board/read?bno=${boardDto.bno}&page=${page}&pageSize=${pageSize}' />">${boardDto.title}</a></td>
                    <td>${boardDto.writer}</td>
                    <td>${boardDto.reg_date}</td>
                    <td>${boardDto.view_cnt}</td>
                </tr>
            </c:forEach>
        </table>
        <br>
        <div>
            <c:if test="${ph.showPrev}">
                <a href="<c:url value='/board/list?page=${ph.beginPage - 1}&pageSize=${ph.pageSize} '/>">&lt;</a>
            </c:if>
            <c:forEach var="i" begin="${ph.beginPage}" end="${ph.endPage}">
                <a href="<c:url value='/board/list?page=${i}&pageSize=${ph.pageSize} '/>">${i}</a>
            </c:forEach>
            <c:if test="${ph.showNext}">
                <a href="<c:url value='/board/list?page=${ph.endPage + 1}&pageSize=${ph.pageSize} '/>">&gt;</a>
            </c:if>
        </div>
    </div>
    
    <script>
        let msg = "${param.msg}";
        if (msg == "DEL_OK") alert("์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
        if (msg == "DEL_ERR") alert("์‚ญ์ œ ์‹คํŒจ");
    </script>
    </body>
    </html>

get ๋ฐฉ์‹์ด๋ผ model์— ์žˆ๋Š”๊ฒŒ parameter๋กœ ์ „๋‹ฌ์ด ๋จ

<script>
    let msg = "${param.msg}";
    if (msg == "DEL_OK") alert("์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
    if (msg == "DEL_ERR") alert("์‚ญ์ œ ์‹คํŒจ");
</script>

์—ฌ๊ธฐ์„œ ์ƒˆ๋กœ๊ณ ์นจํ•˜๋ฉด ์‚ญ์ œ ์‹คํŒจํ–ˆ๋‹ค๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ๊ณ„์† ๋œธ

๋ฉ”์‹œ์ง€๊ฐ€ ํ•œ ๋ฒˆ๋งŒ ๋œจ๊ฒŒํ•  ๋•Œ ์“ฐ๋Š” ๊ฒŒ redirectAttributes๋‹ค. model์ด ์•„๋‹ˆ๋ผ redirectAttribute์— ์ €์žฅํ•˜๋ฉด ๋ฉ”์‹œ์ง€๊ฐ€ ํ•œ ๋ฒˆ๋งŒ ๋‚˜์˜จ๋‹ค.

BoardController.java

  • ์ „์ฒด์ฝ”๋“œ
    package com.fastcampus.ch4.controller;
    
    import com.fastcampus.ch4.domain.*;
    import com.fastcampus.ch4.service.*;
    import org.springframework.beans.factory.annotation.*;
    import org.springframework.stereotype.*;
    import org.springframework.ui.*;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.servlet.mvc.support.*;
    
    import javax.servlet.http.*;
    import java.time.*;
    import java.util.*;
    
    @Controller
    @RequestMapping("/board")
    public class BoardController {
        @Autowired
        BoardService boardService;
    
        @PostMapping("/remove")
        public String remove(Integer bno, Integer page, Integer pageSize, Model m, HttpSession session, RedirectAttributes rattr) {
            String writer = (String) session.getAttribute("id");
    
            try {
                m.addAttribute("page", page);
                m.addAttribute("pageSize", pageSize);
    
                int rowCnt = boardService.remove(bno, writer);
    
                if (rowCnt != 1) {
                    throw new Exception("board remove error");
                }
    
                // ์ผํšŒ์„ฑ. ํ•œ ๋ฒˆ๋งŒ ์“ฐ๊ณ  ์‚ฌ๋ผ์ง„๋‹ค.(์„ธ์…˜์„ ์ด์šฉํ•จ. ์„ธ์…˜์— ์ž ๊น ์ €์žฅํ–ˆ๋‹ค๊ฐ€ ํ•œ ๋ฒˆ ์‚ฌ์šฉํ•˜๊ณ  ์‚ญ์ œ.)
                rattr.addFlashAttribute("msg", "DEL_OK"); //
            } catch (Exception e) {
                e.printStackTrace();
                rattr.addFlashAttribute("msg", "DEL_ERR");
            }
    
            return "redirect:/board/list";
        }
    
        @GetMapping("/read")
        public String read(Integer bno, Integer page, Integer pageSize, Model m) {
            try {
                BoardDto boardDto = boardService.read(bno);
    //            m.addAttribute("boardDto", boardDto); // ์•„๋ž˜ ๋ฌธ์žฅ๊ณผ ๋™์ผ
                m.addAttribute(boardDto); // ์ด๋ฆ„ ์ƒ๋žต ์‹œ ํƒ€์ž…์˜ ์ฒซ ๊ธ€์ž๊ฐ€ ์†Œ๋ฌธ์ž๋กœ ๋ฐ”๋€๊ฒŒ key๊ฐ€๋œ๋‹ค.
                m.addAttribute("page", page);
                m.addAttribute("pageSize", pageSize);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return "board";
        }
    
        @GetMapping("/list")
    //    public String list(int page, int pageSize, Model m, HttpServletRequest request) {
        public String list(Integer page, Integer pageSize, Model m, HttpServletRequest request) { // page ๊ฐ’์ด ์•ˆ ๋“ค์–ด์™”์„ ๋•Œ ์—๋Ÿฌ ๋ฐœ์ƒ X
            if (!loginCheck(request))
                return "redirect:/login/login?toURL=" + request.getRequestURL();  // ๋กœ๊ทธ์ธ์„ ์•ˆํ–ˆ์œผ๋ฉด ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™
    
            if (page == null) page = 1;
            if (pageSize == null) pageSize = 10;
    
            try {
                int totalCnt = boardService.getCount();
                PageHandler pageHandler = new PageHandler(totalCnt, page, pageSize);
    
                Map map = new HashMap();
                map.put("offset", (page - 1) * pageSize);
                map.put("pageSize", pageSize);
    
                List<BoardDto> list = boardService.getPage(map);
                m.addAttribute("list", list);
                m.addAttribute("ph", pageHandler);
                m.addAttribute("page", page);
                m.addAttribute("pageSize", pageSize);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return "boardList"; // ๋กœ๊ทธ์ธ์„ ํ•œ ์ƒํƒœ์ด๋ฉด, ๊ฒŒ์‹œํŒ ํ™”๋ฉด์œผ๋กœ ์ด๋™
        }
    
        private boolean loginCheck(HttpServletRequest request) {
            // 1. ์„ธ์…˜์„ ์–ป์–ด์„œ
            HttpSession session = request.getSession();
            // 2. ์„ธ์…˜์— id๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ, ์žˆ์œผ๋ฉด true๋ฅผ ๋ฐ˜ํ™˜
            return session.getAttribute("id") != null;
        }
    }

RedirectAttributes์˜ addFlashAttribute : ์ผํšŒ์„ฑ. ํ•œ ๋ฒˆ๋งŒ ์“ฐ๊ณ  ์‚ฌ๋ผ์ง„๋‹ค.(์„ธ์…˜์„ ์ด์šฉํ•จ. ์„ธ์…˜์— ์ž ๊น ์ €์žฅํ–ˆ๋‹ค๊ฐ€ ํ•œ ๋ฒˆ ์‚ฌ์šฉํ•˜๊ณ  ์‚ญ์ œ.)

@PostMapping("/remove")
public String remove(Integer bno, Integer page, Integer pageSize, Model m, HttpSession session, RedirectAttributes rattr) {
    String writer = (String) session.getAttribute("id");

    try {
        m.addAttribute("page", page);
        m.addAttribute("pageSize", pageSize);

        int rowCnt = boardService.remove(bno, writer);

        if (rowCnt != 1) {
            throw new Exception("board remove error");
        }

        // ์ผํšŒ์„ฑ. ํ•œ ๋ฒˆ๋งŒ ์“ฐ๊ณ  ์‚ฌ๋ผ์ง„๋‹ค.(์„ธ์…˜์„ ์ด์šฉํ•จ. ์„ธ์…˜์— ์ž ๊น ์ €์žฅํ–ˆ๋‹ค๊ฐ€ ํ•œ ๋ฒˆ ์‚ฌ์šฉํ•˜๊ณ  ์‚ญ์ œ.)
        rattr.addFlashAttribute("msg", "DEL_OK"); //
    } catch (Exception e) {
        e.printStackTrace();
        rattr.addFlashAttribute("msg", "DEL_ERR");
    }

    return "redirect:/board/list";
}

boardList.jsp

param.์“ฐ๋ฉด ์•ˆ ๋˜๊ณ  msg๋งŒ ์จ์•ผ๋จ

  • ์ „์ฒด์ฝ”๋“œ
    <%@ page language="java" contentType="text/html; charset=UTF-8" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>fastcampus</title>
        <link rel="stylesheet" href="<c:url value='/css/menu.css'/>">
    </head>
    <body>
    <div id="menu">
        <ul>
            <li id="logo">fastcampus</li>
            <li><a href="<c:url value='/'/>">Home</a></li>
            <li><a href="<c:url value='/board/list'/>">boardDto</a></li>
            <li><a href="<c:url value='/login/login'/>">login</a></li>
            <li><a href="<c:url value='/register/add'/>">Sign in</a></li>
            <li><a href=""><i class="fas fa-search small"></i></a></li>
        </ul>
    </div>
    <div style="text-align:center">
        <table border="1">
            <tr>
                <th>๋ฒˆํ˜ธ</th>
                <th>์ œ๋ชฉ</th>
                <th>์ด๋ฆ„</th>
                <th>๋“ฑ๋ก์ผ</th>
                <th>์กฐํšŒ์ˆ˜</th>
            </tr>
            <c:forEach var="boardDto" items="${list}">
                <tr>
                    <td>${boardDto.bno}</td>
                    <td><a href="<c:url value='/board/read?bno=${boardDto.bno}&page=${page}&pageSize=${pageSize}' />">${boardDto.title}</a></td>
                    <td>${boardDto.writer}</td>
                    <td>${boardDto.reg_date}</td>
                    <td>${boardDto.view_cnt}</td>
                </tr>
            </c:forEach>
        </table>
        <br>
        <div>
            <c:if test="${ph.showPrev}">
                <a href="<c:url value='/board/list?page=${ph.beginPage - 1}&pageSize=${ph.pageSize} '/>">&lt;</a>
            </c:if>
            <c:forEach var="i" begin="${ph.beginPage}" end="${ph.endPage}">
                <a href="<c:url value='/board/list?page=${i}&pageSize=${ph.pageSize} '/>">${i}</a>
            </c:forEach>
            <c:if test="${ph.showNext}">
                <a href="<c:url value='/board/list?page=${ph.endPage + 1}&pageSize=${ph.pageSize} '/>">&gt;</a>
            </c:if>
        </div>
    </div>
    
    <script>
        let msg = "${msg}";
        if (msg == "DEL_OK") alert("์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
        if (msg == "DEL_ERR") alert("์‚ญ์ œ ์‹คํŒจ");
    </script>
    </body>
    </html>
// let msg = "${param.msg}";
let msg = "${msg}";
if (msg == "DEL_OK") alert("์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
if (msg == "DEL_ERR") alert("์‚ญ์ œ ์‹คํŒจ");

SQL Injection ์‹ค์Šต

sql injection์„ ๋ง‰์œผ๋ ค๋ฉด statement ์“ฐ๋ฉด ์•ˆ ๋˜๊ณ  preparedstatement๋ฅผ ์จ์•ผ ํ•œ๋‹ค.

4. ๊ฒŒ์‹œ๋ฌผ ์“ฐ๊ธฐ ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„

[GET] /board/write

  1. BoardController

    write

    @GetMapping("/write")
    public String write(Model m) {
        m.addAttribute("mode", "new"); // ์ฝ๊ธฐ๊ณผ ์“ฐ๊ธฐ์— ์‚ฌ์šฉ. ์“ฐ๊ฒŒ์— ์‚ฌ์šฉํ•  ๋•Œ๋Š” mode = new
        return "board";
    }
  1. board.jsp

    mode ์—ฌ๋ถ€์— ๋”ฐ๋ผ h2์™€ readonly

    <h2>๊ฒŒ์‹œ๋ฌผ ${mode == "new" ? "๊ธ€์“ฐ๊ธฐ" : "์ฝ๊ธฐ"}</h2>
    <form action="" id="form">
        <input type="hidden" name="bno" value="${boardDto.bno}" />
        <input type="text" name="title" value="${boardDto.title}" ${mode == "new" ? '' : 'readonly'}>
        <textarea name="content" id="" cols="30" rows="10" ${mode == "new" ? '' : 'readonly'}>${boardDto.content}</textarea>
        <button type="button" id="writeBtn" class="btn">๋“ฑ๋ก</button>
        <button type="button" id="modifyBtn" class="btn">์ˆ˜์ •</button>
        <button type="button" id="removeBtn" class="btn">์‚ญ์ œ</button>
        <button type="button" id="listBtn" class="btn">๋ชฉ๋ก</button>
    </form>

jsp์—์„œ๋Š” mode ๊ฐ’์„ ํ™•์ธํ•ด์„œ new์ผ ๋•Œ๋Š” ์“ฐ๊ธฐ ์„ธํŒ… ์•„๋‹ ๋•Œ๋Š” ๋ณด์—ฌ๋งŒ์ค€๋‹ค.

[POST] /board/write

board.jsp

$('#writeBtn').on("click", function() {
    let form = $('#form');
    form.attr("action", "<c:url value='/board/write'/>");
    form.attr("method", "post");
    form.submit();
});

BoardController.java

  1. ๋ช‡ํŽ˜์ด์ง€์ธ์ง€ ์ƒ๊ด€์—†์ด ์ตœ๊ทผ ๊ฒŒ์‹œ๋ฌผ๋กœ ๋ณด๋‚ด๋ฉด ๋จ
    return "redirect:/board/list"
  1. ๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก ์‹œ boardDto์— writer๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.
    String writer = (String) session.getAttribute("id");
    boardDto.setWriter(writer);
  1. boardService.write๊ฐ€ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— try-catch๋กœ ๊ฐ์‹ธ์ค€๋‹ค.
    try {
        int rowCnt = boardService.write(boardDto);
    } catch (Exception e) {
        e.printStackTrace();
    }
  1. boardService.write์˜ ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ›์•„์„œ rowCnt๊ฐ€ 1์ด ์•„๋‹ˆ๋ฉด exception์„ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.
    throw new Exception("Write failed");
  1. ์ €์žฅ ์‹คํŒจ ์‹œ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ–ˆ๋˜ ๋‚ด์šฉ์„ ๋‹ค์‹œ ๋ณด์—ฌ์ฃผ์–ด์•ผํ•จ

    model์„ ์ด์šฉํ•ด์„œ ๋‹ค์‹œ ๋ณด์—ฌ์ค€๋‹ค.

    } catch (Exception e) {
        e.printStackTrace();
        m.addAttribute("boardDto", boardDto);
        return "board";
    }
  1. ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ํด๋ผ์ด์–ธํŠธ์ชฝ์— ์•Œ๋ ค์ค˜์•ผ ํ•œ๋‹ค. (์„ธ์…˜์„ ์ด์šฉํ•œ 1ํšŒ์„ฑ ์ €์žฅ)
    try {
        int rowCnt = boardService.write(boardDto);
    
        if (rowCnt != 1)
            throw new Exception("Write failed");
    
        m.addAttribute("msg", "WRT_OK");
    
    } catch (Exception e) {
        e.printStackTrace();
        m.addAttribute("boardDto", boardDto);
        m.addAttribute("msg", "WRT_ERR");
        return "board";
    }

    ๊ทธ๋Ÿฐ๋ฐ ๊ฒฐ๊ณผ๋ฅผ model์— ๋‹ด์œผ๋ฉด ์ฃผ์†Œ์ฐฝ์— ๋‚จ์•„์„œ alert์ฐฝ์ด ๊ณ„์† ๋œจ๊ธฐ ๋•Œ๋ฌธ์— RedirectAttributes๋ฅผ ์“ด๋‹ค.

    flash โ†’ ์„ธ์…˜์„ ์ด์šฉํ•œ 1ํšŒ์„ฑ ์ €์žฅ

    rattr.addFlashAttribute("msg", "WRT_OK");
    m.addAttribute("msg", "WRT_ERR");

boardList.jsp

if (msg == "WRT_OK") alert("์„ฑ๊ณต์ ์œผ๋กœ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");

board.jsp

let msg = "${msg}";
if (msg == "WRT_ERR") alert("๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.");

์‹คํŒจ ์‹œ์—๋„ ์ž˜ ๋™์ž‘ํ•˜๋Š”์ง€ ๋ฌด์กฐ๊ฑด ๋‹ค ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ด์•ผ ํ•œ๋‹ค.

5. ๊ฒŒ์‹œ๋ฌผ ์ˆ˜์ • ๊ธฐ๋Šฅ์˜ ๊ตฌํ˜„

[GET] /board/modify

attribute VS property

HTML์—์„œ๋Š” ๋‘˜์„ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•œ๋‹ค.

HTML ํƒœ๊ทธ ๋‚ด๋ถ€์— ์ž‘์„ฑ๋œ ๊ฒƒ์„ attribute๋ผ๊ณ  ํ•˜๊ณ , ์ƒ์„ฑ๋œ ๊ฐ์ฒด์˜ ์†์„ฑ์„ property๋ผ๊ณ  ํ•œ๋‹ค.(Java์—์„œ์˜ iv์ž„)

[POST] /board/modify

์‹ค์Šต

board.jsp

$('#modifyBtn').on("click", function() {
    // 1. ์ฝ๊ธฐ ์ƒํƒœ์ด๋ฉด ์ˆ˜์ • ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ
    let form = $('#form');
    let isReadOnly = $("input[name=title]").attr('readonly');

		if (isReadOnly == 'readonly') {
        $("input[name=title]").attr('readonly', false); // title
        $("textarea").attr('readonly', false);
        $("#modifyBtn").html("๋“ฑ๋ก");
        $("h2").html("๊ฒŒ์‹œ๋ฌผ ์ˆ˜์ •")
    }

    // 2. ์ˆ˜์ • ์ƒํƒœ์ด๋ฉด, ์ˆ˜์ •๋œ ๋‚ด์šฉ์„ ์„œ๋ฒ„๋กœ ์ „์†ก
    form.attr("action", "<c:url value='/board/modify'/>")
    form.attr("method", "post");
    form.submit();
});

BoardController.java

@PostMapping("/write")
public String modify(BoardDto boardDto, Model m, HttpSession session, RedirectAttributes rattr) {
    String writer = (String) session.getAttribute("id");
    boardDto.setWriter(writer);

    try {
        int rowCnt = boardService.modify(boardDto);

        if (rowCnt != 1)
            throw new Exception("modify failed");

        rattr.addFlashAttribute("msg", "MOD_OK");

    } catch (Exception e) {
        e.printStackTrace();
        m.addAttribute("boardDto", boardDto);
        m.addAttribute("msg", "MOD_ERR");
        return "board";
    }

    return "redirect:/board/list";
}

boardMapper.xml

์ˆ˜์ •/์‚ญ์ œ ๋ฒ„ํŠผ์€ ๋ณธ์ธ์ผ ๋•Œ๋งŒ ๋‚˜ํƒ€๋‚˜์•ผ ํ•œ๋‹ค.

<update id="update" parameterType="BoardDto">
    UPDATE board
    SET   title = #{title}
      , content = #{content}
      , up_date = now()
    WHERE bno = #{bno} and writer = #{writer}
</update>

๊ณผ์ œ

๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • ํ›„ ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€์•ผ ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๊ณ  ์žˆ๋˜ ์œ„์น˜๋กœ ๊ฐ€์•ผ ํ•œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ฒŒ์‹œ๊ธ€์„ ์ˆ˜์ •ํ•˜๋Š” ๋„์ค‘ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒŒ์‹œ๊ธ€์„ ์‚ญ์ œํ•˜๋ฉด, โ€œ์‚ญ์ œ๋˜์—ˆ๊ฑฐ๋‚˜ ์—†๋Š” ๊ฒŒ์‹œ๊ธ€์ž…๋‹ˆ๋‹ค.โ€๋ผ๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค. ์ด๋•Œ ํ™”๋ฉด์€ ์œ ์ง€๋˜์–ด์•ผ ํ•œ๋‹ค.

๋‹ค๋ฅธ ์„œ๋น„์Šค๋“ค์ด ๊ฒŒ์‹œํŒ์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ์‹คํŒจํ–ˆ์„ ๋•Œ์˜ ์ƒํ™ฉ์„ ํ…Œ์ŠคํŠธํ•ด๋ณด์ž. ์ด๋Ÿฐ ๊ฒฝํ—˜์€ ๊ฐœ๋ฐœ ๋Šฅ๋ ฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๋ฐ ํฐ ๋„์›€์ด ๋œ๋‹ค.

07~08 ๊ฒŒ์‹œํŒ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜๊ธฐ(1)

1. ๊ฒŒ์‹œํŒ ๊ฒ€์ƒ‰

  1. ๋™์  ์ฟผ๋ฆฌ : ๊ฒ€์ƒ‰ ๋Œ€์ƒ์— ๋”ฐ๋ผ SQL ์ฟผ๋ฆฌ๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์–ด๋–ค ํ•ญ๋ชฉ์„ ๊ธฐ์ค€์œผ๋กœ(์ œ๋ชฉ+๋‚ด์šฉ, ์ œ๋ชฉ, ๋‚ด์šฉ) ๊ฒ€์ƒ‰ํ•˜๋Š”์ง€์— ๋”ฐ๋ผ ์ฟผ๋ฆฌ๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด์•ผ ํ•œ๋‹ค.
  1. ํŽ˜์ด์ง€ ์ด๋™ ์ฒ˜๋ฆฌ : ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์—๋„ ํŽ˜์ด์ง• ํ•„์š”

์ด์ œ page์™€ pageSize์™ธ์—๋„ ๊ฒ€์ƒ‰ ์˜ต์…˜๊ณผ ํ‚ค์›Œ๋“œ๋ฅผ ์ „์†กํ•ด์•ผ ํ•œ๋‹ค.

2. MyBatis์˜ ๋™์  ์ฟผ๋ฆฌ

<sql>๊ณผ <include>

๊ณตํ†ต ๋ถ€๋ถ„์„ <sql>๋กœ ์ •์˜ํ•˜๊ณ  <include>๋กœ ํฌํ•จ์‹œ์ผœ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

<if>

ํŠน์ • ์กฐ๊ฑด์ด ๋งŒ์กฑ๋  ๋•Œ๋งŒ SQL ๋ฌธ์žฅ์„ ์ƒ์„ฑํ•œ๋‹ค.

์œ„์˜ ์ผ€์ด์Šค์—์„œ๋Š” ์—ฌ๋Ÿฌ ์กฐ๊ฑด ์ค‘์— ํ•˜๋‚˜๋งŒ ๋งŒ์กฑํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— if๋ฌธ๋ณด๋‹ค๋Š” choose๊ฐ€ ์ ํ•ฉํ•˜๋‹ค.

<choose> <when>

<foreach>

๋ฐฐ์—ด์ด๋‚˜ ์ปฌ๋ ‰์…˜์˜ ๊ฐ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ๋ฐ˜๋ณต์ ์œผ๋กœ SQL์„ ์ƒ์„ฑํ•œ๋‹ค.

WHERE bno IN(1, 2, 3) ์—์„œ ๊ด„ํ˜ธ ์•ˆ์ด ๋ช‡ ๊ฐœ์ธ์ง€ ์ •ํ•ด์ ธ ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” sql๋ฌธ์„ ์ž‘์„ฑํ•˜๊ธฐ ์–ด๋ ต๋‹ค. ๊ทธ๋Ÿด ๋•Œ ์“ฐ๋Š” ๊ฒŒ foreach๋‹ค.

๋ฐฐ์—ด์„ ์ฃผ๋ฉด ๊ด„ํ˜ธ ์•ˆ์—๋‹ค๊ฐ€ โ€˜,โ€™ ๋ฅผ ๊ตฌ๋ถ„์ž๋กœ ํ•ด์„œ ์œ„์ฒ˜๋Ÿผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

๋ฐฐ์—ด์— ๋“ค์–ด์žˆ๋Š” ๊ฐ’์ด [1, 2, 3, 4]๋ผ๋ฉด WHERE bno IN (1, 2, 3, 4)์™€ ๊ฐ™์€ SQL์ด ์ƒ์„ฑ๋œ๋‹ค.

์‹ค์Šต

1. sql ์ž‘์„ฑ

boardMapper.xml

searchSelectPage ์ถ”๊ฐ€ : ๊ฒŒ์‹œํŒ์˜ ๊ฒŒ์‹œ๊ธ€์„ ํŽ˜์ด์ง€ ๋‹จ์œ„๋กœ ๊ฒ€์ƒ‰ํ•œ๋‹ค.

<select id="searchSelectPage" parameterType="SearchCondition" resultType="BoardDto">
    SELECT bno, title, content, writer, view_cnt, comment_cnt, reg_date
    FROM board
     WHERE TRUE
       AND title LIKE concat('%', #{keyword}, '%')
    ORDER BY reg_date DESC, bno DESC
        LIMIT #{offset}, #{pageSize}
</select>

[ parameterType ์„ค์ • ]

ํŒจํ‚ค์ง€๋ช…์„ ๋ชจ๋‘ ์ž…๋ ฅํ•˜๋Š” ๋Œ€์‹  โ€˜SearchConditionโ€™๊ณผ ๊ฐ™์€ ๋ณ„์นญ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค. ์ด ๋ณ„์นญ์€ mybatis-config.xml ํŒŒ์ผ์˜ <typeAliases> ๋ถ€๋ถ„์— ๋“ฑ๋กํ•œ๋‹ค.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="BoardDto" type="com.fastcampus.ch4.domain.BoardDto"/>
        <typeAlias alias="SearchCondition" type="com.fastcampus.ch4.domain.SearchCondition" />
    </typeAliases>
</configuration>

searchResultCnt ์ถ”๊ฐ€ : ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ๋ช‡ ๊ฐœ์ธ์ง€ ์•Œ์•„์•ผ ํŽ˜์ด์ง•์„ ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€

<select id="searchResultCnt" parameterType="SearchCondition" resultType="int">
    SELECT count(*)
    FROM board
    WHERE TRUE
      AND title LIKE concat('%', #{keyword}, '%')
</select>

๋™์ ์ฟผ๋ฆฌ๋Š” ์‹ค์ˆ˜ํ•˜๊ธฐ ์‰ฝ๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ์ ์ธ ๋™์ž‘์„์ด ๋‹ค ๋Œ์•„๊ฐ€๋Š”์ง€ ๋จผ์ € ํ™•์ธํ•ด๋ณด๊ณ  ๊ทธ ๋’ค์— ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

2. DAO

[ DAO ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€]

๋ฐฉ๊ธˆ ๋งŒ๋“  ์ฟผ๋ฆฌ๋ฌธ์„ ํ˜ธ์ถœํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

@Override
    public int searchResultCnt(SearchCondition sc) throws Exception {
    return session.selectOne(namespace + "searchResultCnt", sc);
} // T selectOne(String statement, Object parameter)

@Override
public List<BoardDto> searchSelectPage(SearchCondition sc) throws Exception {
    return session.selectList(namespace + "selectPage", sc);
} // List<E> selectList(String statement, Object parameter)

[ SearchCondition ํด๋ž˜์Šค ์ƒ์„ฑ ]

SearchCondition = ๊ฒ€์ƒ‰์กฐ๊ฑด

์ƒ์„ฑ์ž๋Š” offset์„ ์ œ์™ธํ•˜๊ณ  ์ƒ์„ฑํ•œ๋‹ค.

[ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ]

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ •๋ง ๊ผผ๊ผผํžˆ ํ•ด์•ผ ํ•˜๋Š”๋ฐ ์‹œ๊ฐ„ ๊ด€๊ณ„์ƒ ๊ฐ„๋‹จํžˆ ํ•œ๊ฒƒ.

์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ์ฒ ์ €ํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์„์ง€ ๊ณ ๋ฏผ์„ ๋งŽ์ด ํ•ด์•ผ ํ•œ๋‹ค.

@Test
public void searchSelectPageTest() throws Exception {
    boardDao.deleteAll();

    for (int i = 0; i <= 20; i++) {
        BoardDto boardDto = new BoardDto("title" + i, "asdfafd", "adff");
        boardDao.insert(boardDto);
    }

    SearchCondition sc = new SearchCondition(1, 10, "title2", "T");
     List<BoardDto> list = boardDao.searchSelectPage(sc);
    System.out.println("boardList = " + list);
    assertTrue(list.size() == 2);
}

@Test
public void searchResultCntTest() throws Exception {
    boardDao.deleteAll();

    for (int i = 0; i <= 20; i++) {
        BoardDto boardDto = new BoardDto("title" + i, "asdfafd", "adff");
        boardDao.insert(boardDto);
    }

    SearchCondition sc = new SearchCondition(1, 10, "title2", "T");
    int list = boardDao.searchResultCnt(sc);
    System.out.println("boardList = " + list);
    assertTrue(list == 2);
}

[ SQL ์‹คํ–‰ ํ™•์ธ ]

log4jdbc ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ œ๋กœ ์‹คํ–‰๋˜๋Š” SQL์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

  1. Maven Repository
  1. log4jdbc.log4j2.properties
    log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
  1. logback.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration>
    <configuration>
        <include resource="org/springframework/boot/logging/logback/base.xml"/>
    
        <!-- log4jdbc-log4j2 -->
        <logger name="jdbc.sqlonly" level="INFO"/>
        <logger name="jdbc.sqltiming" level="INFO"/>
        <logger name="jdbc.audit" level="WARN"/>
        <logger name="jdbc.resultset" level="INFO"/>
        <logger name="jdbc.resultsettable" level="INFO"/>
        <logger name="jdbc.connection" level="INFO"/>
    </configuration>
  1. root-context.xml

    2์ค„ ์ถ”๊ฐ€

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy" />
        <property name="url" value="jdbc:log4jdbc:mysql://localhost:3306/springbasic?useUnicode=true&amp;characterEncoding=utf8" />
    <!--        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>-->
    <!--        <property name="url" value="jdbc:mysql://localhost:3306/springbasic?useUnicode=true&amp;characterEncoding=utf8"></property>-->
        <property name="username" value="coals0115"></property>
        <property name="password" value="1234"></property>
    </bean>

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๊ณ  test๋ฅผ ๋Œ๋ฆฌ๋ฉด sql์ด ์‹คํ–‰๋˜๋Š” ๊ณผ์ •์ด ์ญ‰ ๋‚˜์˜จ๋‹ค.

INFO : jdbc.sqltiming - SELECT count(*) FROM board WHERE TRUE AND title LIKE concat('%', 'title2', '%') ORDER BY reg_date 
DESC, bno DESC LIMIT 0, 10 
 {executed in 8 msec}
INFO : jdbc.audit - 23. PreparedStatement.execute() returned true
INFO : jdbc.resultset - 23. ResultSet.new ResultSet returned 
INFO : jdbc.audit - 23. PreparedStatement.getResultSet() returned net.sf.log4jdbc.sql.jdbcapi.ResultSetSpy@4f67e3df
INFO : jdbc.resultset - 23. ResultSet.getMetaData() returned com.mysql.cj.jdbc.result.ResultSetMetaData@45d64d27 - Field level information: 
	com.mysql.cj.result.Field@34fe326d[dbName=null,tableName=null,originalTableName=null,columnName=count(*),originalColumnName=null,mysqlType=8(FIELD_TYPE_BIGINT),sqlType=-5,flags= BINARY, charsetIndex=63, charsetName=ISO-8859-1]
INFO : jdbc.resultset - 23. ResultSet.getType() returned 1003
INFO : jdbc.resultset - 23. ResultSet.isClosed() returned false
INFO : jdbc.resultset - 23. ResultSet.next() returned true
INFO : jdbc.resultset - 23. ResultSet.getInt(count(*)) returned 2
INFO : jdbc.resultset - 23. ResultSet.isClosed() returned false
INFO : jdbc.resultsettable - 
|---------|
|count(*) |
|---------|
|2        |
|---------|

3. Service

BoardServiceImpl.java

@Override
public int getSearchResultCnt(SearchCondition sc) throws Exception {
    return boardDao.searchResultCnt(sc);
}

@Override
public List<BoardDto> getSearchResultPage(SearchCondition sc) throws Exception {
    return boardDao.searchSelectPage(sc);
}

BoardService.java

int getSearchResultCnt(SearchCondition sc) throws Exception;

List<BoardDto> getSearchResultPage(SearchCondition sc) throws Exception;

service ํ…Œ์ŠคํŠธ๋Š” ์‹œ๊ฐ„๊ณผ๊ณ„์ƒน ์ƒ๋žต, ์•Œ์•„์„œ ํ•ด๋ณด๊ธฐ

4. Controller

BoardController.java

public String list(@RequestParam(defaultValue = "1") Integer page,
                   @RequestParam(defaultValue = "10") Integer pageSize, String option, String keyword, Model m, HttpServletRequest request) { // page ๊ฐ’์ด ์•ˆ ๋“ค์–ด์™”์„ ๋•Œ ์—๋Ÿฌ ๋ฐœ์ƒ X

[ 1. Controller ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆ˜์ • ]

(page, pageSize, option, keyword) ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๊ธธ๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์ฒ˜๋Ÿผ ๊น”๋”ํ•˜๊ฒŒ SearchCondition ๊ฐ์ฒด ํ•˜๋‚˜ ๋ฐ›๋„๋ก ๋ณ€๊ฒฝ!(์ž๋™์œผ๋กœ ์•ž์— ModelAttribute ๋ถ™๋Š”๋‹ค.)

public String list(SearchCondition sc, Model m, HttpServletRequest request)

[ 2. ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง ]

SearchCondition ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ, ํŽ˜์ด์ง• ๊ด€๋ จ ์ฝ”๋“œ๊ฐ€ ๊ฐ„๋‹จํ•ด์กŒ๋‹ค. ๊ธฐ์กด์—๋Š” ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ์™€ ํŽ˜์ด์ง€ ํฌ๊ธฐ๋ฅผ ์ง์ ‘ ์ฒ˜๋ฆฌํ–ˆ์ง€๋งŒ, ์ด์ œ๋Š” PageHandler๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. , ํŽ˜์ด์ง€ ์˜คํ”„์…‹๊ณผ ํŽ˜์ด์ง€ ํฌ๊ธฐ๋ฅผ ๋งต ๊ฐ์ฒด์— ๋‹ด์•„์„œ boardService.getPage ๋ฉ”์„œ๋“œ์— ์ „๋‹ฌํ•˜๋Š” ๋ถ€๋ถ„๋„ ์‚ญ์ œ๋จ

Before

@GetMapping("/list")
//    public String list(int page, int pageSize, Model m, HttpServletRequest request) {
public String list(SearchCondition sc, Model m, HttpServletRequest request) { // page ๊ฐ’์ด ์•ˆ ๋“ค์–ด์™”์„ ๋•Œ ์—๋Ÿฌ ๋ฐœ์ƒ X
    if (!loginCheck(request))
        return "redirect:/login/login?toURL=" + request.getRequestURL();  // ๋กœ๊ทธ์ธ์„ ์•ˆํ–ˆ์œผ๋ฉด ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™

    try {
        int totalCnt = boardService.getCount();
//            PageHandler pageHandler = new PageHandler(totalCnt, page, pageSize);
        PageHandler pageHandler = new PageHandler(totalCnt, sc);

        // ์•„๋ž˜๋Š” ๋‹ค ๋‚ด๋ถ€์  ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋ณ€๊ฒฝ
//            if (page < 0 || page > pageHandler.getTotalPage())
//                page = 1;
//            if (pageSize < 0 || pageSize > 50)
//                pageSize = 10;

        // ์–˜๋„ ์ง€์šฐ๊ธฐ
//            Map map = new HashMap();
//            map.put("offset", (page - 1) * pageSize);
//            map.put("pageSize", pageSize);

        List<BoardDto> list = boardService.getPage(map);
        m.addAttribute("list", list);
        m.addAttribute("ph", pageHandler);
        m.addAttribute("page", page);
        m.addAttribute("pageSize", pageSize);
    } catch (Exception e) {
        e.printStackTrace();
    }

    return "boardList"; // ๋กœ๊ทธ์ธ์„ ํ•œ ์ƒํƒœ์ด๋ฉด, ๊ฒŒ์‹œํŒ ํ™”๋ฉด์œผ๋กœ ์ด๋™
}

After

@GetMapping("/list")
//    public String list(int page, int pageSize, Model m, HttpServletRequest request) {
public String list(SearchCondition sc, Model m, HttpServletRequest request) { // page ๊ฐ’์ด ์•ˆ ๋“ค์–ด์™”์„ ๋•Œ ์—๋Ÿฌ ๋ฐœ์ƒ X
    if (!loginCheck(request))
        return "redirect:/login/login?toURL=" + request.getRequestURL();  // ๋กœ๊ทธ์ธ์„ ์•ˆํ–ˆ์œผ๋ฉด ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™

    try {
        int totalCnt = boardService.getCount();
        PageHandler pageHandler = new PageHandler(totalCnt, page, pageSize);

        if (page < 0 || page > pageHandler.getTotalPage())
            page = 1;
        if (pageSize < 0 || pageSize > 50)
            pageSize = 10;

        Map map = new HashMap();
        map.put("offset", (page - 1) * pageSize);
        map.put("pageSize", pageSize);

        List<BoardDto> list = boardService.getPage(map);
        m.addAttribute("list", list);
        m.addAttribute("ph", pageHandler);
        m.addAttribute("page", page);
        m.addAttribute("pageSize", pageSize);
    } catch (Exception e) {
        e.printStackTrace();
    }

    return "boardList"; // ๋กœ๊ทธ์ธ์„ ํ•œ ์ƒํƒœ์ด๋ฉด, ๊ฒŒ์‹œํŒ ํ™”๋ฉด์œผ๋กœ ์ด๋™
}


PageHandler.java

//    private int page; // ํ˜„์žฌ ํŽ˜์ด์ง€
//    private int pageSize; // ํ•œ ํŽ˜์ด์ง€์˜ ํฌ๊ธฐ
//    private String option;
//    private String keyword;
   private SearchCondition sc; 

public PageHandler(int totalCnt, SearchCondition sc) {
    this.totalPage = totalCnt;
    this.sc = sc;

    doPaging(totalCnt, sc);
}

[ PageHandelr ์ƒ์„ฑ์ž ๋ฉ”์„œ๋“œ๋กœ ๋ณ€๊ฒฝ ]

public PageHandler(int totalCnt, int page, int pageSize) {
    this.totalCnt = totalCnt;
    this.page = page;
    this.pageSize = pageSize;

    totalPage = (int) Math.ceil(totalCnt / (double) pageSize); // ๋‚จ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ฌ๋ฆผ์ฒ˜๋ฆฌ
    beginPage = (page - 1) / naviSize * naviSize + 1;
    endPage = Math.min(beginPage + naviSize - 1, totalPage); // endPage๊ฐ€ totalPage๋ณด๋‹ค ์ž‘์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—
    showPrev = beginPage != 1; // ์‹œ์ž‘ ํŽ˜์ด์ง€๊ฐ€ 1์ผ ๋•Œ๋งŒ ์•ˆ ๋‚˜์˜ค๋ฉด ๋œ๋‹ค.
    showNext = endPage != totalPage; // ๋‹ค์Œ์œผ๋กœ ๊ฐˆ๊ฒŒ ์—†์œผ๋‹ˆ๊นŒ ๋ณด์—ฌ์ฃผ์ง€์•Š์Œ
}
public PageHandler(int totalCnt, SearchCondition sc) {
    this.totalPage = totalCnt;
    this.sc = sc;

    doPaging(totalCnt, sc);
}

// ํŽ˜์ด์ง• ๊ณ„์‚ฐํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ๊ฒŒ 3๊ฐ€์ง€
public void doPaging(int totalCnt, SearchCondition sc){
    this.totalCnt = totalCnt;

    totalPage = (int) Math.ceil(totalCnt / (double) sc.getPageSize()); // ๋‚จ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ฌ๋ฆผ์ฒ˜๋ฆฌ
    beginPage = (sc.getPage() - 1) / naviSize * naviSize + 1;
    endPage = Math.min(beginPage + naviSize - 1, totalPage); // endPage๊ฐ€ totalPage๋ณด๋‹ค ์ž‘์„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—
    showPrev = beginPage != 1; // ์‹œ์ž‘ ํŽ˜์ด์ง€๊ฐ€ 1์ผ ๋•Œ๋งŒ ์•ˆ ๋‚˜์˜ค๋ฉด ๋œ๋‹ค.
    showNext = endPage != totalPage; // ๋‹ค์Œ์œผ๋กœ ๊ฐˆ๊ฒŒ ์—†์œผ๋‹ˆ๊นŒ ๋ณด์—ฌ์ฃผ์ง€์•Š์Œ
}

[ 2. board.jsp, boardList.jsp ๋ณ€๊ฒฝ ]

jsp๋ฅผ ๋ฐ”๊ฟ”๋ณด์ž.

  • board.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
    <%@ page session="true" %>
    <c:set var="loginId" value="${sessionScope.id}"/>
    <c:set var="loginOutLink" value="${loginId=='' ? '/login/login' : '/login/logout'}"/>
    <c:set var="loginOut" value="${loginId=='' ? 'Login' : 'ID='+=loginId}"/>
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>fastcampus</title>
        <link rel="stylesheet" href="<c:url value='/css/menu.css'/>">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
        <script src="https://code.jquery.com/jquery-1.11.3.js"></script>
        <style>
            * {
                box-sizing: border-box;
                margin: 0;
                padding: 0;
                font-family: "Noto Sans KR", sans-serif;
            }
    
            .container {
                width: 50%;
                margin: auto;
            }
    
            .writing-header {
                position: relative;
                margin: 20px 0 0 0;
                padding-bottom: 10px;
                border-bottom: 1px solid #323232;
            }
    
            input {
                width: 100%;
                height: 35px;
                margin: 5px 0px 10px 0px;
                border: 1px solid #e9e8e8;
                padding: 8px;
                background: #f8f8f8;
                outline-color: #e6e6e6;
            }
    
            textarea {
                width: 100%;
                background: #f8f8f8;
                margin: 5px 0px 10px 0px;
                border: 1px solid #e9e8e8;
                resize: none;
                padding: 8px;
                outline-color: #e6e6e6;
            }
    
            .frm {
                width: 100%;
            }
    
            .btn {
                background-color: rgb(236, 236, 236); /* Blue background */
                border: none; /* Remove borders */
                color: black; /* White text */
                padding: 6px 12px; /* Some padding */
                font-size: 16px; /* Set a font size */
                cursor: pointer; /* Mouse pointer on hover */
                border-radius: 5px;
            }
    
            .btn:hover {
                text-decoration: underline;
            }
        </style>
    </head>
    <body>
    <div id="menu">
        <ul>
            <li id="logo">fastcampus</li>
            <li><a href="<c:url value='/'/>">Home</a></li>
            <li><a href="<c:url value='/board/list'/>">Board</a></li>
            <li><a href="<c:url value='${loginOutLink}'/>">${loginOut}</a></li>
            <li><a href="<c:url value='/register/add'/>">Sign in</a></li>
            <li><a href=""><i class="fa fa-search"></i></a></li>
        </ul>
    </div>
    <script>
        let msg = "${msg}";
        if (msg == "WRT_ERR") alert("๊ฒŒ์‹œ๋ฌผ ๋“ฑ๋ก์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด ์ฃผ์„ธ์š”.");
        if (msg == "MOD_ERR") alert("๊ฒŒ์‹œ๋ฌผ ์ˆ˜์ •์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด ์ฃผ์„ธ์š”.");
    </script>
    <div class="container">
        <h2 class="writing-header">๊ฒŒ์‹œํŒ ${mode=="new" ? "๊ธ€์“ฐ๊ธฐ" : "์ฝ๊ธฐ"}</h2>
        <form id="form" class="frm" action="" method="post">
            <input type="hidden" name="bno" value="${boardDto.bno}">
    
            <input name="title" type="text" value="${boardDto.title}"
                   placeholder="  ์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”." ${mode=="new" ? "" : "readonly='readonly'"}><br>
            <textarea name="content" rows="20"
                      placeholder=" ๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”." ${mode=="new" ? "" : "readonly='readonly'"}>${boardDto.content}</textarea><br>
    
            <c:if test="${mode eq 'new'}">
                <button type="button" id="writeBtn" class="btn btn-write"><i class="fa fa-pencil"></i> ๋“ฑ๋ก</button>
            </c:if>
            <c:if test="${mode ne 'new'}">
                <button type="button" id="writeNewBtn" class="btn btn-write"><i class="fa fa-pencil"></i> ๊ธ€์“ฐ๊ธฐ</button>
            </c:if>
            <c:if test="${boardDto.writer eq loginId}">
                <button type="button" id="modifyBtn" class="btn btn-modify"><i class="fa fa-edit"></i> ์ˆ˜์ •</button>
                <button type="button" id="removeBtn" class="btn btn-remove"><i class="fa fa-trash"></i> ์‚ญ์ œ</button>
            </c:if>
            <button type="button" id="listBtn" class="btn btn-list"><i class="fa fa-bars"></i> ๋ชฉ๋ก</button>
        </form>
    </div>
    <script>
        $(document).ready(function () {
            let formCheck = function () {
                let form = document.getElementById("form");
                if (form.title.value == "") {
                    alert("์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.");
                    form.title.focus();
                    return false;
                }
    
                if (form.content.value == "") {
                    alert("๋‚ด์šฉ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.");
                    form.content.focus();
                    return false;
                }
                return true;
            }
    
            $("#writeNewBtn").on("click", function () {
                location.href = "<c:url value='/board/write'/>";
            });
    
            $("#writeBtn").on("click", function () {
                let form = $("#form");
                form.attr("action", "<c:url value='/board/write'/>");
                form.attr("method", "post");
    
                if (formCheck())
                    form.submit();
            });
    
            $("#modifyBtn").on("click", function () {
                let form = $("#form");
                let isReadonly = $("input[name=title]").attr('readonly');
    
                // 1. ์ฝ๊ธฐ ์ƒํƒœ์ด๋ฉด, ์ˆ˜์ • ์ƒํƒœ๋กœ ๋ณ€๊ฒฝ
                if (isReadonly == 'readonly') {
                    $(".writing-header").html("๊ฒŒ์‹œํŒ ์ˆ˜์ •");
                    $("input[name=title]").attr('readonly', false);
                    $("textarea").attr('readonly', false);
                    $("#modifyBtn").html("<i class='fa fa-pencil'></i> ๋“ฑ๋ก");
                    return;
                }
    
                // 2. ์ˆ˜์ • ์ƒํƒœ์ด๋ฉด, ์ˆ˜์ •๋œ ๋‚ด์šฉ์„ ์„œ๋ฒ„๋กœ ์ „์†ก
                form.attr("action", "<c:url value='/board/modify${searchCondition.queryString}'/>");
                form.attr("method", "post");
                if (formCheck())
                    form.submit();
            });
    
            $("#removeBtn").on("click", function () {
                if (!confirm("์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?")) return;
    
                let form = $("#form");
                form.attr("action", "<c:url value='/board/remove${searchCondition.queryString}'/>");
                form.attr("method", "post");
                form.submit();
            });
    
            $("#listBtn").on("click", function () {
                location.href = "<c:url value='/board/list${searchCondition.queryString}'/>";
            });
        });
    </script>
    </body>
    </html>
  • boardList.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
    <%@taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt_rt" %>
    <%@ page session="true" %>
    <c:set var="loginId" value="${sessionScope.id}"/>
    <c:set var="loginOutLink" value="${loginId=='' ? '/login/login' : '/login/logout'}"/>
    <c:set var="loginOut" value="${loginId=='' ? 'Login' : 'ID='+=loginId}"/>
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>fastcampus</title>
        <link rel="stylesheet" href="<c:url value='/css/menu.css'/>">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
        <script src="https://code.jquery.com/jquery-1.11.3.js"></script>
        <style>
            * {
                box-sizing: border-box;
                margin: 0;
                padding: 0;
                font-family: "Noto Sans KR", sans-serif;
            }
    
            a {
                text-decoration: none;
                color: black;
            }
    
            button,
            input {
                border: none;
                outline: none;
            }
    
            .board-container {
                width: 60%;
                height: 1200px;
                margin: 0 auto;
                /* border: 1px solid black; */
            }
    
            .search-container {
                background-color: rgb(253, 253, 250);
                width: 100%;
                height: 110px;
                border: 1px solid #ddd;
                margin-top: 10px;
                margin-bottom: 30px;
                display: flex;
                justify-content: center;
                align-items: center;
            }
    
            .search-form {
                height: 37px;
                display: flex;
            }
    
            .search-option {
                width: 100px;
                height: 100%;
                outline: none;
                margin-right: 5px;
                border: 1px solid #ccc;
                color: gray;
            }
    
            .search-option > option {
                text-align: center;
            }
    
            .search-input {
                color: gray;
                background-color: white;
                border: 1px solid #ccc;
                height: 100%;
                width: 300px;
                font-size: 15px;
                padding: 5px 7px;
            }
    
            .search-input::placeholder {
                color: gray;
            }
    
            .search-button {
                /* ๋ฉ”๋‰ด๋ฐ”์˜ ๊ฒ€์ƒ‰ ๋ฒ„ํŠผ ์•„์ด์ฝ˜  */
                width: 20%;
                height: 100%;
                background-color: rgb(22, 22, 22);
                color: rgb(209, 209, 209);
                display: flex;
                align-items: center;
                justify-content: center;
                font-size: 15px;
            }
    
            .search-button:hover {
                color: rgb(165, 165, 165);
            }
    
            table {
                border-collapse: collapse;
                width: 100%;
                border-top: 2px solid rgb(39, 39, 39);
            }
    
            tr:nth-child(even) {
                background-color: #f0f0f070;
            }
    
            th,
            td {
                width: 300px;
                text-align: center;
                padding: 10px 12px;
                border-bottom: 1px solid #ddd;
            }
    
            td {
                color: rgb(53, 53, 53);
            }
    
            .no {
                width: 150px;
            }
    
            .title {
                width: 50%;
            }
    
            td.title {
                text-align: left;
            }
    
            td.writer {
                text-align: left;
            }
    
            td.viewcnt {
                text-align: right;
            }
    
            td.title:hover {
                text-decoration: underline;
            }
    
            .paging {
                color: black;
                width: 100%;
                align-items: center;
            }
    
            .page {
                color: black;
                padding: 6px;
                margin-right: 10px;
            }
    
            .paging-active {
                background-color: rgb(216, 216, 216);
                border-radius: 5px;
                color: rgb(24, 24, 24);
            }
    
            .paging-container {
                width: 100%;
                height: 70px;
                display: flex;
                margin-top: 50px;
                margin: auto;
            }
    
            .btn-write {
                background-color: rgb(236, 236, 236); /* Blue background */
                border: none; /* Remove borders */
                color: black; /* White text */
                padding: 6px 12px; /* Some padding */
                font-size: 16px; /* Set a font size */
                cursor: pointer; /* Mouse pointer on hover */
                border-radius: 5px;
                margin-left: 30px;
            }
    
            .btn-write:hover {
                text-decoration: underline;
            }
        </style>
    </head>
    <body>
    <div id="menu">
        <ul>
            <li id="logo">fastcampus</li>
            <li><a href="<c:url value='/'/>">Home</a></li>
            <li><a href="<c:url value='/board/list'/>">Board</a></li>
            <li><a href="<c:url value='${loginOutLink}'/>">${loginOut}</a></li>
            <li><a href="<c:url value='/register/add'/>">Sign in</a></li>
            <li><a href=""><i class="fa fa-search"></i></a></li>
        </ul>
    </div>
    <script>
        let msg = "${msg}";
        if (msg == "LIST_ERR") alert("๊ฒŒ์‹œ๋ฌผ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด ์ฃผ์„ธ์š”.");
        if (msg == "READ_ERR") alert("์‚ญ์ œ๋˜์—ˆ๊ฑฐ๋‚˜ ์—†๋Š” ๊ฒŒ์‹œ๋ฌผ์ž…๋‹ˆ๋‹ค.");
        if (msg == "DEL_ERR") alert("์‚ญ์ œ๋˜์—ˆ๊ฑฐ๋‚˜ ์—†๋Š” ๊ฒŒ์‹œ๋ฌผ์ž…๋‹ˆ๋‹ค.");
    
        if (msg == "DEL_OK") alert("์„ฑ๊ณต์ ์œผ๋กœ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
        if (msg == "WRT_OK") alert("์„ฑ๊ณต์ ์œผ๋กœ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
        if (msg == "MOD_OK") alert("์„ฑ๊ณต์ ์œผ๋กœ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
    </script>
    <div style="text-align:center">
        <div class="board-container">
            <div class="search-container">
                <form action="<c:url value="/board/list"/>" class="search-form" method="get">
                    <select class="search-option" name="option">
                        <option value="A" ${ph.sc.option=='A' || ph.sc.option=='' ? "selected" : ""}>์ œ๋ชฉ+๋‚ด์šฉ</option>
                        <option value="T" ${ph.sc.option=='T' ? "selected" : ""}>์ œ๋ชฉ๋งŒ</option>
                        <option value="W" ${ph.sc.option=='W' ? "selected" : ""}>์ž‘์„ฑ์ž</option>
                    </select>
    
                    <input type="text" name="keyword" class="search-input" type="text" value="${ph.sc.keyword}"
                           placeholder="๊ฒ€์ƒ‰์–ด๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”">
                    <input type="submit" class="search-button" value="๊ฒ€์ƒ‰">
                </form>
                <button id="writeBtn" class="btn-write" onclick="location.href='<c:url value="/board/write"/>'"><i
                        class="fa fa-pencil"></i> ๊ธ€์“ฐ๊ธฐ
                </button>
            </div>
    
            <table>
                <tr>
                    <th class="no">๋ฒˆํ˜ธ</th>
                    <th class="title">์ œ๋ชฉ</th>
                    <th class="writer">์ด๋ฆ„</th>
                    <th class="regdate">๋“ฑ๋ก์ผ</th>
                    <th class="viewcnt">์กฐํšŒ์ˆ˜</th>
                </tr>
                <c:forEach var="boardDto" items="${list}">
                    <tr>
                        <td class="no">${boardDto.bno}</td>
                        <td class="title"><a
                                href="<c:url value="/board/read${ph.sc.queryString}&bno=${boardDto.bno}"/>">${boardDto.title}</a>
                        </td>
                        <td class="writer">${boardDto.writer}</td>
                        <c:choose>
                            <c:when test="${boardDto.reg_date.time >= startOfToday}">
                                <td class="regdate"><fmt:formatDate value="${boardDto.reg_date}" pattern="HH:mm"
                                                                    type="time"/></td>
                            </c:when>
                            <c:otherwise>
                                <td class="regdate"><fmt:formatDate value="${boardDto.reg_date}" pattern="yyyy-MM-dd"
                                                                    type="date"/></td>
                            </c:otherwise>
                        </c:choose>
                        <td class="viewcnt">${boardDto.view_cnt}</td>
                    </tr>
                </c:forEach>
            </table>
            <br>
            <div class="paging-container">
                <div class="paging">
                    <c:if test="${ph.totalCnt==null || ph.totalCnt==0}">
                        <div> ๊ฒŒ์‹œ๋ฌผ์ด ์—†์Šต๋‹ˆ๋‹ค.</div>
                    </c:if>
                    <c:if test="${ph.totalCnt!=null && ph.totalCnt!=0}">
                        <c:if test="${ph.showPrev}">
                            <a class="page"
                               href="<c:url value="/board/list${ph.sc.getQueryString(ph.beginPage-1)}"/>">&lt;</a>
                        </c:if>
                        <c:forEach var="i" begin="${ph.beginPage}" end="${ph.endPage}">
                            <a class="page ${i==ph.sc.page? "paging-active" : ""}"
                               href="<c:url value="/board/list${ph.sc.getQueryString(i)}"/>">${i}</a>
                        </c:forEach>
                        <c:if test="${ph.showNext}">
                            <a class="page"
                               href="<c:url value="/board/list${ph.sc.getQueryString(ph.endPage+1)}"/>">&gt;</a>
                        </c:if>
                    </c:if>
                </div>
            </div>
        </div>
    </div>
    </body>
    </html>

PageHandler์— queryString ์ฒ˜๋ฆฌํ•ด์ฃผ๋Š” ๊ฑธ ๋„ฃ์–ด์ค˜์•ผ ํ•œ๋‹ค.

์–˜๋„ค๋“ค์„ ๋ฌถ์€ ๊ฒŒ SearchCondition์ธ๋ฐ, ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๋‚ด์šฉ์„ ๋ดค๋‹ค๊ฐ€ ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„์˜ฌ ๋•Œ ์•„๋ž˜ ๊ฐ’๋“ค์„ ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค. ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง์œผ๋กœ ์ด ๊ฐ’๋“ค์„ ๋‹ค ์ค˜์•ผํ•จ. ์ด๊ฑธ ๋‹ค ์ฃผ๋Š” ๊ฒŒ ๋ฒˆ๊ฑฐ๋กญ๊ธฐ ๋•Œ๋ฌธ์— getQueryString()์ด๋ผ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค ๊ฒƒ์ด๋‹ค.

Integer page = 1;
Integer pageSize = 10;
Integer offset = 0;
String keyword = "";
String option = "";

UriComponentBuilder๋ผ๋Š” util ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

์ง€์ •๋œ ํŽ˜์ด์ง€๋กœ ํŽ˜์ด์ง€๊ฐ€ ์„ธํŒ…์ด ๋˜๋„๋ก.

public String getQueryString(Integer page) {
    return UriComponentsBuilder.newInstance()
            .queryParam("page", page)
            .queryParam("pageSize", sc.getPageSize())
            .queryParam("option", sc.getOption())
            .queryParam("keyword", sc.getKeyword())
            .build().toString();
}

์œ„ ๋‘ ๋ฉ”์„œ๋“œ๋Š” ์ฝ”๋“œ ์ค‘๋ณต์ด๋ผ ์ค‘๋ณต ์ œ๊ฑฐํ•ด์•ผํ•จ

ํŽ˜์ด์ง€๋ฅผ ์ง€์ •ํ•ด์ฃผ๋ฉด ๊ทธ ํŽ˜์ด์ง€๋ฅผ ์“ฐ๊ณ , ์ง€์ •ํ•ด์ฃผ์ง€ ์•Š์œผ๋ฉด sc์˜ getPage๋ฅผ ๊ฐ€์ ธ๋‹ค์“ด๋‹ค.

public String getQueryString(Integer page) {
    return UriComponentsBuilder.newInstance()
            .queryParam("page", page)
            .queryParam("pageSize", sc.getPageSize())
            .queryParam("option", sc.getOption())
            .queryParam("keyword", sc.getKeyword())
            .build().toString();
}

public String getQueryString() {
    return getQueryString(sc.getPage());
}

getQueryString์€ PageHandler๋ณด๋‹ค SearchCondition์ด ์ ํ•ฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ฎ๊ฒจ์ฃผ์ž.

ํ†ฐ์บฃ ์‹คํ–‰ํ•˜๊ณ  Board๋ฅผ ๋ณด๋ฉด, ๊ฒ€์ƒ‰์€ ์ž˜ ๋˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ํŽ˜์ด์ง•์ด ์ •์ƒ์ ์ด์ง€ ์•Š์Œ. offset ๋•Œ๋ฌธ์— ๊ทธ๋Ÿผ.

offset์€ page์™€ pageSize๋กœ ๊ณ„์‚ฐ๋˜๋Š” ๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ตณ์ด iv๋กœ ๋„ฃ์„ ํ•„์š”๊ฐ€ ์—†๋‹ค?

์˜คํžˆ๋ ค ์ด ๊ฐ’์ด ์žˆ์œผ๋ฉด ๊ณ„์† ๊ด€๋ฆฌ๋ฅผ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

public class SearchCondition {
    Integer page = 1;
    Integer pageSize = 10;
    Integer offset = 0;
    String keyword = "";
    String option = "";
}

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์„ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ ,

offset์„ ์ฝ์„ ๋•Œ page์™€ pageSize๋กœ ๊ณ„์‚ฐ๋งŒ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

setter๋Š” ์ง€์šฐ๊ณ  getOffset ์ˆ˜์ •.

public Integer getOffset() {
    return (page - 1) * pageSize;
}

boardMapper์—์„œ #{offset}์‹œ getOffset์„ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ทธ๋ƒฅ offset์ด ์•„๋‹˜โ€ฆ

๊ทธ๋ž˜์„œ getOffset์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.

ํŽ˜์ด์ง•์ด ์ž˜ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.


์ด์ œ boardMapper.xml๋กœ ๊ฐ€์„œ ์ œ๋Œ€๋กœ๋œ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž.

๋™์  sql๋กœ ๋งŒ๋“ค๊ธฐ

boardMapper.xml

<select id="searchSelectPage" parameterType="SearchCondition" resultType="BoardDto">
    SELECT bno, title, content, writer, view_cnt, comment_cnt, reg_date
    FROM board
     WHERE TRUE
     <choose>
         <when test='option=="T"'>
             AND title LIKE CONCAT('%', #{keyword}, '%')
         </when>
         <when test='option=="W"'>
             AND writer LIKE CONCAT('%', #{keyword}, '%')
         </when>
         <otherwise>
             AND (writer LIKE CONCAT('%', #{keyword}, '%')
              OR content LIKE CONCAT('%', #{keyword}, '%'))
         </otherwise>
     </choose>
    ORDER BY reg_date DESC, bno DESC
        LIMIT #{offset}, #{pageSize}
</select>

<select id="searchResultCnt" parameterType="SearchCondition" resultType="int">
    SELECT count(*)
    FROM board
    WHERE TRUE
    <choose>
        <when test='option=="T"'>
            AND title LIKE CONCAT('%', #{keyword}, '%')
        </when>
        <when test='option=="W"'>
            AND writer LIKE CONCAT('%', #{keyword}, '%')
        </when>
        <otherwise>
            AND (writer LIKE CONCAT('%', #{keyword}, '%')
            OR content LIKE CONCAT('%', #{keyword}, '%'))
        </otherwise>
    </choose>
</select>

BoardDaoImplTest ์ด ์ž˜ ๋Œ์•„๊ฐ€๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž. searchSelectPageTest ์˜ option์„ W๋กœ๋„ ๋ฐ”๊ฟ”์„œ ํ…Œ์ŠคํŠธ ํ•ด๋ณด์•„์•ผ ํ•œ๋‹ค.

@Test
public void searchSelectPageTest() throws Exception {
    boardDao.deleteAll();

    for (int i = 0; i <= 20; i++) {
        BoardDto boardDto = new BoardDto("title" + i, "asdfafd", "adff" + i);
        boardDao.insert(boardDto);
    }

    SearchCondition sc = new SearchCondition(1, 10, "title2", "T");
     List<BoardDto> list = boardDao.searchSelectPage(sc);
    System.out.println("boardList = " + list);
    assertTrue(list.size() == 2);

    sc = new SearchCondition(1, 10, "adff2", "W");
    list = boardDao.searchSelectPage(sc);
    System.out.println("boardList = " + list);
    assertTrue(list.size() == 2);
}

@Test
public void searchResultCntTest() throws Exception {
    boardDao.deleteAll();

    for (int i = 0; i <= 20; i++) {
        BoardDto boardDto = new BoardDto("title" + i, "asdfafd", "adff" + i);
        boardDao.insert(boardDto);
    }

    SearchCondition sc = new SearchCondition(1, 10, "adff2", "W");
    int list = boardDao.searchResultCnt(sc);
    System.out.println("boardList = " + list);
    assertTrue(list == 2);

    sc = new SearchCondition(1, 10, "adff2", "W");
    list = boardDao.searchResultCnt(sc);
    System.out.println("boardList = " + list);
    assertTrue(list == 2);
}

searchResultCntTest ๋„ ๋˜‘๊ฐ™์ด ํ…Œ์ŠคํŠธ ํ•˜๋ฉด ๋จ. ์‚ฌ์‹ค์€ ๋” ๊ผผ๊ผผํžˆ ํ•ด์•ผ ํ•œ๋‹ค.!!

์œ„์˜ ์ฟผ๋ฆฌ์—์„œ ์ด๋ถ€๋ถ„์ด ์ค‘๋ณต์ด๋‹ค. ์ด๋Ÿด ๋•Œ ์“ฐ๋ฉด ์ข‹์€ ๊ฒŒ ์ผ๋‹จ ์ด๋ถ€๋ถ„์„ cutํ•ด์„œ ๋ณ„๋„์˜ sql๋ฌธ์œผ๋กœ ๋งŒ๋“ ๋‹ค.

<choose>
    <when test='option=="T"'>
        AND title LIKE CONCAT('%', #{keyword}, '%')
    </when>
    <when test='option=="W"'>
        AND writer LIKE CONCAT('%', #{keyword}, '%')
    </when>
    <otherwise>
        AND (writer LIKE CONCAT('%', #{keyword}, '%')
        OR content LIKE CONCAT('%', #{keyword}, '%'))
    </otherwise>
</choose>

์ด๋ ‡๊ฒŒ sql์„ ๋ถ„๋ฆฌํ•ด์„œ ์ฝ”๋“œ๋ฅผ ํ›จ์”ฌ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๊ณ  ๋ณ€๊ฒฝํ•  ์ผ์ด ์ƒ๊ฒจ๋„ ํ•œ ๊ตฐ๋ฐ๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋œ๋‹ค.

๋ฐ”๊พผ ๋’ค์—๋Š” ๋ฌธ์ œ๊ฐ€ ์—†๋Š”์ง€ ๊ผญ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค.

<sql id="searchCondition">
    <choose>
        <when test='option=="T"'>
            AND title LIKE CONCAT('%', #{keyword}, '%')
        </when>
        <when test='option=="W"'>
            AND writer LIKE CONCAT('%', #{keyword}, '%')
        </when>
        <otherwise>
            AND (writer LIKE CONCAT('%', #{keyword}, '%')
            OR content LIKE CONCAT('%', #{keyword}, '%'))
        </otherwise>
    </choose>
</sql>

<select id="searchSelectPage" parameterType="SearchCondition" resultType="BoardDto">
    SELECT bno, title, content, writer, view_cnt, comment_cnt, reg_date
    FROM board
     WHERE TRUE
    <include refid="searchCondition" />
    ORDER BY reg_date DESC, bno DESC
        LIMIT #{offset}, #{pageSize}
</select>

<select id="searchResultCnt" parameterType="SearchCondition" resultType="int">
    SELECT count(*)
    FROM board
    WHERE TRUE
    <include refid="searchCondition" />
</select>

โญย ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ ํ•„์š”์„ฑ

๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์žˆ์„ ๋•Œ ํ…Œ์ŠคํŠธ๋ฅผ ๊ผญ ๋‹ค์‹œ ๋Œ๋ ค๋ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ ์žŠ์ง€ ๋ง๊ธฐ

TDD๊ฐ€ ๋‹จ์ˆœํžˆ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๊ฒƒ๋งŒ์ด ์•„๋‹ˆ๋ผ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ๋ฐœ์ƒํ–ˆ๊ฑฐ๋‚˜ ๋ฆฌํŒฉํ† ๋ง์„ ํ•ด์•ผ ํ•  ๋•Œ, ๋ฆฌํŒฉํ† ๋ง ํ•˜๊ณ ๋‚˜์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ผ์ผํžˆ ํ•˜๋‚˜์”ฉ ๋˜ ํ•ด๋ณด๋Š” ๊ฒƒ๋ณด๋‹จ ๊ธฐ์กด์— ๋งŒ๋“ค์–ด๋‘” ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ ค๋ดค์„ ๋•Œ ์ด์ƒ์ด ์—†์œผ๋ฉด ๋ฆฌํŒฉํ† ๋ง์ด ์ž˜ ๋˜์—ˆ๋‹ค๊ณ  ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ tdd๊ฐ€ ๋ฆฌํŒฉํ† ๋ง์— ์žˆ์–ด์„œ๋„ ๊ต‰์žฅํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง„๋‹ค.

์‹ค๋ฌด์—์„œ ๊ฐœ๋ฐœํ•  ๋•Œ ์ข€ ๋ฏธ์ง„ํ•œ ์ฝ”๋“œ๊ฐ€ ์žˆ์–ด์„œ ๊ณ ์น˜๊ณ  ์‹ถ์€๋ฐ ์†์„ ๋ชป ๋Œ€๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค. ์™œ๋ƒ๋ฉด ๊ดœํžˆ ์ž˜๋ชป ๊ณ ์ณค๋‹ค๊ฐ€ ๋‹ค๋ฅธ๋ฐ ์˜ํ–ฅ์„ ์ค€๋‹ค๋˜๊ฐ€ ์‹ค์ˆ˜ํ•˜๋ฉด ์•ˆ ๋˜๋‹ˆ๊นŒ ๋ฆฌํŒฉํ† ๋ง์„ ์ฃผ์ €ํ•˜๊ฒŒ๋˜๋Š”๋ฐ, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž˜ ๋งŒ๋“ค์–ด๋†“์€ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋ฏฟ๊ณ  ํŽธ์•ˆํ•˜๊ฒŒ ๋ฆฌํŒฉํ† ๋ง ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฆฌํŒฉํ† ๋ง ํ•ด๋†“๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ๋Œ๋ ธ์„ ๋•Œ ํ†ต๊ณผํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ์—†๋Š” ๊ฒƒ. โ†’ ๋ฆฌํŒฉํ† ๋ง ํ™œ๋ฐœํ•˜๊ฒŒ ๊ฐ€๋Šฅ ๊ณ„์† ์ข‹์€ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

ใ…ก๋Ÿผ ์ฝ”๋“œ ์งง์•„์ง€๊ณ  ,์ฝ”๋“œ์˜ ์ค‘๋ณต๋„ ์ค„์–ป๋ฅด๊ณ  ์œ ์ง€๋ณด์ˆ˜ํ•˜๋Š”๋ฐ ํ›จ์”ฌ ๋„์›€์ด ๋œ๋‹ค.

๊ท€์ฐฎ๊ธด ํ•˜๋”๋ผ๋„ ์ž๊พธ tddํ•ด์„œ ๋” ๋งŽ์€ ๋ถ€๋ถ„์„ tdd๋กœ ์ปค๋ฒ„ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.


โญย JSTL ๋ฒ„์ „ ๋ถˆ์ผ์น˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

board.jsp

๊ฐ€๋” ๋ฒ„์ „ ๋ถˆ์ผ์น˜ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค. ํ•ด๊ฒฐ์ฑ…์ด ๋’ค์— _rt๋ฅผ ๋ถ™์ด๋Š” ๊ฒƒ.

jstl์—์„œ ์—๋Ÿฌ๊ฐ€ ๋งŽ์ด ๋‚œ๋‹ค. ๊ทธ๋Ÿด ๋•Œ๋Š” ๋ฒ„ใ…“ใ…ˆใ„ด์„ ์ž˜ ๋งž์ถฐ์ค˜์•ผ ํ•˜๋Š”๋ฐ ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•์ด _rt๋ฅผ ๋ถ™์ด๋Š” ๊ฒƒ.

์ด ์  ์•Œ์•„๋‘๊ณ .

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


๐Ÿšจย ์Šคํฌ๋ฆฝํŠธ ๊ณต๊ฒฉ ๋ง‰๊ธฐ(c:out ํ™œ์šฉ)

๊ธ€์“ฐ๊ธฐ์—์„œ script alert์„ ์ž…๋ ฅํ•˜๊ณ  ๊ธ€์„ ์“ฐ๋ฉด?

๋‚ด์šฉ๊ณผ ์ œ๋ชฉ์— ๋„ฃ๊ณ 

๋“ฑ๋ก๋˜๋ฉด์„œ hello๊ฐ€ ๋œธ

๋‚ด์šฉ์„ ๋ณด๋ฉด ๊นจ์ ธ์„œ ๋‚˜์˜จ๋‹ค. ๊ทธ๋ž˜์„œ ์—ฌ๊ธฐ์— <script>alert(โ€Helloโ€)</script> ๋ฅผ ๋„ฃ๊ฒŒ ํ•˜๋ฉด ์•ˆ ๋œ๋‹ค.

์—ฌ๊ธฐ์„  alert์ด๋ผ ๋‹คํ–‰์ด์ง€๋งŒ, ํ•ด์ปค๊ฐ€ ์•…์˜์ ์ธ ์ฝ”๋“œ๋ฅผ ๋„ฃ์„ ์ˆ˜๋„ ์žˆ๋‹ค.

ํ•ด๊ฒฐ์ฑ….

๋‚ด์šฉ์„ ๊ทธ๋ƒฅ ๋ณด์—ฌ์ฃผ๋ฉด ์•ˆ ๋˜๊ณ , ์ฝ”์–ด ํƒœ๊ทธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ out ํƒœ๊ทธ๋ฅผ ์จ์•ผ ํ•œ๋‹ค.

์ด๋ ‡๊ฒŒ out์œผ๋กœ ๊ฐ์‹ธ๋ฉด, ๊ทธ ์•ˆ์— ํƒœ๊ทธ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ ์ž๋™์œผ๋กœ ๋ณ€ํ™˜ํ•ด์ค€๋‹ค.

<input name="title" type="text" value="${boardDto.title}" placeholder="  ์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”." ${mode=="new" ? "" : "readonly='readonly'"}><br>
<input name="title" type="text" value="<c:out value='${boardDto.title}' />" placeholder="  ์ œ๋ชฉ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”." ${mode=="new" ? "" : "readonly='readonly'"}><br>

+) content๋„ ๋ณ€๊ฒฝ
<c:out value="${boardDto.content}" />

boardList.jsp์˜ ์ œ๋ชฉ์—๋„๋„ ๋ณ€๊ฒฝํ•ด์•ผ..

๋ณ€๊ฒฝ ํ›„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋ฉด

๋ณด์ด๋Š” ๊ฑด ์ž˜ ๋ณด์ด๋Š”๋ฐ ๋‹จ์ˆœ text๋กœ ์ธ์‹ํ•ด์„œ ์‹คํ–‰์ด ์•ˆ ๋œ๋‹ค. ํ•ด์ปค๊ฐ€ ์•…์˜์ ์ธ ์ฝ”๋“œ๋ฅผ ๋„ฃ์–ด๋„ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ ์ธ์‹์„ ์•ˆ ํ•˜๊ณ  ํ…์ŠคํŠธ๋กœ ์ธ์‹ํ•ด์„œ ๊ณต๊ฒฉ ๋ฐฉ์–ด ๊ฐ€๋Šฅ ๊ทธ๋ž˜์„œ outํƒœ๊ทธ๋ฅผ ์“ด๋‹ค.

09. REST API์™€ AJAX

1. JSON์ด๋ž€?

  • Java Script Object Notation

๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ  ๋ฐ›์„ ๋•Œ XML์„ ๋งŽ์ด ์ผ๋Š”๋ฐ ๋„ˆ๋ฌด ๋ณต์žกํ•˜๊ณ , ์‹ค์ œ Data๋ณด๋‹ค ํƒœ๊ทธ๊ฐ€ ๋” ๋งŽ๋‹ค. ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ๋” ํผโ€ฆ

๊ทธ๋ž˜์„œ ๋ณต์žกํ•œ XML ๋Œ€์‹ ์— ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ฐ€์ž๊ณ  ํ•ด์„œ JSON์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.(XML โ†’ JSON)

JSON = ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ ๊ฐ์ฒด๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•

{ ์†์„ฑ๋ช…1: ์†์„ฑ๊ฐ’1, ์†์„ฑ๋ช…2: ์†์„ฑ๊ฐ’2, ...}

[{ ์†์„ฑ๋ช…: ์†์„ฑ๊ฐ’, ...}, { ์†์„ฑ๋ช…: ์†์„ฑ๊ฐ’, ...}, ... ] // ๊ฐ์ฒด ๋ฐฐ์—ด
{ ํ‚ค1:{ ์†์„ฑ๋ช…: ์†์„ฑ๊ฐ’, ...}, ํ‚ค2: { ์†์„ฑ๋ช…: ์†์„ฑ๊ฐ’, ...}, /// } // Map

2. stringify()์™€ parse()

  • JS ๊ฐ์ฒด๋ฅผ ์„œ๋ฒ„๋กœ ์ „์†กํ•˜๋ ค๋ฉด, ์ง๋ ฌํ™”(๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜)๊ฐ€ ํ•„์š”
  • ์„œ๋ฒ„๊ฐ€ ๋ณด๋‚ธ ๋ฐ์ดํ„ฐ(JSON ๋ฌธ์ž์—ด)๋ฅผ JS๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•  ๋•Œ, ์—ญ์ง๋ ฌํ™”๊ฐ€ ํ•„์š”

๐Ÿ’กย ์ง๋ ฌํ™”? : ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ

JSON.stringfiy() : ๊ฐ์ฒด๋ฅผ JSON ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜(์ง๋ ฌํ™”, JS ๊ฐ์ฒด โ†’ ๋ฌธ์ž์—ด)

JSON.parse() : JSON ๋ฌธ์ž์—ด์„ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜(์—ญ์ง๋ ฌํ™”, ๋ฌธ์ž์—ด โ†’ JS ๊ฐ์ฒด)

๊ฐ์ฒด๋ฅผ ์ €์žฅํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ? ๊ฐ์ฒด๋Š” ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋œ ๊ฐ’์ด๋‹ค.

์•„๋ž˜์ฒ˜๋Ÿผ ์ง๋ ฌํ™”ํ•ด์„œ ๋ฌธ์ž์—ด๋กœ ๋งŒ๋“ค๋ฉด ๋ฌธ์ž์—ด์ด๋ผ HTTP(Text ๊ธฐ๋ฐ˜ ํ”„๋กœํ† ์ฝœ)๋กœ ์ „์†ก์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค. ๊ทธ๋ž˜์„œ JS ๊ฐ์ฒด๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์ด๋‹ค.

์‹ค์Šต

JS๋Š” ๋™์ ์œผ๋กœ ๋ฉค๋ฒ„๋ฅผ ์ถ”๊ฐ€, ์‚ญ์ œ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

๐Ÿ’กย dir๋กœ ๊ฐ์ฒด๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ฉค๋ฒ„๋ฅผ ๋‹ค ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

JS์—์„œ๋Š” ๊ฐ์ฒด๋กœ ๋‹ค๋ฃจ๋‹ค๊ฐ€ ์ „์†กํ•  ๋•Œ๋Š” ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์ „์†กํ•œ๋‹ค.

์ด๋Ÿด ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ stringfy(), parse()๋‹ค.

var obj = {name: "abc", age: 10, hp: '010-5517-3236'}

let strObj = JSON.stringify(obj);
console.log(strObj); // '{"name":"abc","age":10,"hp":"010-1234-5678"}'

typeof strObj // 'string'
typeof obj // 'object'

let obj2 = JSON.parse(strObj);
console.log(obj2); // {name: 'abc', age: 10, hp: '010-1234-5678'}

3. Ajax๋ž€? - Asynchronous Javascript And XML

  • ๋น„๋™๊ธฐ(Asynchronous) ํ†ต์‹ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ  ๋ฐ›๊ธฐ ์œ„ํ•œ ๊ธฐ์ˆ ์ด๋‹ค. - ์š”์ฆ˜์€ JSON์„ ์ฃผ๋กœ ์‚ฌ์šฉ.
  • AJAX๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์›นํŽ˜์ด์ง€ ์ „์ฒด(data + UI)๊ฐ€ ์•„๋‹Œ ์ผ๋ถ€(data)๋งŒ ์—…๋ฐ์ดํŠธ๊ฐ€ ๊ฐ€๋Šฅ

    ์˜›๋‚ ์—๋Š” ํ™”๋ฉด์ด ์กฐ๊ธˆ๋งŒ ๋ฐ”๋€Œ์–ด๋„ ์›นํŽ˜์ด์ง€ ์ „์ฒด๋ฅผ ์‘๋‹ต์œผ๋กœ ๋ฐ›์•„ updateํ•˜๋Š” ๋น„ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ–ˆ์—ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ๋„คํŠธ์›Œํฌ๊ฐ€ ๋‚ญ๋น„๋  ๋ฟ๋”๋Ÿฌ ์†๋„๋„ ๋А๋ฆฌ๋‹ค. ๊ทธ๋ž˜์„œ ์ผ๋ถ€๋งŒ update ํ•  ๋•Œ๋Š” AJAX๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

๋™๊ธฐ VS ๋น„๋™๊ธฐ

๋™๊ธฐ

๋ฉ”์„œ๋“œ ํ˜ธ์ถœ๊ณผ ๋˜‘๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค. ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ฉ”์„œ๋“œ๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ์ค„ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‘๋‹ต์ด ์™€์•ผ ๋‹ค๋ฅธ ์ผ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋น„๋™๊ธฐ

๊ทธ๋Ÿฐ๋ฐ ๋น„๋™๊ธฐ๋Š” ์š”์ฒญ์„ ํ•ด๋†“๊ณ  ์‘๋‹ต์„ ์ค„ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š์•„๋„ ๋œ๋‹ค. ๊ทธ๋ž˜์„œ ์š”์ฒญ๋งŒ ๋ณด๋‚ด๋‘๊ณ  ๋‹ค๋ฅธ ์ผ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋Œ€์‹  ์š”์ฒญ์ด ์–ธ์ œ ์ฒ˜๋ฆฌ๋˜์—ˆ๋Š”์ง€๋Š” ๋ชจ๋ฅธ๋‹ค.

์ด๋Ÿด ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์ด๋‹ค. ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋Š” ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋Š” ๊ฑธ ์•Œ๋ ค์ค€๋‹ค.

โ“์ด๋ฆ„์ด ์ฝœ๋ฐฑ์ธ ์ด์œ ?

์ฝœ๋ฐฑ ํ•จ์ˆ˜์˜ ์ด๋ฆ„์€ ์นœ๊ตฌ์—๊ฒŒ ์ „ํ™”๋ฅผ ํ–ˆ๋Š”๋ฐ ์นœ๊ตฌ๊ฐ€ ์ง‘์— ์—†์„ ๋•Œ ๋ฉ”์‹œ์ง€๋ฅผ ๋‚จ๊ฒจ๋†“์œผ๋ฉด ์นœ๊ตฌ๊ฐ€ ์ง‘์— ๋Œ์•„์™”์„ ๋•Œ ๋‹ค์‹œ ์ „ํ™”๋ฅผ ์ฃผ๋Š” ๊ฒƒ์—์„œ ์œ ๋ž˜ํ–ˆ๋‹ค.

๋„ˆ ์ง‘์— ๋Œ์•„์˜ค๋ฉด ๋‚˜ํ•œํ…Œ ์ „ํ™” ์ข€ ์ค˜. ์—ฌ๊ธฐ์„œ๋Š” ์ฒ˜๋ฆฌ ๋๋‚˜๋ฉด ๋‚˜ํ•œํ…Œ ์—ฐ๋ฝ์ฃผ๋ผ๋Š” ๋œป

์›น ๋ธŒ๋ผ์šฐ์ €๋กœ ์˜ˆ๋ฅผ ๋“ค๋ฉด ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํƒญ์ด ์กด์žฌํ•œ๋‹ค. ๋ธŒ๋ผ์šฐ์ € ์ „์ฒด๋Š” ๋ฉ€ํ‹ฐ ์“ฐ๋ ˆ๋“œ์ด์ง€๋งŒ ๊ฐ ํƒญ์€ ์‹ฑ๊ธ€ ์“ฐ๋ ˆ๋“œ๋‹ค. ํƒญ์„ ์ถ”๊ฐ€ํ•  ๋•Œ๋งˆ๋‹ค ์‹ฑ๊ธ€ ์“ฐ๋ ˆ๋“œ๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค. ์‹ฑ๊ธ€ ์“ฐ๋ ˆ๋“œ๋กœ์ธํ•ด ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์—†๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์‚ฌ์šฉ์ž๋Š” ํ•˜๋‚˜์˜ ํ™”๋ฉด์—์„œ ์—ฌ๋Ÿฌ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์ด ๊ฒฝ์šฐ์— ๋™๊ธฐ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ์‘๋‹ต์ด ์˜ฌ ๋•Œ๊นŒ์ง€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฉˆ์ถ”๊ฒŒ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋น„๋™๊ธฐ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ์ผ๋‹จ ์š”์ฒญ์„ ๋ณด๋‚ด๋†“๊ณ  ๋‹ค๋ฅธ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋น„๋™๊ธฐ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•œ๋‹ค.

๐Ÿ’ก๋™๊ธฐ ๋ฐฉ์‹์€ ์š”์ฒญ์„ ํ•˜๊ณ  ์‘๋‹ต์ด ์˜ฌ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š”๋ฐ ๋น„๋™๊ธฐ๋Š” ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ์ผ์„ ํ•œ๋‹ค๋Š” ๊ฒŒ ํ•ต์‹ฌ์ด๋‹ค.

+) ๋น„๋™๊ธฐ ๋ฐฉ์‹์€ ํŽ˜์ด์ง€ ์ „์ฒด์˜ ๋ฆฌ๋กœ๋“œ ์—†์ด ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

4. jQuery๋ฅผ ์ด์šฉํ•œ Ajax

jQuery์—์„œ ์ œ๊ณตํ•˜๋Š” ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜๋‹ค.

์š”์ฒญ์„ ํ•˜๋ฉด์„œ ์ž‘์—…์ด ๋๋‚˜๋ฉด ์ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ผ๊ณ  ์ฝœ๋ฐฑ ํ•จ์ˆ˜ 2๊ฐœ๋ฅผ ์ค€๋‹ค. ๊ทธ๋Ÿผ ์„œ๋ฒ„๋Š” ์š”์ฒญ์„ ๋‹ค ์ฒ˜๋ฆฌํ•œ ๋’ค์— ๋‘˜ ์ค‘์— ํ•˜๋‚˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•จ์œผ๋กœ์จ ๋น„๋™๊ธฐ ์š”์ฒญ์˜ ์„ฑ๊ณต/์‹คํŒจ ์—ฌ๋ถ€๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

$(document).ready(function () {
    let person = { name: "abc", age: 10};
    let person2 = {};

    $.ajax({
        type: 'POST',                                   // ์š”์ฒญ ๋ฉ”์„œ๋“œ
        url: '/send',                                   // ์š”์ฒญ URI
        headers: {"content-type": "application/json"},  // ์š”์ฒญ ํ—ค๋”
				contentType: 'application/json; charset=utf-8', // ์„œ๋ฒ„๋กœ ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ ํƒ€์ž…
        dataType: 'text',                               // ์„œ๋ฒ„์—์„œ ๋ฐ›์„ ๋ฐ์ดํ„ฐ ํ˜•์‹
        data: JSON.stringify(person),                   // ์„œ๋ฒ„๋กœ ์ „์†กํ•  ๋ฐ์ดํ„ฐ. stringify()๋กœ ์ง๋ ฌํ™” ํ•„์š”.
        success: function (result) { // ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์‘๋‹ต์ด ๋„์ฐฉํ•˜๋ฉด ํ˜ธ์ถœ๋  ํ•จ์ˆ˜
            person2 = JSON.parse(result);
            alert(result);           // result๋Š” ์„œ๋ฒ„๊ฐ€ ์ „์†กํ•œ ๋ฐ์ดํ„ฐ
        },
        error: function () {         // ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ํ˜ธ์ถœ๋  ํ•จ์ˆ˜
            alert("error")
        }
    });
});
jQuery.ajax()
https://api.jquery.com/jquery.ajax/#jQuery-ajax-url-settings

์‹ค์Šต

SimpleRestController.java

@Controller
public class SimpleRestController {
    @GetMapping("/ajax")
    public String ajax() {
        return "ajax";
    }

    @PostMapping("/send")
    @ResponseBody
    public Person test(@RequestBody Person p) {
        System.out.println("p = " + p);
        p.setName("ABC");
        p.setAge(p.getAge() + 10);

        return p;
    }
}

Person.java

public class Person {
    private String name;
    private String age;

    public Person() {}

    public Person(String name, String age) {
        this.name = name;
        this.age = age;
    }
	
		// getter, setter, toString() ์ƒ์„ฑ
}

ajax.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="https://code.jquery.com/jquery-1.11.3.js"></script>
</head>
<body>
<h2>{name:"abc", age:10}</h2>
<button id="sendBtn" type="button">SEND</button>
<h2>Data From Server :</h2>
<div id="data"></div>
<script>
    $(document).ready(function () {
        let person = {name: "abc", age: 10};
        let person2 = {};

        $("#sendBtn").click(function () {
            $.ajax({
                type: 'POST',       // ์š”์ฒญ ๋ฉ”์„œ๋“œ
                url: '/ch4/send',  // ์š”์ฒญ URI
                headers: {"content-type": "application/json"}, // ์š”์ฒญ ํ—ค๋”
                dataType: 'text', // ์ „์†ก๋ฐ›์„ ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…
                data: JSON.stringify(person),  // ์„œ๋ฒ„๋กœ ์ „์†กํ•  ๋ฐ์ดํ„ฐ. stringify()๋กœ ์ง๋ ฌํ™” ํ•„์š”.
                success: function (result) {
                    person2 = JSON.parse(result);    // ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์‘๋‹ต์ด ๋„์ฐฉํ•˜๋ฉด ํ˜ธ์ถœ๋  ํ•จ์ˆ˜
                    alert("received=" + result);       // result๋Š” ์„œ๋ฒ„๊ฐ€ ์ „์†กํ•œ ๋ฐ์ดํ„ฐ
                    $("#data").html("name=" + person2.name + ", age=" + person2.age);
                },
                error: function () {
                    alert("error")
                } // ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ํ˜ธ์ถœ๋  ํ•จ์ˆ˜
            }); // $.ajax()

            alert("the request is sent")
        });
    });
</script>
</body>
</html>

send ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด, {name : โ€œabcโ€, age : 10} ๊ฐ์ฒด๊ฐ€ stringify๋˜์–ด์„œ(๋ฌธ์ž์—ด๋กœ ์ง๋ ฌํ™”.) ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์ง„๋‹ค. ๊ทธ๋Ÿผ ์„œ๋ฒ„๊ฐ€ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค์Œ์— ๋‹ค์‹œ ์ค€๋‹ค.

@PostMapping("/send")
@ResponseBody
public Person test(@RequestBody Person p) {
    System.out.println("p = " + p);
    p.setName("ABC");
    p.setAge(p.getAge() + 10);

    return p;
}

JSON(Javascript ๊ฐ์ฒด)๋กœ ๋ณด๋‚ธ ๊ฒŒ ์ž๋ฐ” ๊ฐ์ฒด๋กœ ๋ฐ”๋€Œ์–ด์„œ ๋“ค์–ด์˜จ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋ฆ„๊ณผ ๋‚˜์ด๋ฅผ ๋ฐ”๊ฟ”์„œ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

success: function (result) {
    person2 = JSON.parse(result);    // ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์‘๋‹ต์ด ๋„์ฐฉํ•˜๋ฉด ํ˜ธ์ถœ๋  ํ•จ์ˆ˜
    alert("received=" + result);       // result๋Š” ์„œ๋ฒ„๊ฐ€ ์ „์†กํ•œ ๋ฐ์ดํ„ฐ
    $("#data").html("name=" + person2.name + ", age=" + person2.age);
},

๊ทธ๋Ÿผ sucess์—์„œ ๋ฐ˜ํ™˜ํ•œ ๊ฑธ result๋กœ ๋ฐ›๋Š”๋‹ค. ๊ทธ๋ฆฌ๊ณ  parseํ•ด์„œ ์ถœ๋ ฅํ•จ

์ง€๊ธˆ ์˜ˆ์ƒ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ์ด์œ ๋Š” Jackson Databind๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•˜๋‚˜ ํ•„์š”ํ•˜๋‹ค.

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.15.1</version>
</dependency>

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€ ํ›„ ์‘๋‹ต์ด ์„ฑ๊ณต์ ์œผ๋กœ ์˜จ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

5. Ajax ์š”์ฒญ๊ณผ ์‘๋‹ต ๊ณผ์ •

  1. ์„œ๋ฒ„์—๊ฒŒ JS ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ์ง์ ‘ ์ „์†กํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์ „์†กํ•œ๋‹ค.(HTTP๊ฐ€ ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ํ”„๋กœํ† ์ฝœ์ด๊ธฐ ๋•Œ๋ฌธ)
  1. POST๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด, ์„œ๋ฒ„๋Š” ๋ฐ›์€ ๋ฌธ์ž์—ด์„ jackson-databind๋ฅผ ์‚ฌ์šฉํ•ด ์ž๋ฐ” ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ด์„œ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•œ๋‹ค.

    ์ด๋•Œ jackson-databind๋Š” ๋ฌธ์ž์—ด์„ ์ž๋ฐ” ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

  1. test ๋ฉ”์„œ๋“œ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ณ  ์„œ๋ฒ„๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ด์„œ ์‘๋‹ต์„ ์ œ๊ณตํ•˜๋Š”๋ฐ, jackson-databind๊ฐ€ ์ž๋ฐ” ๊ฐ์ฒด๋ฅผ JSON ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์ „์†กํ•œ๋‹ค.
  1. ํด๋ผ์ด์–ธํŠธ๋Š” ์ด๋ฅผ ๋ฐ›์•„์„œ JSON.parse()๋ฅผ ์‚ฌ์šฉํ•ด Javascript ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.

์ด ๊ณผ์ •์€ ๋ณ„๊ฑฐ์—†๊ณ  ์ „์†กํ•˜๋ ค๋ฉด ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์ฃผ๊ณ  ๋ฐ›์•„์•ผ ํ•ด์„œ ์ด๋ ‡๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

JS ๊ฐ์ฒด๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊ฟ”์„œ ์ฃผ๋ฉด jackson-databind ๊ฐ€ Java ๊ฐ์ฒด๋กœ ๋ฐ”๊ฟ”์ฃผ๊ณ ,

์ž‘์—…์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ฒด๋กœ ์ฃผ๋ฉด jackson-databind๊ฐ€ ์•Œ์•„์„œ JSON ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊ฟ”์ฃผ๊ณ ,

ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” JSON.parse()ํ•ด์„œ JS ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ด ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.


@PostMapping("/send")
@ResponseBody
public Person test(@RequestBody Person p) {
		System.out.println("p = " + p);
		p.setName("ABC");
		p.setAge(p.getAge() + 10);

		return p;
}

์ด๋•Œ, ๊ทธ๋ƒฅ ์ž๋™์œผ๋กœ ๋ณ€ํ™˜์„ ํ•ด์ฃผ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ @RequestBody์™€ @ResponseBody ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

@RequestBody ๊ฐ€ ์žˆ์–ด์•ผ ์š”์ฒญ ๋‚ด์šฉ์ด ์ž๋ฐ” ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜๋œ๋‹ค.

์‘๋‹ต ์‹œ์—๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ @ResponseBody ์–ด๋…ธํ…Œ์ด์…˜์„ ์จ์•ผ ์‘๋‹ต์˜ ๋ฐ”๋””๋กœ ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค.

๐Ÿ’ก๊ฒฐ๋ก  : JSON์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ  ๋ฐ›์„ ๋•Œ๋Š” ๋ฐ›์„ ๊ฐ์ฒด ์•ž์— @RequestBody ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์•ผ ํ•˜๊ณ , ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ๋Š” @ResponseBody ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

6. @RestController

  • @ResponseBody ๋Œ€์‹ , ํด๋ž˜์Šค์— @RestController ์‚ฌ์šฉ ๊ฐ€๋Šฅ

    JSON ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ๋Š” ๋ฉ”์„œ๋“œ ์•ž์— @ResponseBody๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด๊ฑธ ๋ฉ”์„œ๋“œ๋งˆ๋‹ค ์ผ์ผํžˆ ๋‹ค ๋ถ™์ด๊ธฐ๊ฐ€ ๊ท€์ฐฎ์œผ๋‹ˆ ํด๋ž˜์Šค ์•ž์— @RestController๋ฅผ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ๋ฐ”๋€Œ์—ˆ๋‹ค.

    @RestControllerย ์• ๋„ˆํ…Œ์ด์…˜์ด ๋ถ™์€ ํด๋ž˜์Šค ๋‚ด์˜ ๋ฉ”์„œ๋“œ๋“ค์€ ์ž๋™์œผ๋กœย @ResponseBody๊ฐ€ ๋ถ™์€ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ๋œ๋‹ค.

7. REST๋ž€?

  • Roy Fielding์ด ์ œ์•ˆํ•œ ์›น์„œ๋น„์Šค ๋””์ž์ธ ์•„ํ‚คํ…์ณ ์ ‘๊ทผ ๋ฐฉ์‹
  • ํ”„๋กœํ† ์ฝœ์— ๋…๋ฆฝ์ ์ด๋ฉฐ, ์ฃผ๋กœ HTTP๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ตฌํ˜„
  • ๋ฆฌ์†Œ์Šค ์ค‘์‹ฌ์˜ API ๋””์ž์ธ - HTTP ๋ฉ”์„œ๋“œ๋กœ ์ˆ˜ํ–‰ํ•  ์ž‘์—…์„ ์ •์˜

๋ฆฌ์†Œ์ŠคPOSTGETPUTDELETE
/customers์ƒˆ ๊ณ ๊ฐ ๋งŒ๋“ค๊ธฐ๋ชจ๋“  ๊ณ ๊ฐ ๊ฒ€์ƒ‰๊ณ ๊ฐ ๋Œ€๋Ÿ‰ ์—…๋ฐ์ดํŠธ๋ชจ๋“  ๊ณ ๊ฐ์ œ๊ฑฐ
/customers/1Error๊ณ ๊ฐ 1์— ๋Œ€ํ•œ ์„ธ๋ถ€ ์ •๋ณด ๊ฒ€์ƒ‰๊ณ ๊ฐ 1์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๊ณ ๊ฐ 1์˜ ์„ธ๋ถ€ ์ •๋ณด ์—…๋ฐ์ดํŠธ๊ณ ๊ฐ 1 ์ œ๊ฑฐ
/customers/1/orders๊ณ ๊ฐ 1์— ๋Œ€ํ•œ ์ƒˆ์ฃผ๋ฌธ ๋งŒ๋“ค๊ธฐ๊ณ ๊ฐ 1์— ๋Œ€ํ•œ ๋ชจ๋“  ์ฃผ๋ฌธ ๊ฒ€์ƒ‰๊ณ ๊ฐ 1์˜ ์ฃผ๋ฌธ ๋Œ€๋Ÿ‰ ์—…๋ฐ์ดํŠธ๊ณ ๊ฐ 1์˜ ๋ชจ๋“  ์ฃผ๋ฌธ ์ œ๊ฑฐ

REST API๋Š” Representational State Transfer์˜ ์•ฝ์ž๋กœ, URI๋ฅผ ๋ฆฌ์†Œ์Šค ์ค‘์‹ฌ์ ์œผ๋กœ ์„ค๊ณ„ํ•˜๊ณ  HTTP ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ˆ˜ํ–‰ํ•  ์ž‘์—…์„ ์ •์˜ํ•œ๋‹ค.

HTTP ๋ฉ”์„œ๋“œ์˜ ์ข…๋ฅ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์ข…๋ฅ˜์„ค๋ช…
GET์ฝ๊ธฐ(r)
POST์“ฐ๊ธฐ(w)
PUT๋ฆฌ์†Œ์Šค ์—…๋ฐ์ดํŠธ / ํŒŒ์ผ, ๋ฆฌ์†Œ์Šค ์—…๋กœ๋“œ
DELETE๋ฆฌ์†Œ์Šค ์‚ญ์ œ / ํŒŒ์ผ, ๋ฆฌ์†Œ์Šค ์‚ญ์ œ
PATCH์ผ๋ถ€ ์ˆ˜์ •

์ด๋ ‡๊ฒŒ ๋งŽ์€ HTTP ๋ฉ”์„œ๋“œ๋“ค์ด ์žˆ๋Š”๋ฐ, GET๊ณผ POST๋งŒ ์“ฐ์ง€๋ง๊ณ  ๋ฉ”์„œ๋“œ๋ฅผ ์ž˜ ํ™œ์šฉํ•˜๊ณ  ๋Œ€์‹ ์— ๋ฆฌ์†Œ์Šค ๋ถ€๋ถ„์„ ์‹ฌํ”Œํ•˜๊ฒŒ ๊ฐ€์ž๋Š” ์˜๋„์ด๋‹ค. ์‹ฌํ”Œํ•˜๊ฒŒ ๊ฐ€๋ฉด ์œ ์ง€๋ณด์ˆ˜๋„ ์‰ฝ๊ณ  ์•Œ์•„๋ณด๊ธฐ๋„ ์‰ฌ์›€.

๋ฆฌ์†Œ์Šค๋Š” ๋‹ค ๋ช…์‚ฌ๋กœ๋งŒ ์ด๋ฃจ์–ด์ ธ์žˆ๋‹ค. ๋Œ€์‹  ๋ฉ”์„œ๋“œ๋กœ ์ˆ˜ํ–‰ํ•  ์ž‘์—…์„ ์ •์˜ํ•˜๋Š”๊ฒŒ ๋ฐ”๋กœ REST ๋ฐฉ์‹์˜ API ์„ค๊ณ„๋‹ค.

/customers

s๊ฐ€ ๋ถ™์–ด์„œ ๋ฆฌ์†Œ์Šค๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ์žˆ๋Š” ๊ฑธ ์˜๋ฏธํ•œ๋‹ค.

POST์ธ ๊ฒฝ์šฐ ์“ฐ๊ธฐ, ์ƒˆ๋กœ์šด ๊ณ ๊ฐ ์ •๋ณด๋ฅผ ์“ด๋‹ค.

GET์€ ์ฝ๊ธฐ. ๋ชจ๋“  ๊ณ ๊ฐ์„ ๊ฒ€์ƒ‰ํ•œ๋‹ค.

/customers/1

๊ณ ๊ฐ1์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป์–ด์˜จ๋‹ค.

PUT์ด๋‚˜ PATCH์ธ ๊ฒฝ์šฐ์—๋Š” ๊ณ ๊ฐ 1๋ฒˆ์˜ ์ •๋ณด๋ฅผ UPDATEํ•œ๋‹ค.

์ด๋Ÿฐ์‹์œผ๋กœ ์›น ์„œ๋น„์Šค์— ๋Œ€ํ•œ API๋ฅผ ๋””์ž์ธํ•˜๋Š” ๊ฑธ REST ๋ฐฉ์‹์ด๋ผ๊ณ  ํ•œ๋‹ค.

์›น API ๋””์ž์ธ ๋ชจ๋ฒ” ์‚ฌ๋ก€ - Azure Architecture Center
ํ”Œ๋žซํผ ๋…๋ฆฝ์„ฑ๊ณผ ์„œ๋น„์Šค ์ง„ํ™”๋ฅผ ์ง€์›ํ•˜๋Š” ์›น API ์„ค๊ณ„๋ฅผ ์œ„ํ•œ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ์•Œ์•„๋ด…๋‹ˆ๋‹ค.
https://learn.microsoft.com/ko-kr/azure/architecture/best-practices/api-design

8. REST API๋ž€?

โœ”๏ธ
Representational State Tansfer API โ€“ REST๊ทœ์•ฝ์„ ์ค€์ˆ˜ํ•˜๋Š” API

REST is a set of architectural constraints, not a protocol or a standard.
API developers can implement REST in a variety of ways.

API(Application Programming Interface)
An API is
a set of definitions and protocols for building and integrating application software.
Itโ€™s sometimes referred to as
a contract between an information provider and an information userโ€” establishing the content required from the consumer (the call) and the content required by the producer (the response)

What is a REST API?
A REST API (also known as RESTful API) is an application programming interface that conforms to the constraints of REST architecture. REST stands for representational state transfer.
https://www.redhat.com/en/topics/api/what-is-a-rest-api

๊ทœ์น™๋“ค์„ ์ •ํ•ด๋†“์€ ๊ฒŒ REST. REST์—์„œ ์ •ํ•œ ๊ทœ์น™์„ ์ž˜ ์ค€์ˆ˜ํ•˜๋Š” API๋ฅผ REST API๋ผ๊ณ  ํ•œ๋‹ค.

ํ”„๋กœํ† ์ฝœ์— ๋Œ€ํ•œ ํ‘œ์ค€์€ ์•„๋‹ˆ๊ณ  ๊ทœ์น™๋งŒ ์ •์˜ํ•ด๋‘๊ณ  ๊ทธ๊ฑธ ์ง€ํ‚ค๋ฉด REST API๋ผ๊ณ  ํ•œ๋‹ค. ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ฐœ๋ฐœ์ž๋“ค์ด REST API๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

โ“API๋ž€?

API๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ธํ„ฐํŽ˜์ด์Šค๋‹ค.

์ธํ„ฐํŽ˜์ด์Šค๋Š” ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ชฝ๊ณผ ์‚ฌ์šฉํ•˜๋Š” ์ชฝ ์‚ฌ์ด์˜ ์•ฝ์†์ด๋‹ค.

์›น์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ์ชฝ์—์„œ API๋ฅผ ์„ค๊ณ„ํ•ด์„œ ์˜คํ”ˆํ•˜๋ฉด, ์‚ฌ์šฉ์ž๋“ค์ด ๊ทธ๊ฑธ ๋ณด๊ณ ์„œ ์‚ฌ์šฉํ•œ๋‹ค.

์ด ๊ทœ์น™๋“ค์„ ๋‹ค์ง€ํ‚ค๋Š” ๊ฒŒ ์‰ฝ์ง€๋Š” ์•Š๋‹ค. ์ƒํ™ฉ์— ๋งž๊ฒŒ, ์„œ๋น„์Šค์˜ ๋ชฉ์ ์— ๋งž๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ๋žŒ์ด ํŽธ๋ฆฌํ•˜๊ฒŒ ์„ค๊ณ„๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค. REST API๊ฐ€ ๋งž๋ƒ ์•„๋‹ˆ๋ƒ๋Š” ๋‘˜์งธ์˜ ๋ฌธ์ œ์ด๋‹ค.

9. RESTful API์˜ ์„ค๊ณ„

REST ๋ฐฉ์‹์˜ API๋ฅผ ์ž˜ ์ง€ํ‚จ ๊ฑธ RESTful API๋ผ๊ณ  ํ•œ๋‹ค.

์•„๋ž˜๋Š” ์„ค๊ณ„์˜ ์˜ˆ์‹œ์ด๋‹ค.

๋Œ“๊ธ€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๊ฑด๋ฐ ๋Œ“๊ธ€ ๊ธฐ๋Šฅ์€ ํŽ˜์ด์ง€ ์ „์ฒด๊ฐ€ ๋ฐ”๋€Œ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์ผ๋ถ€๋งŒ ๋ฐ”๋€Œ๋ฉด ๋œ๋‹ค. ๊ทธ๋ž˜์„œ ๋Œ“๊ธ€ ๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœํ•  ๋•Œ RestController์™€ REST ๋ฐฉ์‹์˜ API ์„ค๊ณ„๋ฅผ ์ ์šฉํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

์ž‘์—…URIHTTP ๋ฉ”์„œ๋“œ์„ค๋ช…
์ฝ๊ธฐ/comment/read?cno=๋ฒˆํ˜ธGET์ง€์ •๋œ ๋ฒˆํ˜ธ์˜ ๋Œ“๊ธ€์„ ๋ณด์—ฌ์ค€๋‹ค.
์“ฐ๊ธฐ/comment/writePOST์ž‘์„ฑํ•œ ๊ฒŒ์‹œ๋ฌผ์„ ์ €์žฅํ•œ๋‹ค.
์‚ญ์ œ/comment/removePOST๋Œ“๊ธ€์„ ์‚ญ์ œํ•œ๋‹ค.
์ˆ˜์ •/comment/modifyPOST์ˆ˜์ •๋œ ๊ฒŒ์‹œ๋ฌผ์„ ์ €์žฅํ•œ๋‹ค.
์ž‘์—…URIHTTP ๋ฉ”์„œ๋“œ์„ค๋ช…
์ฝ๊ธฐ/commentsGET๋ชจ๋“  ๋Œ“๊ธ€์„ ๋ณด์—ฌ์ค€๋‹ค.
์ฝ๊ธฐ/comments/{cno}GET์ง€์ •๋œ ๋ฒˆํ˜ธ์˜ ๋Œ“๊ธ€์„ ๋ณด์—ฌ์ค€๋‹ค.
์“ฐ๊ธฐ/commentsPOST์ƒˆ๋กœ์šด ๋Œ“๊ธ€์„ ๋ณด์—ฌ์ค€๋‹ค.
์‚ญ์ œ/comments/{cno}DELETE์ง€์ •๋œ ๋ฒˆํ˜ธ์˜ ๋Œ“๊ธ€์„ ์‚ญ์ œํ•œ๋‹ค.
์ˆ˜์ •/comments/{cno}PUT / PATCH์ˆ˜์ •๋œ ๋Œ“๊ธ€์„ ์ €์žฅํ•œ๋‹ค.

URI์— ๋™์‚ฌ๋ฅผ ๋„ฃ์ง€์•Š๊ณ  HTTP ๋ฉ”์„œ๋“œ๊ฐ€ ๋‹ด๋‹นํ•˜๊ฒŒ ํ–ˆ๋‹ค. URI์—๋Š” ๋ช…์‚ฌ๋งŒ ๋‚จ๊ฒจ์„œ ์‹ฌํ”Œํ•ด์กŒ๋‹ค.

RESTfulํ•˜๊ฒŒ ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ์ด ์‰ฝ์ง€๋Š” ์•Š๋‹ค. ์ด๋ฒˆ์—๋Š” ๋ง›๋งŒ ๋ณด๋Š” ๊ฒƒ.. ์–˜๊ฐ€ ๋ญ”์ง€ ์•„๋Š” ์ •๋„๋ฉด๋จ

10~13. ๋Œ“๊ธ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„

โญย 1. DAO ์ž‘์„ฑ

๋Œ“๊ธ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์ˆœ์„œ

  1. DB ํ…Œ์ด๋ธ” ์ƒ์„ฑ
  1. Mapper XML ์ž‘์„ฑ
  1. DAO ์ž‘์„ฑ & ํ…Œ์ŠคํŠธ
  1. Service ์ž‘์„ฑ & ํ…Œ์ŠคํŠธ
  1. ์ปจํŠธ๋กค๋Ÿฌ ์ž‘์„ฑ & ํ…Œ์ŠคํŠธ
  1. ๋ทฐ(UI) ์ž‘์„ฑ & ํ…Œ์ŠคํŠธ

์‹ค์Šต

  • CommentDTO
    package com.fastcampus.ch4.domain;
    
    import java.util.Date;
    import java.util.Objects;
    
    public class CommentDto {
        private Integer cno;
        private Integer bno;
        private Integer pcno;
        private String comment;
        private String commenter;
        private Date regDate;
        private Date upDate;
    
        public CommentDto() {}
    
        public CommentDto(Integer bno, Integer pcno, String comment, String commenter) {
            this.bno = bno;
            this.pcno = pcno;
            this.comment = comment;
            this.commenter = commenter;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            CommentDto that = (CommentDto) o;
            return Objects.equals(cno, that.cno) && Objects.equals(bno, that.bno) && Objects.equals(pcno, that.pcno) && Objects.equals(comment, that.comment) && Objects.equals(commenter, that.commenter);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(cno, bno, pcno, comment, commenter);
        }
    
    		// getter, setter, toString() ์ƒ๋žต.
    }
  1. comment ํ…Œ์ด๋ธ” ์ƒ์„ฑ
  1. commentMapper.xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.fastcampus.ch4.dao.CommentMapper">
        <delete id="deleteAll" parameterType="int">
            DELETE
            FROM comment
            WHERE bno = #{bno}
        </delete>
    
        <select id="count" parameterType="int" resultType="int">
            SELECT COUNT(*)
            FROM comment
            WHERE bno = #{bno}
        </select>
    
        <delete id="delete" parameterType="map">
            DELETE
            FROM comment
            WHERE cno = #{cno}
              AND commenter = #{commenter}
        </delete>
    
        <insert id="insert" parameterType="CommentDto">
            INSERT INTO comment
                (bno, pcno, comment, commenter, reg_date, up_date)
            VALUES (#{bno}, #{pcno}, #{comment}, #{commenter}, NOW(), NOW())
        </insert>
    
        <select id="selectAll" parameterType="int" resultType="CommentDto">
            SELECT cno, bno, pcno, comment, commenter, reg_date, up_date
            FROM comment
            WHERE bno = #{bno}
            ORDER BY reg_date ASC, cno ASC
        </select>
    
        <select id="select" parameterType="int" resultType="CommentDto">
            SELECT cno, bno, pcno, comment, commenter, reg_date, up_date
            FROM comment
            WHERE cno = #{cno}
        </select>
    
        <update id="update" parameterType="CommentDto">
            UPDATE comment
            SET comment = #{comment}
              , up_date = NOW()
            WHERE cno = #{cno}
              AND commenter = #{commenter}
        </update>
    </mapper>
  1. CommentDAO.java

    commentMapper.xml์— ์žˆ๋Š” SQL์„ ํ˜ธ์ถœํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ํ•˜๋‚˜์”ฉ ์ž‘์„ฑํ•œ๋‹ค.

    cno๋Š” ์ž๋™์ฆ๊ฐ€๋กœ ์ƒ์„ฑํ•ด๋‘์—ˆ์œผ๋‹ˆ ์ œ์™ธํ•  ๊ฒƒ

    +) ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ถ”์ถœ๊นŒ์ง€.

  1. mybatis-config.xml

    typeAlias ๋“ฑ๋ก

    <typeAlias alias="CommentDto" type="com.fastcampus.ch4.domain.CommentDto"/>
  1. CommentDaoImplTest
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/root-context.xml"})
    public class CommentDaoImplTest {
        @Autowired
        CommentDao commentDao;
    
        @Test
        public void count() throws Exception {
            commentDao.deleteAll(1);
            assertTrue(commentDao.count(1) == 0);
        }
    
        @Test
        public void delete() throws Exception {
            commentDao.deleteAll(1);
            CommentDto commentDto = new CommentDto(1, 0, "comment", "asdf");
            assertTrue(commentDao.insert(commentDto) == 1);
            assertTrue(commentDao.count(1) == 1);
        }
    
        @Test
        public void insert() throws Exception {
            commentDao.deleteAll(1);
            CommentDto commentDto = new CommentDto(1, 0, "comment", "asdf");
            assertTrue(commentDao.insert(commentDto) == 1);
            assertTrue(commentDao.count(1) == 1);
    
            commentDto = new CommentDto(1, 0, "comment", "asdf");
            assertTrue(commentDao.insert(commentDto) == 1);
            assertTrue(commentDao.count(1) == 2);
        }
    
        @Test
        public void selectAll() throws Exception {
            commentDao.deleteAll(1);
            CommentDto commentDto = new CommentDto(1, 0, "comment", "asdf");
            assertTrue(commentDao.insert(commentDto) == 1);
            assertTrue(commentDao.count(1) == 1);
    
            List<CommentDto> list = commentDao.selectAll(1);
            assertTrue(list.size() == 1);
    
            commentDto = new CommentDto(1, 0, "comment", "asdf");
            assertTrue(commentDao.insert(commentDto) == 1);
            assertTrue(commentDao.count(1) == 2);
    
            list = commentDao.selectAll(1);
            assertTrue(list.size() == 2);
        }
    
        @Test
        public void select() throws Exception {
            commentDao.deleteAll(1);
            CommentDto commentDto = new CommentDto(1, 0, "comment", "asdf");
            assertTrue(commentDao.insert(commentDto) == 1);
            assertTrue(commentDao.count(1) == 1);
    
            List<CommentDto> list = commentDao.selectAll(1);
            String comment = list.get(0).getComment();
            String commenter = list.get(0).getCommenter();
            assertTrue(comment.equals(commentDto.getComment()));
            assertTrue(commenter.equals(commentDto.getCommenter()));
        }
    
        @Test
        public void update() throws Exception {
            commentDao.deleteAll(1);
            CommentDto commentDto = new CommentDto(1, 0, "comment", "asdf");
            assertTrue(commentDao.insert(commentDto) == 1);
            assertTrue(commentDao.count(1) == 1);
    
            List<CommentDto> list = commentDao.selectAll(1);
            commentDto.setCno(list.get(0).getCno());
            commentDto.setComment("comment2");
            assertTrue(commentDao.update(commentDto) == 1);
    
            list = commentDao.selectAll(1);
            String comment = list.get(0).getComment();
            String commenter = list.get(0).getCommenter();
            assertTrue(comment.equals(commentDto.getComment()));
            assertTrue(commenter.equals(commentDto.getCommenter()));
        }
    }
  1. CommentService

    CommentService๋Š” BoardDAO์™€ CommentDAO ๋‘˜ ๋‹ค ์ฃผ์ž…๋ฐ›์•„์•ผ ํ•œ๋‹ค.

    ๋Œ“๊ธ€์ด ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋˜๋ฉด BoardDAO์—๊ฒŒ๋„ ์˜ํ–ฅ์ด ๊ฐ€๊ธฐ ๋•Œ๋ฌธ. board ํ…Œ์ด๋ธ”์„ ๋ณด๋ฉด ๋Œ“๊ธ€์ด ๋ช‡ ๊ฐœ ๋‹ฌ๋ ธ๋Š”์ง€ ๋ณด์—ฌ์ฃผ๋Š” comment_cnt๋ผ๋Š” ์ปฌ๋Ÿผ์ด ์žˆ๋‹ค.

    โญย ์ด๋ฅผ ์œ„ํ•ด ์•„๋ž˜ ๋‘๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

    1. ํ•„๋“œ ์ฃผ์ž… ๋ฐฉ์‹

    2. ์ƒ์„ฑ์ž ์ฃผ์ž… ๋ฐฉ์‹

    @Autowired
    BoardDao boardDao;
    @Autowired
    CommentDao commentDao;

    BoardDao boardDao;
    CommentDao commentDao;
    
    @Autowired
    public CommentServiceImpl(CommentDao commentDao, BoardDao boardDao) {
        this.commentDao = commentDao;
        this.boardDao = boardDao;
    }

    โญย ํ•„๋“œ ์ฃผ์ž… ๋ฐฉ์‹๋ณด๋‹ค๋Š” ์ƒ์„ฑ์ž ์ฃผ์ž… ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์ž.

    ํ•„๋“œ ์ฃผ์ž…์€ ๊ฐ„๋‹จํ•˜์ง€๋งŒ, ์ƒ์„ฑ์ž ์ฃผ์ž…์„ ์‚ฌ์šฉํ•˜๋ฉด ์˜์กด์„ฑ์ด ๋ˆ„๋ฝ๋˜์—ˆ์„ ๊ฒฝ์šฐ ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค๋งŒ ์ƒ์„ฑ์ž ์ฃผ์ž…์€ ํด๋ž˜์Šค์— ์ƒ์„ฑ์ž๊ฐ€ ํ•˜๋‚˜๋งŒ ์žˆ์„ ๋•Œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋Š” ์ ์— ์ฃผ์˜ํ•˜์ž.

    • ์ถ”๊ฐ€ ์„ค๋ช…?

      ํ•„๋“œ์— ์ง์ ‘ @Autowired๋ฅผ ๋ถ™์ผ ๊ฒฝ์šฐ, ํ•˜๋‚˜์—๋Š” ๋ถ™์˜€๋Š”๋ฐ ํ•˜๋‚˜๋Š” ์•ˆ ๋ถ™์ด๋Š” ์‹ค์ˆ˜๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

      ์ƒ์„ฑ์ž๋กœ ์ฃผ์ž…๋ฐ›์œผ๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ CommentDaoImpl์—์„œ @Repository๋ฅผ ์ง€์šธ ๊ฒฝ์šฐ ๊ฐ์ฒด ์ƒ์„ฑ์ด ์•ˆ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ ๊ณณ์—์„œ ์ฃผ์ž…์„ ๋ชป ๋ฐ›๋Š”๋‹ค.

      ์ด๋ ‡๊ฒŒ ์ฃผ์ž…์ด ์•ˆ ๋  ๊ฒฝ์šฐ์— ์ปดํŒŒ์ผ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ด๋Ÿฐ ์žฅ์  ๋•Œ๋ฌธ์— ์ƒ์„ฑ์ž ์ฃผ์ž…์„ ์‚ฌ์šฉํ•œ๋‹ค.


    remove ๋ฉ”์„œ๋“œ๋ฅผ ์ฃผ๋ชฉํ•˜์ž.

    ๋Œ“๊ธ€ ์‚ญ์ œ ์‹œ์— board์˜ comment ์ˆ˜๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ๋Œ“๊ธ€์„ ์ง€์›Œ์•ผ ํ•œ๋‹ค.

    ๋‘ ๊ฐ€์ง€ ์ž‘์—… ๋ชจ๋‘ ์„ฑ๊ณตํ•ด์•ผ ํ•˜๋ฏ€๋กœ ํŠธ๋žœ์žญ์…˜์„ ๊ฑธ์—ˆ๊ณ  Exception๊ณผ ๊ทธ ์ž์†๋“ค์˜ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋กค๋ฐฑํ•˜๋„๋ก โ€˜rollbackForโ€™๋ฅผ ์ง€์ •ํ–ˆ๋‹ค.

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int remove(Integer cno, Integer bno, String commenter) throws Exception {
        int rowCnt = boardDao.updateCommentCnt(bno, -1);
        System.out.println("updateCommentCnt - rowCnt = " + rowCnt);
    //        throw new Exception("test"); // ์‹ค์ œ๋กœ ๋กค๋ฐฑ์ด๋˜๋Š”์ง€ ๋ณด๋ ค๊ณ  ํ…Œ์ŠคํŠธ ์šฉ๋„๋กœ ๋„ฃ์€ ๊ฒƒ
        rowCnt = commentDao.delete(cno, commenter);
        System.out.println("rowCnt = " + rowCnt);
        return rowCnt;
    }
  1. CommentServiceImplTest

    ์ผ๋ถ€๋Ÿฌ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ  Tx๊ฐ€ ์ทจ์†Œ๋˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.

    package com.fastcampus.ch4.service;
    
    import com.fastcampus.ch4.dao.*;
    import com.fastcampus.ch4.domain.*;
    import org.junit.*;
    import org.junit.runner.*;
    import org.springframework.beans.factory.annotation.*;
    import org.springframework.test.context.*;
    import org.springframework.test.context.junit4.*;
    
    import java.util.*;
    
    import static org.junit.Assert.*;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/root-context.xml"})
    public class CommentServiceImplTest {
        @Autowired
        CommentService commentService;
        @Autowired
        CommentDao commentDao;
        @Autowired
        BoardDao boardDao;
    
        @Test
        public void remove() throws Exception {
            boardDao.deleteAll();
    
            BoardDto boardDto = new BoardDto("hello", "hello", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
            Integer bno = boardDao.selectAll().get(0).getBno();
            System.out.println("bno = " + bno);
    
            commentDao.deleteAll(bno);
            CommentDto commentDto = new CommentDto(bno, 0, "hi", "qwer");
    
            assertTrue(boardDao.select(bno).getComment_cnt() == 0);
            assertTrue(commentService.write(commentDto) == 1);
            assertTrue(boardDao.select(bno).getComment_cnt() == 1);
    
            Integer cno = commentDao.selectAll(bno).get(0).getCno();
    
            // ์ผ๋ถ€๋Ÿฌ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ณ  Tx๊ฐ€ ์ทจ์†Œ๋˜๋Š”์ง€ ํ™•์ธํ•ด์•ผ.
            int rowCnt = commentService.remove(cno, bno, commentDto.getCommenter());
            assertTrue(rowCnt == 1);
            assertTrue(boardDao.select(bno).getComment_cnt() == 0);
        }
    
        @Test
        public void write() throws Exception {
            boardDao.deleteAll();
    
            BoardDto boardDto = new BoardDto("hello", "hello", "asdf");
            assertTrue(boardDao.insert(boardDto) == 1);
            Integer bno = boardDao.selectAll().get(0).getBno();
            System.out.println("bno = " + bno);
    
            commentDao.deleteAll(bno);
            CommentDto commentDto = new CommentDto(bno, 0, "hi", "qwer");
    
            assertTrue(boardDao.select(bno).getComment_cnt() == 0);
            assertTrue(commentService.write(commentDto) == 1);
    
            Integer cno = commentDao.selectAll(bno).get(0).getCno();
            assertTrue(boardDao.select(bno).getComment_cnt() == 1);
        }
    }

๊ฐœ๋ฐœ ์‹œ์— ์–ด๋–ค ์ˆœ์„œ๋กœ ๊ฐœ๋ฐœํ•˜๋Š”์ง€ ์ž˜ ์•Œ์•„๋‘ฌ์•ผ ํ•œ๋‹ค.

2. Controller ์ž‘์„ฑ

list : ์ง€์ •๋œ ๊ฒŒ์‹œ๋ฌผ์˜ ๋ชจ๋“  ๋Œ“๊ธ€์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฉ”์„œ๋“œ

@Controller
public class CommentController {
    @Autowired
    CommentService service;

    // ์ง€์ •๋œ ๊ฒŒ์‹œ๋ฌผ์˜ ๋ชจ๋“  ๋Œ“๊ธ€์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฉ”์„œ๋“œ
    @GetMapping("comments")
    @ResponseBody public List<CommentDto> list(Integer bno) {
        List<CommentDto> list = null;
        try {
            list = service.getList(bno);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }
}

๋Œ“๊ธ€์ด ์ •์ƒ์ ์œผ๋กœ ๊ฐ€์ ธ์™€์กŒ์„ ๋•Œ์˜ ์ƒํƒœ ์ฝ”๋“œ๋Š” 200. ์ •์ƒ์ ์œผ๋กœ ๋ฐ›์•„์˜จ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ๋ณด์ž.

์˜ˆ์™ธ ๋ฐœ์ƒํ•˜๋”๋ผ๋„ ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ 200์œผ๋กœ ๋ฐ˜ํ™˜๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Œ์—๋„ ๋ถˆํ•˜๊ณ  ์„ฑ๊ณตํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ผ ์ˆ˜ ์žˆ๋‹ค. ์›๋ž˜๋ผ๋ฉด ์„œ๋ฒ„์—์„œ ๋ฐœ์ƒํ•œ ์˜ค๋ฅ˜ ๋•Œ๋ฌธ์— 500๋ฒˆ๋Œ€ ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•œ๋‹ค.

// ์ง€์ •๋œ ๊ฒŒ์‹œ๋ฌผ์˜ ๋ชจ๋“  ๋Œ“๊ธ€์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฉ”์„œ๋“œ
@GetMapping("comments")
@ResponseBody public ResponseEntity<List<CommentDto>> list(Integer bno) {
    List<CommentDto> list = null;
    try {
        list = service.getList(bno);
        return new ResponseEntity<List<CommentDto>>(list, HttpStatus.OK); // 200
    } catch (Exception e) {
        e.printStackTrace();
        return new ResponseEntity<List<CommentDto>>(list, HttpStatus.BAD_REQUEST); // 400
    }
}

์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ResonseEntity๋ฅผ ์‚ฌ์šฉํ•ด ๊ฒฐ๊ณผ๋ฌผ๊ณผ ํ•จ๊ป˜ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ 500๊ณผ ๊ฐ™์€ ์ ์ ˆํ•œ ์ƒํƒœ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

+) ์—ฌ๊ธฐ์„œ ์š”์ฒญ ๋˜๋Š” ์‘๋‹ต ๋ฉ”์‹œ์ง€์˜ ์ „์†ก ๋Œ€์ƒ์„ ์—”ํ‹ฐํ‹ฐ๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

HTTP ์ƒํƒœ ์ฝ”๋“œ๋Š” ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์š”์ฒญ์˜ ๊ฒฐ๊ณผ๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ์ค‘์š”ํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค. ์ ์ ˆํ•œ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ๋Š” ์š”์ฒญ์ด ์„ฑ๊ณตํ–ˆ๋Š”์ง€, ์‹คํŒจํ–ˆ๋Š”์ง€, ์‹คํŒจํ–ˆ๋‹ค๋ฉด ๊ทธ ์ด์œ ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋”ฐ๋ผ์„œ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์€ ๊ฐœ๋ฐœ์—์„œ ์ค‘์š”ํ•œ ๋ถ€๋ถ„์ด๋‹ค.

์œ„์ฒ˜๋Ÿผ ResponseEntity๋ฅผ ์ด์šฉํ•ด์„œ ์ƒํ™ฉ์— ๋งž๋Š” ์ƒํƒœ์ฝ”๋“œ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

remove : ์ง€์ •๋œ ๋Œ“๊ธ€์„ ์‚ญ์ œํ•˜๋Š” ๋ฉ”์„œ๋“œ

// ์ง€์ •๋œ ๋Œ“๊ธ€์„ ์‚ญ์ œํ•˜๋Š” ๋ฉ”์„œ๋“œ
@DeleteMapping("/comments/{cno}") // /comments/1?bno=1085 <- ์‚ญ์ œํ•  ๋Œ“๊ธ€ ๋ฒˆํ˜ธ
@ResponseBody
public ResponseEntity<String> remove(@PathVariable Integer cno, Integer bno, HttpSession session) {
    String commenter = (String) session.getAttribute("id");
    try {
        int rowCnt = service.remove(cno, bno, commenter);

        if (rowCnt != 1)
            throw new Exception("Delete Failed");

        return new ResponseEntity<>("DEL_OK", HttpStatus.OK);
    } catch (Exception e) {
        e.printStackTrace();
        return new ResponseEntity<>("DEL_ERR", HttpStatus.BAD_REQUEST);
    }
}

์ฟผ๋ฆฌ ์ŠคํŠธ๋ง์ผ ๊ฒฝ์šฐ๋Š” ๊ทธ๋ƒฅ ์“ฐ๋ฉด ๋˜๋Š”๋ฐ, ์œ„ ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง์ด ์•„๋‹ˆ๋ผ URI์˜ ์ผ๋ถ€๋ฅผ ์ฝ์–ด์˜ค๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ณ€์ˆ˜ ์•ž์—๋‹ค @PathVariable์„ ์จ์•ผ ํ•œ๋‹ค.

[ Postman์„ ์ด์šฉํ•œ remove ๋ฉ”์„œ๋“œ์˜ ํ…Œ์ŠคํŠธ ]

์ž‘์„ฑํ•œ ๋ฉ”์„œ๋“œ๊ฐ€ ์ž˜ ๋™์ž‘ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์ž.

๋ธŒ๋ผ์šฐ์ €์—์„œ DELETE ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— Postman๊ณผ ๊ฐ™์€ API ํ…Œ์ŠคํŠธ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

comment๋Š” asdf๋กœ ํ•˜๋“œ์ฝ”๋”ฉ.

status๋„ 200์ด๊ณ , ์‘๋‹ต๋„ del_ok(๋ฌธ์ž์—ด)๋กœ ์˜จ๊ฑธ๋ด์„œ ์ž˜ ์ฒ˜๋ฆฌ๋œ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

+) ์ž˜ ์‚ญ์ œ๋˜์—ˆ๋Š”์ง€ db์—์„œ๋„ ํ™•์ธํ•ด๋ณด๊ธฐ

write : ๋Œ“๊ธ€์„ ๋“ฑ๋กํ•˜๋Š” ๋ฉ”์„œ๋“œ

delete์™€ ๋˜‘๊ฐ™์€๋ฐ DTO ๋ฐ›๋Š” ๋ถ€๋ถ„๋งŒ ๋‹ค๋ฅด๋‹ค.

// ๋Œ“๊ธ€์„ ๋“ฑ๋กํ•˜๋Š” ๋ฉ”์„œ๋“œ
@PostMapping("comments") // /comments?bno=1085 POST
@ResponseBody
public ResponseEntity<String> write(@RequestBody CommentDto commentDto, Integer bno, HttpSession session) {
//        String commenter = (String) session.getAttribute("id");
    String commenter = "asdf";
    commentDto.setCommenter(commenter);
    commentDto.setBno(bno);

    System.out.println("commentDto = " + commentDto);

    try {
        int rowCnt = service.write(commentDto);

        if (rowCnt != 1)
            throw new Exception("Write failed.");

        return new ResponseEntity<>("WRT_OK", HttpStatus.OK);

    } catch (Exception e) {
        e.printStackTrace();
        return new ResponseEntity<>("WRT_OK", HttpStatus.BAD_REQUEST);
    }
}

[ Postman์„ ์ด์šฉํ•œ write ๋ฉ”์„œ๋“œ์˜ ํ…Œ์ŠคํŠธ ]

ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„๋กœ JSON ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•  ๋•Œ, 'Content-type: application/json' ํ—ค๋”๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.

โ†’ ๋‚ด๊ฐ€ ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ(entity)์˜ Content-type์ด json์ด๋ผ๊ณ  ์•Œ๋ ค์ค˜์•ผ ํ•จ

modify : ๋Œ“๊ธ€์„ ์ˆ˜์ •ํ•˜๋Š” ๋ฉ”์„œ๋“œ

๋งˆ์ง€๋ง‰์œผ๋กœ ๋Œ“๊ธ€์„ ์ˆ˜์ •ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

// ๋Œ“๊ธ€์„ ์ˆ˜์ •ํ•˜๋Š” ๋ฉ”์„œ๋“œ
@PatchMapping("comments/{cno}") // /comments?bno=1085 POST
@ResponseBody
public ResponseEntity<String> modify(@PathVariable Integer cno, @RequestBody CommentDto commentDto) {
    commentDto.setCno(cno);
    System.out.println("commentDto = " + commentDto);

    try {
        int rowCnt = service.modify(commentDto);

        if (rowCnt != 1)
            throw new Exception("Write failed.");

        return new ResponseEntity<>("MOD_OK", HttpStatus.OK);

    } catch (Exception e) {
        e.printStackTrace();
        return new ResponseEntity<>("MOD_ERR", HttpStatus.BAD_REQUEST);
    }
}

์ง€๊ธˆ CommentController.java์— @ResponseBody ๊ฐ€ ๋ถ™์–ด์žˆ๋Š”๋ฐ, ์ด๊ฑธ ๋‹ค ์ง€์šฐ๊ณ , ํด๋ž˜์Šค ์•ž์— ๋ถ™์ผ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿผ ์ด ํด๋ž˜์Šค์˜ ๋ชจ๋“  ๋ฉ”์„œ๋“œ์— @ResponseBody๋ฅผ ๋ถ™์ธ๋‹ค๋Š” ์˜๋ฏธ๋‹ค.

@Controller
@ResponseBody

@ResponseBody๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ’์„ HTTP ์‘๋‹ต ๋ฐ”๋””์— ์ž‘์„ฑํ•œ๋‹ค. ์ด ์–ด๋…ธํ…Œ์ด์…˜์ด ํด๋ž˜์Šค์— ๋ถ™์–ด ์žˆ์œผ๋ฉด, ํ•ด๋‹น ํด๋ž˜์Šค์˜ ๋ชจ๋“  ๋ฉ”์„œ๋“œ์— ์ ์šฉ๋œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ € ๋‘ ๊ฐœ๋ฅผ ํ•ฉ์นœ ๊ฒŒ @RestController๋‹ค.

@RestController = @Controller + @ResponseBody

๊ฐ€๋ณด๋ฉด ๋‘ ๊ฐœ๋‹ค ๋“ค์–ด์žˆ๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ณ„๊ฐœ๊ฐ€ ์•„๋‹ˆ๋ผ ๋‘๊ฐœ๋ฅผ ๋ฌถ์–ด๋†“์€ ๊ฒƒ์ž„ ใ…ใ…‡๋ผใ…“๋ฐ๋Ÿฌ๋ฐ€ใ…‡

3. UI ์ž‘์„ฑ

๋Œ“๊ธ€ ๊ฐ€์ ธ์˜ค๊ธฐ

dataType(์ „์†ก ๋ฐ›์„ ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…)์„ ๋ช…์‹œํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์ด json์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— success์—์„œ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ parseํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

  • ์ถ”๊ฐ€
    • dataType์€ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ˜ํ™˜๋ฐ›์„ ๋ฐ์ดํ„ฐ์˜ ์œ ํ˜•์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
    • dataType์ด ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ, jQuery๋Š” ์„œ๋ฒ„์˜ ์‘๋‹ต ํ—ค๋”๋ฅผ ๋ณด๊ณ  ๋ฐ์ดํ„ฐ ์œ ํ˜•์„ ๊ฒฐ์ •ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.
    • dataType์ด json์œผ๋กœ ์„ค์ •๋˜๋ฉด, ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ˜ํ™˜๋ฐ›์€ JSON ๋ฌธ์ž์—ด์€ ์ž๋™์œผ๋กœ JavaScript ๊ฐ์ฒด๋กœ ํŒŒ์‹ฑ๋ฉ๋‹ˆ๋‹ค.

    ๊ฒฐ๊ตญ, dataType ์„ค์ • ์—ฌ๋ถ€์— ๊ด€๊ณ„์—†์ด ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ˜ํ™˜๋œ ๋ฐ์ดํ„ฐ๊ฐ€ JSON ํ˜•์‹์ด๋ฉด success ์ฝœ๋ฐฑ ํ•จ์ˆ˜์—์„œ ์ถ”๊ฐ€์ ์ธ ํŒŒ์‹ฑ ์ž‘์—…์„ ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

let showList = function (bno) {
    $.ajax({
        type: 'GET',       // ์š”์ฒญ ๋ฉ”์„œ๋“œ
        url: '/comments?bno=' + bno,  // ์š”์ฒญ URI
        success: function (result) {
            $("#commentList").html(result);    // ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์‘๋‹ต์ด ๋„์ฐฉํ•˜๋ฉด ํ˜ธ์ถœ๋  ํ•จ์ˆ˜
        },
        error: function () {
            alert("error")
        } 
    }); // $.ajax()
}

<button id="sendBtn" type="button">SEND</button>
<h2>Data From Server :</h2>
<div id="commentList"></div>
<script>
    let bno = 1085;

    // ํŠน์ • ๊ฒŒ์‹œ๋ฌผ์— ๋‹ฌ๋ ค์žˆ๋Š” ๋Œ“๊ธ€๋“ค์„ ๊ฐ€์ ธ์˜จ๋‹ค.
    let showList = function (bno) {
        $.ajax({
            type: 'GET',       // ์š”์ฒญ ๋ฉ”์„œ๋“œ
            url: '/comments?bno=' + bno,  // ์š”์ฒญ URI
            success: function (result) {
                $("#commentList").html(toHtml(result)); // ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์‘๋‹ต์ด ๋„์ฐฉํ•˜๋ฉด ํ˜ธ์ถœ๋  ํ•จ์ˆ˜
            },
            error: function () {
                alert("error")
            }
        }); // $.ajax()
    }

    $(document).ready(function () {
        $("#sendBtn").click(function () {
            showList(bno);
        })
    });

    let toHtml = function (comments) {
        let tmp = "<ul>";

        comments.forEach(function (comment) {
            tmp += '<li data-cno=' + comment.cno
            tmp += ' data-pcno=' + comment.pcno
            tmp += ' data-bno=' + comment.bno + '>'
            tmp += ' commenter=<span class="commenter">' + comment.commenter + '</span>'
            tmp += ' comment=<span class="comment">' + comment.comment + '</span>'
            tmp += ' up_date=' + comment.up_date
            tmp += '</li>'
        });

        return tmp + "</ul>";
    }

๊ฒฐ๊ณผ

๋Œ“๊ธ€ ์‚ญ์ œํ•˜๊ธฐ

tmp += '<button class="delBtn">์‚ญ์ œ</button>' // ์‚ญ์ œ ๋ฒ„ํŠผ ์ถ”๊ฐ€

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด alert ์ฐฝ์ด ์•ˆ ๋œฌ๋‹ค.

$(document).ready(function () {
    $(".delBtn").click(function () {
        alert("delBtn");
    });
});

๋จผ์ € ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋”ฉ์ด ๋œ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์€ ์ƒํƒœ์ž„

๐Ÿ’ก๋™์ ์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ์š”์†Œ์— ์ด๋ฒคํŠธ๋ฅผ ๊ฑฐ๋Š” ๋ฐฉ๋ฒ•

๋™์ ์œผ๋กœ ์ƒ์„ฑ๋œ ์š”์†Œ(์ด ๊ฒฝ์šฐ .delBtn)์— ์ด๋ฒคํŠธ๋ฅผ ์—ฐ๊ฒฐํ•˜๋ ค๋ฉด, ๊ณ ์ •๋œ ์ƒ์œ„์š”์†Œ(#commentList)์— ์ด๋ฒคํŠธ๋ฅผ ๊ฑธ์–ด์•ผ ํ•œ๋‹ค. (์ด๋ฒคํŠธ ์œ„์ž„)

์ด๋ ‡๊ฒŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋œ๋‹ค. #commentList ์•ˆ์— ์žˆ๋Š” .delBtn ํด๋ž˜์Šค ์š”์†Œ์—๋‹ค๊ฐ€ ํด๋ฆญ ์ด๋ฒคํŠธ๋ฅผ ๊ฑด๋‹ค.

// $(".delBtn").click(function () {
$("#commentList").on("click", ".delBtn", function () {
    alert("delBtn");
});

// $(".delBtn").click(function () {
$("#commentList").on("click", ".delBtn", function () {
    let cno = $(this).parent().attr("data-cno");
    let bno = $(this).parent().attr("data-bno");

    $.ajax({
        type: 'DELETE',       // ์š”์ฒญ ๋ฉ”์„œ๋“œ
        url: '/comments/' + cno + '?bno=' + bno,  // ์š”์ฒญ URI
        success: function (result) {
            alert(result);
            showList(bno);
        },
        error: function () {
            alert("error")
        }
    }); // $.ajax()
});

์‚ฌ์šฉ์ž ์ •์˜ ์†์„ฑ์€ ๊ธฐ์กด ์†์„ฑ๊ณผ ์ถฉ๋Œ๋‚  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— data-๋ฅผ ๋ถ™์—ฌ์ฃผ๋Š” ๊ฒŒ ์ข‹๋‹ค.

data- ๋กœ ์‹œ์ž‘ํ•˜๋Š” ์• ๋“ค์€ dataset์ด๋ผ๋Š” ์†์„ฑ ์•ˆ์— map ํ˜•ํƒœ๋กœ ์Œ“์ธ๋‹ค ์ฐธ๊ณ ๋กœ ์•Œ์•„๋‘๊ธฐ.(dataset ์†์„ฑ ํ†ตํ•ด ์ ‘๊ทผ ๊ฐ€๋Šฅ)

๋Œ“๊ธ€ ์“ฐ๊ธฐ

send ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋ฉด Input ํƒœ๊ทธ์— ์žˆ๋Š” ๋‚ด์šฉ์ด ์ €์žฅ๋˜๋„๋ก ํ•˜๊ธฐ

comment : <input type="text" name="comment"> <br>
<button id="sendBtn" type="button">SEND</button>


$("#sendBtn").click(function () {
    let comment = $("input[name=comment]").val();

    $.ajax({
        type: 'POST',       // ์š”์ฒญ ๋ฉ”์„œ๋“œ
        url: '/comments?bno=' +  bno,  // ์š”์ฒญ URI
        headers: {"content-type": "application/json"}, // ์š”์ฒญ ํ—ค๋”
        data: JSON.stringify({bno : bno, comment : comment}),  // ์„œ๋ฒ„๋กœ ์ „์†กํ•  ๋ฐ์ดํ„ฐ. stringify()๋กœ ์ง๋ ฌํ™” ํ•„์š”.
        success: function (result) {
            alert(result);
            showList(bno);
        },
        error: function () {
            alert("error")
        } // ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ํ˜ธ์ถœ๋  ํ•จ์ˆ˜
    }); // $.ajax()
});

๋Œ“๊ธ€ ์ˆ˜์ •

$("span.comment", $(this).parent()) : ํด๋ฆญ๋œ ์ˆ˜์ • ๋ฒ„ํŠผ์— ๋ถ€๋ชจ์— ์žˆ๋Š” span๋งŒ ๊ฐ€์ง€๊ณ  ์˜จ๋‹ค.

$("#commentList").on("click", ".modBtn", function () {
    let cno = $(this).parent().attr("data-cno");
    let bno = $(this).parent().attr("data-bno");
    let comment = $("span.comment", $(this).parent()).text();

    // 1. comment์˜ ๋‚ด์šฉ์„ input์— ๋ฟŒ๋ ค์ฃผ๊ธฐ
    $("input[name=comment]").val(comment);
    // 2. cno ์ „๋‹ฌํ•˜๊ธฐ
    $("#modBtn").attr("data-cno", cno);

    $("#modBtn").click(function () {
        let cno = $(this).attr("data-cno");
        let comment = $("input[name=comment]").val();

        $.ajax({
            type: 'PATCH',       // ์š”์ฒญ ๋ฉ”์„œ๋“œ
            url: '/comments/' + cno, // ์š”์ฒญ URI /ch4/comments/70 POST
            headers: {"content-type": "application/json"}, // ์š”์ฒญ ํ—ค๋”
            data: JSON.stringify({cno : cno, comment : comment}),  // ์„œ๋ฒ„๋กœ ์ „์†กํ•  ๋ฐ์ดํ„ฐ. stringify()๋กœ ์ง๋ ฌํ™” ํ•„์š”.
            success: function (result) {
                alert(result);
                showList(bno);
            },
            error: function () {
                alert("error")
            } // ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ํ˜ธ์ถœ๋  ํ•จ์ˆ˜
        }); // $.ajax()
    });
});

4. ๋Œ€๋Œ“๊ธ€

11:50

<div id="replyForm" style="display: none;">
    <input type="text" name="replyComment">
    <button id="wrtRepBtn" type="button">๋“ฑ๋ก</button>
</div>

$("#commentList").on("click", ".replyBtn", function () {
    // 1. replyForm์„ ์˜ฎ๊ธฐ๊ณ 
    $("#replyForm").appendTo($(this).parent());

    // 2. ๋‹ต๊ธ€์„ ์ž…๋ ฅํ•  ํผ์„ ๋ณด์—ฌ์ค€๋‹ค.
    $("#replyForm").css("display", "block");
});
$("#wrtRepBtn").click(function () {
    let comment = $("input[name=replyComment]").val();
    let pcno = $("#replyForm").parent().attr("data-pcno");

    if (comment.trim() == '') {
        alert("๋Œ“๊ธ€์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.");
        $("input[name=replyComment]").focus();

        return;
    }

    $.ajax({
        type: 'POST',       // ์š”์ฒญ ๋ฉ”์„œ๋“œ
        url: '/comments?bno=' +  bno,  // ์š”์ฒญ URI
        headers: {"content-type": "application/json"}, // ์š”์ฒญ ํ—ค๋”
        data: JSON.stringify({pcno : pcno, bno : bno, comment : comment}),  // ์„œ๋ฒ„๋กœ ์ „์†กํ•  ๋ฐ์ดํ„ฐ. stringify()๋กœ ์ง๋ ฌํ™” ํ•„์š”.
        success: function (result) {
            alert(result);
            showList(bno);
        },
        error: function () {
            alert("error")
        } // ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ํ˜ธ์ถœ๋  ํ•จ์ˆ˜
    }); // $.ajax()

    // ์ดˆ๊ธฐํ™” ์ž‘์—…
    $("#replyForm").css("display", "none");
    $("input[name=replyComment]").val('');
    $("#replyForm").appendTo("body"); // ๋Œ€๋Œ“๊ธ€ ์ž…๋ ฅ form์„ ์›๋ž˜ ์ž๋ฆฌ๋กœ ๋‹ค์‹œ ๋˜๋Œ๋ ค๋†“๊ธฐ
});

  1. ๊ธฐ์กด ์ฟผ๋ฆฌ์—์„œ ๋Œ“๊ธ€์„ ๊ฐ€์ ธ์˜ค๋Š” ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜์ •

    ๋‹ต๊ธ€์ด ์—ฌ๋Ÿฌ๊ฐœ์ผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— cno๋กœ๋„ ์ •๋ ฌ์„ ํ•ด์•ผ ํ•œ๋‹ค.

    SELECT cno, bno, ifnull(pcno, cno) as pcno, comment, commenter, reg_date, up_date
    FROM comment
    WHERE bno = 1085
    ORDER BY pcno asc;
  1. ใ„ดใ…‡ใ…‡
    if (comment.cno != comment.pcno)
    		tmp += 'ใ„ด'
  1. ๋Œ€๋Œ“๊ธ€์—์„œ ๋‹ต๊ธ€์„ ๋ˆ„๋ฅด๋ฉด ์ตœ์ƒ์˜ ๋Œ“๊ธ€์˜ ๋Œ€๋Œ“๊ธ€๋กœ ๋“ค์–ด๊ฐ€์–Œ(๋Œ€๋Œ“๊ธ€ 1๋‹จ๊ณ„๋งŒ ํ—ˆ์šฉ)
    let pcno = $("#replyForm").pa rent().attr("data-cno");
    let pcno = $("#replyForm").parent().attr("data-pcno");

github comment.html์— ๋Œ€๋Œ“๊ธ€ ๋Œ“๊ธ€ ๋””์ž์ธ ์žˆ์Œ. ์ ์šฉํ•ด์„œ ๋งˆ๋“ค์–ด๋ณด๊ธฐ

์ˆ˜์ •, ์‚ญ์ œ๋Š” ํ•ด๋‹น ์•„์ด๋””์ธ ์‚ฌ๋žŒ์—๊ฒŒ๋งŒ ๋‚˜์˜ค๋„๋ก


Uploaded by N2T