웹 프로젝트에서 @Test 어노테이션 없이 특정 메서드가 잘 동작하는지 테스트 하는 것 은 매우 어려운 일이다.
스프링 프레임 워크에서 어떻게 메서드를 테스트하는지 알아보자.
대부분 DB연동과정에서 테스트를 진행하기 때문에 Maven에서 Mybatis 연동 과정에서 문제가 없는지 테스트하는 메서드를 작성해보자.
먼저 pom.xml에사 DB연동을 위한 라이브러리와 Mybatis를 사용하기 위한 라이브러리를 다운받는다.
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc</artifactId>
<version>6.0</version>
</dependency>
<!-- Spring jdbc jdbc와 tx가 추가됐다. -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.3</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework-version}</version>
</dependency>
ojdbc는 원격 리파지토리에서 제공되지 않음으로 maven툴을 사용해 로컬 리파지토리에 저장하고
pom.xml
에서 내려받자.
그리고 루트 Dispatcher에 DataSource
와 MyBatis
연동객체를 생성하자.
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"
value="oracle.jdbc.driver.OracleDriver"></property>
<property name="username" value="scott"></property>
<property name="password" value="tiger"></property>
<property name="url"
value="jdbc:oracle:thin:@172.17.107.68:1521:xe"></property>
</bean>
<!-- SqlSessionFactoryBean 빈 객체 생성 -->
<bean id="sqlSessionFactoryBean"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:/mybatis-config.xml"></property>
</bean>
<!-- SqlSession 빈 객체 생성 -->
<bean id="sqlSession"
class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactoryBean" />
</bean>
테스트를 위한 설정은 모두 끝났다!
주의: jUnit의 버전은 4. 1 1 이싱을 사용해야 한다.
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
버전이 일치하지 않으면 java.lang.NoClassDefFoundError: org/junit/runners/modelMultiFailureException
과 같은 에러가 발생….
다음과 같이 테스트용 객체를 src/text/java
디렉토리안 패키지에 생성하자.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"file:src/main/webapp/WEB-INF/spring/**/*.xml"})
public class MyBatisTest {
@Inject
private SqlSessionFactory sqlFactory;
@Test
public void testFactory() {
System.out.println(sqlFactory);
}
@Test
public void testSession() throws Exception {
try (SqlSession session = sqlFactory.openSession()){
System.out.println(session);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
@ContextConfiguration
어노테이션의 locations
설정을 통해 참고할 xml파일들을 가져오자.
(위 패턴대로라면 root-context.xml
과 servlet-context.xml
이 가져와질것)
junit으로 해당 클래스를 실행시키면 @Test
어노테이션이 설정된 2개 메서드의 결과가 콘솔에 출력된다.
INFO : org.springframework.web.servlet.handler.SimpleUrlHandlerMapping - Mapped URL path [/resources/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
INFO : org.springframework.jdbc.datasource.DriverManagerDataSource - Loaded JDBC driver: oracle.jdbc.driver.OracleDriver
...
...
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory@512baff6
org.apache.ibatis.session.defaults.DefaultSqlSession@4b41dd5c
...
...
WARN : org.springframework.beans.factory.support.DisposableBeanAdapter - Invocation of destroy method 'close' failed on bean with name 'sqlSession': java.lang.UnsupportedOperationException: Manual close is not allowed over a Spring managed SqlSession
지금까지 MVC(Model2)구조에 대해서 여러번 배웠는데 스프링에선 Front Controller방식의 한층더 분업화된 강제적인 형태를 제공한다.
기존 Jsp/Servlet
에서의 컨트롤러가 맡았던 역할, url을 매핑하고 핸들러 객체로 비지니스 로직 수행명령
이제 이 역할은 스프링 MVC
의 Front Controller
가 맡게된다.
Jsp/Servlet
의 Handler
의 역할을 스프링 MVC
의 Controller
의 메서드가 맡게되었고 이를 컨트롤러 메서드라 부른다.
JSON객체를 사용하려면 json-simple 같은 jar파일을 설치해야 했다.
http://www.java2s.com/Code/Jar/j/Downloadjsonsimple11jar.htm
스프링에선 json을 위한 라이브러리를 pom.xml
에서 설치 가능하다.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.4</version>
</dependency>
그리고 반환 형 앞에 @ResponseBody
어노테이션을 적용한다.
@RequestMapping("doJSON")
public @ResponseBody ProductVO doJSON() {
logger.info("doF Called....");
ProductVO vo = new ProductVO("sampel product", 30000);
return vo ;
}
출력값
{"name":"sampel product","price":30000.0}
위에선 간단한 메서드를 테스트하였는데 컨트롤러를 테스트 하는 것 도 가능하다.
tomcat없이 어떻게 컨트롤러를 테스트하는지 알아보자.
우선 컨트롤러 테스트를 위해선 pom.xml에서 servlet 버전을 높여주어야 한다.
<!-- Servlet -->
<!--
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<!-- <scope>provided</scope> -->
</dependency>
<artifactId>
과 <version>
를 위와같이 변경한다.
변경하지 않으면 다음 오류 발생.
java.lang.NocClassDefFoundError:javax/servletSessionCookieConfig
package org.zerock.web;
import javax.inject.Inject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations= {"fild:src/main/webapp/WEB-INF/spring/**/*.xml"})
public class SampleControllerTest {
private static final Logger logger
= LoggerFactory.getLogger(SampleControllerTest.class);
@Inject
private WebApplicationContext wac;
private MockMvc mocMvc;
@Before
public void setup() {
this.mocMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
logger.info("setup.....");
}
@Test
public void testDoA() throws Exception {
mocMvc.perform(MockMvcRequestBuilders.get("/doA"));
}
}
@RunWith
과 @ContextConfiguration
설정은 똑같고 @WebAppConfiguration
가 추가되었다.
Dispatcher 생성시 같이 생성되는 빈 객체 WebApplicationContext
를 주입받는다.
테스트에서 가장 많이 필요로 하는 것이 DB와 연동후 sql작업이다.
맨처음에 Mybatis와 연동되는지 확인하였으니 Sqlsession
을 통해 DB에 실행 쿼리문을 날려보자.
MyBatis를 통해 쿼리를 사용하려면 다음 작업을 해야한다.
먼저 다음 테이블을 만들고 DB에서 sysdate
시간을 가져오는 테스트와 member를 INSERT
하는 메서드를 테스트해보자.
CREATE TABLE tbl_member (
userid VARCHAR2(50) NOT NULL,
userpw VARCHAR2(50) NOT NULL,
username VARCHAR2(50) NOT NULL,
email VARCHAR2(100) NOT NULL,
regdate DATE DEFAULT sysdate,
updatedate DATE DEFAULT sysdate,
PRIMARY KEY(userid)
);
조심해야 할것.
<mapper namespace="org.zerock.mapper.MemberMapper">
의 namespace가 중복되는일 없도록 하자.
팁 mybatis-config.xml
에서 다음과 같이 typeAliases
태그를 추가하면 import시킨것처럼 풀클래스명을 쓰지 않아도 된다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="org.zerock.domain"/> <!-- import한것 처럼, 네임스페이스 추가한것처럼 생략가능 -->
</typeAliases>
</configuration>
`
자신이 사용할 쿼리에 맞는 함수명을 잘 적용하자.
매개변수와 반환타입도 기능에 맞춰 설계
public interface MemberDAO {
public String getTime();
public void insertMember(MemberVO vo);
}
매퍼파일의 중요한 설정음 namespace
이다. 이 namespace를 사용해 함수를 호출하기 때문.
간단하게 인터페이스의 풀 클래스네임을 적용해도 되지만 별도의 이름을 주어도 상관없다(잘 구분할수 있는 이름으로!).
<?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.mapper.MemberMapper">
<select id="getTime" resultType="string">
SELECT sysdate FROM dual
</select>
<insert id="insertMember">
INSERT INTO tbl_member (userid, userpw, username, email) VALUES
(#{userid}, #{userpw}, #{username}, #{email})
</insert>
</mapper>
매퍼파일에 설정된 getTime
쿼리를 사용하고 싶다면 org.zerock.mapper.MemberMapper.getTime
으로 사용한다.
이제 매퍼파일에 정의된 각 쿼리를 호출할 수 있도록 인터페이스를 구현한 클래스 작성
@Repository
public class MemberDAOImpl implements MemberDAO{
@Inject
private SqlSession sqlSession;
private static final String namespace = "org.zerock.mapper.MemberMapper";
@Override
public String getTime() {
return sqlSession.selectOne(namespace+".getTime");
}
@Override
public void insertMember(MemberVO vo) {
sqlSession.insert(namespace+".insertMember", vo);
}
@Override
public MemberVO readMember(String userid) throws Exception {
return sqlSession.selectOne(namespace+".selectMember", userid);
}
@Override
public MemberVO readWithPW(String userid, String userpw) throws Exception {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userid", userid);
paramMap.put("userpw", userpw);
return sqlSession.selectOne(namespace+".readWithPW", paramMap);
}
}
https://kouzie.github.io/spring/Spring-스프링-MyBatis/#sqlsessiontemplate을-이용한-dao-새성
위 페이지에선sqlSession.getMapper(인터페이스.class)
를 통해 인터페이스에 정의된 메서드명에 해당하는 쿼리 설정을 매핑시킨 클래스가 구현되어 메서드를 호출하였다.
NoticeDao mybatis_NoticeDao = this.sqlSession.getMapper(NoticeDao.class);
Notice notice = mybatis_NoticeDao.getNotices(seq);
위의 예제에선 바로 MyBatis연동객체의 메서드를 바로 호출한다. sqlSession.selectOne
이렇게 사용한다면 사실 DAO 인터페이스를 구현하지 않아도 상관 없다. 인터페이스 구현클래스 만들지 않고 바로 연동객체 메서드 호출하니까!
그래도 인터페이스를 만들도록 하자
인터페이스까지 다 만들어 졌다면 테스트 클래스 생성.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
locations= {"file:src/main/webapp/WEB-INF/spring/**/*.xml"}
)
public class MemberDaoTest {
@Inject
private MemberDAO dao;
@Test
public void testTime() throws Exception {
System.out.println(dao.getTime());
}
@Test
public void testInsertMember() throws Exception {
MemberVO vo = new MemberVO();
vo.setUserid("user1");
vo.setUserpw("user1");
vo.setUsername("user");
vo.setEmail("user@user.com");
dao.insertMember(vo);
}
}
매개변수가 하나가 아닐경우 Map 콜렉션 객체를 사용하자.
public MemberVO readWithPW(String userid, String userpw) throws Exception {
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userid", userid);
paramMap.put("userpw", userpw);
return sqlSession.selectOne(namespace+".readWithPW", paramMap);
}
<select id="readWithPW" resultType="string">
SELECT * FROM tbl_member
WHERE userid = #{userid} AND userpw=#{userpw}
</select>
Mybatis 사용시 쿼리문에 문자열 비교연산자나 부등호를 처리할 때 태그의 <>
인지 분간이 안된다.
이외에도 특수문자 사용하는데 제한이 있을 수 있다.
<select id ="list" parameterType="int" resultType="board.test.testDto">
SELECt *
FROM employees
WHERE salary > 100
</select>
<![CDATA[...sql...]]>
를 사용해야 오류가 나지 않는다.
CDATA 안에 들어가는 문장을 문자열로 인식하게 합니다.
<select id = "list" parameterType="int" resultType="board.test.testDto">
<![CDATA[
select *
from employees
where salary > 100
]]>
</select>
페이징 처리의 가장 힘든점은 다시 목록으로 돌아갈 때 뒤로가가 아닌 목록보기 버튼 등을 클릭해 이동할 경우 다시 본래 페이지로 Request요청하는 url을 전송해야 한다.
예를들어 4페이지에 검색어 kouzie로 게시글을 검색했을 때 특정글을 보고 다시 목록보기를 클릭하면 4페이지의 kouzie를 검색하는 url을 요청해야한다는 뜻.
이런 파라미터를 페이지 이동마다 연동해야하는데 매우 귀찮은 작업이다.
jsp안에서 이동경로를 수정하거나 javascript로 수정하는데
jsp안에서 수정할 경우 좀더 UriComponentBuilder
클래스를 사용하면 좀더 편하게 할 수 있다.
@Test
public void testURI() throws Exception {
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.path("/board/read")
.queryParam("bno", 12)
.queryParam("perPageNum", 20)
.build();
logger.info(uriComponents.toString());
}
/board/read?bno=12&perPageNum=20
url이 동적으로 변경되어야 한다면 아래처럼 expand()
사용
@Test
public void testURI2() throws Exception {
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.path("/{module}/{page}")
.queryParam("bno", 12)
.queryParam("perPageNum", 20)
.build()
.expand("board", "read")
.encode();
logger.info(uriComponents.toString());
}
<div class="text-center">
<ul class="pagination pagination-sm inline">
<c:if test="${ pageMaker.prev }">
<li><a href="listPage?page=${ pageMaker.makeQuery(pageMaker.startPage-1)}">«</a></li>
</c:if>
<c:forEach begin="${ pageMaker.startPage }"
end="${ pageMaker.endPage }" var="idx">
<%-- <li ${ pageMaker.cri.page == idx ? 'class="active"' : '' }><a
href="listPage${ pageMaker.makeQuery(idx)}">${ idx }</a></li> --%>
<li ${ pageMaker.cri.page == idx ? 'class="active"' : '' }><a
href="${idx}">${ idx }</a></li>
</c:forEach>
<c:if test="${ pageMaker.next }">
<li><a href="listPage?page=${ pageMaker.makeQuery(pageMaker.endPage+1)}">»</a></li>
</c:if>
</ul>
</div>
<script>
$(".pagination li a").on("click", function(event) {
event.preventDefault();
var targetPage = $(this).attr("href");
location.href = "/board/listPage?page="+targetPage;
});
</script>
아쉽지만 페이징 처리는 스프링의 메서드가 약간 도와줄뿐 크게 바뀌는건 없다.
MyBatis를 사용하면 동적쿼리를 어떻게 만들까?
다행이 JSTL에서 사용했던 if
, choose(when, otherwise)
, trim
, foreach
표현식을 사용할 수 있다.
http://www.mybatis.org/mybatis-3/ko/dynamic-sql.html
<sql id="search">
<if test="searchType != null">
<!-- ${ keyword } -->
<bind name="pattern" value="'%' + keyword + '%'" />
<if test="searchType == 't'.toString()">
and title like #{ pattern }
</if>
<if test="searchType == 'c'.toString()">
and content like #{ pattern }
</if>
<if test="searchType == 'w'.toString()">
and writer like #{ pattern }
</if>
</if>
</sql>
<select id="searchCountPaging" resultType="int">
<![CDATA[
SELECT COUNT(bno)
FROM tbl_board
WHERE bno > 0
]]>
<include refid="search"/>
</select>
REST는 Representational State Transfer
의 약어로
URL이 하나의 고유한 리소스(Resource)를 대표하도록 설계된다는 개념.
예를 들어 /boards/123
은 게시물 중에서 123번이라는 고유한 의미를 가지도록 설계, GET
, POST
방식을 통해 추가적인 비지니스 로직을 수행하도록 설정
REST API는 외부에서 위와같은 방식의 URL패턴을 통해 사용자가 원하는 정보를 제공하는 방식,
여러 오픈소스에서 REST API
형식으로 개발하였고 이러 이런 서비스 제공방법을 Restful하다고 표현한다.
@RestController
지금까지 Spring의 jackson 라이브러리와 컨트롤러 메서드 앞에 @ResponseBody
어노테이션을 사용해 View
페이지를 반환하는 것 이 아닌 JSON
형식의 데이터를 반환하였다.
@RestController
어노테이션을 사용하면 Ajax/JSON
처리만을 위한 컨트롤러를 생성 가능하다. 굳이 @ResponseBody
어노테이션을 사용하지 않아도 사용자에게 데이터 반환이 가능해진다.
@RestController
@RequestMapping("/replies")
public class ReplyController {
@Inject
ReplyService service;
@RequestMapping(value="", method=RequestMethod.POST)
public ResponseEntity<String> register(@RequestBody ReplyVO vo) {
ResponseEntity<String> entity = null;
try {
service.addReply(vo);
entity = new ResponseEntity<>("SUCCESS", HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
entity = new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
return entity;
}
...
...
}
ResponseEntity
를 반환하는데 응답 http 데이터의 헤더안의
status
까지 지정할 수 있다.
@RequestBody
어노테이션을 사용하면 HTTP Request 데이터 안의 몸체 데이터중 해당 객체를 위한 정보를 검색해 자동으로 매치해준다.
@ModelAttribute
어노테이션은 자동 매칭 뿐 아니라 Model객체에 알아서 넣어줌RESTful 방식으로 조회, 삭제, 수정 처리
Http method
에는 GET
, POST
방식외에 여러가지 전송방식이 있다.
DELETE, PUT, PATCH
$.ajax({
type: "delete",
url: "/replies/"+rno,
headers: {
"Content-Type": "application/json",
"X-HTTP-Method-Override": "DELETE"
},
dataType: "text",
success: function(result) {
if (result == "SUCCESS") {
...
...
}
}
})
type에 delete
을 사용하여 http전송방식을 delete
로 지정한다.
@RequestMapping(value="/{rno}", method= {RequestMethod.PUT, RequestMethod.PATCH})
public ResponseEntity<String> update(
@PathVariable("rno") int rno,
@RequestBody ReplyVO vo) {
...
...
}
@RequestMapping(value="/{rno}", method=RequestMethod.DELETE)
public ResponseEntity<String> remove(@PathVariable("rno") int rno) {
...
...
}
headers: {
"Content-Type": "application/json",
"X-HTTP-Method-Override": "DELETE"
}
크롬 환경에선 $.ajax
함수를 사용하면 자동으로 content-type
은 json형식이고 type
필드를 통해 method
도 지정가능하기에
header
객체를 정의할 필요 없지만 타 브라우저에선 method 타입이 get/post
밖에 없을 수 있음으로 http header에 별도로 method방식을 지정해줘야 spring에서 인식한다.
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/</url-pattern>
</filter-mapping>
그리고 필터를 통해 hidden method를 읽을 수 있도록 web.xml에서 설정하면 끝.
@PathVariable
/boards/123
같은 URL이 요청되었다면 뒤의 123
의 정보를 사용하여 사용자에게 게시글 정보를 출력하는 페이지를 반환하거나 해당 정보를 반환해야 한다.
@RequestMapping(value="/all/{bno}", method=RequestMethod.GET)
public BoardVO getBoardData(
@PathVariable("bno") int bno,
) {
...
...
...
}
여러개의 파라미터도 전달 받을 수 있다.
@RequestMapping(value="/all/{bno}/{page}", method=RequestMethod.GET)
public ResponseEntity<Map<String, Object>> list(
@PathVariable("bno") int bno,
@PathVariable("page") int page
) {
...
...
}
@PathVariable
어노테이션을 사용하면 URL안의 특정 정보를 파라미터로 받을 수 있다.
@ControllerAdvice
어노테이션을 사용한 에외처리컨트롤러에서 예외가 발생하면 이 예외를 받아 출력하는 페이지를 만들자.
콘솔로 보는것 보다 효율적이다.
@ControllerAdvice
public class CommonExceptionAdvice {
private static final Logger logger = LoggerFactory.getLogger(CommonExceptionAdvice.class);
@ExceptionHandler
public ModelAndView errorModelAndView(Exception e) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("/error_common");
modelAndView.addObject("exception", e);
return modelAndView;
}
}
<!-- error_common.jsp -->
<body>
<h4>${ exception.getMessage() }</h4>
<ul>
<c:forEach items="${ exception.getStackTrace() }" var="stack">
<li>${ stack.toString() }</li>
</c:forEach>
</ul>
</body>
redirect할 때 RedirectAttributes , FlashMap클래스를 이용해 url에 변수명을 노출시키지 않고 전달 가능하다.
지금까지 url에 노출시키지 않고 전달하려면 requset의 setAttribute
를 사용하였는데 redirect할 경우 날라가버리기 때문에 사용하지 못한다.
그렇다고 session에 임시데이터를 저장해 사용하기엔 너무 귄찮다.
Spring에선 이를 해결하기 위해 redirect되는 과정에서 데이터를 넘길 수 있도록 Session에 FlashMap이란 공간을 두고 여기서 데이터를 저장하고 redirect시 꺼내서 사용한 후 소멸시키는 기능을 제공한다.
@RequestMapping(value="modify", method=RequestMethod.POST)
public String modifyPOST(BoardVO board, RedirectAttributes rttr) throws Exception {
logger.info("modify post....");
service.modify(board);
rttr.addFlashAttribute("result", "success");
return "redirect:/board/listAll";
}
반대로 url에 노출시키고 싶을 땐 redirect할 url뒤에 문자열로 붙여도 되지만 addAttribute
메서드를 사용해도 된다.
@RequestMapping(value="remove", method=RequestMethod.GET)
public String remove(
@RequestParam("bno") int bno,
Criteria cri,
RedirectAttributes rttr
) throws Exception {
service.remove(bno);
rttr.addAttribute("page", cri.getPage());
rttr.addAttribute("perPageNum", cri.getPerPageNum());
rttr.addFlashAttribute("result", "success");
return "redirect:/board/listPage";
}
가끔 Test를 통해서도 결과가 잘 안나올 때 실제 MyBatis를 통해 Sql문이 어떻게 만들어지는지 확인하고 싶다면 아래와 같이 코딩
@Override
public int listSearchCount(SearchCriteria cri) throws Exception {
logger.info(
"SQL - BoardDAO Imple : " +
sqlSession.
getConfiguration().
getMappedStatement(namespace+".searchCountPaging").
getBoundSql(cri).
getSql()
);
return sqlSession.selectOne(namespace+".searchCountPaging", cri);
}
http://blog.naver.com/PostView.nhn?blogId=duco777&logNo=220605479481