@SpringBootApplication
application.properties 설정 파일을 읽어들여서 동작을 준비한다
현재 이 클래스가 존재하는 패키지를 포함해서 모든 하위 패키지를 scan 해서 bean 으로 만들 것들은 다 만들고 필요하다면 의존성 주입도 실행한다
이 프로젝트는 spring web 프로젝트이기 때문에 내장 tomcat 도 실행해서 서비스를 준비한다
public static void main(String[] args) {
SpringApplication.run(Spring03JspApplication.class, args);
}
➜ Run as spring boot app 메뉴를 선택해서 실행하면 여기가 실행된다
HomeController 클래스

Spring03JspApplication 에 코드 추가@EventListener(ApplicationReadyEvent.class)
public void openChrome() {
String url = "http://localhost:9000/";
// 운영체제를 얻어와서 이름을 소문자로
String os = System.getProperty("os.name").toLowerCase();
ProcessBuilder builder = null;
try {
if (os.contains("win")) {
builder = new ProcessBuilder("cmd.exe", "/c", "start chrome " + url);
} else if (os.contains("mac")) {
builder = new ProcessBuilder("/usr/bin/open", "-a", "Google Chrome", url);
} else {
System.out.println("지원 하지 않는 운영체제 입니다.");
return;
}
builder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
HttpServletRequest : HTTP 의 모든 기능을 다루는 객체
view page 로 넘길 데이터만 담는 객체
Model 객체도 컨트롤러 메소드의 매개변수에 선언만 하면 자동으로 spring 이 전달해 준다
<li><a href="${pageContext.request.contextPath}/fortune2">오늘의 운세2</a></li>
FortuneController 에 코드 추가@GetMapping("/fortune2")
public String fortune2(Model model) {
String fortuneToday ="동쪽으로 가면 귀인을 만나요!";
// Model 객체에 담으면 자동으로 HttpServletRequest 객체에 담긴다
model.addAttribute("fortuneToday",fortuneToday);
return "fortune";
}
Step04_MyBatis 프로젝트 생성
# port 번호 설정
server.port=9000
# view page 위치에 붙을 접두어 설정
spring.mvc.view.prefix=/WEB-INF/views/
# view page 위치에 붙을 접미어 설정
spring.mvc.view.suffix=.jsp
# DB 정보
# oracle DataSource Setting ( Connection Pool )
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.username=scott
spring.datasource.password=TIGER
준비 작업
spring04 에 하위 패키지 controller , dto , repository 생성
@Setter
@Getter
public class MemberDto {
private int num;
private String name;
private String addr;
}
public interface MemberDao {
public List<MemberDto> selectAll();
public void insert(MemberDto dto);
// update, delete 는 수정, 삭제된 row 의 갯수를 리턴하는 모양으로 Dao 메소드를 정의한다
public int update(MemberDto dto);
public int deleteByNum(int num);
public MemberDto getByNum(int num);
}
// Dao 에는 보통 @Repository 어노테이션을 붙여서 bean 을 만든다 (내부적으로 추가 기능을 제공해준다)
@Repository
public class MemberDaoImpl implements MemberDao{
// MyBatis 를 사용할 때 필요한 핵심 객체
@Autowired
private SqlSession session;
@Override
public List<MemberDto> selectAll() {
return session.selectList("member.selectAll");
}
@Override
public void insert(MemberDto dto) {
session.insert("member.insert", dto);
}
@Override
public int update(MemberDto dto) {
return 0;
}
@Override
public int deleteByNum(int num) {
return 0;
}
@Override
public MemberDto getByNum(int num) {
return null;
}
}
MyBatis 설치
xml 파일 생성 방법


MemberMapper.xml 생성
memberinsert#{name}
getName() 메소드를 호출해서 리턴되는 값을 해당 위치에 바인딩해 준다#{addr}
getAddr() 메소드를 호출해서 리턴되는 값을 해당 위치에 바인딩해 준다<mapper namespace="member">
<insert id="insert" parameterType="com.example.spring04.dto.MemberDto">
INSERT INTO member
(num, name, addr)
VALUES(member_seq.NEXTVAL, #{name}, #{addr})
</insert>
</mapper>
<update id="update" parameterType="com.example.spring04.dto.MemberDto">
UPDATE member
SET name=#{name}, addr=#{addr}
WHERE num=#{num}
</update>
@Override
public int update(MemberDto dto) {
session.update("member.update", dto);
return 0;
}
<delete id="delete" parameterType="int">
DELETE
FROM member
WHERE num=#{num}
</delete>
@Override
public int deleteByNum(int num) {
return session.delete("member.delete", num);
}
select 문 추가
resultType 을setNum(), setName(), setAddr() 메소드를 호출하면서 자동으로 데이터를 담아준다<select id="select" parameterType="int" resultType="com.example.spring04.dto.MemberDto">
SELECT num, name, addr
FROM member
WHERE num=#{num}
</select>
MemberDaoImpl 에 getByNum 메소드 코드 추가
@Override
public MemberDto getByNum(int num) {
MemberDto dto = session.selectOne("member.select", num);
return dto;
}
⭐ .selectOne() 메소드를 호출한 경우 resultType이 메소드의 리턴 type이 된다
MemberDaoImpl 에 selectAll 메소드 코드 수정
.selectAll() 를 호출하면 리턴 type 은 무조건 List<T>@Override
public List<MemberDto> selectAll() {
List<MemberDto> list = session.selectList("member.selectAll");
return list;
}
<select id="selectAll" resultType="com.example.spring04.dto.MemberDto">
SELECT num, name, addr
FROM member
ORDER BY num ASC
</select>
⭐ .selectList() 메소드를 호출한 경우
resultType 이 메소드가 리턴해준 List의 generic type으로 설정
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model) {
List<String> notice = List.of("공지사항 입니다", "공지사항1", "공지사항2");
model.addAttribute("notice", notice);
return "home";
}
}
# mybatis 에서 사용하는 xml 문서가 어디에 있는지 알려주기
# classpath: 은 resources 폴더를 가리킨다
mybatis.mapper-locations=classpath:mapper/*.xml
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<div class="container">
<h1>인덱스 페이지 입니다</h1>
<h2>공지사항</h2>
<ul>
<c:forEach var="tmp" items="${notice}">
<li>${tmp}</li>
</c:forEach>
</ul>
</div>
// 크롬 브라우저를 자동으로 열어주는 메소드
@EventListener(ApplicationReadyEvent.class)
public void openChrome() {
String url = "http://localhost:9000/";
// 운영체제의 얻어와서 이름을 소문자로
String os = System.getProperty("os.name").toLowerCase();
ProcessBuilder builder = null;
try {
if (os.contains("win")) {
builder = new ProcessBuilder("cmd.exe", "/c", "start chrome " + url);
} else if (os.contains("mac")) {
builder = new ProcessBuilder("/usr/bin/open", "-a", "Google Chrome", url);
} else {
System.out.println("지원 하지 않는 운영체제 입니다.");
return;
}
builder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
<ul>
<li><a href="${pageContext.request.contextPath}/member/list">회원 목록보기</a></li>
</ul>
@Controller
public class MemberController {
// 필요한 의존 객체를 주입 받는다
@Autowired private MemberDao dao;
@GetMapping("/member/list")
public String list(Model model) {
// 회원 목록
List<MemberDto> list = dao.selectAll();
// 응답에 필요한 객체를 Model 객체에 담는다
model.addAttribute("list", list);
// "/WEB-INF/views/member/list.jsp" 에서 응답하기
return "member/list";
}
}
<div class="container">
<h1>회원 목록</h1>
<table>
<thead>
<tr>
<th>번호</th>
<th>이름</th>
<th>주소</th>
</tr>
</thead>
<tbody>
<%--
"list" 라는 키값으로 담긴 데이터는 List<MemberDto> 이다
따라서 tmp 는 MemberDto type 이다
tmp.getNum() 하면 번호를 얻어낼 수 있는데 EL 에서는 tmp.num 해도
자동으로 getter 메소드를 호출해준다
--%>
<c:forEach var="tmp" items="${list}">
<tr>
<td>${tmp.getNum()}</td>
<td>${tmp.name}</td>
<td>${tmp.addr}</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
@SpringBootTest
class Spring04MyBatisApplicationTests {
@Autowired
private SqlSession session;
@Autowired
private MemberDao dao;
@Test
void contextLoads() {
Assertions.assertNotNull(session, "SqlSession 은 null 이면 안된다");
Assertions.assertNotNull(dao, "MemberDao 은 null 이면 안된다");
}
@Test
void sqlsession_dao_injected() {
Assertions.assertNotNull(session, "SqlSession 은 null 이면 안된다");
Assertions.assertNotNull(dao, "MemberDao 은 null 이면 안된다");
}
@Test
@DisplayName("MemberDao 와 SqlSession 객체 DI 성공 여부 테스트")
void test01() {
Assertions.assertNotNull(session, "SqlSession 은 null 이면 안된다");
Assertions.assertNotNull(dao, "MemberDao 은 null 이면 안된다");
}
@Test
@DisplayName("회원 목록 select 테스트")
void selectTest() {
// 회원 목록을 select 한다
List<MemberDto> list = dao.selectAll();
Assertions.assertNotNull(list, "selectAll() 은 null 이 아니여야 한다");
}
}

import static org.junit.jupiter.api.Assertions.*;
➜ Assertions 클래스에 존재하는 모든 static 자원을 import 해서
마치 이 클래스의 멤버 메소드처럼 사용할 수 있다
@SpringBootTest
public class MemberDaoTest {
// 필요한 의존 객체 주입받기
@Autowired
private MemberDao dao;
@Test
@DisplayName("MemberDao 가 bean 으로 관리 되는지 여부")
void test01() {
assertNotNull(dao);
}
}
MemberMapper insert 문에 selectKey 코드 추가
selectKey 를 이용하면 insert 전에 pk 값을 미리 얻어내서 MemberDto 객체에 담을 수 있다keyProperty="num" 여기에 num 과 MemberDto 의 필드명과 일치하면 된다<insert id="insert" parameterType="com.example.spring04.dto.MemberDto">
<selectKey keyProperty="num" resultType="int" order="BEFORE">
SELECT member_seq.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO member
(num, name, addr)
VALUES(#{num}, #{name}, #{addr})
</insert>
@Transactional // 트랜잭션을 관리하도록 하고
@Rollback // insert, update, delete 등의 동작은 rollback 되도록 한다
⚠️ 테스트여도 실제 DB 를 사용하기에 쓰레기 값이 들어갈 수 있으니 Rollback 한다
@Test
@DisplayName("회원정보 추가 테스트")
void insert() {
MemberDto dto = new MemberDto();
dto.setName("유재석");
dto.setAddr("압구정");
// mybatis 에서 selectKey 설정을 했기 때문에 insert 하고 나면 dto 객체에 추가한 회원의 번호가 들어 있다
dao.insert(dto);
// 데이터가 잘 들어 갔는지 select 해보기
MemberDto found = dao.getByNum(dto.getNum());
assertNotNull(found);
}
int insertFixture(String name, String addr) {
MemberDto dto = new MemberDto();
dto.setName(name);
dto.setAddr(addr);
// mybatis 에서 selectKey 설정을 했기 때문에 insert 하고 나면 dto 객체에 추가한 회원의 번호가 들어 있다
dao.insert(dto);
return dto.getNum();
}
@Test
@DisplayName("회원정보 추가 테스트")
void insert() {
// 미리 만들어준 메소드로 데이터를 넣고 PK 를 리턴 받아서 사용한다
int num = insertFixture("유재석", "압구정");
// 데이터가 잘 들어갔는지 select 해보기
MemberDto found = dao.getByNum(num);
// select 된 정보가 null 이 아니여야 한다
assertNotNull(found);
// select 된 정보의 name 이 반드시 "유재석" 이어야 한다
assertEquals("유재석", found.getName());
// select 된 정보의 addr 이 반드시 "압구정" 이어야 한다
assertEquals("압구정", found.getAddr());
}
@Test
@DisplayName("회원정보 삭제 테스트")
void delete() {
int num = insertFixture("유재석", "압구정");
// 삭제하고 삭제된 row 의 갯수를 리턴 받는다
int rows = dao.deleteByNum(num);
// 삭제된 row 의 갯수는 반드시 1이어야 한다
assertEquals(1, rows);
// select 된 정보는 반드시 null 이어야 한다
assertNull(dao.getByNum(num));
}
@Test
@DisplayName("회원정보 수정 테스트")
void update() {
// 데이터를 추가하고
int num = insertFixture("유재석", "압구정");
// 불러온 다음
MemberDto dto = dao.getByNum(num);
// 필드를 수정하고
dto.setName("박명수");
dto.setAddr("이태원");
// 수정 반영
int rows = dao.update(dto);
// 수정된 데이터 불러와서
MemberDto dto2 = dao.getByNum(num);
assertEquals(1, rows);
assertEquals("박명수", dto2.getName());
assertEquals("이태원", dto2.getAddr());
}
@Test
@DisplayName("회원 목록 테스트")
void selectAll() {
// 데이터를 추가하고
int num = insertFixture("유재석", "압구정");
// 회원 목록을 select 해서 목록에 방금 추가된 정보가 잘 들어 있는지 확인한다
List<MemberDto> list = dao.selectAll();
boolean isExist = false;
for(MemberDto tmp : list) {
if(tmp.getNum() == num) {
isExist = true; // 하나라도 true 가 나오면 값을 변수에 담고
break; // 반복문 탈출
}
}
// isExist 는 반드시 true 여야 한다
assertTrue(isExist);
}
@Test
@DisplayName("회원 목록 테스트2")
void selectAll2() {
// 데이터를 추가하고
int num = insertFixture("유재석", "압구정");
// 회원 목록을 select 해서 목록에 방금 추가된 정보가 잘 들어 있는지 확인한다
List<MemberDto> list = dao.selectAll();
/*
* item.getNum() == num 이 하나라도 true 가 있다면 (하나라도 일치하다면)
* true 를 리턴하는 메소드가 .anyMatch() 메소드
* */
boolean isExist = list.stream().anyMatch(item -> item.getNum() == num);
// isExist 는 반드시 true 여야 한다
assertTrue(isExist);
}
@WebMvcTest(HomeController.class)
public class HomeControllerTest {
// 가짜 Mvc 객체를 주입 받는다
// 가상의 get, post 등의 요청을 하면서 테스트를 할 수 있다
@Autowired
private MockMvc mvc;
@Test
@DisplayName("/ 요청시 view page 와 모델 데이터 확인")
void testHome() throws Exception {
/*
* GET 방식 "/" 요청을 수행했을 때
* 1. 정상응답(200) 이어야 하고
* 2. view page 의 이름이 "home" 이어야 하고
* 3. Model 객체에 "notice" 라는 키값으로 담은 내용이 있어야
* 위의 테스트는 통과된다
* */
mvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("home"))
.andExpect(model().attributeExists("notice"));
}
}