89일차 Mybatis1

쿠우·2022년 8월 8일
0

기본적인 내용 다시 짚고 넘어가기

웹 3계층 - 3-tier(layer) Architecture 라고도 부름

(1) Presentation Layer - 표현 계층
(2) Business Layer - 비지니스 계층
(3) Persistence Layer - 영속성 계층 (DB계층이라고도 부름)
* 영속성: 영구적으로 저장한다는 의미
(영구적인 대표적인 저장소 => 데이터베이스)


VO패턴, DTO패턴

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


MyBatis SQL mapper framework란?

가. 웹3계층 중에, 영속성계층(Persistence Layer) 구현을 위한
프레임워크(framework)이다!

나. 바로 "프레임워크(framework)"이다! 즉, 프로그램 실행흐름의
제어권을 가져감! <-> 라이브러리는 개발자가 실행흐름을 제어

다. 목적(하는일): 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 Download

=> 우리는 Maven 을 사용하여, 의존성(즉, 필요한 라이브러리들)을 관리하기 때문에, 아래와 같이 "pom.xml" 파일에 의존성 추가하면 끝!

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

2. MyBatis의 설정파일(XML파일이어야 함)을 생성

a. 설정파일의 이름: 우리 맘대로 지어도 되지만, 일반적으로 "mybatis-config.xml"이라고 지음.
b. 설정파일을 생성하고, 이 XML파일의 위치는 "src/main/resources" 소스폴더에 저장
(설정파일은 프로그램 소스파일이 아니되, MyBatis 실행에 반드시 필요한 소위 "자원파일"이기 때문에...)
c. MyBatis 가 SQL을 처리할 수 있는 실행환경을 설정파일에 설정

     <environments default="사용할 실행환경의 이름">

        <environment id="실행환경이름1">
           <transactionManager type="JDBC" />   // 트랜잭션 관리자의 유형 설정

           <dataSource type="UNPOOLED">      // Connection Pool의 형태를 갖추고 있지 않다! 라는 의미
              기본적인 JDBC 연결생성을 위한 4가지의 정보 설정(url, driver, user, pass)
           </dataSource>
        </environment>
        ...
        <environment id="실행환경이름N">
           <transactionManager type="JDBC" />   // 트랜잭션 관리자의 유형 설정

           <dataSource type="POOLED">         // Connection Pool의 형태를 갖추고 있다! 라는 의미
              기본적인 JDBC 연결생성을 위한 4가지의 정보 설정(url, driver, user, pass) +
              Connection Pool의 특성에 관여하는 설정 정보를 추가로 가짐
           </dataSource>
        </environment>

     </environments>   
  * 트랜잭션 관리자가 무엇인가? => "트랜잭션" 관리(즉, TCL언어의 사용이 가능)
    (1) WAS 안에 이미 구현체가 있음
    (2) JDBC Driver 라이브러리 안에도 이미 구현체가 있음
    (3) Spring Framework 안에도 이미 구현체가 있음
    (4) 데이터베이스 인스턴스 안에도 이미 구현체가 있음

    - 위의 모든 트랜잭션관리자란 구현체는 모두, 
      같은 표준(X/Open XA규약)에 따라 구현하게 되어 있음
 * 데이터 소스(Data Source)가 무엇인가? => "Connection Pool"을 제공하는 객체
    - 그럼, Connection Pool은 무엇인가? => JDBC Connection 들이 잔뜩 들어있는 Pool
    - Connection Pool의 규격(OOP언어에서는 자바 인터페이스를 의미한다!)을 가지고 있음
    - 실제타입: javax.sql.DataSource (I) 인터페이스(규격)
    - Connection Pool 은 DataSource 라고 부르는 인터페이스의 무엇입니까!? => "구현객체"

      public class ConnectionPool implements javax.sql.DataSource {

      } // 인터페이스의 구현클래스
     
  * Connection Pool 의 특징:
      - Pool 안에 생성될 JDBC Connection 개수에 대하여, 최대 몇개/최소 몇개라는 정보를 가짐
      - JDBC Connection 의 필요량에 따라, Max개수만큼 늘어났다가(extended),
        Min 개수만큼 줄어듦(shrinked)

- mybatis-config.xml 설정을 예제를 통해 배우기

알아본 점
(1) environment 태그를 통해 실행환경 이름을 특정한다.
(2) transactionManager 태그 속성에 type에는 3가지 값이 올 수 있다. JDBC WAS 등등
(3) dataSource 타입을 통해서 기타 형식을 갖춘 연결을 추가 설정 할 수 있게 정한다.
(4) mappers태그를 통해 매퍼할 파일들을 정의한다.

<?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>
      <typeAlias type="org.zerock.myapp.domain.BoardVO" alias="boardVO" />
    </typeAliases>


    <environments default="production">
      <environment id="development">
        <transactionManager type="JDBC"/>
  
        <dataSource type="UNPOOLED">
          <!-- <property name="driver" value="oracle.jdbc.OracleDriver"/> -->
          <!-- <property name="url" value="jdbc:oracle:thin:@atp20201_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP"/> -->
  
          <property name="driver" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"/>
          <property name="url" value="jdbc:log4jdbc:oracle:thin:@atp20201_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP"/>
  
          <property name="username" value="ADMIN"/>
          <property name="password" value="Oracle123128"/>
  
          <!-- <property name="defaultTransactionIsolationLevel" value="" /> -->
          <!-- <property name="defaultNetworkTimeout" value="1000" /> -->
          <property name="driver.encoding" value="utf8" />
        </dataSource>
      </environment>
    
      <environment id="production">
        <transactionManager type="JDBC"/>
  
        <dataSource type="POOLED">
          <!-- <property name="driver" value="oracle.jdbc.OracleDriver"/> -->
          <!-- <property name="url" value="jdbc:oracle:thin:@atp20112301_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP"/> -->
  
          <property name="driver" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"/>
          <property name="url" value="jdbc:log4jdbc:oracle:thin:@atp203201_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP"/>
  
          <property name="username" value="ADMIN"/>
          <property name="password" value="Oracle1231248"/>
  
          <property name="poolMaximumActiveConnections" value="10" />
          <property name="poolMaximumIdleConnections" value="3" />
          <property name="poolTimeToWait" value="20000" />
          <property name="poolPingQuery" value="SELECT 1 FROM dual" />
          <property name="poolPingEnabled" value="true" />
          <property name="poolPingConnectionsNotUsedFor" value="60000" />
  
          <property name="driver.encoding" value="utf8" />
        </dataSource>
      </environment>
    </environments>


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

        <!-- 2. Mapper Interface 등록#1 : 각각의 Mapper Interface 등록방법 -->
        <!-- <mapper class="org.zerock.myapp.mapper.BoardMapper"/> -->

        <!-- 3. Mapper Interface 등록#2 : Mapper Interface 들이 모여있는 패키지단위로 등록하는 방법 -->
        <package name="org.zerock.myapp.mapper" />
    </mappers>

</configuration>

3. Mapper XML 파일에, MyBatis가 수행할 SQL문장을 만들어 저장하자! (2가지 방식)

(1) Mapper XML파일에 SQL문장을 저장

  • 우선 Mapper XML 파일을 생성하되, 파일의 이름은 관례상 아래와 같이 정한다:
    (파일명 관례: 테이블명+Mapper.xml)
    • 예1: tbl_board 테이블에 대한 SQL문장을 저장하는 Mapper XML파일의 이름 =>
      Board+Mapper.xml => BoardMapper.xml 이 되더라!!!
    • 예2: tbl_member 테이블에 대한 SQL문장을 저장하는 Mapper XML파일의 이름 =>
      Member+Mapper.xml => MemberMapper.xml 이 되더라!!!

BoardMapper.xml 예제

-알아본 점
(1) select,delete,insert,update 성격에 맞게 태그를 만들어준다.
(2) resultType 속성을 통해서 어떤 타입으로 반환할지 정한다.
(3) 미리 sql문장의 실행 여부를 확인하고 넣어라


<mapper namespace="BoardMapper">

    <select
        id="getCurrentTime"
        resultType="java.lang.String">
        SELECT to_char(current_date, 'yyyy/MM/dd HH24:mi:ss') AS now FROM dual
        
        <!--
         수행시킬 SQL 문장을 만들어 여기다 놓으라!!
        - SQL 개발 도구에서 유효한 SQL문장을 우선 만들고 카피해 넣어라 
        - 여기서 오타나 잘못된 문장을 잡아내기가 굉장히 힘들다.
        --> 
    </select>

    <!-- 마이바티스는 바인드변수에 넣어줄 값을, 바인드 변수명을 이용해서 획득합니다. -->
    <!-- 1. 하나의 Map<K,V>객체를 넘겨주면, 바인드 변수명을 Key로 해서, Value값을 얻어내어 사용 -->
    <!-- 2. 하나의 자바빈즈 객체를 넘겨주면, 바인드 변수명을 프로퍼티명으로 해서, get프로퍼티명()메소드
            호출로 값을 얻어내어 사용 -->
    <select
        id="selectAllBoards"
        resultType="org.zerock.myapp.domain.BoardVO">
        SELECT * FROM tbl_board WHERE bno > #{theBno} AND title LIKE '%'||#{search}||'%'
        
    </select>

</mapper>

(2) 자바 인터페이스의 추상메소드에 저장:

===> "Mapper Interface"라고 부른다!!! (***)
MyBatis가 제공하는 Annotation을 추상메소드 위에 붙이고, 이 Annotation의 속성으로 SQL문장을 저장

BoardMapper Interface 의 예제

-알아본 점
(1)구현객체는 myBatis에서 자동으로 생성
(2)Mapper Interface페이스라고 부른다.
(3)select,delete,insert,update 어노테이션의 변수로 sql문을 넣어준다.

// MyBatis가 수행할 SQL 문장을 저장하는 자바 인터페이스 => "Mapper Interface" 라고 부른다!!
// MyBatis가 제공하는 Annotation의 속성에 SQL문장을 저장
// MyBatis가 구현객체를 자동으로 생성해준다. 
public interface BoardMapper {
	
	// @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);
	
	// 마이바티스의 SQL 문장에서, 바인드 변수는 #{변수명} 형식으로 기재함
	// 마이바티스는 처리할 SQL문장의 바인드 변수의 개수가 1개 뿐일 경우에는, 
	// 전달한 인자값을 무조건 바인드 값으로 binding 시킴
	@Select("SELECT * FROM tbl_board WHERE bno = #{theBno}")
	public abstract BoardVO selectBoard(Integer bno);

} // end interface

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

(1) SqlSessionFactoryBuilder 객체를 생성
(2) Builder객체의 build(마이바티스설정파일에대한InputStream객체)메소드를 호출 => SqlSessionFactory 객체 생성
(3) SqlSessionFactory.openSession() 메소드 호출로, 하나의 SqlSession 객체를 생산
(4) SqlSession.selectOne(String mappedStatement) 메소드로, Mapper XML파일에 등록된 "특정" SQL문장 실행
.selectList(String mappedStatement) Ditto.

 왜 "Mapped Statement" 라고 할까!? 
 => 2개의 정보를 이용해서, 1개의 SQL문장이 특정되는 구조이기 떄문에 이런 용어를 사용

  (1) namespace + (2) "." + (3) id => 2개의 정보로 1개의 SQL문장이 사상(Mapping된 것임)   


  (주의) SqlSession.selectOne("SELECT sysdate FROM dual"); 을 의미하는 것이 아님! 
  어디까지나 2개의 정보를 '.' 문자로 이어서 만든 문자열을 지정                          

- JUnit을 통해 전처리로 SqlSessionFactory 정보 주입

-알아본 점
(1)SqlSessionFactoryBuilder 객체의 build 메서드를 사용해 mybatis-config.xml설정파일의 정보를 토대로 sqlSessionFactory를 만든다.
(2) 흐름순서
1. InputStream 객체 안에 Resources.getResourceAsStream()메소드를 이용해 설정 파일을 담는다.
2. SqlSessionFactoryBuilder() 객체를 생성 build 메소드 안에 InputStream(설정파일정보)을 담는다.
3. this.sqlSessionFactory 선언해둔 객체의 변수에 넣으면 전처리 끝


@Log4j2
@NoArgsConstructor

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BoardMapperTests {
		
	private SqlSessionFactory sqlSessionFactory;
	
	
	@BeforeAll
	void beforeAll() throws IOException {	// 1회성 전처리 작업 수행
		log.trace("beforeAll() invoked.");
		
		// 마이바티스 설정파일에 대한 입력스트림 객체 생성
		String path = "mybatis-config.xml";
		InputStream is = Resources.getResourceAsStream(path);// 호출하는 리소스의 패키지 로컬 경로 반환
		
		// 단한번 SqlSessionFactory 공장객체를 생성해서, 필드에 저장
		// SqlSessionFactoryBuilder => SqlSessionFactory 인스턴스를 만든다.
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		this.sqlSessionFactory = builder.build(is); //어떤 설정으로 인스턴스를 만들지 선택해준다.
		
		assertNotNull(this.sqlSessionFactory);
		log.info("\t+ this.sqlSessionFactory: {}", this.sqlSessionFactory);
	} // beforeAll

- ~~Mapper.xml 을 이용해 사용하는 법

-알아본 점
(1)sqlSessionFactory객체의 openSession()메소드를 이용해 sqlSession객체를 만든다.
(2)sqlSession 객체에 CRUD에 따른 메소드에 맞춰서 namespace.sqlId 를 넣어주면 설정해놓은 결과타입으로 반환해준다.

~~Mapper.xml

    <select
        id="getCurrentTime"
        resultType="java.lang.String">
        SELECT to_char(current_date, 'yyyy/MM/dd HH24:mi:ss') AS now FROM dual
        
        <!--
         수행시킬 SQL 문장을 만들어 여기다 놓으라!!
        - SQL 개발 도구에서 유효한 SQL문장을 우선 만들고 카피해 넣어라 
        - 여기서 오타나 잘못된 문장을 잡아내기가 굉장히 힘들다.
        --> 
    </select>

Test.java


//	@Disabled
	@Test
	@Order(1)
	@DisplayName("testGetCurrentTime")
	@Timeout(value=10, unit=TimeUnit.SECONDS)
	void testGetCurrentTime() throws SQLException {
		log.trace("testGetCurrentTime() invoked.");
		
		// SqlSessionFactory 인스턴스에서 세션을 열어 찍어내라 
//		@Cleanup("close")
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		try (sqlSession) {					
			// 마이바티스는 아래와 같이, 2개의 값을 이용해서, 다수개의 Mapper XML 파일이 있을 때,
			//  (1) 어느 특정 Mapper XML을 사용할지 결정하고 (by "namespace")
			//  (2) 다시 결정된 Mapper XML 파일안에 있는 많은 SQL 태그중에, 어느 태그의 SQL문장을 사용할지를
			//      결정(by "id")
			String namespace = "BoardMapper";	// Mapper XML 파일마다 지정된 이름(namespace)
			String sqlId = "getCurrentTime";	// 특정 Mapper XML 파일안에 있는 특정 SQL 태그의 식별자(id)			
			String sql = namespace + "." + sqlId;	// 최종 수행시킬 SQL문장의 식별자값! 2가지 정보가 있어야 sqlSession을 이용 가능
			
			//
			String now = sqlSession.<String>selectOne(sql);
			
			assert now != null;
			log.info("\t+ now: {}", now);
		} // try-with-resources
	} // testGetCurrentTime
profile
일단 흐자

0개의 댓글