https://offbyone.tistory.com/381 < java로 설정하는 방법 나와있음
(우리는 xml파일로 설정)
xml 사용하지않고 자바로 설정한다면 config 패키지를 따로 만들지만 xml로만 설정한다면 패키지 따로 안만든다.
Spring framework 기반으로 웹서비스를 개발할 때에는,
웹 3계층을 거꾸로(뒤에서 ~ 앞으로) 개발:
(1) 영속성 계층 구현 > (2) 비지니스 계층구현 > (1) 표현계층 구현
테스트의 중요성
반드시, 설정이든 구현이든, 새로운 것을 만들거나 설정
하면, 제대로 기능이 동작하는지 테스트 하고 지나가야
합니다(== 테스트-주도 개발, TDD, Test-Driven Dev.)
때문에, 점진적 개발을 수행
이러한 방식이, 시간이 더 오래걸릴것이라 생각할 수 있지만, 실제 경험은
반대입니다. 오히려, 버그를 최대한 줄여주어서, 개발기간을 단축시킵니다.
web.xml 설정 (어떤거 설정했는지만 보고 넘기기)
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
id="WebApp_ID"
version="4.0">
<display-name>chap08</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.htm</welcome-file>
</welcome-file-list>
<!-- 지정한 jsp에 대해 정적으로 해당 요소들을 구성하게 깅제한다. -->
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<page-encoding>utf8</page-encoding>
<include-prelude>/WEB-INF/views/include.jsp</include-prelude>
<trim-directive-whitespaces>true</trim-directive-whitespaces>
<default-content-type>text/html; charset=utf8</default-content-type>
</jsp-property-group>
</jsp-config>
<!-- 세션만료기간 설정 -->
<session-config>
<session-timeout>10</session-timeout>
</session-config>
<!-- 예외처리 설정 (예외타입기반) -->
<error-page>
<exception-type>java.lang.NullPointerException</exception-type>
<location>/WEB-INF/views/errors/null.jsp</location>
</error-page>
<!-- 예외처리 설정 (응답코드기반) -->
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/views/errors/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/views/errors/404.jsp</location>
</error-page>
<!-- The definition of the Root Spring Beans Container(WebApplicationContext) shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!--
Creates the Spring Container shared by all Servlets and Filters
Spring Beans Container를 생성해주는 것이 ContextLoaderListener이다.
-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<!-- Handler : 웹브라우저에서 온 요청을 실제 처리하는 컨트롤러의 메소드를 의미 -->
<!-- NoHandlerFoundException -->
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- GET방식이든 POST방식이든, 요청메시지에 포함된 전송파라미터의 값이 깨지지 않도록(예:한글)
utf8 문자집합으로 인코딩을 미리 수행하는 역할(선처리, Pre-processing) -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
- MyBatis Mapper Interface 기반으로 개발
- 매퍼 인터페이스에서 매개변수의 값을 어떻게 주고,
XML로 처리할것과 어노테이션으로 처리할 것을 어떻게 구분하는지 확인해야한다
package org.zerock.myapp.mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.zerock.myapp.domain.BoardDTO;
import org.zerock.myapp.domain.BoardVO;
import org.zerock.myapp.exception.DAOException;
// 마이바티스 매퍼인터페이스
public interface BoardMapper {
// 1. 게시판의 전체 목록 조회하기
@Select("SELECT /*+ index_desc(tbl_board) */ * FROM tbl_board ")
public abstract List<BoardVO> selectAllList() throws DAOException;
// 2. CREATE = INSERT 새로운 게시글 등록 - Mapper XML 파일로 처리
// 복잡한 값에 대해서는 XML로 처리
// 비즈니스 데이터를 사용할 때 Map 객체보다는 dto 사용한다.
// 특정 속성 값을 읽을 때는 getter로 읽음
public abstract Integer insert(BoardDTO dto) throws DAOException;
// 새로이 입력된 게시글의 bno를 반환받기를 원하는 경우
public abstract Integer insertSelectKey(BoardDTO dto) throws DAOException;
//3. 기존 게시글 상세조회하기 - Mapper XML 파일로 처리
public abstract BoardVO select(BoardDTO dto) throws DAOException;
// 4. 기존게시글 수정하기 - Mapper XML 파일로 처리
public abstract Integer update(BoardDTO dto) throws DAOException;
// 5. 기존 게시글 삭제하기
@Delete("DELETE FROM tbl_board WHERE bno = #{bno}")
public abstract Integer delete(@Param("bno") Integer dto) throws DAOException;
}// end interface
- SQL 의 저장 (2가지 방식 모두 혼용): Annotation + Mapper 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="org.zerock.myapp.mapper.BoardMapper">
<insert id="insert">
INSERT INTO tbl_board(title, content,writer)
VALUES(#{title},#{content},#{writer})
</insert>
<insert id="insertSelectKey">
<selectKey keyProperty="bno" order="BEFORE" resultType="int">
SELECT "ISEQ$$_95888".nextval FROM dual
</selectKey>
INSERT INTO tbl_board(bno, title, content, writer)
VALUES(#{bno}, #{title},#{content},#{writer})
</insert>
<select id="select" resultType="org.zerock.myapp.domain.BoardVO">
SELECT * FROM tbl_board WHERE bno = #{bno}
</select>
<update id="update">
UPDATE tbl_board
SET
title = #{title},
content = #{content},
writer = #{writer},
update_ts = current_date
WHERE
bno = #{bno}
</update>
</mapper>
@Value
public class BoardVO {
private Integer bno;
private String title;
private String content;
private String writer;
// 정보통신망법의 요구사항에 따라, 중요 데이터 테이블에는
// 아래와 같이, 레코드가 최초 생성된 시각과 최종 수정된 시각을 유지할 수 있게 컬럼 정의
private Timestamp insertTs;
private Timestamp updateTs;
}// end class
package org.zerock.myapp.domain;
import lombok.Data;
@Data
public class BoardDTO {
private Integer bno;
private String title;
private String content;
private String writer;
}//end class
package org.zerock.myapp.mapper;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.zerock.myapp.domain.BoardDTO;
import org.zerock.myapp.domain.BoardVO;
import org.zerock.myapp.exception.DAOException;
import lombok.Cleanup;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
//JUnit5
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/spring/root-context.xml")
@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BoardMapperTests {
// BoardMapper 를 주입받아서, 테스트를 수행
@Setter(onMethod_= {@Autowired})
private BoardMapper mapper;
@BeforeAll
void beforeAll() {
log.trace("beforeAll() invoked.");
assertNotNull(this.mapper);
log.info("\t + mapper:{}", this.mapper);
}//beforeAll
@Test
@Order(1)
@DisplayName("1. BoardMapper.selectAllList test.")
@Timeout(value=3, unit=TimeUnit.SECONDS)
void testSelectAllList() throws DAOException{
log.trace("testSelectAllList() invoked.");
@Cleanup("clear")
List<BoardVO> list =this.mapper.selectAllList();
for(BoardVO vo: list) {
log.info("\t + vo: {}",vo);
} //for
}// testSelectAllList
@Test
@Order(2)
@DisplayName("2. BoardMapper.insert(dto) test.")
@Timeout(value=3, unit=TimeUnit.SECONDS)
void testInsert() throws DAOException{
log.trace("testInsert() invoked.");
BoardDTO dto = new BoardDTO();
dto.setTitle("TITLE_NEW");
dto.setContent("CONTENT_NEW");
dto.setWriter("WRITER_NEW");
int affectedLines = this.mapper.insert(dto);
log.info("\t + affectedLines : {}", affectedLines);
}// testInsert
@Test
@Order(3)
@DisplayName("3. BoardMapper.select(bno) test.")
@Timeout(value=3, unit=TimeUnit.SECONDS)
void testSelect() throws DAOException{
log.trace("testSelect() invoked.");
BoardDTO dto = new BoardDTO();
dto.setBno(299);
log.info("\t + result : {}" , this.mapper.select(dto));
}// testSelect
@Test
@Order(4)
@DisplayName("4. BoardMapper.update(dto) test.")
@Timeout(value=3, unit=TimeUnit.SECONDS)
void testUpdate() throws DAOException{
log.trace("testUpdate() invoked.");
BoardDTO dto = new BoardDTO();
dto.setBno(79);
dto.setTitle("TITLE_UPDATE");
dto.setContent("CONTENT_UPDATE");
dto.setWriter("WRITER_UPDATE");
log.info("\t + result : {}", this.mapper.update(dto) ==1);
}// testUpdate
@Test
@Order(5)
@DisplayName("2. BoardMapper.delete(bno) test.")
@Timeout(value=3, unit=TimeUnit.SECONDS)
void testDelete() throws DAOException{
log.trace("testDelete() invoked.");
log.info("\t + result : {}" , this.mapper.delete(33) == 1);
}// selectAllList
}// end class