59일차 내용 정리

채공부·2025년 8월 14일

Spring Framework

@SpringBootApplication

  1. application.properties 설정 파일을 읽어들여서 동작을 준비한다

  2. 현재 이 클래스가 존재하는 패키지를 포함해서 모든 하위 패키지를 scan 해서 bean 으로 만들 것들은 다 만들고 필요하다면 의존성 주입도 실행한다

  3. 이 프로젝트는 spring web 프로젝트이기 때문에 내장 tomcat 도 실행해서 서비스를 준비한다

public static void main(String[] args) {
	SpringApplication.run(Spring03JspApplication.class, args);
}

➜ Run as spring boot app 메뉴를 선택해서 실행하면 여기가 실행된다

HomeController 클래스

  • @Controller 어노테이션이 붙어있기 때문에 spring 은 new HomeController() 해서 객체를 생성한 다음 spring bean container 에서 직접 관리
  • 또한 @Controller 어노테이션이 붙은 객체는 클라이언트의 요청을 처리하는 특별한 객체이기 때문에 해당 동작을 하기 위한 준비 작업도 이루어진다

  • 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();
	}
}

Model 인터페이스

HttpServletRequest : HTTP 의 모든 기능을 다루는 객체

view page 로 넘길 데이터만 담는 객체

Model 객체도 컨트롤러 메소드의 매개변수에 선언만 하면 자동으로 spring 이 전달해 준다

  • home.jsp 링크 추가
<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";
}

MyBatis

Step04_MyBatis 프로젝트 생성

  • application.properties 코드 추가
# 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 생성

  • MemberDto 클래스 생성
@Setter
@Getter
public class MemberDto {
	private int num;
	private String name;
	private String addr;
}
  • MemberDao 인터페이스 생성
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);
}
  • MemberDaoImpl 클래스 생성
// 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 생성

    • 이 xml 문서의 namespace : member
    • 실행할 sql 문의 id : insert
    • parameter 로 전달되는 데이터의 type : MemberDto

#{name}

  • MemberDto 객체의 getName() 메소드를 호출해서 리턴되는 값을 해당 위치에 바인딩해 준다

#{addr}

  • MemberDto 객체의 getAddr() 메소드를 호출해서 리턴되는 값을 해당 위치에 바인딩해 준다
    따라서 name, addr 은 dto 객체의 필드명과 일치시켜야 한다
<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 문 추가
<update id="update" parameterType="com.example.spring04.dto.MemberDto">
	UPDATE member
	SET name=#{name}, addr=#{addr}
	WHERE num=#{num}
</update>
  • MemberDaoImpl 에 update 메소드 코드 추가
@Override
public int update(MemberDto dto) {
	session.update("member.update", dto);
	return 0;
}
  • delete 문 추가
<delete id="delete" parameterType="int">
	DELETE
    FROM member
	WHERE num=#{num}
</delete>
  • MemberDaoImpl 에 deleteByNum 메소드 코드 추가
@Override
public int deleteByNum(int num) {
	return session.delete("member.delete", num);
}
  • select 문 추가

    • select 문을 사용할 때는 select 된 row 을 어떤 type 으로 받을지 결정해야 한다
    • num, name, addr 이 select 된 상황에서 resultType
      MemberDto 로 설정을 하면 select 된 row 하나당 MemberDto 객체를 생성해서 setNum(), setName(), setAddr() 메소드를 호출하면서 자동으로 데이터를 담아준다
    • 단) select 문에 명시한 칼럼명과 dto 의 필드명이 일치해야 한다
<select id="select" parameterType="int" resultType="com.example.spring04.dto.MemberDto">
	SELECT num, name, addr
	FROM member
	WHERE num=#{num}
</select>
  • MemberDaoImpl 에 getByNum 메소드 코드 추가

    • select 되는 row 가 1개면 sessiob.selectOne() 메소드를 사용
    • select 되는 row 가 여러 개일 가능성이 있으면 session.selectList() 메소드를 사용
@Override
public MemberDto getByNum(int num) {
	MemberDto dto = session.selectOne("member.select", num);
	return dto;
}
⭐ .selectOne() 메소드를 호출한 경우 resultType이 메소드의 리턴 type이 된다
  • MemberDaoImpl 에 selectAll 메소드 코드 수정

    • .selectAll() 를 호출하면 리턴 type 은 무조건 List<T>
    • List 의 generic type T 는 그때 그때 다르다
    • resultType 이 바로 List 의 generic type 으로 설정
@Override
public List<MemberDto> selectAll() {
	List<MemberDto> list = session.selectList("member.selectAll");
	return list;
}
  • selectAll 문 추가
<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으로 설정
  • HomeController 클래스 생성
@Controller
public class HomeController {
	
	@GetMapping("/")
	public String home(Model model) {
		List<String> notice = List.of("공지사항 입니다", "공지사항1", "공지사항2");
		model.addAttribute("notice", notice);
		
		return "home";
	}
}
  • application.properties 코드 추가
# mybatis 에서 사용하는 xml 문서가 어디에 있는지 알려주기
# classpath: 은 resources 폴더를 가리킨다
mybatis.mapper-locations=classpath:mapper/*.xml
  • home.jsp 생성
<%@ 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>
  • Spring04MyBatisApplication 에 메소드 추가
// 크롬 브라우저를 자동으로 열어주는 메소드
@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();
	}
}
  • home.jsp 링크 코드 추가
<ul>
	<li><a href="${pageContext.request.contextPath}/member/list">회원 목록보기</a></li>
</ul>
  • MemberController 클래스 생성
@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";
	}
}
  • member 폴더 생성 후 그 안에 list.jsp 생성
<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>

단위 테스트

  • Spring04MyBatisApplicationTests 에 코드 작성
    • 여기에 test 코드를 넣고 run As JUnit Test 를 하면 단위 테스트 가능
@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 이 아니여야 한다");
	}
}

  • MemberDaoTest 클래스 생성
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>
  • MemberDaoTest 에 코드 추가
@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);
}
  • 이름과 주소를 전달하면 전달된 정보를 DB 에 저장하고 해당 정보의 PK 를 리턴하는 메소드
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();
}
  • insert 메소드 수정
@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());
}
  • delete 메소드 추가
@Test
@DisplayName("회원정보 삭제 테스트")
void delete() {
	int num = insertFixture("유재석", "압구정");
	// 삭제하고 삭제된 row 의 갯수를 리턴 받는다
	int rows = dao.deleteByNum(num);
	// 삭제된 row 의 갯수는 반드시 1이어야 한다
	assertEquals(1, rows);
	// select 된 정보는 반드시 null 이어야 한다
	assertNull(dao.getByNum(num));
}
  • update 메소드 추가
@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());	
}
  • selectAll 메소드 추가
@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);
}
  • selectAll2 메소드 추가 = selectAll 메소드 한 줄 코딩
@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

  • HomeControllerTest 클래스 생성
    • HomeController 를 테스트하기 위한 클래스
@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"));
	}
}
profile
학원 공부 내용 정리

0개의 댓글