package sp.aop;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice; // 이후라고하면 MethodAfterAdvice 구현받으면 된다.
//추가
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
//Advice : 모든 Class에 공통으로 사용할 기능( 소스코드 -> method 중심 )
//interface 대신(OOP 방식) 에 Advice Class 를 작성해서 구현(AOP 방식) -> Aspect
//실행 위치를 특정 Method 의 앞에서 실행 -> MethodBeforeAdvice 를 구현
public class BeforeLogAdvice implements MethodBeforeAdvice { //공통관심 모듈 클래스를 만든다.
private Log log = LogFactory.getLog(getClass()); //Log 객체를 얻어오는 문장
//1) Spring의 AOP(method 중심) -> 핵심 CLass의 Method
//2) 생성된 객체를 배열로 받아온다.
//3) target Class(핵심 Class의 객체를 얻어온다.)
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
//TODO auto-generated method stub
log.info(method.toString()+" Method : "+target+"에서 호출 전!");
}//before() END
}//BeforeLogAdvice CLASS END
package sp.aop;
//모든 핵심 class 에서 공통으로 사용할 목적으로의 Method 작성, 인터페이스는 메소드 옆에 {} 지정하면 안된다.
public interface TestService {
// 해당 메소드들은 public abstract void 와 같이 추상메소드로 작성된다.
public void save(String msg);// 입력
public void write(); //출력
}//TestService INTERFACE END
app.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<!-- 1. 핵심 Class 빈즈 등록 -->
<bean id="testServiceImpl" class="sp.aop.TestServiceImpl" />
<!-- 2. Advice Class 빈즈 등록 -->
<bean id="beforeLog" class="sp.aop.BeforeLogAdvice" />
<!-- 3.PointCut 생성 -> 어느 위치에서 AOP Method 를 지정해서 실행
value="접근 지정 반환명 package 명... Class 명 하위 package 명(..) 특정 Method명 0개 이상"
(*) 매개변수 한 개 표시, (*,*) 매개변수 2개 표시 -->
<bean id="writePointcut"
class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*write.*" />
</bean>
<!-- 4. Advice + PointCut(Advisor) 설정 -->
<bean id="testAdvisor"
class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="beforeLog" />
<property name="pointcut" ref="writePointcut" />
</bean>
</beans>
package sp.aop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; // xml문서를 찾을 수 있게 하는 lib
public class ResultMain {
public static void main(String[] args) {
String path="sp/aop/app.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(path);
//TestService service= (TestService)context.getBean("testServiceImpl");
//원래는 위의 것이 일반적이지만, AOP 객체를 얻어오기 위해서는, 밑에처럼 AOP객체를 얻는다.
//AOP객체를 생성 -> Advisor 작동 -> Advice + pointcut 실행
TestService service = (TestService)context.getBean("testService");
service.save("AOP 적용 연습");
//before advice(before() 작동 실행) -> 실행상태에서 처리
service.write();
}//main()END
}//CLASS END
package com.ohj.controller;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
//Test for Controller
@WebAppConfiguration
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml", "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {
// MockMvc : 가짜 MVC
@Setter(onMethod_= {@Autowired})
private WebApplicationContext ctx;
private MockMvc mockMvc;
//무조건 실행하는 메소드
//@Before : import 할 때 JUnit을 이용.
//이 어노테이션이 적용 된 메서드는 모든 테스트 전에 매번 실행 되는 매서드가 된다.
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
}
//MockMvcRequestBuilders를 이용하면 마치 GET방식의 호출을 할 수 있다.
@Test
public void testList() throws Exception{
//BoardController.java쪽
log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
}
@Test
public void testRegister() throws Exception{
String resultPage= mockMvc
.perform(MockMvcRequestBuilders.post("/board/register")
.param("title", "테스트 새글 제목")
.param("content", "테스트 새글 내용")
.param("writer", "user00"))
.andReturn().getModelAndView().getViewName();
log.info(resultPage);
}
}
package com.ohj.controller;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.ui.Model; // 스프링은 모델이다.(모델 앤 뷰 객체)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.ohj.domain.BoardVO;
import com.ohj.service.BoardService;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
private BoardService service;
@GetMapping("/list")
public void list(Model model) {
log.info("list");
model.addAttribute("list", service.getList());
}// list() END
// 이 리스트 메소드가 호출되려면 /board/list 로 되어야 한다.
//등록처리 와 테스트
/*public BoardController()에 POST방식으로 처리 되는 register()추가
리턴 타입 : String
리다이렉트어튜비트 파라미터 : 등록 작업이 끝난 후, 다시 목록 화면으로 이동하기 위해
추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 이용.
리턴 할 떄에는 'redirect:'라는 접두어를 사용!
스프링 MVC가 내부적으로 response.sendRedirect()를 처리해주기 때문에 편리하다.
*/
@PostMapping("register")
public String register(BoardVO board, RedirectAttributes rttr) {
log.info("register:"+board);
service.register(board);
rttr.addFlashAttribute("result",board.getBno());
return "redirect:/board/list";
}
}//BoardController CLASS END
// BoardControllerTests :
//log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
스프링 MVC(기본프레임워크) + mybatis(DB프레임워크)
주제 : 게시물 관리
작업순서
1. 요구분석(요구사항) - Naming/내부구조(비즈니스영역(로직관련/ Service)/영속영역(데이터관련/domain,mapper)/프레젠테이션영역(사용자UI관련))
2. 개발환경 구축
3. 플랫폼 구축 (스프링 MVC 프로젝트 생성)
4. 데이터베이스 테이블
5. 데이터베이스 연결 테스트
6. 영속 계층 테스트
7. 비즈니스 계층 테스트
1. 요구 분석 : CRUD 기능 구현
2.JDK 1.8 이상(JAVA(jdk(환경변수(user/system(path)) 잡기)/jre)
*톰캣과 오라클은 8080 포트를 공통으로 잡고 있기 때문에 포트번호를 각각 따로 설정해준다.
오라클 11g EX (c:\에 다운)
톰캣 9.0 이상 (c:\에 다운)
이클립스 + STS플러그인(마켓플레이스에서 다운 가능)
마이바티스 플러그인(마켓플레이스에서 다운 가능)/workspace(인코딩 UTF-8설정해주기)
3.pom.xml
Maven을 위한 통합 버전 관리
스프링 5.3 아성
자바 1.8 이상
Log4j(콘솔창 로그 찍어주는 것/log.info(""+__))/Juinit(단위별 테스트/@Test)
4. 오라클
tbl_board
bno(PK(Number))
titie(varchar2)
content(varchar2)
writer(varchar2)
regdate(Date(default sysdate))
updatedate(Date(default sysdate))
오라클 - 오라클디벨로퍼
- cmd
1. 계정/비밀번호 생성
2. 접속/ 리소스 원한 설정
3. 테이블 생성
bno :Pk,title,writer,contents,regdate(등록날짜),updatedate(수정날짜)
4. dummy data
5.데이터베이스 연결테스트
- (root-context.xml 수집
1.하카리 환경설정(*참고 : JDBC에 비해 약 15~30% 빠른 처리 속도를 가짐)
2.데이터소스 객체
3.sqlsessionfactory 객체
4.마이바티스 관련 파일 위치)
6.영속 계층 테스트
7.비즈니스 계층 테스트
lombok
-@setter
-@AllArgconstructher
*lombok.jar
java jar lombok.jar
Java Bean 형태의 객체 생성시
1. getter/setter(@setter)
2. 생성자(@AllArgconstructher)
3. toString() wowjddml
Test용 클래스
1.
@단위테스트
@Context 환경 파일 위치
@log 출력
public class __ {
@setter(onMethod = Autowired)
2. 멤버변수
3.실제테스트 메서드(){
@test
}
}
가상으로 URL과 파라미터등을 마치 브라우저에서 사용하는 것처럼 만들어서 컨트롤러를 실행 해 볼 수 있다.
등록처리 와 테스트
public BoardController()에 POST방식으로 처리 되는 register()추가 리턴 타입 : String 리다이렉트어튜비트 파라미터 : 등록 작업이 끝난 후, 다시 목록 화면으로 이동하기 위해 추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 이용.
리턴 할 떄에는 'redirect:'라는 접두어를 사용! 스프링 MVC가 내부적으로 response.sendRedirect()를 처리해주기 때문에 편리하다.
테스트 코드
의 post() : POST방식으로 데이터를 전달 할 수 있다.
param(): 전달해야 하는 파라미터들을 지정 할 수 있다. 마치 <input> 처럼
이와 같은 방식으로 코드를 작성하면
최초 작성시에는 일이 많게 느껴지지만,
오류 매번 입력할 필요가 없기 때문에 오류 발생 및 수정하는 경우, 반복적인 테스트가 수월함
package com.ohj.controller;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
//Test for Controller
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml",
"file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {
// MockMvc : 가짜 MVC
@Setter(onMethod_= {@Autowired})
private WebApplicationContext ctx;
private MockMvc mockMvc;
//무조건 실행하는 메소드
//@Before : import 할 때 JUnit을 이용.
//이 어노테이션이 적용 된 메서드는 모든 테스트 전에 매번 실행 되는 매서드가 된다.
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
}
//MockMvcRequestBuilders 를 이용하면 마치 GET방식의 호출을 할 수 있다.
@Test
public void testList() throws Exception{
log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
}
}
상세보기
조회 처리와 테스트
@GetMapping : 특별한 경우가 아니라면, 조회는 GET방식으로 처리
@RequestParam : bno 값을 좀 더 명시적으로 처리하기 위함
Model 파라미터 : 화면 쪽으로 해당 게시번호의 게시물을 전달하기 위함
package com.ohj.controller;
import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.ui.Model; // 스프링은 모델이다.(모델 앤 뷰 객체)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.ohj.domain.BoardVO;
import com.ohj.service.BoardService;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
private BoardService service;
@GetMapping("/list")
public void list(Model model) {
log.info("list");
model.addAttribute("list", service.getList());
}// list() END
// 이 리스트 메소드가 호출되려면 /board/list 로 되어야 한다.
//등록처리 와 테스트
/*public BoardController()에 POST방식으로 처리 되는 register()추가
리턴 타입 : String
리다이렉트어튜비트 파라미터 : 등록 작업이 끝난 후, 다시 목록 화면으로 이동하기 위해
추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 이용.
리턴 할 떄에는 'redirect:'라는 접두어를 사용!
스프링 MVC가 내부적으로 response.sendRedirect()를 처리해주기 때문에 편리하다.
*/
@PostMapping("register")
public String register(BoardVO board, RedirectAttributes rttr) {
log.info("register:"+board);
service.register(board);
rttr.addFlashAttribute("result",board.getBno());
return "redirect:/board/list";
}
// Model 파라미터 : 화면 쪽으로 해당 게시번호의 게시물을 전달하기 위함
@GetMapping("/get")
public void get(@RequestParam("bno") Long bno, Model model) {
log.info("/get");
model.addAttribute("board",service.get(bno));
}
}//BoardController CLASS END
// BoardControllerTests :
//log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
package com.ohj.controller;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
//Test for Controller
@WebAppConfiguration
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml", "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {
// MockMvc : 가짜 MVC
@Setter(onMethod_= {@Autowired})
private WebApplicationContext ctx;
private MockMvc mockMvc;
//무조건 실행하는 메소드
//@Before : import 할 때 JUnit을 이용.
//이 어노테이션이 적용 된 메서드는 모든 테스트 전에 매번 실행 되는 매서드가 된다.
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
}
//MockMvcRequestBuilders를 이용하면 마치 GET방식의 호출을 할 수 있다.
@Test
public void testList() throws Exception{
//BoardController.java쪽
log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
}
@Test
public void testRegister() throws Exception{
String resultPage= mockMvc
.perform(MockMvcRequestBuilders.post("/board/register")
.param("title", "테스트 새글 제목")
.param("content", "테스트 새글 내용")
.param("writer", "user00"))
.andReturn().getModelAndView().getViewName();
log.info(resultPage);
}
@Test
public void testGet() throws Exception{
log.info(mockMvc.perform(MockMvcRequestBuilders
.get("/board/get")
.param("bno","2"))
.andReturn()
.getModelAndView().getModelMap()
);
}
}
수정 처리와 테스트
변경된 내용을 수집해서 BoardVO파라미터로 처리한 후
BoardService를 호출
수정 작업을 시작하는 화면의 경우 GET방식으로 접금하지만
실제 작업은 PoST 방식으로 동작하므로 @PostMapping을 이용하여 처리!
service.modify()는 수정 여부를 boolean으로 처리하므로
성공한 경우에만 RedirectArrtibutes에 추가
package com.ohj.controller;
import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.ui.Model; // 스프링은 모델이다.(모델 앤 뷰 객체)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.ohj.domain.BoardVO;
import com.ohj.service.BoardService;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
private BoardService service;
@GetMapping("/list")
public void list(Model model) {
log.info("list");
model.addAttribute("list", service.getList());
}// list() END
// 이 리스트 메소드가 호출되려면 /board/list 로 되어야 한다.
//등록처리 와 테스트
/*public BoardController()에 POST방식으로 처리 되는 register()추가
리턴 타입 : String
리다이렉트어튜비트 파라미터 : 등록 작업이 끝난 후, 다시 목록 화면으로 이동하기 위해
추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 이용.
리턴 할 떄에는 'redirect:'라는 접두어를 사용!
스프링 MVC가 내부적으로 response.sendRedirect()를 처리해주기 때문에 편리하다.
*/
@PostMapping("register")
public String register(BoardVO board, RedirectAttributes rttr) {
log.info("register:"+board);
service.register(board);
rttr.addFlashAttribute("result",board.getBno());
return "redirect:/board/list";
}
// Model 파라미터 : 화면 쪽으로 해당 게시번호의 게시물을 전달하기 위함
@GetMapping("/get")
public void get(@RequestParam("bno") Long bno, Model model) {
log.info("/get");
model.addAttribute("board",service.get(bno));
}
@PostMapping("/modify")
public String modify(BoardVO board, RedirectAttributes rttr) {
log.info("modify:"+board);
if(service.modify(board)) {
rttr.addFlashAttribute("result","success");
}
return "redirect:/board/list";
}
}//BoardController CLASS END
// BoardControllerTests :
//log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
package com.ohj.controller;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
//Test for Controller
@WebAppConfiguration
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml", "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml"})
@Log4j
public class BoardControllerTests {
// MockMvc : 가짜 MVC
@Setter(onMethod_= {@Autowired})
private WebApplicationContext ctx;
private MockMvc mockMvc;
//무조건 실행하는 메소드
//@Before : import 할 때 JUnit을 이용.
//이 어노테이션이 적용 된 메서드는 모든 테스트 전에 매번 실행 되는 매서드가 된다.
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
}
//MockMvcRequestBuilders를 이용하면 마치 GET방식의 호출을 할 수 있다.
@Test
public void testList() throws Exception{
//BoardController.java쪽
log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
}
@Test
public void testRegister() throws Exception{
String resultPage= mockMvc
.perform(MockMvcRequestBuilders.post("/board/register")
.param("title", "테스트 새글 제목")
.param("content", "테스트 새글 내용")
.param("writer", "user00"))
.andReturn().getModelAndView().getViewName();
log.info(resultPage);
}
@Test
public void testGet() throws Exception{
log.info(mockMvc.perform(MockMvcRequestBuilders
.get("/board/get")
.param("bno","2"))
.andReturn()
.getModelAndView().getModelMap()
);
}
@Test
public void testModify() throws Exception{
String resultPage= mockMvc.perform(MockMvcRequestBuilders
.post("/board/modify")
.param("bno", "1")
.param("title", "테스트 수정된 제목")
.param("content", "테스트 수정된 내용")
.param("writer", "user00"))
.andReturn()
.getModelAndView()
.getViewName();
}
}
삭제 처리와 테스트
삭제는 반드시 POST방식으로만 처리
package com.ohj.controller;
import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.ui.Model; // 스프링은 모델이다.(모델 앤 뷰 객체)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.ohj.domain.BoardVO;
import com.ohj.service.BoardService;
import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {
private BoardService service;
@GetMapping("/list")
public void list(Model model) {
log.info("list");
model.addAttribute("list", service.getList());
}// list() END
// 이 리스트 메소드가 호출되려면 /board/list 로 되어야 한다.
//등록처리 와 테스트
/*public BoardController()에 POST방식으로 처리 되는 register()추가
리턴 타입 : String
리다이렉트어튜비트 파라미터 : 등록 작업이 끝난 후, 다시 목록 화면으로 이동하기 위해
추가적으로 새롭게 등록된 게시물의 번호를 같이 전달하기 위해서 이용.
리턴 할 떄에는 'redirect:'라는 접두어를 사용!
스프링 MVC가 내부적으로 response.sendRedirect()를 처리해주기 때문에 편리하다.
*/
@PostMapping("register")
public String register(BoardVO board, RedirectAttributes rttr) {
log.info("register:"+board);
service.register(board);
rttr.addFlashAttribute("result",board.getBno());
return "redirect:/board/list";
}
// Model 파라미터 : 화면 쪽으로 해당 게시번호의 게시물을 전달하기 위함
@GetMapping("/get")
public void get(@RequestParam("bno") Long bno, Model model) {
log.info("/get");
model.addAttribute("board",service.get(bno));
}
@PostMapping("/modify")
public String modify(BoardVO board, RedirectAttributes rttr) {
log.info("modify:"+board);
if(service.modify(board)) {
rttr.addFlashAttribute("result","success");
}
return "redirect:/board/list";
}
@PostMapping("/remove")
public String remove(@RequestParam("bno") Long bno, RedirectAttributes rttr) {
log.info("remove:"+bno);
if(service.remove(bno)) {
rttr.addFlashAttribute("result","success");
}
return "redirect:/board/list";
}
}//BoardController CLASS END
// BoardControllerTests :
//log.info(mockMvc.perform(MockMvcRequestBuilders.get("/board/list")).andReturn().getModelAndView().getModelMap());
//BoardMapper.java
package com.ohj.mapper;
import java.util.List;
//XML파일에 작성 해놨음으로 주석 처리한다.
//import org.apache.ibatis.annotations.Select;
import com.ohj.domain.BoardVO;
public interface BoardMapper {
//BoardMapper.XML파일에 작성 해놨음으로 주석 처리한다.
// @Select("select * from tbl_board where bno > 0")
public List<BoardVO> getList();
//단순히 insert만 해주는 메소드
public void insert(BoardVO board);
//insert를 해줬을 때 해당 VO 멤버 값을 꺼낼 수 있는 메소드
public void insertSelectKey(BoardVO board);
}
<insert id="insert"> : 단순히 시퀀스의 다음 값을 구해서 insert 할 때 사용
insert into SQL 문:
몇 건의 데이터가 변경 되었는지 만을 알려주기 떄문에
추가된 데이터의 PK값을 알 수는 없지만,
한번의 SQL처리만으로 작업이 완료 된다는 장점이 있다.
<insert id="insertSelectKey"> : @SelectKey 라는 어노테이션을 사용.
@SelestKey:
주로 PK 값을 미이(before)SQL문을 통하여 처리해두고,
특정한 이름으로 결과를 보관하는 방식
@Insert 할 때 SQL 문을 보면 #{bno}와 같이 이미 처리된 결과를 이용.
테스트 코드의
각 메서드에서 마지막에 log.info(board)를 작성하는 이유:
lombok이 만들어조는 toString()을 이용하여
bno 멤버 변수(인스턴스 변수)의 값을 알아보기 위함.
//BoardMapperTests.java
package com.ohj.mapper;
import org.junit.Test; // 처리 과정을 찍어주는 임포트
import org.junit.runner.RunWith; // 처리 과정을 찍어주는 임포트
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.ohj.domain.BoardVO;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {
@Setter(onMethod_ = @Autowired)
BoardMapper mapper;
@Test
public void testGetList() {
// 리스트 반환(board객체를 print 출력하면 toString 메소드에 의해서 객체에 대한 번지수가 나온다.)
mapper.getList().forEach(board -> log.info(board));
}// testGetList END
@Test
public void testInsert() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글");
board.setContent("새로 작성하는 내용");
board.setWriter("newbie");
mapper.insert(board);
log.info(board);
}//testInsert() END
@Test
public void testInsertSelectKey() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글 SelectKey");
board.setContent("새로 작성하는 내용 SelectKey");
board.setWriter("newbie");
mapper.insertSelectKey(board);
//이유 : 롬복이 자동으로 tosrtinf을 이용하요 객체를 만들어주고 그 객체가 가지고 있는 값을 각각 확인해보기 위해서
log.info(board);
}//testInsertSelectKey() END
}// BoardMapperTests END
실행 결과
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@1a968a59, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@4667ae56, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@77cd7a0, org.springframework.test.context.support.DirtiesContextTestExecutionListener@204f30ec, org.springframework.test.context.transaction.TransactionalTestExecutionListener@e25b2fe, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@754ba872]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@589838eb, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@42dafa95, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@6500df86, org.springframework.test.context.support.DirtiesContextTestExecutionListener@402a079c, org.springframework.test.context.transaction.TransactionalTestExecutionListener@59ec2012, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@4cf777e8]
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from URL [file:src/main/webapp/WEB-INF/spring/root-context.xml]
INFO : org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@7791a895: startup date [Wed May 13 11:36:14 KST 2020]; root of context hierarchy
INFO : org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
INFO : jdbc.connection - 1. Connection opened
INFO : jdbc.audit - 1. Connection.new Connection returned
INFO : com.zaxxer.hikari.pool.PoolBase - HikariPool-1 - Driver does not support get/set network timeout for connections. (net.sf.log4jdbc.sql.jdbcapi.ConnectionSpy.getNetworkTimeout()I)
INFO : jdbc.audit - 1. Connection.setReadOnly(false) returned
INFO : jdbc.audit - 1. Connection.setAutoCommit(true) returned
INFO : jdbc.audit - 1. Connection.isValid(1) returned true
INFO : jdbc.audit - 1. Connection.getTransactionIsolation() returned 2
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
INFO : jdbc.connection - 2. Connection opened
INFO : jdbc.audit - 2. Connection.new Connection returned
INFO : jdbc.audit - 2. Connection.setReadOnly(false) returned
INFO : jdbc.audit - 2. Connection.setAutoCommit(true) returned
INFO : jdbc.connection - 3. Connection opened
INFO : jdbc.audit - 3. Connection.new Connection returned
INFO : jdbc.audit - 3. Connection.setReadOnly(false) returned
INFO : jdbc.audit - 3. Connection.setAutoCommit(true) returned
INFO : jdbc.connection - 4. Connection opened
INFO : jdbc.audit - 4. Connection.new Connection returned
INFO : jdbc.audit - 4. Connection.setReadOnly(false) returned
INFO : jdbc.audit - 4. Connection.setAutoCommit(true) returned
INFO : jdbc.connection - 5. Connection opened
INFO : jdbc.audit - 5. Connection.new Connection returned
INFO : jdbc.audit - 5. Connection.setReadOnly(false) returned
INFO : jdbc.audit - 5. Connection.setAutoCommit(true) returned
INFO : jdbc.audit - 1. Connection.getAutoCommit() returned true
INFO : com.ohj.persistence.DataSourceTests - org.apache.ibatis.session.defaults.DefaultSqlSession@2d6764b2
INFO : com.ohj.persistence.DataSourceTests - HikariProxyConnection@1877062907 wrapping net.sf.log4jdbc.sql.jdbcapi.ConnectionSpy@79351f41
INFO : jdbc.connection - 6. Connection opened
INFO : jdbc.audit - 6. Connection.new Connection returned
INFO : jdbc.audit - 6. Connection.setReadOnly(false) returned
INFO : jdbc.audit - 6. Connection.setAutoCommit(true) returned
INFO : jdbc.audit - 1. Connection.clearWarnings() returned
INFO : jdbc.audit - 1. Connection.getAutoCommit() returned true
INFO : jdbc.connection - 7. Connection opened
INFO : jdbc.audit - 7. Connection.new Connection returned
INFO : jdbc.audit - 7. Connection.setReadOnly(false) returned
INFO : jdbc.audit - 7. Connection.setAutoCommit(true) returned
INFO : jdbc.connection - 8. Connection opened
INFO : jdbc.audit - 8. Connection.new Connection returned
INFO : jdbc.audit - 8. Connection.setReadOnly(false) returned
INFO : jdbc.audit - 8. Connection.setAutoCommit(true) returned
INFO : jdbc.connection - 9. Connection opened
INFO : jdbc.audit - 9. Connection.new Connection returned
INFO : jdbc.audit - 9. Connection.setReadOnly(false) returned
INFO : jdbc.audit - 9. Connection.setAutoCommit(true) returned
INFO : jdbc.connection - 10. Connection opened
INFO : jdbc.audit - 10. Connection.new Connection returned
INFO : jdbc.audit - 10. Connection.setReadOnly(false) returned
INFO : jdbc.audit - 10. Connection.setAutoCommit(true) returned
INFO : jdbc.audit - 1. PreparedStatement.new PreparedStatement returned
INFO : jdbc.audit - 1. Connection.prepareStatement(select * from tbl_board where bno > 0) returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@7096b474
INFO : jdbc.sqlonly - select * from tbl_board where bno > 0
INFO : jdbc.sqltiming - select * from tbl_board where bno > 0
{executed in 56 msec}
INFO : jdbc.audit - 1. PreparedStatement.execute() returned true
INFO : jdbc.resultset - 1. ResultSet.new ResultSet returned
INFO : jdbc.audit - 1. PreparedStatement.getResultSet() returned net.sf.log4jdbc.sql.jdbcapi.ResultSetSpy@2f9a01c1
INFO : jdbc.resultset - 1. ResultSet.getMetaData() returned oracle.jdbc.driver.OracleResultSetMetaData@458342d3
INFO : jdbc.resultset - 1. ResultSet.getType() returned 1003
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(BNO) returned 1
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(TITLE) returned 테스트제목
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(CONTENT) returned 테스트 내용
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(WRITER) returned user00
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(REGDATE) returned 2020-05-12 10:24:30.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(UPDATEDATE) returned 2020-05-12 10:24:30.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(BNO) returned 2
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(TITLE) returned 테스트제목
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(CONTENT) returned 테스트 내용1
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(WRITER) returned user01
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(REGDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(UPDATEDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(BNO) returned 3
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(TITLE) returned 테스트제목
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(CONTENT) returned 테스트 내용2
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(WRITER) returned user02
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(REGDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(UPDATEDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(BNO) returned 4
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(TITLE) returned 테스트제목
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(CONTENT) returned 테스트 내용3
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(WRITER) returned user03
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(REGDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(UPDATEDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(BNO) returned 5
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(TITLE) returned 테스트제목
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(CONTENT) returned 테스트 내용4
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getString(WRITER) returned user04
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(REGDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultset - 1. ResultSet.getTimestamp(UPDATEDATE) returned 2020-05-12 10:24:42.0
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultsettable -
|----|------|--------|-------|----------------------|----------------------|
|bno |title |content |writer |regdate |updatedate |
|----|------|--------|-------|----------------------|----------------------|
|1 |테스트제목 |테스트 내용 |user00 |2020-05-12 10:24:30.0 |2020-05-12 10:24:30.0 |
|2 |테스트제목 |테스트 내용1 |user01 |2020-05-12 10:24:42.0 |2020-05-12 10:24:42.0 |
|3 |테스트제목 |테스트 내용2 |user02 |2020-05-12 10:24:42.0 |2020-05-12 10:24:42.0 |
|4 |테스트제목 |테스트 내용3 |user03 |2020-05-12 10:24:42.0 |2020-05-12 10:24:42.0 |
|5 |테스트제목 |테스트 내용4 |user04 |2020-05-12 10:24:42.0 |2020-05-12 10:24:42.0 |
|----|------|--------|-------|----------------------|----------------------|
INFO : jdbc.resultset - 1. ResultSet.next() returned false
INFO : jdbc.resultset - 1. ResultSet.close() returned void
INFO : jdbc.audit - 1. Connection.getMetaData() returned oracle.jdbc.driver.OracleDatabaseMetaData@665e9289
INFO : jdbc.audit - 1. PreparedStatement.isClosed() returned false
INFO : jdbc.audit - 1. PreparedStatement.close() returned
INFO : jdbc.audit - 1. Connection.clearWarnings() returned
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=1, title=테스트제목, content=테스트 내용, writer=user00, regdate=Tue May 12 10:24:30 KST 2020, updateDate=Tue May 12 10:24:30 KST 2020)
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=2, title=테스트제목, content=테스트 내용1, writer=user01, regdate=Tue May 12 10:24:42 KST 2020, updateDate=Tue May 12 10:24:42 KST 2020)
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=3, title=테스트제목, content=테스트 내용2, writer=user02, regdate=Tue May 12 10:24:42 KST 2020, updateDate=Tue May 12 10:24:42 KST 2020)
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=4, title=테스트제목, content=테스트 내용3, writer=user03, regdate=Tue May 12 10:24:42 KST 2020, updateDate=Tue May 12 10:24:42 KST 2020)
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=5, title=테스트제목, content=테스트 내용4, writer=user04, regdate=Tue May 12 10:24:42 KST 2020, updateDate=Tue May 12 10:24:42 KST 2020)
INFO : jdbc.audit - 1. Connection.getAutoCommit() returned true
INFO : jdbc.audit - 1. PreparedStatement.new PreparedStatement returned
INFO : jdbc.audit - 1. Connection.prepareStatement(select seq_board.nextval from dual) returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@350ec41e
INFO : jdbc.sqlonly - select seq_board.nextval from dual
INFO : jdbc.sqltiming - select seq_board.nextval from dual
{executed in 8 msec}
INFO : jdbc.audit - 1. PreparedStatement.execute() returned true
INFO : jdbc.resultset - 1. ResultSet.new ResultSet returned
INFO : jdbc.audit - 1. PreparedStatement.getResultSet() returned net.sf.log4jdbc.sql.jdbcapi.ResultSetSpy@69637b10
INFO : jdbc.resultset - 1. ResultSet.getMetaData() returned oracle.jdbc.driver.OracleResultSetMetaData@71984c3
INFO : jdbc.resultset - 1. ResultSet.getType() returned 1003
INFO : jdbc.resultset - 1. ResultSet.next() returned true
INFO : jdbc.resultset - 1. ResultSet.getLong(NEXTVAL) returned 6
INFO : jdbc.resultset - 1. ResultSet.wasNull() returned false
INFO : jdbc.resultsettable -
|--------|
|nextval |
|--------|
|6 |
|--------|
INFO : jdbc.resultset - 1. ResultSet.next() returned false
INFO : jdbc.resultset - 1. ResultSet.close() returned void
INFO : jdbc.audit - 1. Connection.getMetaData() returned oracle.jdbc.driver.OracleDatabaseMetaData@665e9289
INFO : jdbc.audit - 1. PreparedStatement.isClosed() returned false
INFO : jdbc.audit - 1. PreparedStatement.close() returned
INFO : jdbc.audit - 1. PreparedStatement.new PreparedStatement returned
INFO : jdbc.audit - 1. Connection.prepareStatement(insert into tbl_board(bno, title, content, writer)
values (?, ?, ?, ?)) returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@470a696f
INFO : jdbc.audit - 1. PreparedStatement.setLong(1, 6) returned
INFO : jdbc.audit - 1. PreparedStatement.setString(2, "새로 작성하는 글 SelectKey") returned
INFO : jdbc.audit - 1. PreparedStatement.setString(3, "새로 작성하는 내용 SelectKey") returned
INFO : jdbc.audit - 1. PreparedStatement.setString(4, "newbie") returned
INFO : jdbc.sqlonly - insert into tbl_board(bno, title, content, writer) values (6, '새로 작성하는 글 SelectKey', '새로 작성하는
내용 SelectKey', 'newbie')
INFO : jdbc.sqltiming - insert into tbl_board(bno, title, content, writer) values (6, '새로 작성하는 글 SelectKey', '새로 작성하는
내용 SelectKey', 'newbie')
{executed in 3 msec}
INFO : jdbc.audit - 1. PreparedStatement.execute() returned false
INFO : jdbc.audit - 1. PreparedStatement.getUpdateCount() returned 1
INFO : jdbc.audit - 1. PreparedStatement.isClosed() returned false
INFO : jdbc.audit - 1. PreparedStatement.close() returned
INFO : jdbc.audit - 1. Connection.clearWarnings() returned
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=6, title=새로 작성하는 글 SelectKey, content=새로 작성하는 내용 SelectKey, writer=newbie, regdate=null, updateDate=null)
INFO : jdbc.audit - 1. Connection.getAutoCommit() returned true
INFO : jdbc.audit - 1. PreparedStatement.new PreparedStatement returned
INFO : jdbc.audit - 1. Connection.prepareStatement(insert into tbl_board(bno, title, content, writer)
values (seq_board.nextval, ?, ?, ?)) returned net.sf.log4jdbc.sql.jdbcapi.PreparedStatementSpy@28d6290
INFO : jdbc.audit - 1. PreparedStatement.setString(1, "새로 작성하는 글") returned
INFO : jdbc.audit - 1. PreparedStatement.setString(2, "새로 작성하는 내용") returned
INFO : jdbc.audit - 1. PreparedStatement.setString(3, "newbie") returned
INFO : jdbc.sqlonly - insert into tbl_board(bno, title, content, writer) values (seq_board.nextval, '새로 작성하는 글',
'새로 작성하는 내용', 'newbie')
INFO : jdbc.sqltiming - insert into tbl_board(bno, title, content, writer) values (seq_board.nextval, '새로 작성하는 글',
'새로 작성하는 내용', 'newbie')
{executed in 3 msec}
INFO : jdbc.audit - 1. PreparedStatement.execute() returned false
INFO : jdbc.audit - 1. PreparedStatement.getUpdateCount() returned 1
INFO : jdbc.audit - 1. PreparedStatement.isClosed() returned false
INFO : jdbc.audit - 1. PreparedStatement.close() returned
INFO : jdbc.audit - 1. Connection.clearWarnings() returned
INFO : com.ohj.mapper.BoardMapperTests - BoardVO(bno=null, title=새로 작성하는 글, content=새로 작성하는 내용, writer=newbie, regdate=null, updateDate=null)
INFO : jdbc.connection - 11. Connection opened
INFO : jdbc.audit - 11. Connection.new Connection returned
INFO : com.ohj.persistence.JDBCTests - net.sf.log4jdbc.sql.jdbcapi.ConnectionSpy@77602954
INFO : jdbc.connection - 11. Connection closed
INFO : jdbc.audit - 11. Connection.close() returned
INFO : org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@7791a895: startup date [Wed May 13 11:36:14 KST 2020]; root of context hierarchy
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
INFO : jdbc.connection - 1. Connection closed
INFO : jdbc.audit - 1. Connection.close() returned
INFO : jdbc.connection - 2. Connection closed
INFO : jdbc.audit - 2. Connection.close() returned
INFO : jdbc.connection - 3. Connection closed
INFO : jdbc.audit - 3. Connection.close() returned
INFO : jdbc.connection - 4. Connection closed
INFO : jdbc.audit - 4. Connection.close() returned
INFO : jdbc.connection - 5. Connection closed
INFO : jdbc.audit - 5. Connection.close() returned
INFO : jdbc.connection - 6. Connection closed
INFO : jdbc.audit - 6. Connection.close() returned
INFO : jdbc.connection - 7. Connection closed
INFO : jdbc.audit - 7. Connection.close() returned
INFO : jdbc.connection - 8. Connection closed
INFO : jdbc.audit - 8. Connection.close() returned
INFO : jdbc.connection - 9. Connection closed
INFO : jdbc.audit - 9. Connection.close() returned
INFO : jdbc.connection - 10. Connection closed
INFO : jdbc.audit - 10. Connection.close() returned
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
테스트 결과의 마지막을 살펴보면
BoardVO 클래스의 toString()을 이용해 bno멤버변수의값을 알아보기 위한 것이다
실행결과를 보면
'select seq_board.nextval from dual'쿼리가 먼저 실행되고
여기서 생성된 결과를 이용하여
bno값을 처리 되는 것을 볼 수 있다.
테스트 코드의 마지막 부분을 보면
BoardVO 객체의 bno값이 지정된 것을 확인할 수 있다.
* 참고 : 시퀸스의 값이므로 테스트 할 때 마다 다른 값이 나온다.
시퀸스 값: 중복 없는 값을 위한 것일 뿐 다른 의미는 없다.
@SelectKey를 이용하는 방식은
SQL을 한번더 실행하는 부담이 있기는 하지만,
자동으로 추가되는 PK값을 확인해야하는 상황에서 유용하게 사용될 수 있다.
전체 조회 -@어노테이션
- XML
레코드 삽입 - xml
1.insert
2.미리 PK값을 꺼내서 insert
(상세보기(BoardVO 객체를 리턴 받는다), 삭제, 수정)
insert가 된 데이터를 조회하는 작업
PK를 이용하여 처리하므로
BoardMapper 의 파라미터 역시 BoardVO 클래스의 bno 타입 정보를 이용하여 작업
//BoardMapper.java
package com.ohj.mapper;
import java.util.List;
//XML파일에 작성 해놨음으로 주석 처리한다.
//import org.apache.ibatis.annotations.Select;
import com.ohj.domain.BoardVO;
public interface BoardMapper {
//BoardMapper.XML파일에 작성 해놨음으로 주석 처리한다.
// @Select("select * from tbl_board where bno > 0")
public List<BoardVO> getList();
//단순히 insert만 해주는 메소드
public void insert(BoardVO board);
//insert를 해줬을 때 해당 VO 멤버 값을 꺼낼 수 있는 메소드
public void insertSelectKey(BoardVO board);
//지정한 글 한 개만 보여주는 메소드
public BoardVO read(long bno);
}
bno, title, content, writer regdate, updateDate 속성 값으로 처리 된다.
마이바티스는 bno라는 컬럼이 존재하면
BoardVO인스튼서의 setBno()를 호출하여 해당 데이터를 세팅한다.
마이바티스의 모든 파라미터와 리턴 타입의 처리는
get 파라미터명(), set 컬럼명() 의 규칙으로 호출된다.
테스트 코드(BoardMapperTests.java)
현재 테이블에 존재하는 데이터의 bno 컬럼의 값을 이용해서 확인.
데이터 읽기에 의한 테스트 메서드 추가
//BoardMapperTests.java
package com.ohj.mapper;
import org.junit.Test; // 처리 과정을 찍어주는 임포트
import org.junit.runner.RunWith; // 처리 과정을 찍어주는 임포트
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.ohj.domain.BoardVO;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {
@Setter(onMethod_ = @Autowired)
BoardMapper mapper;
@Test
public void testGetList() {
// 리스트 반환(board객체를 print 출력하면 toString 메소드에 의해서 객체에 대한 번지수가 나온다.)
mapper.getList().forEach(board -> log.info(board));
}// testGetList END
@Test
public void testInsert() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글");
board.setContent("새로 작성하는 내용");
board.setWriter("newbie");
mapper.insert(board);
log.info(board);
}//testInsert() END
@Test
public void testInsertSelectKey() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글 SelectKey");
board.setContent("새로 작성하는 내용 SelectKey");
board.setWriter("newbie");
mapper.insertSelectKey(board);
//이유 : 롬복이 자동으로 tosrtinf을 이용하요 객체를 만들어주고 그 객체가 가지고 있는 값을 각각 확인해보기 위해서
log.info(board);
}//testInsertSelectKey() END
@Test
public void testRead() {
//존재하는 게시물 번호로 테스트
BoardVO board = mapper.read(5L);//5L :long 타입이기 때문에 대문자 L을 써준다.
log.info(board);
}
}// BoardMapperTests END
삭제하기
package com.ohj.mapper;
import java.util.List;
//XML파일에 작성 해놨음으로 주석 처리한다.
//import org.apache.ibatis.annotations.Select;
import com.ohj.domain.BoardVO;
public interface BoardMapper {
//BoardMapper.XML파일에 작성 해놨음으로 주석 처리한다.
// @Select("select * from tbl_board where bno > 0")
public List<BoardVO> getList();
//단순히 insert만 해주는 메소드
public void insert(BoardVO board);
//insert를 해줬을 때 해당 VO 멤버 값을 꺼낼 수 있는 메소드
public void insertSelectKey(BoardVO board);
//지정한 글 한 개만 보여주는 메소드
public BoardVO read(long bno);
//삭제하기
public int delete(long bno);
}
package com.ohj.mapper;
import org.junit.Test; // 처리 과정을 찍어주는 임포트
import org.junit.runner.RunWith; // 처리 과정을 찍어주는 임포트
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.ohj.domain.BoardVO;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {
@Setter(onMethod_ = @Autowired)
BoardMapper mapper;
@Test
public void testGetList() {
// 리스트 반환(board객체를 print 출력하면 toString 메소드에 의해서 객체에 대한 번지수가 나온다.)
mapper.getList().forEach(board -> log.info(board));
}// testGetList END
@Test
public void testInsert() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글");
board.setContent("새로 작성하는 내용");
board.setWriter("newbie");
mapper.insert(board);
log.info(board);
}//testInsert() END
@Test
public void testInsertSelectKey() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글 SelectKey");
board.setContent("새로 작성하는 내용 SelectKey");
board.setWriter("newbie");
mapper.insertSelectKey(board);
//이유 : 롬복이 자동으로 tosrtinf을 이용하요 객체를 만들어주고 그 객체가 가지고 있는 값을 각각 확인해보기 위해서
log.info(board);
}//testInsertSelectKey() END
@Test
public void testRead() {
//존재하는 게시물 번호로 테스트
BoardVO board = mapper.read(5L);//5L :long 타입이기 때문에 대문자 L을 써준다.
log.info(board);
}
@Test
public void testDelete() {
log.info("DELETE COUNT:" + mapper.delete(3L));
}
}// BoardMapperTests END
수정하기
게시물의 업데이트는 제목, 내용, 작성자를 수정한다고 가정
업데이트할 때에는
최종 수정시간을 데이터베이스 내의 현재 시간으로 수정한다
업데이트와 딜리트와 마찬가지로
'몇 개의 데이터가 수정되었는가'를 반환 받을 수 있도록 int 타입으로 메서드의 리턴 값을 설계
package com.ohj.mapper;
import java.util.List;
//XML파일에 작성 해놨음으로 주석 처리한다.
//import org.apache.ibatis.annotations.Select;
import com.ohj.domain.BoardVO;
public interface BoardMapper {
//BoardMapper.XML파일에 작성 해놨음으로 주석 처리한다.
// @Select("select * from tbl_board where bno > 0")
public List<BoardVO> getList();
//단순히 insert만 해주는 메소드
public void insert(BoardVO board);
//insert를 해줬을 때 해당 VO 멤버 값을 꺼낼 수 있는 메소드
public void insertSelectKey(BoardVO board);
//지정한 글 한 개만 보여주는 메소드
public BoardVO read(long bno);
//삭제하기
public int delete(long bno);
//수정하기
public int update(BoardVO board);
}
//BoardMapperTests.java
package com.ohj.mapper;
import org.junit.Test; // 처리 과정을 찍어주는 임포트
import org.junit.runner.RunWith; // 처리 과정을 찍어주는 임포트
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.ohj.domain.BoardVO;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardMapperTests {
@Setter(onMethod_ = @Autowired)
BoardMapper mapper;
@Test
public void testGetList() {
// 리스트 반환(board객체를 print 출력하면 toString 메소드에 의해서 객체에 대한 번지수가 나온다.)
mapper.getList().forEach(board -> log.info(board));
}// testGetList END
@Test
public void testInsert() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글");
board.setContent("새로 작성하는 내용");
board.setWriter("newbie");
mapper.insert(board);
log.info(board);
}//testInsert() END
@Test
public void testInsertSelectKey() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글 SelectKey");
board.setContent("새로 작성하는 내용 SelectKey");
board.setWriter("newbie");
mapper.insertSelectKey(board);
//이유 : 롬복이 자동으로 tosrtinf을 이용하요 객체를 만들어주고 그 객체가 가지고 있는 값을 각각 확인해보기 위해서
log.info(board);
}//testInsertSelectKey() END
@Test
public void testRead() {
//존재하는 게시물 번호로 테스트
BoardVO board = mapper.read(5L);//5L :long 타입이기 때문에 대문자 L을 써준다.
log.info(board);
}
@Test
public void testDelete() {
log.info("DELETE COUNT:" + mapper.delete(3L));
}
@Test
public void testUpdate() {
BoardVO board = new BoardVO();
board.setBno(5L);
board.setTitle("수정된 제목");
board.setContent("수정된 내용");
board.setWriter("user00");
int count = mapper.update(board);
log.info("UPDATE COUNT:" + count);
}
}// BoardMapperTests END
비즈니스 계층: 로직을 기준으로 해서 처리
예) 쇼핑몰에서 상품을 구매한다고 가정
해당 쇼핑몰의 로직 예: 물건을 구매한 회원에게는 포인트를 올려준다
영속계층의 설계 : 상품과 회원을 나누어서 설계
비즈니스 계층 설계 : 상품영역과 회원영역을 동시에 사용하여 하나의 로직을 처리
구매서비스 - 상품 처리 객체
- 회원 처리 객체
현재 단일 테이블을 사용하고 있기 때문에 위와 같은 구조는 아니지만,
설계를 할 때는 원칙적으로 영역을 구분하여 작성
일반적으로 비즈니스 영역에 있는 객체들을 서비스(Service)라는 용어로 사용
1. 비즈니스 계층의 설정
1-1. 비즈니스 계층을 위한 패키지 작성
2. 비즈니스 계층 설계
각 계층 간의 연결은 인터페이스를 이용하요 느슨한 연결(결합)을 한다.
게시물을
BoardService 인터페이스와 인터페이스를 구현 받는 BoardServicBoardServiceImple를 만들어준다.
2. BoardService 인터페이스에 메서드 선언 추가
BoardService 매서드 설계시,
메서드 이름은 현실적인 로직의 이름을 사용
명백하게 반환해야하는 데이터가 있는(select)메서드 : 리턴타입을 명시
get() : 게시물을 특정한 게시물을 가져오는 메서드
getList(): 전체 리스트를 구하는 메서드.
이 두가지 메서드는 처음부터 리턴 타입을 결정하여 진행
package com.ohj.service;
import java.util.List;
import com.ohj.domain.BoardVO;
public interface BoardService {
//BoardMepper.java 와 연결 된다.
public void register(BoardVO board); // 새 글을 등록 할 때 사용
public BoardVO get(Long bno); //내가 지정한 글 번호의 레코드를 꺼내올 때 사용
public boolean modify(BoardVO board); // 기존 글을 수정 할 때 사용
public boolean remove(Long bno); //해당 글을 삭제 할 때
public List<BoardVO> getList();// 전체 데이터를 조회할 때
}
BoardService를 저장하는 순간,
BoardServiceImpl에 오류가 발생하면 마우스를 대고 add를 해주면 아래와 같은 코드가 생성 된다.
package com.ohj.service;
import java.util.List;
import com.ohj.domain.BoardVO;
public class BoardServiceImpl implements BoardService {
public BoardServiceImpl() {
}
@Override
public void register(BoardVO board) {
// TODO Auto-generated method stub
}
@Override
public BoardVO get(Long bno) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean modify(BoardVO board) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean remove(Long bno) {
// TODO Auto-generated method stub
return false;
}
@Override
public List<BoardVO> getList() {
// TODO Auto-generated method stub
return null;
}
}
클래스 선언부에 어노테이션 추가
package com.ohj.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired; // 빈을 자동 주입
import org.springframework.stereotype.Service; //역할 알려주는애안에 있는 서비스(Service라고 어노테이션을 달면 얘는 서비스 역할을 하는애다 라고 알려주는 것)
import com.ohj.domain.BoardVO;
//수정 삭제 메소드를 가지고 있음
import com.ohj.mapper.BoardMapper;
//소스트리를 자동으로 만들어줌
//lombok으로 소스들이 매우 줄어들게 됨
import lombok.AllArgsConstructor;// 전체 값을 모두 받는 클래스(생성자)
import lombok.Setter; //toString으로 자동으로 변환해주기 위한 setter import
import lombok.extern.log4j.Log4j; //로그 값 찍어주는 것(콘솔창 찍어주는 것)
@Log4j
@Service
//스프링 4.3 부터는 단일 파라미터를 뱓는 생성자의 경우, 필요한 파라미터를 자동으로 주입 가능.
@AllArgsConstructor // 모든 파라미터를 이용하는 생성자를 생성
public class BoardServiceImpl implements BoardService {
//비즈니스 계층에서 사용하기 때문에 서비스 어노테이션을 붙여 준다.
//일을 수행하기 위해 mepper를 주입해야하는데 오토와이어드를 붙여 준다.
// lombok이 자동 생성자를 생성해주기 때문에 기본 생성자는 없애준다.
//public BoardServiceImpl() {
//}
@Override
public void register(BoardVO board) {
// TODO Auto-generated method stub
}
@Override
public BoardVO get(Long bno) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean modify(BoardVO board) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean remove(Long bno) {
// TODO Auto-generated method stub
return false;
}
@Override
public List<BoardVO> getList() {
// TODO Auto-generated method stub
return null;
}
}
BoardMapper 객체를 위한 멤버(인스턴스) 변수 선언
package com.ohj.service;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.beans.factory.annotation.Autowired; // 빈을 자동 주입
import org.springframework.stereotype.Service; //역할 알려주는애안에 있는 서비스(Service라고 어노테이션을 달면 얘는 서비스 역할을 하는애다 라고 알려주는 것)
import com.ohj.domain.BoardVO;
//수정 삭제 메소드를 가지고 있음
import com.ohj.mapper.BoardMapper;
//소스트리를 자동으로 만들어줌
//lombok으로 소스들이 매우 줄어들게 됨
import lombok.AllArgsConstructor;// 전체 값을 모두 받는 클래스(생성자)
import lombok.Setter; //toString으로 자동으로 변환해주기 위한 setter import
import lombok.extern.log4j.Log4j; //로그 값 찍어주는 것(콘솔창 찍어주는 것)
@Log4j
@Service
//스프링 4.3 부터는 단일 파라미터를 뱓는 생성자의 경우, 필요한 파라미터를 자동으로 주입 가능.
@AllArgsConstructor // 모든 파라미터를 이용하는 생성자를 생성
public class BoardServiceImpl implements BoardService {
//비즈니스 계층에서 사용하기 때문에 서비스 어노테이션을 붙여 준다.
//일을 수행하기 위해 mepper를 주입해야하는데 오토와이어드를 붙여 준다.
// lombok이 자동 생성자를 생성해주기 때문에 기본 생성자는 없애준다.
//public BoardServiceImpl() {
//}
@Setter(onMethod_ = @Autowired)
private BoardMapper Mapper;
@Override
public void register(BoardVO board) {
log.info("register" + board);
Mapper.insertSelectKey(board);
}
@Override
public BoardVO get(Long bno) {
log.info("get...." + bno);
return Mapper.read(bno);
}
@Override
public boolean modify(BoardVO board) {
log.info("modify...." + board);
return Mapper.update(board)==1;// 값이 1일 떄 지워준다.
}
@Override
public boolean remove(Long bno) {
log.info("remove...." + bno);
return Mapper.delete(bno)==1;
}
@Override
public List<BoardVO> getList() {
log.info("getList....");
return Mapper.getList();
}
}
package com.ohj.service;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Log4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class BoardServiceTests {
@Setter(onMethod_ = {@Autowired})
private BoardService service;
@Test
public void testExist() { // 주입이 잘 되는지 확인을 위한 testExist 메서드
log.info(service);
assertNotNull(service);
}//testExist() END
}//BoardServiceTests CLASS END
package com.ohj.service;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.ohj.domain.BoardVO;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Log4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class BoardServiceTests {
@Setter(onMethod_ = {@Autowired})
private BoardService service;
@Test
public void testExist() { // 주입이 잘 되는지 확인을 위한 testExist 메서드
log.info(service);
assertNotNull(service);
}//testExist() END
@Test
public void testRegister() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글 테스트 ");
board.setContent("새로 작성하는 내용 테스트");
board.setWriter("newbie test");
service.register(board);
log.info("생성된 게시물의 번호:" + board.getBno());
}
}//BoardServiceTests CLASS END
package com.ohj.service;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.ohj.domain.BoardVO;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Log4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class BoardServiceTests {
@Setter(onMethod_ = {@Autowired})
private BoardService service;
@Test
public void testExist() { // 주입이 잘 되는지 확인을 위한 testExist 메서드
log.info(service);
assertNotNull(service);
}//testExist() END
@Test
public void testRegister() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글 테스트 ");
board.setContent("새로 작성하는 내용 테스트");
board.setWriter("newbie test");
service.register(board);
log.info("생성된 게시물의 번호:" + board.getBno());
}
//하나의 글만 가져오는 메소드 테스트
@Test
public void testGet() {
log.info(service.get(1L));
}
}//BoardServiceTests CLASS END
package com.ohj.service;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.ohj.domain.BoardVO;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Log4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class BoardServiceTests {
@Setter(onMethod_ = {@Autowired})
private BoardService service;
@Test
public void testExist() { // 주입이 잘 되는지 확인을 위한 testExist 메서드
log.info(service);
assertNotNull(service);
}//testExist() END
@Test
public void testRegister() {
BoardVO board = new BoardVO();
board.setTitle("새로 작성하는 글 테스트 ");
board.setContent("새로 작성하는 내용 테스트");
board.setWriter("newbie test");
service.register(board);
log.info("생성된 게시물의 번호:" + board.getBno());
}
//하나의 글만 가져오는 메소드 테스트
@Test
public void testGet() {
log.info(service.get(1L));
}
//삭제 테스트
public void testDelete() {
//게시물 번호이 존재여부를 확인 하고 테스트 할 것
log.info("REMOVE RESULT:" + service.remove(7L));
}
//수정 테스트
public void testUptate() {
BoardVO board = service.get(1L);
if(board == null) {
return;
}
board.setTitle("제목을 수정테스트 합니다.");
log.info("MODIFY RESULT:" + service.modify(board));
}
}//BoardServiceTests CLASS END
시퀸스를 생성 할 때에는 다른 오브젝트들(테이블 등)과 구별하기 위하여 'seq_'로 시작하거나, 't_'와 같이 구분이 가능한 단어를 앞에 붙여준다.
tbl_board 테이블 구성요소
bno : 고유의 번호 title : 제목 content : 내용 writer : 작성자
테이블을 설계할 때에는
가능하면 레코드의 생성 시간과 최종 수정 시간을 같이 기록하는 것이 일반적
regdate : 생성 시간
updatedate : 최종 수정 시간
레코드가 생성 된 시간을 자동으로 기록될 수 있도록 기본 값을 sysdate로 설정
PK(Primary Key)를 지정 할 때에는 'pk_'로 시작하는 이름을 붙여주는 것이 일반적.
반드시 의미를 구분 할 수 있도록 생성해준다.
테이블 생성
create sequence seq_board;
create table tbl_board (
bno number(10,2),
title varchar2(200) not null,
content varchar2(2000) not null,
writer varchar2(50) not null,
regdate date default sysdate,
updatedate date default sysdate
);
테이블 수정
alter table tbl_board
add constraint pk_board
primary key(bno);
테이블 저장
commit;
테이블을 생성하고 나면, 테스트를 위한 여러개의 데이터를 추가하게 되는데, 이런 의미 없는 데이터를 'tpy data' 혹은 'dummy data' 라고 한다.
1
사용자의 Request(요청d)는 Front - Controller인 DispatcherServlet을 통해서 처리한다.
생성된 프로젝트의 web.xml을 보면 아래와 같이
모든 Request를 DispatcherServlet이 받도록 처리하고 있다.
<!-- 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> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
2, 3
HandlerMapping은 Request의 처리를 담당하는 컨트롤러를 찾기 위해서 존재한다. HandlerMapping 인터페이스를 구현한 여러 객체들 중
RequestMappingHandlerMapping 같은 경우는
개발자가 @RequestMapping 어노테이션이 적용된 것을 기준으로 판단하게 된다.
적절한 컨트롤러가 찾아졌다면 HandlerAdapter를 이용해서 해당 컨트롤러를 동작시킨다.
4
Controller는 개발자가 작성하는 클래스로 실제 Request를 처리하는 로직을 작성하게 된다.
이때 View에 전달해야 하는 데이터는 주로 Model이라는 객체에 담아서 전달한다.
Controller는 다양한 타입의 결과를 반환하는데
이에 대한 처리는 ViewResolver를 이용하게 된다.
5
ViewResolver는 Controller가 반환한 결과를
어떤 View를 통해서 처리하는 것이 좋을지 해석하는 역할이다.
가장 흔하게 사용하는 설정은 servlet-context.xml에 정의된 InternalResourceViewResolver다.
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <beans:property name="prefix" value="/WEB-INF/views/" /> <beans:property name="suffix" value=".jsp" /> </beans:bean>
6, 7
View는 실제로 응답 보내야 하는 데이터를 Jsp 등을 이용해서 생성하는 역할을 하게된다.
만들어진 응답은 DispatcherServlet을 통해서 전송된다.
=================================================================
위의 그림을 보면 모든 Request는 DispatcherServlet을 통하도록 설계되는데,
이런 방식을 Front-Controller 패턴이라고 한다.
Front-Controller 패턴을 이용하면 전체 흐름을 강제로 제한할 수 있다.
예를 들어 HttpServlet을 상속해서 만든 클래스를 이용하는 경우
특정 개발자는 이를 활용할 수 있지만
다른 개발자는 자신이 원래 하던 방식대로 HttpServlet을 그대로 상속해서 개발할 수도 있다.
Front-Controller 패턴을 이용하는 경우에는
모든 Request의 처리에 대한 분배가 정해진 방식대로만 동작할기 때문에
좀 더 엄격한 구조를 만들어 낼 수 있다.
spring-beans : 스프링 컨테이너를 이용해서 객체를 생성하는 기본 기능 제공
spring-context : 객체 생성, 라이프 사이클 관리, 스키마 확장 등의 기능 제공
spring-aop : 프락시 기반 AOP 기능 제공
spring-web : REST 클라이언트, 데이터 변환 처리, 서블릿 필터, 파일 업로드 지원 등 웹개발에 필요한 기능 제공
★spring-webmvc : 스프링 기반의 웹 MVC 프레임워크, 웹 애플리케이션 개발하는데 필요한 컨트롤러 구현 제공
spring-websocket : 스프링 MVC에서 웹소켓을 사용하기 위한 기능 지원
spring-oxm : XML과 자바 객체 간 매핑을 처리하기 위한 API 제공
spring-tx : 트랜잭션 처리 위한 추상 레이어 제공
spring-jdbc : JDBC 프로그래밍을 보다 쉽게 할 수 있는 템플릿 제공, 이를 이용하면 JDBC 프로그램에서 반복적으로 입력해야하는 코드 줄일 수 있음
srping-orm : 하이버네이트, JPA, MyBatis 등과 연동 지원
spring-jms : JMS 서버와 메시지를 쉽게 주고 받을 수 있도록 템플릿과 아노테이션 등 제공
sring-context-support : 스케줄링, 메일발송, 캐시연동, 벨로시티 등 부가기능 제공
POJO(Plain Old Java Object) 기반의 프레임워크
- 자바 객체의 라이프사이클을 스프링 컨테이너가 직접 관리하며, 스프링 컨테이너로부터 필요한 객체를 얻어올 수 잇음
★DI(Dependency Injection)을 지원
- 각 계층이나 서비스들 사이 또는 객체들 사이에 의존성이 존재할 경우 스프링 프레임워크가 서로를 연결시켜줌 이는 클래스들 사이에 약한 결합을 가능하게 함(의존성↓)
------ 주입 방법 : 생성자, 멤버, 어노테이션, xml
AOP(Aspect Oriented Programming)를 지원
- 트랜잭션, 로깅, 보안 등 여러 모듈에서 공통적으로 지원하는 기능을 분리하여 사용 가능, 확장성 높음
- 스프링 프레임워크의 소스는 모두 라이브러리로 분리시켜 놓음으로써 필요한 라이브러리만 가져다 쓸 수 있다 그리고 많은 외부 라이브러리들도 이미 스프링 프레임워크와 연동되고 있다
- Model2 방식의 MVC Framework를 지원
전체적인 설계가 만들어지고 거기에 대한 관점을 보는 것 : AOP
maven : 스프링
gradle : 안드로이드 프로젝트
기본적인 웹 게시물 관리
스프링 MVC와 Mybatis를 이용한
CRUD(등록, 수정, 삭제, 조회)와
페이징 처리
검색기능 의 게시물 관리를 제작.
중요하게 고려해야 할 부분
스프링 MVC를 이용하는 웹 프로젝트 전체 구조에 대한 이해
개발의 각 단계
Presentation(화면 계층 ) : 화면에 보여주는 기술을 사용하는 영역
Servlet/JSP나 스프링 MVC가 담당하는 영역.
프로젝트의 성격에 맞추어 앱으로 제작하거나,
CS(Client - Server)로 구성되는 경우도 있다.
스프링 MVC와 JSP를 이용한 화면 구성이 이에 속한다.
Business(비즈니스 계층) : 순수한 비느니스 로직을 담고 있는 영역
이 영역이 중요한 이유
고객기 원하는 요구사항을 반영하는 계층.
이 영역의 설계는 고객의 요구 사항과 정확히 일치해야 한ㄷ.
이 영역은 주로 xxxService와 같은 이름으로 구성하고,
메서드 이름 역시 고객들이 사용하는 용어를 그대로 사용하는 것이 일반적.
Persistence(영속 또는 데이터 계층) : 데이터를 어떤 방식으로 보관하고, 사용하는가에 대한 설계가 들어가는 계층.
일반적인 경우, 데이터베이스를 많이 이용하지만,
경우에 따라서 네트워크 호출이나 원격 호출 등의 기술이 접목될 수도 있다.
이 영역은 Mybatis와 mybatis-spring을 이용하여 구성.
Spring MVC : Presentation Tier를 구성,
root-context.xml, servleet-context.xml 등의 설정 파일이 해당 영역의 설정을 담당
Spring Core : POJO(Plain-Old-Java-Object)의 영역.
스프링의 의존성 주입을 이용해서 객체 간의 연관구조를 완성하여 사용
Mybatis : 현실적으로 mybatis-spring을 이용하여 구성하는 영역.
SQL에 대한 처리를 담당하는 구조
프로젝트를 3-tier로 구성하는 이유는 유지보수에 대한 필요성 때문
각 영역은 독립적으로 설계되어 추후 특정 기술이 변하더라도 필요한 부분을 전자제품의 부품처럼
쉽게 교환할 수 있게 하는 방식
각 영역은 설계 당시부터 영역을 구분하고,
해당 연결 부위는 인터페이스를 이용하여 설계하는 것이 일반적
1. 네이밍 규칙
xxxController :
스프링 MVC에서 동작하는 Controller 클래스를 설계할 때 사용
xxxService, xxxServiceImpl :
비즈니스 영역을 담당하는 인터페이스는 xxxService 방식.
인터페이스를 구현한 클래스는 xxxServiceImpl 이름 사용
xxxDAO, xxxRepository :
DAO(Data-Access-Object)나 Repository(저장소) 이름으로 영역을 따로 구성하는 것이 보편적
별도의 DAO를 구성하는 대신, Mybatis의 Mapper 인터페이스를 활용
VO, DTO :
VO나 DTO는 일반적으로 유사한 의미로 사용하는 용어
데이터를 담고 있는 객체를 의미한다는 공통점이 있다
VO :
주로 Read Only 목적이 강하고, 데이터 자체도 immutable(불변)하게 설계하는 것이 정석
DTO :
주로 데이터 수집의 용도가 좀 더 강하다
예) 웹 화면에서 로그인하는 정보를 DTO로 처리하는 방식을 사용
테이블과 관련된 데이터는 VO라는 이름 사용
패키지의 구성은 프로젝트의 크기나 구성원들의 성향으로 결정
규모가 적은 프로젝트 :
Controlelr 영역을 별도의 패키지로 설계
Service 역역 등을 하나의 패키지로 설계
규모가 큰 프로젝트 :
많은 Service 클래스와 Controller들이 흔재할 경우,
비즈니스를 단위별로 구분하고(즉, 비즈니스 단위별로 패키지 작성)
다시 내부에서 Controller 패키지, Service패키지 등으로 다시 나누는 방식 이용
담당자가 명확해지고, 독립적인 설정을 가지는 형태로 개발
[작업 패키지 구성]
com.이니셜 : 메인 패키지
com.이니셜.config : 프로젝트와 관련된 설정 클래스들
com.이니셜.controller : 스프링 MVC의 Controller들
com.이니셜.service : 스프링의 Service 인터페이스와 구현 클래스들
com.이니셜.domain : VO, DTO 클래스들
com.이니셜.persistence : Mybatis Mapper 인터페이스
com.이니셜.exception : 웹 관련 예외처리
com.이니셜.aop : 스프링의 AOP 관련
com.이니셜.security : 스프링의 Security 관련
com.이니셜.util : 각종 유틸리티 클래스 관련
프로젝트를 진행하기 전에
고객의 요구사항을 인식하고, 이를 설계하는 과정이 필요
요구사항 분석 설계 :
고객이 원하는 내용이 무엇이고,
어느 정도까지 구현할 것인가에 대한 프로젝트의 범위를 정하는 것이 목적.
요구사항 :
실제로 방대해 질 수 있으므로 프로젝트에서는 단계를 정확히 구분.
경험이 많은 팀 구성 : 초기버전에 상당히 많은 기능을 포함 시켜서 개발
반대의 경우 : 최대한 단순하고 눈에 보이는 결과를 만들어 내는 형태로 진행.
온전한 문장으로 정리.
주어는 "고객"이고, 목적어는 "대상(domain)"이 된다.
예) 게시판의 경우, 게시물이 대상이 된다.
고객은 새로운 게시물을 등록할 수 있어야 한다.
고객은 특정 게시물을 조회할 수 있어야 한다.
고객은 작성한 게시물으 삭제할 수 있어야 한다.
등.
테이블 : tbl_board
VO클래스 : com.이니셜.domain.BoardVO
게시물과 관련된 로직 : com.이니셜.service.BoardService / com.이니셜.BoardController
요구사항에 따른 화면 설계
예) 고객은 새로운 게시물을 등록할 수 있어야 한다.
세부적인 설계 : '어떤 내용들을 입력하게 될 것인가'
이를 기준으로 테이블이나 클래스의 멤버 변수(인스턴스 변수)들을 설계.
이러한 화면을 설계할 때는 주로 Mock-up(목업) 들을 이용하는 경우가 많다.
대표적인 Mock-up툴 : PPT, Balsqmiq studio, Pencil Mockup 등.
각 화면을 설계하는 단계에서는
사용자가 입력해야 하는 값과 함께
전체 페이지의 흐름을 설계.(유즈케이스 다이어그램)
이 화면의 흐름을 URL로 구성하게 되는데,
이 경우 GET/POST 방식에 대하여 언급한다.
3. 게시물 관리 프로젝트 구성
스프링 프로젝트 이름 : springboard
스프링 프로젝트 생성 : Spring Legacy Project
프로젝트 생성 후, 진행 순서:
pom.xml의 수정
데이터 베이스 관련 처리
스프링 MVC 처리
스프링 폴더 설명
스프링 버전 5 사용하기
사용하기 전, pom.xml 버전 통일하기
스프링 관련 라이브러리 추가
spring-tx : 트랜잭션을 사용하기 위한 라이브러리
spring-jdbc : 데이터베이스와 연동하기 위한 라이브러리
spring-test : 테스트 코드를 단위 테스트하기 위한 라이브러리
<!-- Spring Transaction -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- Spring JDBC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- Spring Test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
</dependency>
Mybatis 관련 라이브러리 추가
HikariCP : 대용량 데이터 처리 속도를 빠르게 하기 위한 라이브러리
MyBatis : 데이터베이스 프레임워크
mybatis-spring : MyBatis 를 사용하기 위한 스프링 라이브러리
Log4jdbc : 데이터베이스 실행 시, 단위 테스트를 위한 라이브러리