100일차 Spring CRUD게시판-영속성계층

쿠우·2022년 8월 29일
0

-규칙에 따른 패키지 구성

  • MVC패턴에 따른 패키지 구성한다.

-각 영역의 네이밍 규칙

  • 해당 영역의 역할에 맞게 이름짓는다.

-영속적인 부분에서 설정할 시 (root-context.xml)

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>

(1) 영속성 계층의 구현 => DAO 의 구현

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

가. tbl_board 테이블의 1개의 레코드를 읽기전용으로 보관 = > VO 구현

@Value
public class BoardVO {
	private Integer bno;
	private String title;
	private String content;
	private String writer;
	
	// 정보통신망법의 요구사항에 따라, 중요 데이터 테이블에는 
	// 아래와 같이, 레코드가 최초 생성된 시각과 최종 수정된 시각을 유지할 수 있게 컬럼 정의
	private Timestamp insertTs;
	private Timestamp updateTs;
	
}// end class

나. tbl_board 테이블의 1개의 레코드를 받기전용 보관 => DTO 구현

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

mybatis랑 JUnit 사용해서 CRUD test하기

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
profile
일단 흐자

0개의 댓글