티스토리 뷰

1.    MyBatis의 용법 ( 기본 2가지, 확장 2가지 )

 

-      [ 기본 2가지 ]

-      (1) SQL Mapper XML 파일에 SQL 문장을 저장하고 개발시, SqlSessionFactory 핵심 객체를 얻어서 이용하는 방식

-      + SqlSessionFactoryBuilder.build( )

-      + SqlSessionFactory.openSession( )

-      + SqlSession.selectOne( sql, param )

-      + SqlSession.selectList( sql, param )

-      + mybatis-config.xml 파일에 Mapper XML 파일의 경로를 등록

-      (2) 소위 Mapper Interface라고 불리는 자바 인터페이스의 추상 메소드 위에 @Select/@Update/@Delete/@Insert 어노테이션 안에 수행할 SQL문장을 등록

-      + mybatis-config.xml 파일에 Mapper Interface로 등록 ( FQCN )

-      + SqlSessionFactory.openSession( )

-      + SqlSession.getMapper( Mapper 인터페이스의 clazz )

-      + MapperProxy 구현객체를 얻어서 메소드 실행 -> 실행메소드 위의 Sql문 실행

 

-      [ 확장 2가지 ]

-      : mybatis의 연동 라이브러리( mybatis-spring )가 제공하는 기능

-      ( 1 ) Mapper Interface의 구현객체( MapperProxy객체 )를 얻는 방식이 달라진다.

-      + root-context.xml 파일에서 <mybatis-spring:scan base-package= “mapper 패키지 위치”>를 통해, 지정된 패키지 안에 있는 모든 자바 인터페이스 각각에 대한 구현 객체(MapperProxy 객체)를 자동으로 Spring Bean ContainerBean으로 등록해 준다.

-      + ex. TimeMapper.java (Interface) -> MapperProxy구현객체 생성 -> 이 구현객체를 Spring Bean ContainerBean으로 자동등록 -> 의존성 주입이 가능해 진다.

-      + @Autowired를 통해 this.timeMapper.getCurrentTime1() 처럼 사용가능

-      ( 2 ) 확장 2번째 기능은 기존의 방법과 다르게 Mapper Interface에 선언된 추상 메소드가 SQL문장을 가지고 있지 않더라도 실행시킬 SQL문장을 XML Mapper파일의 등록된 Mapped Statement( namespace + . + SqlId )을 수행 가능하게 되었다.

-      + , 다음의 4가지 약속을 지켜야 한다.

-      + 약속 1 : resources 폴더 내에서 XML mapper 파일을 저장할 폴더를 Mapper Interface의 패키지 구조와 같게 만들어야 한다.

-      + ex. org.zerock.myapp.mapper.TimeMapper.java ->

-      + src/main/resources/org/zerock/myapp/mapper/ TimeMapper.java

-      + 약속 2 : 위 약속1에서 만든 폴더 아래에, Mapper Interface의 타입이름과 동일한 이름의 XML Mapper파일을 생성해야 한다.

-      + ex. Mapper Interface의 타입이름 - "TimeMapper"

-      + -> XML Mapper 파일의 이름은 -> "TimeMapper.xml"로 생성해야 한다.

-      약속 3 : 위 약속 2에서 생성한 XML Mapper 파일의 아래 2가지 이름을 규칙대로 생성해야 한다.

-      + [1] namespace 속성의 값 : Mapper Interface FQCN과 동일하게 설정

-      + ex. org.zerock.myapp.mapper.TimeMapper.java ->

-      + -> <mapper namespace="org.zerock.myapp.mapper.TimeMapper">

-      + [2] id 속성값 : Mapper Interface의 추상 메소드 이름과 동일하게 설정

-      + ex. org.zerock.myapp.mapper.TimeMapper.abstractMethod1

-      + -> <select id="abstractMethod1">SQL 문장 </select>

-      + 위의 약속의 결과로 만일 org.zerock.myapp.mapper.TimeMapper.java라는 Mapper Interface가 없다면 아래와 같이 SQL문장을 찾아서 실행 :

-      + 1. src/main/resources/org/zerock/myapp/mapper/폴더를 찾는다.

-      + 2. 이 폴더아래에서, "TimeMapper".xml 파일을 찾는다.

-      + 3. 이 찾은 파일안에서, namespaceorg.zerock.myapp.mapper.TimeMapper와 동일한지 확인한다.

-      + 4. 수행시킨 추상메소드의 이름과 동일한 sql id값을 가진 SQL문장 최종 실행한다.

 

2.    확장 기능의 목적

-      : Mapper Interface의 추상 메소드 중에, SQL문장이 없는 경우, 자동으로 위와 같은 과정을 통해서 실행시킬 SQL문장을 XML Mapper파일에서 찾아 자동 수행시키기 위해서다.

 


[ 1. SqlSessionFactoryBean 확인 ] (***)

 

[ 1 - 1. mapper.xml 생성 ]

 

더보기

[ + 코드 보기 ]

<?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="sql1mapper">

    <!-- CRUD의 기본적인 태그를 지니고 있다. -->

    <!-- 태그의 id는 주로 실행시킨 메소드의 이름을 그대로 사용한다. (맘대로 작성해도 ok) -->
    <!-- id의 값은 mapper 파일 내에서 유니크해야 한다. -->

    <!-- resulType은 반환될 구체타입을 지정해 주면 된다. : List타입을 반환하지만 실제로는 List<BoardVO>이기에 BoardVO의 FQCN을 지정해주면 된다. -->
    <!-- 즉, List<E>를 반환한다면 resulType에 <E>를 지정해 주면 된다. -->

    <!-- parameterType는 전송 받을 값을 지정해 준다. -->
    <!-- ex.  #{employee_id} 값 -->

    <!-- ========================================================================================= -->

    <!-- 1. 사원번호가 100번이 넘어가는 사원 출력하는 쿼리 등록 -->
    <select 
        id="DQL1" 
        resultType="org.zerock.myapp.domain.EmployeeVO" 
        parameterType="int">

        <![CDATA[ 
            SELECT * 
            FROM employees 
            WHERE employee_id > #{employee_id} 
        ]]>

    </select>

    <!-- ========================================================================================= -->

</mapper>

 

더보기

[ + 코드 보기 ] 

<?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="sql2mapper">

    <!-- CRUD의 기본적인 태그를 지니고 있다. -->

    <!-- 태그의 id는 주로 실행시킨 메소드의 이름을 그대로 사용한다. (맘대로 작성해도 ok) -->
    <!-- id의 값은 mapper 파일 내에서 유니크해야 한다. -->

    <!-- resulType은 반환될 구체타입을 지정해 주면 된다. : List타입을 반환하지만 실제로는 List<BoardVO>이기에 BoardVO의 FQCN을 지정해주면 된다. -->
    <!-- 즉, List<E>를 반환한다면 resulType에 <E>를 지정해 주면 된다. -->

    <!-- parameterType는 전송 받을 값을 지정해 준다. -->
    <!-- 바인딩 변수가 2개일 경우에는 map을 지정해줘야 한다. -->
    <!-- ex.  #{employee_id} 값 -->

    <!-- ========================================================================================= -->

    <!-- 2. email이 지정한 단어로 시작하고, salary가 지정한 값을 넘는 사원 출력하는 쿼리 등록 -->
    <!-- '%'||#{email}||'%'는 지정한 #{email} 값이 포함되어 있으면 모두 출력하는 것이고 -->
    <!-- #{email}||'%'는 지정한 #{email} 값으로 시작하는 이메일을 반환한다. -->
    <!-- #{email}에 A%를 넣어서, A로 시작하는 이메일을 출력할 수도 있으나 이는 보안상 좋지 못하다. -->
    <select 
        id="DQL2" 
        resultType="org.zerock.myapp.domain.EmployeeVO" 
        parameterType="java.util.Map">

        SELECT * 
            FROM employees 
            WHERE email LIKE #{email}
            AND salary > #{salary}

    </select>

    <!-- ========================================================================================= -->

</mapper>

 

[ 1 - 2. interface 형식의 mapper 생성 ] (****)

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.mapper;

import org.apache.ibatis.annotations.Select;

public interface TimeMapper {
	
	@Select("SELECT to_char(current_date, 'yyyy/MM/dd HH24:MI:SS') As now FROM dual")
	public abstract String getCurrentTime1();

} // end interface

 

[ 1 - 3. MyBatis-config 파일에서 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>
    
    <!-- 서비스 환경 -->


    <!-- 관리할 Mapper 파일 작성해 주기 (***) -->
    <mappers>

        <!-- 인터페이스 방식의 Mapper을 등록할 때에는 class 속성을 사용해야 한다.(***) -->
        <!-- SQL Mapper XML 파일 등록 -->
        <mapper resource="mappers/1/SQLMapper1.xml" />
        <mapper resource="mappers/2/SQLMapper2.xml" />
        <!-- Mapper Interface 등록 -->
        <!-- <mapper class="org.zerock.myapp.mapper.TimeMapper" /> -->

        <!-- 인터페이스 방식의 Mapper이 만약 페키지 안에 있다면, 이러한 방법으로 패키지를 지정해 줄 수도 있다. -->
        <package name="org.zerock.myapp.mapper" />

    </mappers>

</configuration>

 

[ 1 - 4. VO 만들기 ]

 

더보기

[ + 코드 보기 ]

import java.util.Date;

import lombok.Value;

@Value
public class EmployeeVO {
	
	// + VO에서는 NULL이 들어올 수 있기에,
	// + 반드시 참조타입을 사용해야 한다.
	// + VO는 읽기 전용이기에 getter만 생성된다.
	
	// ============================
		// employees 테이블에 있는 스키마 등록
	// ============================
	private Integer employeeId;
	private String firstName;
	private String lastName;
	private String email;
	private String phoneNumber;
	private Date hireDate;
	private String jobId;
	private Double salary;
	private Double commissionPct;
	private Integer managerId;
	private Integer departmentId;

} // end class

 

[ 1 - 5. Test하기 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.persistance;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.zerock.myapp.domain.EmployeeVO;
import org.zerock.myapp.mapper.TimeMapper;

import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

//======================================================
// JUNIT5 - 테스트 메소드 수행시 Spring FrameWork를 구동시키는 어노테이션
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
//file: Spring 프로젝트 폴더를 의미한다. ( chap04를 의미 )
//file:에는 공백이 허용되지 않다.
//spring 폴더 -> root-context가 선택된다.
//와일드 카드도 사용이 가능하다.
//======================================================

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class SqlSessionFactoryTests {
	
//	@Test만 필수이지, 그 외의 것들은 선택이다.
//	@BeforeAll
//	@BeforeEach
//	@Test
//	@AfterAll
//	@AfterEach
	
	// ======================================================
		// 1. 빈 컨테이너에 SqlSessionFactoryBean 요청
	// ======================================================
	
	// @Autowired
	// onMethod_에 1개 이상의 어노테이션을 지정할 수 있다.
	@Setter(onMethod_= { @Autowired }) // ( 추천 )
	private SqlSessionFactory sqlSessionFactory;
	
	// ======================================================
		// 2. sqlSessionFactoryBean 확인
	// ======================================================
	
	@BeforeAll
	void beforeAll() {
		
		log.trace("beforeAll() invoked.");
		
		// SqlSessionFactoryBean이 들어왔는지 확인
		// + 들어왔다면 null일리가 없다.
		Objects.requireNonNull(this.sqlSessionFactory);
		log.info("\t + this.sqlSessionFactory : {}", this.sqlSessionFactory);
		// + this.sqlSessionFactory : org.apache.ibatis.session.defaults.DefaultSqlSessionFactory@123456
		
	} // beforeAll
	
	// ======================================================
		// 3. Mapper 수행
	// ======================================================
	
	@Disabled
	@Test
	@Order(1)
	@DisplayName("1. testSQLMapper1")
	@Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
	void testSQLMapper1() {
		log.trace("\t + testSQLMapper1() invoked.");
		
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		try ( sqlSession; ){
			
			// mappedStatement = namespace명 + sql 아이디
			String mappedStatement = "sql1mapper.DQL1";
			
			// 100은 #{employee_id} 값을 지정해 주는 것이다.
			// 1. SELECT * FROM employees WHERE employee_id > 100 sql문이 실행
			List <EmployeeVO> list = sqlSession.<EmployeeVO>selectList(mappedStatement, 100);
			list.forEach(log::info);
			
		} // try - with - resources
		
	} // testSQLMapper1
	
	// ======================================================
		// 4. Mapper2 수행
	// ======================================================
	
	@Disabled
	@Test
	@Order(1)
	@DisplayName("2. testSQLMapper2")
	@Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
	void testSQLMapper2() {
		log.trace("\t + testSQLMapper2() invoked.");
		
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		try ( sqlSession; ){
			
			// mappedStatement = namespace명 + sql 아이디
			String mappedStatement = "sql2mapper.DQL2";
			
			// 넘겨줄 map 객체 생성
			Map<String, Object> map = new HashMap<>();
			map.put("email", "A%");
			map.put("salary", 3000);
			
			// 바인딩 변수에 map 객체를 넘겨주면 getter를 통해 값을 받아간다.
			List <EmployeeVO> list = sqlSession.<EmployeeVO>selectList(mappedStatement, map);
			list.forEach(log::info);
			
		} // try - with - resources
		
	} // testSQLMapper2
	
	// ======================================================
		// 5. Interface Mapper 수행 (****)
	// ======================================================
	
	@Test
	@Order(1)
	@DisplayName("3. testgetCurrentTime1")
	@Timeout(value = 3000, unit = TimeUnit.MILLISECONDS)
	void testgetCurrentTime1() {
		log.trace("\t + testgetCurrentTime1() invoked.");
		
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		try ( sqlSession; ){
			
			TimeMapper mapper = sqlSession.getMapper(TimeMapper.class);
			Objects.requireNonNull(mapper);
			log.info("\t + mapper : {}", mapper);
			// + mapper : org.apache.ibatis.binding.MapperProxy@1658b239
			
			String now = mapper.getCurrentTime1();
			log.info("\t + now : {}", now);
			
		} // try - with - resources
		
	} // testgetCurrentTime1

} // end class

[ 2. mapper를 Bean으로 등록하기 ] (*****)

 

[ 2 - 1. root-context.xml파일의 namespace 탭에서 설정 ]

 

 

[ 2 - 2. root-context.xml파일에서 태그로 mapper를 Bean으로 등록 ]

 

더보기

[ + 코드 보기 ]

<!-- mybatis와 spring을 연동했을 때에만 사용이 가능한 태그 -->
<!-- mapper 자체를 bean으로 등록시켜 버린다. -->
<mybatis-spring:scan base-package="org.zerock.myapp.mapper" />

<!-- + mybatis-spring 연동 라이브러리가 제공하는 아래의 태그는, -->
<!-- + 지정한 자바 패키지를 스캔해서 지정한 패키지에 있는 자바 인터헤이스를 Mapper Interface로 간주하고 -->
<!-- + 자동으로 Bean으로 등록하는 태그이다. -->

 

[ 2 - 3. Bean으로 등록된 mapper 테스트 ]

 

더보기

[ + 코드 보기 ]

import java.util.Objects;
import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.zerock.myapp.mapper.TimeMapper;

import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

// JUNIT5 - 테스트 메소드 수행시 Spring FrameWork를 구동시키는 어노테이션
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TimeMapperTests {
	
	//======================================================
	
	@Setter(onMethod_= { @Autowired })
	private TimeMapper timeMapper;
	
	//======================================================
	
	@BeforeAll
	void beforeAll() {
		
		log.info("beforeAll() invoked.");
		
		Objects.requireNonNull(this.timeMapper);
		log.info("\t + this.timeMapper : {}", this.timeMapper);
		// + this.timeMapper : org.apache.ibatis.binding.MapperProxy@44114b9f
		
	} // beforeAll
	
	//======================================================
		// Bean으로 출력 (***)
	//======================================================
	
	@Test
	@Order(1)
	@DisplayName("1. testGetCurrentTime1")
	@Timeout(value = 3, unit = TimeUnit.SECONDS)
	void testGetCurrentTime1 () {
		
		log.info("testGetCurrentTime1() invoked.");
		
		String now = this.timeMapper.getCurrentTime1();
		log.info("\t + now : {}",now);
		
	} // testGetCurrentTime1
	
	//======================================================

} // end class

[ 3. 마이바티스의 XML mapper 자동실행 규칙 ] (******)

 

[ 3 - 1. Interface 방식의 Mapper에 추상 메소드 생성 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.mapper;

import org.apache.ibatis.annotations.Select;

public interface TimeMapper {
	
	// ==============================
		// 확장 기능 1
	// ==============================
	@Select("SELECT to_char(current_date, 'yyyy/MM/dd HH24:MI:SS') As now FROM dual")
	public abstract String getCurrentTime1();
	
	// ==============================
		// 확장 기능 2
	// ==============================
	// 이 추상 메소드는 SQL문장을 어노테이션으로 가지지 않는다.
	// TimeMapper.xml 파일에 등록된 Mapped 문장으로 처리된다.
	public abstract String getCurrentTime2();

} // end interface

[ 3 - 2. main/resources 폴더 내에 Mapper가 들어가 있는 패키지 폴더를 생성하고, Mapper의 동일한 위치 / 이름으로 Mapper.xml 파일 생성  ]

 

 

[ 3 - 3. 생성한 Mapper.xml 파일의 namespace으로 Interface 방식의 Mapper의 FQCN을 지정해 주기 ]

 

[ 3 - 4. 생성한 Mapper.xml 파일의 id로 Interface 방식의 Mapper에서 생성한 추상 메소드의 이름을 넣어주기 ]

 

[ 3 - 5. 실행하기 ]

 

더보기

[ + 코드 보기 ]

package org.zerock.myapp.persistance;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.zerock.myapp.mapper.TimeMapper;

import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;


@Log4j2
@NoArgsConstructor

// JUNIT5 - 테스트 메소드 수행시 Spring FrameWork를 구동시키는 어노테이션
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TimeMapperTests {
	
	//======================================================
	
	@Setter(onMethod_= { @Autowired })
	private TimeMapper timeMapper;
	
	//======================================================
	
	@BeforeAll
	void beforeAll() {
		
		log.info("beforeAll() invoked.");
		
		Objects.requireNonNull(this.timeMapper);
		log.info("\t + this.timeMapper : {}", this.timeMapper);
		// + this.timeMapper : org.apache.ibatis.binding.MapperProxy@44114b9f
		
	} // beforeAll
	
	//======================================================
		// Bean으로 출력 (***)
	//======================================================
	
	@Test
	@Order(1)
	@DisplayName("1. testGetCurrentTime1")
	@Timeout(value = 3, unit = TimeUnit.SECONDS)
	void testGetCurrentTime1 () {
		
		log.info("testGetCurrentTime1() invoked.");
		
		String now = this.timeMapper.getCurrentTime1();
		log.info("\t + >>>> 1. now : {} <<<<",now);
		
	} // testGetCurrentTime1
	
	//======================================================
		// 마이바티스의 XML mapper 자동실행 규칙 (*****)
	//======================================================
	
	@Test
	@Order(2)
	@DisplayName("2. testGetCurrentTime2")
	@Timeout(value = 3, unit = TimeUnit.SECONDS)
	void testGetCurrentTime2 () {
		
		log.info("testGetCurrentTime2() invoked.");
		
		String now = this.timeMapper.getCurrentTime2();
		log.info("\t + >>>> 2. now : {} <<<<",now);
		
	} // testGetCurrentTime2
	
	//======================================================

} // end class
728x90
댓글
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
최근에 올라온 글
Total
Today
Yesterday
공지사항