Day 84. Spring Framwork 6 : Mybatis

ho_c·2022년 6월 26일
0

국비교육

목록 보기
65/71
post-thumbnail

지난 시간 파일 업로드, 다운로드를 기점으로 Spring 프레임워크 수업은 일단락됐다. 그래서 오늘부턴 MyBatis 프레임워크를 통해 백 단에서의 DB 작업을 경량화시키면서, 동적 쿼리까지 구현했다.

1. MyBatis 개요

일단 이제까지 내가 DB를 다루기 위해 배운 자바쪽 스택은 다음과 같다.

JDBC → DBCP → Singleton, JNDI → Spring JDBC

처음 배울 때는 JDBC의 Connection 인스턴스로 DB와 연결하고, 쿼리를 보낸 뒤, 반환 값을 반복문으로 DTO에 담거나, commit하고 연결 닫고, 이런 과정의 반복이었다.

그래서 첫째로 DB 연결을 관리하기 위해 DBCP를 적용했고, 추후엔 의존성을 낮추고 연결 포화를 막기 위해 싱글톤과 JNDI를 사용했었다.

이후 스프링으로 넘어와서, DTO에 값을 옮겨담는 작업, Statement에 필요한 값을 세팅하는 걸 스프링 JDBC로 간편하게 진행했다.

하지만 문제는 우리가 값을 세팅해야 한다는 것. 즉, 자바라는 틀 안에 갇혀있다는 것이다. 따라서 자바라는 틀에서 벗어나 값을 세팅하는 작업도 가변적으로 진행하기 위해 MyBatis 프레임워크를 배울 것이다.


2. MyBatis 이점

  • 복잡한 데이터 조회에 대해 동적 쿼리를 쉽게 만들 수 있다.
  • 코드 분량을 Spring JDBC보다 더 축소할 수 있다.
  • Cross Language로 언어의 제약이 없어 타 플랫폼 간의 Migration에 유리하다.
  • 따라서 자바의 영역에 쿼리문을 두지 않는다.

3. MyBatis 초기 세팅

MyBatis를 사용하기 위해선 기본 세팅이 필요하다. 단계별로 진행해보자

1) 기존 DB Dependency 가져오기

JDBC, DBCP, Spring JDBC

기존에 사용하던 Dependency들 모두, 서로 연결되어 DB 작업이 가능하게 된다. MyBatis도 이를 그대로 갖고 DB 작업을 하기에 그대로 pom.xml에 넣어준다.

<!-- Data Related --> 		
<dependency>
   <groupId>com.oracle.database.jdbc</groupId>
    <artifactId>ojdbc8</artifactId>
    <version>21.1.0.0</version>
</dependency>
		
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.9.0</version>
</dependency>
		
<dependency> 
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${org.springframework-version}</version>
</dependency>

2) Maven repository에서 MyBatis, MyBatis Spring 가져오기

MyBatis 사용을 위해 Maven repository MyBatis, MyBatis Spring 이 두 개를 가져온다.
이때, MyBatis Spring과 Spring JDBC 둘 다 MyBatis가 Spring에서 동작하기 위해서 필요한 것이니 꼭 넣어줘야 한다.

<!-- MyBatis -->
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.5.10</version>
</dependency>		
		
<!-- MyBatis Spring-->
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>2.0.7</version>
</dependency>

3) Root-context.xml에서 DataSource bean 가져오기.

Spring JDBC처럼 DB 연결도 MyBatis가 관리하기 때문에, 기존과 같이 DBCP 인스턴스도 가져온다.

<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
	<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
	<property name="url" value="jdbc:oracle:thin:@Localhost:1521:xe"></property>
	<property name="username" value="ID"></property>
	<property name="password" value="PW"></property>
</bean>

4) MyBatis 인스턴스 만들어주기

이제 우리가 사용할 MyBatis 인스턴스를 생성해주자. Maven로 가져왔으니까, root-context.xml에다가 세팅해주면 된다.

MyBatis 인스턴스는 2개가 필요하다. SqlSessionFactoryBean SqlSessionTemplate 인데, 실제 사용하는 건 후자고 전자는 후자를 만드는 공장이라고 생각하면 된다.

[ SqlSessionFactoryBean ]

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource"/>
	<property name="mapperLocations" value="classpath:/mybatis/*-mapper.xml"/>		
</bean>

(1) DBCP DI

SqlSessionFactoryBean 에는 두 개의 인자가 들어간다. 하나는 앞서 만든 DBCP 인스턴스이고, 다른 하나는 쿼리문을 읽어올 XML 파일의 주소이다.

(2) mapperLocations DI

앞서 MyBatis는 이식성을 위해 쿼리문을 자바의 영역에 두지 않는다고 배웠다. 즉, DB 작업을 하기 위해 쿼리문이 필요한데, MyBatis는 쿼리문은 XML파일에 다가 미리 설정해둔다. 따라서 이 쿼리문을 가져오기 위한 해당 파일의 주소를 지정해준다.

이러면 자바가 classpath 코드를 통해서 이클립스(개발환경)가 갖고 있는 클래스패스를 불러오고, 이 안에서 쿼리문을 담아둔 XML문서를 찾아낸다.

(3) SqlSessionTemplate 생성

이렇게 세팅을 하고나면, 실제 우리가 자바의 영역에서 쓸 인스턴스를 생성해야 한다. 그게 바로 SqlSessionTemplate인데, Setter를 사용하지 않고, 생성자를 통해 SqlSessionFactoryBean를 DI한다.

[ SqlSessionTemplate ]

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
	<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>	

5) mapper 문법을 인식할 수 있는 문법 정보 import 해주기

우리가 쿼리를 작성하기 위해 XML파일을 설정한 클래스패스에 생성하면 <?xml version="1.0" encoding="UTF-8" ?> 와 같은 문구만 덩그러니 있다.

다짜고짜 SQL문을 적으면 될 것 같지만, 데이터를 분석하기 위해서는 기존 세팅과 태그를 만들어서 컴퓨터에게 넘겨줘야 한다. 이때 쓰이는 게 mapper 태그인데, 이걸 프로그램이 인식할 수 있도록 해당 태그에 대한 문법 정보를 끌어와야 한다.

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

위 텍스트는 Mybatis 공식 문서에 가면 찾을 수 있다.


6) namespace 정해주기

프로그램이 문서의 내용을 해석할 정보를 주었다면, 이제는 일종의 맵핑을 해야 한다. 즉, 테이블에 따라 쿼리문을 분류해줘야 하는데, namespace는 그 중에서 대분류라고 생각하면 된다.
( 각, 쿼리문에 대한 소분류는 태그들의 id값으로 구분된다. )

<mapper namespace="Contact">
	<insert id="insert">
		insert into contact values(contact_seq.nextval, #{name}, #{contact}, sysdate)
	</insert>
	
	<delete id="deleteBySeq">
		delete from contact where seq = #{value}
	</delete>
	
	<select id="selectAll" resultType="kh.spring.dto.ContactDTO">
		select * from contact
	</select>
	
	<update id="updateBySeq" parameterType="kh.spring.dto.ContactDTO">
		update contact set name = #{name}, contact = #{contact} where seq = #{seq}	
	</update>

</mapper>

4. MyBatis로 CRUD 만들기

전체 세팅은 다 끝났고, 이제는 MyBatis를 이용해서 CRUD를 구현해보자. MyBatis를 사용하려면 우리는 두 파일을 다루게 된다. 하나는 쿼리륻 담을 XML, 다른 하나는 자바에서 MyBatis 프레임워크를 사용할 DAO이다.

DAO에는 기본으로 @Autowried 로 MyBatis 인스턴스를 DI해준다.

@Autowired
private SqlSession mybatis;

0) 값 세팅하기

MyBatis로 쿼리를 날리는 과정은 DAO를 통해서 XML과 세팅할 값을 Mybatis 인스턴스의 멤버 메서드를 넣어주면 자동으로 세팅해 쿼리를 날리고 값을 반환한다.

이때, 중요한 것은 값을 날려주는 방식이다. 이전 단계에서 값을 날려주는, 다르게 세팅하는 방식은 직접 우리가 Setter 메서드를 통해서 쿼리에 값을 하나씩 넣어줬다. 이제는 우리가 값을 보내기만 하면 MyBatis가 진행해준다.

단, 문제는 MyBatis 메서드에 인자로 값을 넘기는 건 ‘가변 인자’가 아니란 것이다. 이걸 주의하면서 세팅법을 알아보자.

- 기본 자료형

MyBatis는 객체(DTO, Map)가 아닌 기본 자료형 한 개만 넘어올 경우, #{value} 를 사용하여 값을 세팅한다.

- 자료형이 여러 가지일 경우

MyBatis는 Spring JDBC처럼 여러 값을 가변 인자로 받아 세팅하는 것을 지원하지 않아 다수의 값을 다루려면 객체를 사용해야 한다. 때문에 바구니에 담아서 넘겨주는데, 그 바구니의 역할을 하는 것이 DAO, Map이다.

① DTO

흔히 우리가 데이터를 받아올 때 사용한 데이터 인스턴스이다. 데이터를 넣어주기 적합하지만, 매번 넣어줄 데이터에 맞춰서 DTO를 만들고 모아놔야 한다.


② Map

컬렉션 중에 하나로 key-value로 세팅할 값(value)과 이를 사용하기 위한 키(key)로 데이터를 분류하기 때문에 전달 용도로 MyBatis에서 지원한다. 굳이 비유하자면 JSON의 자바 버전이라고 생각하는게 편하다.

Map의 기본 사용법

맵을 사용하기 위해선 Map 인스턴스를 만들어야 된다. 우리는 Map 중에서도 HashMap을 사용한다.

// 인스턴스 생성 <key의 자료형, value의 자료형>
Map<String, String> map = new HashMap<>();

그 후에는 .put을 넣어줘서 Map 인스턴스 안에 값을 세팅해준다. 이때 value의 자료형은 Object도 된다. 정말 다양한 값을 쉽게 보내줄 수 있어서 용이하다.

map.put("id", “admin”);
map.put("pw",1234);

MyBatis에서 사용법

HashMap 인스턴스에 값을 다 세팅했다면, MyBatis에게 메서드로 넘겨주기만 하면 된다.

return mybatis.selectOne("Member.login", map);

이 값을 제대로 사용하기 위해선 Member.login이 맵핑된 XML에서 어떻게 사용할지 우리가 세팅해줘야 한다.

<select id="login" resultType="boolean">
	select count(*) from member where id=#{id} and pw=#{pw}
</select>

우리가 앞서 세팅한 키를 #{} 안에 넣어주면 끝이다.


그럼 이제 CRUD를 만들어보자.

1) .insert()

일단 DAO에서 DB에 데이터를 넣기 위한 MyBatis 메서드는 .insert()이다.

public int insert(ContactDTO dto) throws Exception{

	return mybatis.insert("Contact.insert", dto);
}

첫 번째 인자는 우리가 앞서 맵핑해둔, XML파일 내의 namespace와 내부의 작성된 XML 쿼리 태그의 id 값이다.

두 번째 인자는 XML에서 세팅될 객체 또는 값이다.

<mapper namespace="Contact">
	<insert id="insert">
		insert into contact values(contact_seq.nextval, #{name}, #{contact}, sysdate)
	</insert>
</mapper>

위 코드는 XML파일의 코드이다. 앞서 설명한 첫 번째 인자를 따라, 메서드는 해당 쿼리를 인식하게 되고 그 쿼리 안에 두 번째 인자로 받은 DTO의 값을 세팅하게 된다.

값을 세팅할 때는 #{ 변수명 } 으로 세팅하는데 EL처럼 ${ 변수명 }도 가능하다. 하지만 둘의 차이는 ‘’(홑따옴표)이다. 전자의 쿼리 결과는 ‘변수명’의 형태를 띄지만, 후자는 변수명이 되어서 보안 측면에서 쿼리 주입이 가능하기에 위험하다.

결과적으로 객체로 묶어서 보낸 값의 변수명을 그대로 써주게 되면, 자바의 관점에서는 자동으로 #{name} == dto.getName(); 같은 Getter를 사용한다고 볼 수 있다.


2) .select()

데이터를 조회하는 select 역시, DAO와 XML 모두 select이라는 키워드를 사용해서 DAO 자체는 크게 달라지지 않는다. 다만 XML에서 반환값을 잘 설정해줘야 한다. 만약 조건을 선택할 경우, insert처럼 2번째 인자로 값을 넘겨주면 된다.

public List<ContactDTO> selectAll() throws Exception{

	return mybatis.selectList("Contact.selectAll");
}
<select id="selectAll" resultType="kh.spring.dto.ContactDTO">
	select * from contact
</select>

포인트는 resultType이라는 속성이다. 실제 쿼리를 MyBatis가 받아서 처리해주기 때문에 우리는 MyBatis에 어떤 데이터 타입으로 반환해줘야 하는지 알려줘야 한다. (개발자가 거기까지 생각할 수 없으니까..)

추가로 boolean형의 경우, 반환값이 0이면 false, 1이면 true를 반환해준다.


3) .delete

삭제 기능도 별반 다를 건 없다. <delete> 태그를 사용하고 쿼리문을 넣어준 뒤, 맵핑만 하면 된다.

public int delete(int seq) throws Exception {

	return mybatis.delete("Contact.deleteBySeq", seq);
}
<delete id="deleteBySeq">
	delete from contact where seq = #{value}
</delete>

4) .update

수정 기능도 다른 건 크게 없다. 다만 select에서 생겼던 고민이 어떤 형태로 쿼리 결과를 받아올지 몰라서 resultType 라는 속성을 썻다면, update는 어떤 형태로 값을 세팅할지 몰라서 parameterType을 사용한다.

public int update(ContactDTO dto) throws Exception {

	return mybatis.update("Contact.updateBySeq", dto);
}
<update id="updateBySeq" parameterType="kh.spring.dto.ContactDTO">
	update contact set name = #{name}, contact = #{contact} where seq = #{seq}	
</update>
profile
기록을 쌓아갑니다.

0개의 댓글