SP 코딩단 교재를 참고하여 공부를 진행하였습니다
일반적으로 웹 프로젝트는 3-tire 방식으로 구성한다.
Presentation Tier(화면계층) : 화면에 보여주는 기술을 사용하는 영역
Business(비즈니스 계층) : 순수한 비즈니스 로직을 담고 있는 영역
Persistence Tier(영속 계층 혹은 데이터 계층) : 데이터를 어떤 방식으로 보관하고, 사용하는가에 대한 설계가 들어가는 계층
계층에 대한 설명을 스프링 MVC와 맞춰서 설명하면 다음과 같은 구조가 된다.
스프링 MVC 영역은 Presentation Tier를 구성하게 되는데 각 영역은 사실 별도의 설정을 가지는 단위로 볼 수 있다. => root-context.xml, servlet-context.xml 등의 설정 파일이 해당 영역의 설정을 담당
스프링 Core 영역은 흔히 POJO의 영역이다. 스프링의 의존성 주입을 이용해서 객체 간의 연관구조를 완성해서 사용한다.
MyBatis 영역은 현실적으로는 mybatis-spring을 이용해서 구성하는 영역이다. SQL에 대한 처리를 담당하는 구조이다.
1. 프로젝트 생성 및 기본 설정하기
1) Spring Legacy Project 생성하여 아래와 같이 설정하기
프로젝트명 : ex02
top-level : org.zerock.controller
2) pom.xml 수정과 Oracle JDBC Driver 추가하기
- 스프링 버전과 Java 버전 수정하기
tx, jdbc, test 라이브러리 추가
MyBatis를 이용할 것이므로 HikariCP, MyBatis, mybatis-spring, Log3jdbc 라이브러리 추가 + Lombok 사용을 위해 라이브러리 추가
테스트와 Lombok을 위해 jUnit 4.12 버전으로 수정
Servlet은 3.1.0 버전으로 수정
서블릿과 JDK8의 기능을 활용하기 위해 Maven 관련 Java 버전도 1.8로 수정
위와 같이 설정을 다했다면 프로젝트 우클릭 -> Maven -> update project 클릭하여 필요한 라이브러리 다운로드 받기
그 후에 Oracle JDBC Driver를 프로젝트의 Build Path에 추가하고, Deployment Assembly에도 추가하자
다음 단계를 진행하기 전에 꼭 Servers 탭에서 서버를 Stop한 뒤 Add and Remove로 프로젝트 변경 후 Clean을 진행한 다음 Start 시킨 뒤 프로젝트 우클릭 -> Run As -> Run on Server 클릭하여 home.jsp가 잘 응답되어지는지 확인하자~
3) 테이블 생성과 Dummy 데이터 생성
* 더미 데이터란?
테이블을 생성 후 여러 개의 데이터를 추가해줄 때 의미 없는 데이터를 흔히 토이 데이터(toy data) 또는 더미 데이터(dummy data)라고 한다.
4) 데이터베이스 관련 설정 및 테스트
root-context.xml에는 mybatis-spring 네임스페이스를 추가하고 DataSource의 설정과 MyBatis의 설정을 추가하자
root-context.xml은 내부적으로 Log4jdbc를 이용하는 방식으로 구성되어 있으므로 log4jdbc.log4j2.properties 파일을 추가해 줄 것이다(이전 예제에서 사용했던 파일을 복사하여 붙여넣기)
DataSource와 MyBatis의 연결이 반드시 필요하니 DataSourceTests 클래스와 JDBCTests 클래스를 추가하여 테스트를 통해 에러없이 처리되는지 확인해보자
2. 영속/비즈니스 계층의 CRUD 구현하기
이제 코드를 이용해서 데이터에 대한 CRUD 작업을 진행할 것이다.
영속 계층의 작업은 항상 다음과 같은 순서로 진행한다.
1) 테이블의 컬럼 구조를 반영하는 VO 클래스 생성
2) MyBatis의 Mapper 인터페이싀 작성/XML 처리
3) 작성한 Mapper 인터페이스의 테스트
1) VO 클래스의 작성
org.zerock.domain 패키지를 생성하고 BoardVO 클래스를 생성하자
@Data라는 Lombok에서 제공하는 어노테이션을 이용하여 생성자와 getter/setter가 자동으로 생성되어진다.
2) Mapper 인터페이스와 Mapper XML
☞ Mapper 인터페이스
root-context.xml에 org.zerock.mapper 패키지를 스캔하도록 설정해주었기 때문에 org.zerock.mapper 패키지를 생성하고 BoardMapper 인터페이스를 추가하고 아래와 같이 getList() 메서드도 생성하자
작성된 BoardMapper 인터페이스를 테스트할 수 있게 테스트 환경인 src/test/java에 org.zerock.mapper 패키지를 생성한 뒤 jUnit으로 BoardMapperTests 클래스를 추가하자
BoardMapperTests 클래스는 스프링을 이용해서 BoardMapper 인터페이스의 구현체를 주입받아서 동작하게한다. 클래스의 선언부에는 PersistenceConfig 클래스를 이용해서 스프링의 설정을 이용하고 있음을 명시하고 있다. testGetList()의 결과는 SQL 디벨로퍼에서 SELECT 쿼리문 과 동일해야 정상적으로 동작한 것이다.
☞ Mapper XML 파일
BoardMapperTests를 이용해서 테스트가 완료되었다면 src/main/resources 내에 패키지와 동일한 org/zerock/mapper 단계의 폴더를 생성하고 XML 파일을 작성하자
BoardMapper.xml 파일 안에는 아래와 같이 작성한다.
XML 파일에 SQL 문을 처리했으니 BoardMapper 인터페이스에서 SQL은 제거하러가자!
인터페이스를 수정했으니 기존 BoardMapperTests 클래스로 테스트를 해본 뒤 아까와 동일한 결과가 나오는지 확인해보자! 결과는 아주 잘 실행이 되고 있다 :)
3. 영속 영역의 CRUD 구현하기
영속 영역은 기본적으로 CRUD 작업을 하기 때문에 테이블과 VO(DTO) 등 약간의 준비만으로도 비즈니스 로직과 무관하게 CRUD 작업을 작성할 수 있다.
MyBaits는 내부적으로 JDBC의 PreparedStatement를 활용하고 필요한 파라미터를 처리하는 '?'에 대한 치환은 '#{속성}'을 이용해서 처리한다.
1) create인 insert 처리하기
tbl_board 테이블은 PK 컬럼으로 bno를 이용하고 시퀀스를 이용해서 자동으로 데이터가 추가될 때 번호가 만들어지는 방식을 사용하고 있다. 이처럼 자동으로 PK 값이 정해지는 경우에는 다음과 같은 2가지 방식으로 처리할 수 있다.
ㄱ. insert만 처리되고 생성된 PK 값을 알 필요가 없는 경우
ㄴ. insert문이 실행되고 생성된 PK 값을 알아야 하는 경우
BoardMapper 인터페이스에는 위의 상황들을 고려해서 다음과 같이 메서드를 아래와 같이 추가 선언할 것이다.
BoardMapper.xml 파일에는 아래와 같이 내용을 추가하자
BoardMapper의 insert() 메서드는 단순히 시퀀스의 다음 값을 구해서 insert 할 때 사용한다.
insert 문은 몇 건의 데이터가 변경되었는지만을 알려주기 때문에 추가된 데이터의 PK 값을 알 수는 없지만, 1번의 SQL 처리만으로 작업이 완료되는 장점이 있다.
insertSelectKey() 메서드는 @SelectKey 라는 MyBatis의 어노테이션을 이용하고 있다.
@SelectKey는 주로 PK 값을 미리(BEFORE) SQL을 통해서 처리해 두고 특정한 이름으로 결과를 보관하는 방식이다.
@Insert 할 때 SQL 문을 보면 #{bno}와 같이 이미 처리된 결과를 이용하는 것을 볼 수 있다.
☞ 테스트 해보기
insert() 메서드에 대한 테스트 코드를 src/test/java 내에 BoardMapperTests 클래스에 새로운 메서드로 작성해보자
테스트 코드 마지막에 log.info(board)를 작성한 이유는 Lombok이 만들어주는 toString() 메서드를 이용해서 bno 멤버 변수의 값을 알아보기 위함이다.
testInsert()의 실행결과 일부이다.
bno의 값이 null로 비어 있는 것을 확인할 수 있다.
@SelectKey를 이용하는 경우 테스트 코드는 아래와 같다.
아래는 테스트 결과의 일부이다.
2) read / delete / update 처리하기
read와 delete, update는 이전에도 처리해 본 적이 있으므로 한꺼번에 설명과 예제 코드를 정리하겠다.
read(select) 처리는 insert가 된 데이터를 조회하는 작업은 PK를 이용해서 처리하므로 BoardMapper의 파라미터 역시 BoardVO 클래스의 bno 타입 정보를 이용해서 처리한다.
MyBatis는 Mapper 인터페이스의 리턴 타입에 맞게 select의 결과를 처리하기 때문에 tbl_board의 모든 컬럼은 BoardVO의 속성값으로 처리된다. 즉, MyBatis는 bno라는 컬럼이 존재하면 인스턴스의 setBno()를 호출하게 된다.
MyBatis의 모든 파라미터와 리턴 타입의 처리는 get파라미터명(), set컬럼명()의 규칙으로 호출된다.
다만 속성이 1개만 존재하는 경우에는 get파라미터명()을 사용하지 않고 #{속성}으로 처리된다.
delete 역시 PK 값을 이용해서 처리하므로 조회하는 작업과 유사하다.
테스트시 삭제가 잘된다면 1이라는 값이 출력될 것이다.
update 처리는 게시물의 제목, 내용, 작성자를 수정한다고 가정하자
업데이트할 때는 최종 수정시간을 DB 내 현재 시간으로 수정한다.
테스트시 업데이트가 잘되었다면 1이라는 값이 출력될 것이다.
여기서 주의할 점은 regdate 컬럼은 최초 생성 시간이므로 건드리지 않는다.
BoardMapper 인터페이스
BoardMapper.xml 파일
BoardMapperTests 클래스
4. 비즈니스 계층
비즈니스 계층은 고객의 요구사항을 반영하는 계층으로 프레젠테이션 계층과 영속 계층의 중간 다리 역할을 하게 된다.
영속 계층은 데이터베이스를 기준으로 해서 설계를 나눠 구현하지만, 비즈니스 계층은 로직을 기준으로 해서 처리하게 된다.
ex) 쇼핑몰에서 상품을 구매한다는 가정
일반적으로 비즈니스 영역에 있는 객체들은 서비스(Service)라는 용어를 많이 사용한다.
1) 비즈니스 계층의 설정
비즈니스 계층을 위해서 프로젝트 내에 org.zerock.service 패키지를 생성하자
설계를 할 때 각 계층 간의 연결은 인터페이스를 이용해서 느슨한 결합(연결)을 해야한다.
BoardService 인터페이스와 인터페이스를 구현한 BoardServiceImpl 클래스를 선언하자
BoardService 인터페이스
BoardServiceImpl 클래스에 가장 중요한 부분은 @Service 어노테이션이다.
@Service는 계층 구조상 주로 비즈니스 영역을 담당하는 객체임을 표시하기 위해 사용한다.
BoardServiceImpl 클래스가 정상적으로 동작하기 위해서는 BoardMapper 객체가 필요하다.
@Autowired와 같이 직접 설정해주거나 Setter를 이용해서 처리할 수도 있다.
여기서는 Lombok을 이용하여 아래와 같이 처리하도록 하자
스프링 4.3부터는 단일 파라미터를 받는 생성자의 경우에는 필요한 파라미터를 자동으로 주입할 수 있다.
@AllArgsConstructor 어노테이션은 모든 파라미터를 이용하는 생성자를 만들기 때문에 실제 코드는 아래와 같이 BoardMapper를 주입받는 생성자가 만들어지게된다.
2) 스프링의 서비스 객체 설정(root-context.xml)
비즈니스 계층의 인터페이스와 구현 클래스가 생성되었다면, 이를 스프링의 빈으로 인식하기 위해서 root-context.xml에 @Service 어노테이션이 있는 org.zerock.service 패키지를 스캔하도록 추가해야한다.
root-context.xml로 가서 Namespaces 탭을 클릭 후 context 체크를 하자
그 후에 <context:component-scan> 태그를 이용하여 패키지를 스캔할 수 있도록 설정하자
3) 비즈니스 계층의 구현과 테스트
BoardMapper와 BoardService, BoardServiceImpl에 대한 구조 설정이 완료되었으니
이제 src/test/java 밑에 org.zerock.service 패키지를 생성한 뒤
BoardServiceTests 클래스를 작성하여 테스트 작업을 진행해보자
BoardService 객체가 생성되고 BoardMapper가 주입되었다면 BoardService 객체와 데이터베이스 관련 로그가 같이 출력될 것이다.
4) 등록 작업의 구현과 테스트
등록 작업은 BoardServiceImpl에서 파라미터로 전달되는 BoardVO 타입의 객체를 BoardMapper를 통해서 처리한다.
BoardServiceImpl 클래스
BoardService는 void 타입으로 설계되었으므로 mapper.insertSelectKey()의 반환 값인 int를 사용하지 않고 있지만, 필요하다면 예외 처리나 void 대신 int 타입을 이용해서 사용할 수 있다
BoardServiceTests 클래스
mapper의 insertSelectKey()를 이용해서 나중에 생성된 게시물의 번호를 확인할 수 있게 작성한 테스트 코드이다.
테스트를 해보면 결과는 아래와 같이 생성된 게시물의 번호를 확인할 수 있다.
5) 목록(리스트) / 조회 / 삭제/수정 작업의 구현과 테스트
BoardServiceImpl 클래스
BoardServiceTests 클래스
테스트 결과
5. 프레젠테이션(웹) 계층의 CRUD 구현
1) Controller의 작성
Controller를 만들기 전에 현재 원하는 기능을 호출하는 방식에 대해 다음과 같이 테이블로 정리하는 것이 좋다!
From 항목은 해당 URL을 호출하기 위해서 별도의 입력화면이 필요하다는 것을 의미한다.
이제 org.zerock.controller 패키지 안에 BoardController 생성하자
@Controller 어노테이션을 추가해서 스프링의 빈으로 인식할 수 있도록 하고 @RequestMapping을 통해서 '/board'로 시작하는 모든 처리를 BoardController가 하도록 지정한다.
(org.zerock.controller 패키지는 servlet-context.xml에 기본 설정되어 있어 별도 설정 필요 X)
2) 목록에 대한 처리와 테스트(스프링 테스트 기능 사용)
BoardController에서 전체 목록을 가져오는 처리를 먼저 해보도록 하겠다.
BoardController는 BoardService 타입의 객체와 같이 연동해야 하므로 의존성에 대한 처리도 같이 해야한다.
BoardController는 BoardService에 대해서 의존적이므로 @AllArgsConstructor를 이용해서 생성자를 만들고 자동으로 주입하도록 한다. 만일 생성자를 만들지 않을 경우에는 @Setter(onMethod_ ={@Autowird})를 이용해서 처리한다.
list()는 게시물의 목록을 전달해야 하므로 Model을 파라미터로 지정하여 이를 통해서 BoardServiceImpl 객체의 getList() 결과를 담아 전달한다(addAttirbute)
src/test/java에 org.zerock.controller 패키지에 BoardControllerTests 클래스를 선언하여 테스를해보자!
테스트 코드는 기존과 좀 다르다.
그 이유는 웹을 개발할 때 매번 URL을 테스트 하기 위해서 Tomcat과 같은 WAS를 실행하는 불편한 단계를 생략하기 위해서이다.
스프링의 테스트 기능을 활용하면 개발 당시에 Tomcat을 실행하지 않고도 스프링과 웹 URL을 테스트할 수 있다.
WAS를 실행하지 않기 위해서는 약간의 추가적인 코드가 필요하다.
<테스트 코드 설명>
테스트 클래스의 선언부에는 @WebAppConfiguration 어노테이션을 적용한다.
해당 어노테이션은 Servlet의 SerlvetContext를 이용하기 위해서인데 스프링에서는 WebApplicationContext라는 존재를 이용하기 위해서이다.
@Before 어노테이션이 적용된 setUp()에서는 import할 때 JUnit을 이용해야한다. 해당 어노테이션이 적용된 메서드는 모든 테스트 전에 매번 실행되는 메서드가 된다.
MockMvc는 가짜 mvc라고 생각하면된다.
가짜로 URL과 파라미터 등을 브라우저에서 사용하는 것처럼 만들어서 Controller를 실행해 볼 수 있다.
testList()는 MockMvcRequestBuilders라는 존재를 이용해서 GET 방식의 호출을 한다.
이후에는 BoardController의 getList()에서 반환된 결과를 이용해서 Model에 어떤 데이터들이 담겨있는지 확인을 한다.
3) 등록 처리와 테스트
BoardController에 POST 방식으로 처리되는 register()를 작성하자
register() 메서드는 String 리턴 타입으로 지정하고, RedirectAttributes를 파라미터로 지정한다.
등록 작업이 끝난 후 다시 목록 화면으로 이동하기 위함인데 추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 RedirectAttributes를 이용한다.
리턴 시에는 redirec: 접두어를 사용하는데 스프링 MVC가 내부적으로 response.sendRedirect()를 처리해준다.
테스트 코드는 아래와 같이 작성하자
테스트할 때 MockMvcRequestBuilder의 post()를 이용하면 POST 방식으로 데이터를 전달할 수 있고, param()을 이용해서 전달해야 하는 파라미터들을 지정할 수 있다.
4) 조회 / 수정 / 삭제 처리와 테스트
조회, 수정, 삭체 처리와 테스트는 한꺼번에 정리하도록 하겠다!
[BoardController 클래스]
조회는 GET 방식으로 처리하므로, @GetMapping 어노테이션을 이용한다.
get() 메서드에는 bno 값을 좀 더 명시적으로 처리하는 @RequestParam을 이용해서 지정한다.
화면 쪽으로 해당 번호의 게시물을 전달해야 하므로 Model을 파라미터로 지정하였다.
수정 작업과 삭제 작업은 POST 방식으로 처리하므로, @PostMapping 어노테이션을 사용하였다.
수정 작업은 변경된 내용을 수집해서 BoardVO 파라미터로 처리하고, BoardService를 호출한다. 수정 작업을 시작하는 화면의 경우 GET 방식으로 접근하지만 실제 작업은 POST 방식으로 동작한다.
삭제는 반드시 POST 방식으로만 처리한다.
remove() 메서드는 삭제 후 페이지의 이동이 필요하므로 RedirectAttributes를 파라미터로 사용하고 redirect: 접두어를 이용해서 삭제 처리 후에 다시 목록 페이지로 이동한다.
[테스트 코드]
테스트 결과
p223부터는 화면처리 부분인데 이 부분은 따로 정리하지 않도록 하겠다.
'TIL > Spring' 카테고리의 다른 글
[TIL] 컴포넌트 스캔 (0) | 2022.08.19 |
---|---|
[TIL] 싱글톤, 싱글톤 컨테이너, 싱글톤 방식의 주의점 (0) | 2022.08.16 |
[TIL] Spring MVC(Spring 5.0.7 버전) (0) | 2022.07.25 |
[TIL] Spring 5.0.7 버전 다루기(5.x 버전) (0) | 2022.07.25 |
[SIST] Spring_days10_MyBatis (0) | 2022.07.22 |