์คํ๋ง์ ์ ์ Ch. 04 MyBatis๋ก ๊ฒ์ํ ๋ง๋ค๊ธฐ
01. MyBatis์ ์๊ฐ์ ์ค์
1. MyBatis๋?
- SQL Mapping Framework - Easy & Simple
์๋ฐ ์ฝ๋์ SQL์ ๋งคํํ๋ค.
- ์๋ฐ ์ฝ๋๋ก๋ถํฐ SQL๋ฌธ์ ๋ถ๋ฆฌํด์ ๊ด๋ฆฌ
- ๋งค๊ฐ๋ณ์ ์ค์ ๊ณผ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ์ฝ์ด์ค๋ ์ฝ๋๋ฅผ ์ ๊ฑฐ
setString(), setInt() | getString(), getInt()
- ์์ฑํ ์ฝ๋๊ฐ ์ค์ด์ ์์ฐ์ฑ ํฅ์ & ์ ์ง ๋ณด์ ํธ๋ฆฌ
E = m๏ปฟ
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>์ผ๋ก ์ด๋ฆ ์งง๊ฒ ํ๊ธฐ
typeAliases : ๋ณ๋ช

02. MyBatis๋ก DAO ์์ฑํ๊ธฐ
1. BoardDao์ ์์ฑ
- DB ํ ์ด๋ธ ์์ฑ
- Mapper XML & DTO ์์ฑ
- DAO ์ธํฐํ์ด์ค ์์ฑ
- 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๋ก ํ๋๊น ํ์ํ๋ค.(์ฒ๋ฆฌ ์ ํ๊ณ ์ปจํธ๋กค๋ฌ๋ก ๋๊ธฐ๋ ์ ํ์ง๋ ์์)
<์์ธ์ฒ๋ฆฌ ๋ฐฉ๋ฒ>
- Service์์ ์ฒ๋ฆฌ
- Controller์์ ์ฒ๋ฆฌ
- ์๋๋ฉด ๋ ๋ค(์์ธ ๋๋์ง๊ธฐ)
์ด๋ค ๋ฐฉ๋ฒ์ผ๋ก ์์ธ์ฒ๋ฆฌํ ์ง ์ 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๋ฌธ์ ํธ์ถํ๋ ๋ฉ์๋๋ฅผ ์ง์ ๋ง๋ค์ด๋ณด๊ธฐ!
- 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
- 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>
- 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); } }
- ์ธํฐํ์ด์ค๋ก ์ถ์ถ
์ธํฐํ์ด์ค๋ก ํด๋์ผ๋ฉด ๋ค์ค์ DAO๊ฐ ๋ณ๊ฒฝ๋ ๋ Service์ชฝ์ ์ํฅ์ ๋ฐ์ง ์๋๋ก, ํด๋์ค๊ฐ ์๋๋ผ ์ธํฐํ์ด์ค ํ์ ์ ์ด์ฉํด์ ์ฃผ์ ๋ฐ๋ ๊ฒ.
Refacter > Extract Interface
๋ค์. BoardDao๋ฅผ ํ ์คํธ ํด๋ณด์.
@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๋ฅผ ์ฐ๋ ๊ฒ ์ข์
์ฅ์
- ์ฑ๋ฅ : sql๋ฌธ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
- ๋ณด์ : 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 ๋ด์ ํน์ ๋ฌธ์ (<, >, &, โฆ)๋ < >๋ก ๋ณํ ํ์
- ๋๋ ํน์๋ฌธ์๊ฐ ํฌํจ๋ ์ฟผ๋ฆฌ๋ฅผ <![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 < > 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๊ฐ ๊ฐ์ ธ์จ๋ค.

์ค์ต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} '/>"><</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} '/>">></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 ์ ์
- ๊ฐ ๊ธฐ๋ฅ๋ณ๋ก URI๋ฅผ ๋จผ์ ์ ํ๋ค.
- ๊ทธ ๋ค์์๋ ๋ฉ์๋์ ์ค๋ช ์ TABLE๋ก ์ ๋ฆฌํ๋ค.
URL๊ณผ URI์ ์ฐจ์ด
URL : ์ ์ฒด ๊ฒฝ๋ก
URI : URL ์ผ๋ถ
์์ | URI | HTTP๋ฉ์๋ | ์ค๋ช |
---|---|---|---|
์ฝ๊ธฐ | /board/read?bno=๋ฒํธ | GET | ์ง์ ๋ ๋ฒํธ์ ๊ฒ์๋ฌผ์ ๋ณด์ฌ์ค๋ค. |
์ญ์ | /board/remove | POST | ๊ฒ์๋ฌผ์ ์ญ์ ํ๋ค. |
์ฐ๊ธฐ | /board/write | GET | ๊ฒ์๋ฌผ์ ์์ฑํ๊ธฐ ์ํ ํ๋ฉด์ ๋ณด์ฌ์ค๋ค. |
/board/write | POST | ์์ฑํ ๊ฒ์๋ฌผ์ ์ ์ฅํ๋ค. | |
์์ | /board/modify?bno=๋ฒํธ | GET | ๊ฒ์๋ฌผ์ ์์ ํ๊ธฐ ์ํด ์ฝ์ด์จ๋ค. |
/board/modify | POST | ์์ ๋ ๊ฒ์๋ฌผ์ ์ ์ฅํ๋ค. |
์ญ์ โ ์ฝ๊ธฐ โ ์ฐ๊ธฐ โ ์์ ์์๋ก ์ฌ์
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} '/>"><</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} '/>">></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} '/>"><</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} '/>">></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

- BoardController
write
@GetMapping("/write") public String write(Model m) { m.addAttribute("mode", "new"); // ์ฝ๊ธฐ๊ณผ ์ฐ๊ธฐ์ ์ฌ์ฉ. ์ฐ๊ฒ์ ์ฌ์ฉํ ๋๋ mode = new return "board"; }
- 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
- ๋ชํ์ด์ง์ธ์ง ์๊ด์์ด ์ต๊ทผ ๊ฒ์๋ฌผ๋ก ๋ณด๋ด๋ฉด ๋จ
return "redirect:/board/list"
- ๊ฒ์๊ธ ๋ฑ๋ก ์ boardDto์ writer๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค.
String writer = (String) session.getAttribute("id"); boardDto.setWriter(writer);
- boardService.write๊ฐ ์์ธ๋ฅผ ๋ฐ์์ํฌ ๊ฐ๋ฅ์ฑ์ด ์๊ธฐ ๋๋ฌธ์ try-catch๋ก ๊ฐ์ธ์ค๋ค.
try { int rowCnt = boardService.write(boardDto); } catch (Exception e) { e.printStackTrace(); }
- boardService.write์ ๊ฒฐ๊ณผ๊ฐ์ ๋ฐ์์ rowCnt๊ฐ 1์ด ์๋๋ฉด exception์ ๋ฐ์์ํจ๋ค.
throw new Exception("Write failed");
- ์ ์ฅ ์คํจ ์ ์ฌ์ฉ์๊ฐ ์
๋ ฅํ๋ ๋ด์ฉ์ ๋ค์ ๋ณด์ฌ์ฃผ์ด์ผํจ
model์ ์ด์ฉํด์ ๋ค์ ๋ณด์ฌ์ค๋ค.
} catch (Exception e) { e.printStackTrace(); m.addAttribute("boardDto", boardDto); return "board"; }
- ์ฒ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ํด๋ผ์ด์ธํธ์ชฝ์ ์๋ ค์ค์ผ ํ๋ค. (์ธ์
์ ์ด์ฉํ 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. ๊ฒ์ํ ๊ฒ์
- ๋์ ์ฟผ๋ฆฌ : ๊ฒ์ ๋์์ ๋ฐ๋ผ SQL ์ฟผ๋ฆฌ๊ฐ ๋ฌ๋ผ์ง๋ ๊ธฐ๋ฅ์ด๋ค. ์ฌ์ฉ์๊ฐ ์ด๋ค ํญ๋ชฉ์ ๊ธฐ์ค์ผ๋ก(์ ๋ชฉ+๋ด์ฉ, ์ ๋ชฉ, ๋ด์ฉ) ๊ฒ์ํ๋์ง์ ๋ฐ๋ผ ์ฟผ๋ฆฌ๊ฐ ๋ณ๊ฒฝ๋์ด์ผ ํ๋ค.
- ํ์ด์ง ์ด๋ ์ฒ๋ฆฌ : ๊ฒ์ ๊ฒฐ๊ณผ์๋ ํ์ด์ง ํ์
์ด์ 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์ ํ์ธํ ์ ์๋ค.
- log4jdbc.log4j2.properties
log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator
- 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>
- 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&characterEncoding=utf8" /> <!-- <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>--> <!-- <property name="url" value="jdbc:mysql://localhost:3306/springbasic?useUnicode=true&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)}"/>"><</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)}"/>">></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")
}
});
});
์ค์ต
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 ์์ฒญ๊ณผ ์๋ต ๊ณผ์

- ์๋ฒ์๊ฒ JS ๊ฐ์ฒด๋ฅผ ๊ทธ๋๋ก ์ง์ ์ ์กํ ์ ์๊ธฐ ๋๋ฌธ์, ๋ฌธ์์ด๋ก ๋ณํํด์ ์ ์กํ๋ค.(HTTP๊ฐ ํ ์คํธ ๊ธฐ๋ฐ ํ๋กํ ์ฝ์ด๊ธฐ ๋๋ฌธ)
- POST๋ก ์์ฒญ์ ๋ณด๋ด๋ฉด, ์๋ฒ๋ ๋ฐ์ ๋ฌธ์์ด์
jackson-databind
๋ฅผ ์ฌ์ฉํด ์๋ฐ ๊ฐ์ฒด๋ก ๋ณํํด์ ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌํ๋ค.์ด๋
jackson-databind
๋ ๋ฌธ์์ด์ ์๋ฐ ๊ฐ์ฒด๋ก ๋ณํํ๋ ์ญํ ์ ํ๋ค.
- test ๋ฉ์๋๋ฅผ ์ํํ๊ณ ์๋ฒ๋ ๊ฐ์ฒด๋ฅผ ๋ฐํํด์ ์๋ต์ ์ ๊ณตํ๋๋ฐ,
jackson-databind
๊ฐ ์๋ฐ ๊ฐ์ฒด๋ฅผ JSON ๋ฌธ์์ด๋ก ๋ณํํด์ ์ ์กํ๋ค.
- ํด๋ผ์ด์ธํธ๋ ์ด๋ฅผ ๋ฐ์์
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 ๋ฉ์๋๋ก ์ํํ ์์ ์ ์ ์
๋ฆฌ์์ค | POST | GET | PUT | DELETE |
---|---|---|---|---|
/customers | ์ ๊ณ ๊ฐ ๋ง๋ค๊ธฐ | ๋ชจ๋ ๊ณ ๊ฐ ๊ฒ์ | ๊ณ ๊ฐ ๋๋ ์ ๋ฐ์ดํธ | ๋ชจ๋ ๊ณ ๊ฐ์ ๊ฑฐ |
/customers/1 | Error | ๊ณ ๊ฐ 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 ๋ฐฉ์์ด๋ผ๊ณ ํ๋ค.

8. 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)

๊ท์น๋ค์ ์ ํด๋์ ๊ฒ REST. REST์์ ์ ํ ๊ท์น์ ์ ์ค์ํ๋ API๋ฅผ REST API๋ผ๊ณ ํ๋ค.
ํ๋กํ ์ฝ์ ๋ํ ํ์ค์ ์๋๊ณ ๊ท์น๋ง ์ ์ํด๋๊ณ ๊ทธ๊ฑธ ์งํค๋ฉด REST API๋ผ๊ณ ํ๋ค. ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ๊ฐ๋ฐ์๋ค์ด REST API๋ฅผ ๊ตฌํํ ์ ์๋ค.
โAPI๋?
API๋ ํ๋ก๊ทธ๋๋ฐ ์ธํฐํ์ด์ค๋ค.
์ธํฐํ์ด์ค๋ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ ์ชฝ๊ณผ ์ฌ์ฉํ๋ ์ชฝ ์ฌ์ด์ ์ฝ์์ด๋ค.
์น์๋น์ค๋ฅผ ์ ๊ณตํ๋ ์ชฝ์์ API๋ฅผ ์ค๊ณํด์ ์คํํ๋ฉด, ์ฌ์ฉ์๋ค์ด ๊ทธ๊ฑธ ๋ณด๊ณ ์ ์ฌ์ฉํ๋ค.
์ด ๊ท์น๋ค์ ๋ค์งํค๋ ๊ฒ ์ฝ์ง๋ ์๋ค. ์ํฉ์ ๋ง๊ฒ, ์๋น์ค์ ๋ชฉ์ ์ ๋ง๊ฒ ์ฌ์ฉํ๋ ์ฌ๋์ด ํธ๋ฆฌํ๊ฒ ์ค๊ณ๋ฅผ ํ๋ ๊ฒ์ด ์ค์ํ๋ค. REST API๊ฐ ๋ง๋ ์๋๋๋ ๋์งธ์ ๋ฌธ์ ์ด๋ค.
9. RESTful API์ ์ค๊ณ
REST ๋ฐฉ์์ API๋ฅผ ์ ์งํจ ๊ฑธ RESTful API๋ผ๊ณ ํ๋ค.
์๋๋ ์ค๊ณ์ ์์์ด๋ค.
๋๊ธ ๊ธฐ๋ฅ์ ๊ตฌํํ ๊ฑด๋ฐ ๋๊ธ ๊ธฐ๋ฅ์ ํ์ด์ง ์ ์ฒด๊ฐ ๋ฐ๋๋ ๊ฒ ์๋๋ผ ์ผ๋ถ๋ง ๋ฐ๋๋ฉด ๋๋ค. ๊ทธ๋์ ๋๊ธ ๊ธฐ๋ฅ์ ๊ฐ๋ฐํ ๋ RestController์ REST ๋ฐฉ์์ API ์ค๊ณ๋ฅผ ์ ์ฉํด๋ณด๋ ค๊ณ ํ๋ค.
์์ | URI | HTTP ๋ฉ์๋ | ์ค๋ช |
---|---|---|---|
์ฝ๊ธฐ | /comment/read?cno=๋ฒํธ | GET | ์ง์ ๋ ๋ฒํธ์ ๋๊ธ์ ๋ณด์ฌ์ค๋ค. |
์ฐ๊ธฐ | /comment/write | POST | ์์ฑํ ๊ฒ์๋ฌผ์ ์ ์ฅํ๋ค. |
์ญ์ | /comment/remove | POST | ๋๊ธ์ ์ญ์ ํ๋ค. |
์์ | /comment/modify | POST | ์์ ๋ ๊ฒ์๋ฌผ์ ์ ์ฅํ๋ค. |
์์ | URI | HTTP ๋ฉ์๋ | ์ค๋ช |
---|---|---|---|
์ฝ๊ธฐ | /comments | GET | ๋ชจ๋ ๋๊ธ์ ๋ณด์ฌ์ค๋ค. |
์ฝ๊ธฐ | /comments/{cno} | GET | ์ง์ ๋ ๋ฒํธ์ ๋๊ธ์ ๋ณด์ฌ์ค๋ค. |
์ฐ๊ธฐ | /comments | POST | ์๋ก์ด ๋๊ธ์ ๋ณด์ฌ์ค๋ค. |
์ญ์ | /comments/{cno} | DELETE | ์ง์ ๋ ๋ฒํธ์ ๋๊ธ์ ์ญ์ ํ๋ค. |
์์ | /comments/{cno} | PUT / PATCH | ์์ ๋ ๋๊ธ์ ์ ์ฅํ๋ค. |
URI์ ๋์ฌ๋ฅผ ๋ฃ์ง์๊ณ HTTP ๋ฉ์๋๊ฐ ๋ด๋นํ๊ฒ ํ๋ค. URI์๋ ๋ช ์ฌ๋ง ๋จ๊ฒจ์ ์ฌํํด์ก๋ค.
RESTfulํ๊ฒ ์ค๊ณํ๋ ๊ฒ์ด ์ฝ์ง๋ ์๋ค. ์ด๋ฒ์๋ ๋ง๋ง ๋ณด๋ ๊ฒ.. ์๊ฐ ๋ญ์ง ์๋ ์ ๋๋ฉด๋จ
10~13. ๋๊ธ ๊ธฐ๋ฅ ๊ตฌํ
โญย 1. DAO ์์ฑ
๋๊ธ ๊ธฐ๋ฅ ๊ตฌํ ์์
- DB ํ ์ด๋ธ ์์ฑ
- Mapper XML ์์ฑ
- DAO ์์ฑ & ํ ์คํธ
- Service ์์ฑ & ํ ์คํธ
- ์ปจํธ๋กค๋ฌ ์์ฑ & ํ ์คํธ
- ๋ทฐ(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() ์๋ต. }
- comment ํ ์ด๋ธ ์์ฑ
- 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>
- CommentDAO.java
commentMapper.xml์ ์๋ SQL์ ํธ์ถํ๋ ๋ฉ์๋๋ฅผ ํ๋์ฉ ์์ฑํ๋ค.
cno๋ ์๋์ฆ๊ฐ๋ก ์์ฑํด๋์์ผ๋ ์ ์ธํ ๊ฒ
+) ์ธํฐํ์ด์ค๋ก ์ถ์ถ๊น์ง.
- mybatis-config.xml
typeAlias ๋ฑ๋ก
<typeAlias alias="CommentDto" type="com.fastcampus.ch4.domain.CommentDto"/>
- 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())); } }
- 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; }
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์ ์๋ ์๋ฆฌ๋ก ๋ค์ ๋๋๋ ค๋๊ธฐ
});
- ๊ธฐ์กด ์ฟผ๋ฆฌ์์ ๋๊ธ์ ๊ฐ์ ธ์ค๋ ์ฟผ๋ฆฌ๋ฅผ ์์
๋ต๊ธ์ด ์ฌ๋ฌ๊ฐ์ผ ์ ์๊ธฐ ๋๋ฌธ์ cno๋ก๋ ์ ๋ ฌ์ ํด์ผ ํ๋ค.
SELECT cno, bno, ifnull(pcno, cno) as pcno, comment, commenter, reg_date, up_date FROM comment WHERE bno = 1085 ORDER BY pcno asc;
- ใดใ
ใ
if (comment.cno != comment.pcno) tmp += 'ใด'
- ๋๋๊ธ์์ ๋ต๊ธ์ ๋๋ฅด๋ฉด ์ต์์ ๋๊ธ์ ๋๋๊ธ๋ก ๋ค์ด๊ฐ์(๋๋๊ธ 1๋จ๊ณ๋ง ํ์ฉ)
let pcno = $("#replyForm").pa rent().attr("data-cno"); let pcno = $("#replyForm").parent().attr("data-pcno");
github comment.html์ ๋๋๊ธ ๋๊ธ ๋์์ธ ์์. ์ ์ฉํด์ ๋ง๋ค์ด๋ณด๊ธฐ
์์ , ์ญ์ ๋ ํด๋น ์์ด๋์ธ ์ฌ๋์๊ฒ๋ง ๋์ค๋๋ก

Uploaded by N2T