본문 바로가기
국비학원

[국비지원] KH 정보교육원 89-90일차 (MyBatis, 마이바티스)

by 도전하는 개발자 2022. 8. 4.



우선 메이븐 프로젝트로 마이바티스 프로젝트를 다시 만들어보자

퀵스타트로 프로젝트 만들고

pom.xml studymybatis 에 있는거 덮어쓰기

pom.xml  들어가서 artifactId name url 프로젝트명과 같게 수정

메인과 테스트 폴더에 있는 app apptest 삭제

빌드패스 들어가서 수정 (소스-라이브러리-오더 순서)

프로퍼티스 들어가서 
컴파일러 - 클래스파일 제너레이선 다 체크
리소스 - utf8로 수정

lg4j2 studymybatis - 메인 -리소시스 에 있는거 덮어쓰기

------------------------------------------------------------------------------------


     *웹 3계층                                                     *MVC패턴

(1) Presentation Layer (표현계층)  ---------------------> View
  - 화면을 만들어 내는 계층
    : HTML, CSS, JSP + MODEL (데이터)

(2) Business Layer (비지니스계층) ---------------------> Controller => Model 생성
  - 비지니스 로직을 수행하는 모든 객체
    : Service 객체
    : DTO 객체

(3) Persistence Layer (영속성계층) ----------------------> DAO
 - 영속성 : 영구적으로 저장
 - DB 테이블을 조작하는 모든 객체
   : DAO 객체 (Data Access Object) 
     Mapper
     VO 객체 (Value Object)

------------------------------------------------------------------------------------

* Framework vs Library
 - 프레임워크 : I just follow framework,  프레임워크의 규칙을 따라야함 (mybatis, django)
 - 라이브러리 : I'm in controll, 내가 호출함 (jQuery, React)

------------------------------------------------------------------------------------

MyBatis SQL Mapper Framework
가. 웹3계층중 영속성계층 구현을 위한 프레임워크이다
나. 프레임워크이기 때문에 프로그램 실행흐름의 제어권을 내주고 따라가야함
다. 목적(하는일) : SQL 문장의 수행을 대신 철
라. 모든 SQL 문장은 2가지 방법으로 저장가능 
   - (1) Mapper XML 파일에 저장 (현업에선 XML Mapper 파일이라고 부름)
   - (2) 자바 인터페이스의 추상메소드에 저장
         (mybatis가 제공하는 Annotation을 추상메소드에 붙여서 그 Annotation 안에 속성으로 SQL 문장을 저장)
         (여기서 사용되는 Annotation은 mybatis 라이브러리가 제공)
마. 스프링의 선행요소 기술로 배우는 이유
   - 스프링 기반으로 개발하더라도, SQL 문장의 처리는 Mybatis가 하고,
     스프링 + Mybatis 2개의 프레임워크를 연동하기 위함.

------------------------------------------------------------------------------------

MyBatis SQL Mapper Framework 사용하기

1. MyBatis Library Dowload 
-> 우리는 메이븐을 사용하여 의존성 (즉 필요한 라이브러리들)을 관리하기 때문에
메이븐 중앙저장소에 접속해서 pom.xml에 의존성 추가해주면 끝이다.

https://mvnrepository.com/artifact/org.mybatis/mybatis/3.5.10에 접속해

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.10</version>
</dependency>

이거 pom.xml에 복붙해주자

2. MyBatis의 설정 파일(XML 파일)을 생성
- 설정파일의 이름은 관례상 "mybatis-config.xml"이라고 지음
- 이 파일의 위치는 src/main/resources에 저장 (자원파일이기 때문)
- MyBatis가 SQL을 처리할 수 있는 실행환경을 설정파일에 설정

https://mybatis.org/mybatis-3/ko/getting-started.html을 참고하여 차근차근 해보자

<configuration>

    <environments default="사용할 실행환경의 이름">
    
        <environment id="실행환경이름1">
            <transactionManager type="JDBC" />   // 트랜잭션 관리자 유형 설정 JDBC로 설정
            
            <dataSource type="UNPOOLED">        // Connection Pool의 형태를 갖추고 있지 않다는 의미
                기본적인 JDBC 연결생성을 위한 4가지 정보 설정 (url, driver, user, pass)
            </dataSource>
        </environment>
    
        <environment id="실행환경이름2">
            <transactionManager type="JDBC" />   // 트랜잭션 관리자 유형 JDBC로 설정
            
            <dataSource type="POOLED">           // Connection Pool의 형태를 갖추고 있다는 의미
    기본적인 JDBC 연결생성을 위한 4가지 정보 설정 (url, driver, user, pass)
      Connection Pool 특성에 관여하는 
            </dataSource>
        </environment>
    
    </environments>
    
</configuration>

* 트랜잭션 관리자란 무엇인가? => 트랜잭션 관리
1) WAS 안에 이미 구현체가 있음
2) JDBC Driver 라이브러리 안에도 이미 구현체가 있음
3) Spring Framework 안에도 이미 구현체가 있음
4) 데이터베이스 인스턴스 안에도 이미 구현체가 있음
- 위의 모든 트랜젝션 관리자란 구현체는 모두 같은 표준에 따라 구현하게 되어 있음.

* 데이터 소스란 무엇인가? => Connection Pool을 제공하는 객체
 - Connection Pool은 무엇인가? => JDBC Connection들이 잔뜩 들어있는 객체
 - Connection Pool의 규격(OOP언어에서는 자바 인터페이스를 의미)을 가지고 있음 
 - 실제타입 : javax.sq.DataSource 인터페이스 (규격)
 - Connection Pool은 DataSource라고 부르는 인터페이스의 구현객체

* Connection Pool의 특징
 - Pool 안에 생성될 JDBC Connection 개수에 대하여 최대 몇개, 최소 몇개라는 정보를 가짐
 - JDBC Connection의 필요량에 따라 Max개수만큼 늘어났다가(exteced), Min 개수만큼 줄어듬 (shrinked)

 

 

3. Mapper XML 파일에 mybatis가 수행할 SQL 문장을 만들어 저장하자 (2가지 방법)
1) Mapper XML 파일에 SQL 문장을 저장
 - Mapper XML 파일명의 관례 -> 테이블명Mapper.xml ex)BoardMapper.xml

2) 자바 인터페이스의 추상메소드에 저장 ("Mapper Interface" 방식)
 - MyBatis가 제공하는 Annotation을 추상메소드 위에 붙이고 이 Annotation 속성으로 SQL 문장을 저장

-> 우선 첫번째 방법으로 해보자
    역시 https://mybatis.org/mybatis-3/ko/configuration.html#settings에서 차근차근 해보자

-> DOCTYPE 복붙해주고 <mapper> 태그로 만들어준다.
    mapper의 namespace와 select id는 고유해야한다.

-> SQL 문장을 <select> <insert> <update> <delete> 등에 알맞게 넣어주면 된다.
    우리는 간단하게 시간을 조회해보자

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<!-- 여러개의 Mapper XML 파일이 존재할 때, 
    각 Mapper XML 파일의 최상위 태그의 namespace 속성 값은 고유해야함 (충돌이 없어야함)
    관례상 파일명과 동일하게 설정해준다. -->
<mapper namespace="BoardMapper">

    <!-- 현재의 Mapper XML 파일 안에 등록되어 있는 수 많은 다른 태그들과 충돌되지 않는
    고유한 식별자 값을 id 속성에 지정해야함 (현재 파일 내에서 충돌이 없어야함) -->
    <select 
        id="getCurrentTime"
        resultType="java.lang.String">
        SELECT to_char(current_date, 'yyyy/MM/dd HH24:mi:ss') FROM dual
    </select>
    
</mapper>

BoardMapper.xml


*이제 mybatis-config.xml에 mappers 태그로 BoardMapper.xml 등록해주자

 

    <mappers>
        <!-- Mapper XML 파일 등록 -->
        <mapper resource="BoardMapper.xml" />
    </mappers>

mybatis-config.xml

---

src/test/java에 persistence 패키지를 만들어주자 (mybatis가 영속성을 다루기 때문에)
BoardMapper.xml을 테스트하는 BoardMapperTests 클래스를 만들어주자

 

@Log4j2
@NoArgsConstructor

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(OrderAnnotation.class)

public class BoardMapperTests {//	Mybatis Framework 객체는 바로 SqlSessionFactory 객체임
//	 1) SqlSessionFactoryBuilder를 통해 생성
//	 2) 이 Builder 객체를 통해 핵심객체인 SqlSessionFactory 객체를 생성하려면
//	    마이바티스의 설정파일 mybatis-config.xml에 대한 InputStream 객체가 필요함
	
	// 1. SqlSessionFactory 객체 생성
	private SqlSessionFactory sqlSessionFactory;
	
	@BeforeAll // 1회성 전처리 작업 수행
	void beforeAll() throws IOException {
		log.trace("beforeAll() invoked.");
		
		
		// 2. 마이바티스의 설정파일(mybatis-config.xml)에 대한 입력스트림 객체 is를 생성하자
		
		// InputStream을 얻을 경로를 필드에 저장해주자		
		String path = "mybatis-config.xml";
		
		@Cleanup // 자원객체 썼으면 닫자
		InputStream is = Resources.getResourceAsStream(path);

		// 단 한번 SqlSessionFactory 공장객체를 생성해서, 필드에 저장
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		this.sqlSessionFactory = builder.build(is);
		
		// 객체가 null인지 여부 검증
		assert this.sqlSessionFactory != null;
		log.info("\t+ this.sqlSessionFactory : {}", this.sqlSessionFactory);
		
	} // beforeAll
	
	
	@Test
	@Order(1)
	@DisplayName("1. testGetCurrentTime")
	@Timeout(value = 30, unit = TimeUnit.SECONDS)
	void testGetCurrentTime( ) throws SQLException {
		log.trace("testGetCurrentTime() invoked.");
		
		// 얘는 밑에서 try-with-resources로 닫아보자
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		// 마이바티스는 다수개의 Mapper XML 파일이 있을 때 아래와 같이 2개의 값을 이용해서 
		// 어떤 Mapper XML 파일을 사용할지 결정하고 (by "namespace")
		// 어떤 태그의 SQL문장을 사용할지 결정      (by "id")
		try (sqlSession) {
			String namespace = "BoardMapper";
			String sqlId = "getCurrentTime";			
			String sql = namespace + "." + sqlId; // Mapped Statement
			
			String now = sqlSession.<String>selectOne(sql);
			assert now != null;
			log.info("\t+ now : {}", now);
			
		} // try-with-resources (Auto-closeable 해야 사용가능)
		
	} // testGetCurrentTime

BoardMapperTests.java

---

4. MyBatis의 SqlSessionFactory를 이용해서  Mapper XML 파일에 등록된 특정 SQL 문장을 수행시키는 방법

 1) SqlSessionFactoryBuilder 객체를 생성
 2) Bulider 객체의 build 메소드를 호출 => SqlSessionFactory 객체 생성
 3) SqlSessionFactory.openSession() 메소드 호출로 하나의 SqlSession 객체를 생산
 4) SqlSession.selsectOne(String mappedStatement) 메소드로 Mapper XML 파일에 등록된 특정 SQL 문장 실행
                  .selectList(String mappedStatement)
    Mapped Statement -> namespace + "." + id = SQL 문장이 Mapping

---

3번으로 돌아가서 2번째 방법인 "Mapper Interface" 방식으로 해보자
src/main/java에 mapper 패키지 만들어주고 그 아래에 BoardMapper 인터페이스 만들어주자

 

// MyBatis가 수행할 SQL 문장을 저장하는 자바 인터페이스 => Mapper Interface라고 부름
public interface BoardMapper {

	// MyBatis가 제공하는 Annotation의 속성에 SQL 문장을 저장
	// 마이바티스의 SQL 문장에서, 바인드 변수는 #{변수명} 형식으로 기재함
	// @Param (바인드 변수명) 형태로 사용해서 바인드 변수에 값 전달
	@Select("SELECT * FROM tbl_board WHERE bno > #{theBno} AND title LIKE '%'||#{search}||'%'")
	public abstract List<BoardVO> selectAllBoards(@Param("theBno")Integer bno, @Param("search")String title); // 여러 값이니까 List 객체이용
	
	@Select("SELECT * FROM tbl_board WHERE bno = #{theBno}")
	public abstract BoardVO selectBoard(@Param("theBno") Integer bno);
	
} // end interface

BoardMapper.java

 

------------------------------------------------


* VO패턴, DTO 패턴
 1) VO패턴 = Value Object (값객체)
  - 영속성 계층에 속한 테이블의 한 개의 레코드를 저장하고 수정할 수 없게 만든 객체를 찍어낼 수 있는 클래스를 만드는 설계방식
  - 이 패턴을 따르는 클래스를 Value Object Class, VO 클래스라고 함
  - 클래스의 필드는 테이블의 스키마와 동일하게 작성해야함

 

------------------------------------------------

src/main/java에 domain 패키지 만들어주고 그 아래에 BoardVO 클래스 만들어주자

 

//2. 직접 작성하지 말고 롬복 어노테이션으로 만들자. 
//@EqualsAndHashCode                 // 중복판단
//@ToString
//@Getter(lombok.AccessLevel.PUBLIC) // 접근제한자를 public으로 설정해준다.
//@AllArgsConstructor

//3. 위에 것들 한번에 만들어주자. cf) DTO는 @Data
@Value 
public class BoardVO {

	// 0) 테이블의 컬럼의 순서와 필드의 순서를 동일하게 작성한다.
	// 1) VO class의 필드는 외부에서 접근하지 못하게 private으로 한다.
	// 2) 테이블의 컬럼은 NULL (결측치)가 있을 수 있기 때문에 기본 타입인 int가 아니고 
	//    결측치를 표현할 수 있는 Integer로 필드를 선언해야한다.
	// 3) INSERT_TS, UPDATE_TS는 한번 작성하면 건드리면 안 되기에 만들지 말자
//	private Integer BNO;
//	private String  TITLE;
//	private String  CONTENT;
//	private String  WRITER;
	
	// 자바 식별자 규칙에 따르면 파이널 상수만이 모두 대문자로 표기하기 때문에 소문자로 바꿔주자
	private Integer bno;
	private String  title;
	private String  content;
	private String  writer;
	

 // 1. 생성자를 직접 작성해보자
//	public BoardVO(Integer BNO, String TITLE, String CONTENT, String WRITER) {
//		this.BNO = BNO;
//		this.TITLE = TITLE;
//		this.CONTENT = CONTENT;
//		this.WRITER = WRITER;
//	} // constructor
//
//	2. 수정불가능(Immutable)한 읽기 전용 객체여야 하기 때문에 (테이블의 값을 조회하는데 값을 수정하면 안 되잖슴)
//	   Setter는 말고 Getter만 만들어준다 (상단탭-source-generate getters and setters)
//	public Integer getBNO() {
//		return BNO;
//	} // getBNO
//
//	public String getTITLE() {
//		return TITLE;
//	} // getTITLE
//
//	public String getCONTENT() {
//		return CONTENT;
//	} // getCONTENT
//
//	public String getWRITER() {
//		return WRITER;
//	} // getWRITER
	
} // end class

BoardVO.java

 


BoardMapperTests에 테스트 메소드를 추가해주자

 

@Log4j2
@NoArgsConstructor

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(OrderAnnotation.class)

public class BoardMapperTests {//	Mybatis Framework 객체는 바로 SqlSessionFactory 객체임
//	 1) SqlSessionFactoryBuilder를 통해 생성
//	 2) 이 Builder 객체를 통해 핵심객체인 SqlSessionFactory 객체를 생성하려면
//	    마이바티스의 설정파일 mybatis-config.xml에 대한 InputStream 객체가 필요함
	
	// 1. SqlSessionFactory 객체 생성
	private SqlSessionFactory sqlSessionFactory;
	
	@BeforeAll // 1회성 전처리 작업 수행
	void beforeAll() throws IOException {
		log.trace("beforeAll() invoked.");
		
		
		// 2. 마이바티스의 설정파일(mybatis-config.xml)에 대한 입력스트림 객체 is를 생성하자
		
		// InputStream을 얻을 경로를 필드에 저장해주자		
		String path = "mybatis-config.xml";
		
		@Cleanup // 자원객체 썼으면 닫자
		InputStream is = Resources.getResourceAsStream(path);

		// 단 한번 SqlSessionFactory 공장객체를 생성해서, 필드에 저장
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		this.sqlSessionFactory = builder.build(is);
		
		// 객체가 null인지 여부 검증
		assert this.sqlSessionFactory != null;
		log.info("\t+ this.sqlSessionFactory : {}", this.sqlSessionFactory);
		
	} // beforeAll
	
	
	@Test
	@Order(1)
	@DisplayName("1. testGetCurrentTime")
	@Timeout(value = 30, unit = TimeUnit.SECONDS)
	void testGetCurrentTime( ) throws SQLException {
		log.trace("testGetCurrentTime() invoked.");
		
		// 얘는 밑에서 try-with-resources로 닫아보자
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		// 마이바티스는 다수개의 Mapper XML 파일이 있을 때 아래와 같이 2개의 값을 이용해서 
		// 어떤 Mapper XML 파일을 사용할지 결정하고 (by "namespace")
		// 어떤 태그의 SQL문장을 사용할지 결정      (by "id")
		try (sqlSession) {
			String namespace = "BoardMapper";
			String sqlId = "getCurrentTime";			
			String sql = namespace + "." + sqlId; // Mapped Statement
			
			String now = sqlSession.<String>selectOne(sql);
			assert now != null;
			log.info("\t+ now : {}", now);
			
		} // try-with-resources (Auto-closeable 해야 사용가능)
		
	} // testGetCurrentTime
	
	@Test
	@Order(2)
	@DisplayName("2. testSelectAllBoards")
	@Timeout(value = 30, unit = TimeUnit.SECONDS)
	void testSelectAllBoards(){
		log.trace("testSelectAllBoards() invoked.");
	
		@Cleanup // 이번에는 Cleanup으로 닫자
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
        // Mapper Interface 방식은 이렇게 한다!
		BoardMapper mapper = sqlSession.<BoardMapper>getMapper(BoardMapper.class);
		
		assertNotNull(mapper);
		log.info("\t+ mapper : {}", mapper);		
		
		List<BoardVO> list = mapper.selectAllBoards(200, "6");
		
		Objects.requireNonNull(list);
		list.forEach(log::info);
	} // testSelectAllBoards
	
	@Test
	@Order(3)
	@DisplayName("3. testSelectBoard")
	@Timeout(value = 30, unit = TimeUnit.SECONDS)
	void testSelectBoard(){
		log.trace("testSelectBoard() invoked.");
	
		@Cleanup // 이번에는 Cleanup으로 닫자
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
        // Mapper Interface 방식은 이렇게 한다!
		// 지정한 Mapper Interface의 구현객체인 MapperProxy 획득
		BoardMapper mapper = sqlSession.<BoardMapper>getMapper(BoardMapper.class);
		
		assertNotNull(mapper);
		log.info("\t+ mapper : {}", mapper);		
		
		BoardVO board = mapper.selectBoard(199);
		
		Objects.requireNonNull(board);
		log.info("\t+ board : {}", board);
	} // testSelectBoard
	
	@Test
	@Order(4)
	@DisplayName("4. testSelectAllBoardsByMapperXML")
	@Timeout(value = 30, unit = TimeUnit.SECONDS)
	void testSelectAllBoardsByMapperXML( ) throws SQLException {
		log.trace("testGetCurrentTime() invoked.");
		
		// 얘는 밑에서 try-with-resources로 닫아보자
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		// 마이바티스는 다수개의 Mapper XML 파일이 있을 때 아래와 같이 2개의 값을 이용해서 
		// 어떤 Mapper XML 파일을 사용할지 결정하고 (by "namespace")
		// 어떤 태그의 SQL문장을 사용할지 결정      (by "id")
		try (sqlSession) {
			String namespace = "BoardMapper";
			String sqlId = "selectAllBoards";			
			String sql = namespace + "." + sqlId; // Mapped Statement
			
			Map<String, Object> params = new HashMap<>();
			params.put("theBno", 200);
			params.put("search", "7");
			
			List<BoardVO> list = sqlSession.<BoardVO>selectList(sql, params);
			assert list != null;
			list.forEach(log::info);
			
		} // try-with-resources (Auto-closeable 해야 사용가능)
		
	} // testGetCurrentTime

} // end class

BoardMapperTests.java