SpringBoot

SQL Mapper - Mybatis

데브킹덕 2024. 9. 2. 18:27

 

 

 

팀 프로젝트가 끝나고 혼자 프로젝트 리뷰를 하다가 

우리나라조차 점점 트렌드인 JPA가 Mybatis의

점유율을 이기는 판국에

왜 Mybatis를 배우고 사용하게 되었는지에 대해 의문이 들었다.

(사실 JPA 아직 찍먹만해봄🤪)

 

 

먼저

영속성부터 찾아봤다.

 

 

영속성

(persistence)

데이터를 생성한 프로그램의 실행이 종료되어도 

사라지지 않는 데이터 특성을 말한다.

 

 

우리는  그래서 애플리케이션을 실행했을때

영속성을 위해

DB에 알맞게 관리한다.

 

 

그래서 Java Database Connectivity 

JDBC API를 제공한다.

 

 

하지만 순수 JDBC는 어마어마하게

작성하는 부분과 중복되는 부분이 많다.

 

//순수 JDBC 를 이용하여 회원 등록 코드
public Member save(Member member) {
    String sql = "insert into member(name) values(?)";
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
        conn = getConnection();
        pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
        pstmt.setString(1, member.getName());
        pstmt.executeUpdate();
        rs = pstmt.getGeneratedKeys();
        if (rs.next()) {
            member.setId(rs.getLong(1));
	} else {
		throw new SQLException("id 조회 실패");
	}	
    	return member;
    } catch (Exception e) {
        throw new IllegalStateException(e);
    } finally {
        close(conn, pstmt, rs);
    }
}

 

 

그래서 간편하게 좀 쓰세요.!

하고 나온 방법이

SQL Mapper와 ORM이다.

 

 

데이터베이스 접속을 편하게 하기 위해

DB에 SQL문을 실행하기 위한 객체를 생성하고

개발자가 작성한 SQL 실행 결과를 객체에 매핑하는 방식인

SQL Mapper

 

 

객체와 관계형DB를 자동으로 매핑해주는

ORM(Object-Relational Mapping)

방식이 대표적인 것으로 알고 있다.

 

 

SQL Mapper에는 대표적으로

JDBC Template, Mybatis가 존재하고

 

 

자바 진영에서 ORM기술 표준으로 사용되는

인터페이스 모음인 JPA 있고

이를 구현한 구현체인 Hibernate 등이 있다.

 

+ 유용한 기술이 추가된

Spring Data JPA가 있다고 알고 있다.

 

 

 

Mybatis 홈페이지에 들어가서 당신이 누구인지  대체 뭐하는 놈인지 확인해 봤다.

 

 

마이바티스는 개발자가 지정한 SQL, 저장프로시저, 고급 매핑을 지원하는 영속성 프레임워크라고 한다.

* 영속성 - 프로그램이 종료되어도 생성한 데이터가 사라지지 않는 성질

 

순수 JDBC로 처리하는 상당 부분의 코드와 파라미터 설정 및 결과 매핑을 대신해 준다.

 

 

자바 프로그램에서 DB와 정보를 주고 받기 위해

원시타입Map 인터페이스 , POJO 설정 등의 개념들을 사용해 데이터를 저장 관리할 수 있다.

매핑하기 위해 XML어노테이션을 사용한다고 한다 .

 

 

* 원시타입: 기본이 되는 데이터 유형 (int, char, boolean)

 

* Map : key-value로 저장

 

* POJO(Plain Old Java Object) : 복잡한 설정이나 기능 없이, field와 데이터를 처리하는 단순 클래스

//POJO
public class Student {
    private String name;
    private int age;
    private int score;
    // 이름, 나이, 성적에 접근하기 위한 메서드들 (getter, setter)
}

 

*XML(eXtensible Markup Language)

- 데이터를 구조화된 형식으로 저장하는 문서형식

- 자바 프로그램이 DB와 정보를 주고받을때 사용

//xml
<student>
    <name>홍길동</name>
    <age>15</age>
    <score>85</score>
</student>

 

 

 

 

 

정리해보자면

모든 마이바티스 앱에서 SqlSessionFactoryBuilder를 사용하여

SqlSessionFactory 인스턴스를 빌드한다.

이때 XML 설정 파일에서 SqlSessionFactory를 빌드 할 수 있는데

Resource라는 유틸성 클래스로 인해 다른 클래스 패스와 위치에서 자원을 로드하는 것을 좀 더 쉽게 해준다.

 

 

XML 설정 파일에서 지정하는 핵심 뽀인트는

트랜잭션을 제어하기 위해 TransactionManager과 함께 DB Connection 인스턴스를

가져오기 위해 DataSource를 포함한다.

 

 

XML 상단 부분은 XML문서의 유효성체크를 위해 필요하다고 한다. 

 

environments 초록색 부분은 환경 설정을 하는 부분이다.

 

빨간 부분을 보면 트랜잭션 관리하는

transactionManager type = "JDBC" 부분을 확인할 수 있다.

* JDBC는 수동으로 commit, rollback을 처리함

* MANAGED는 자동으로 commit, rollback을 처리함

 

 

DB connection 인스턴스를 가져오기 위해

dataSource type=POOLED 및 설정하는 부분을 확인할 수 있다.

 

* POOLED란?

최초 Connection 객체를 생성 시 해당 정보를 pool 영역에 저장해 두고

이후 Connection 객체를 생성할때 이를 재사용한다

 

장점: Connection 객체를 생성하여 Database와 연결을 구축하는데 걸리는 시간이 단축된다. 

단점: 단순한 로직을 수행하는 객체를 만들기엔 설정해야 할 정보가 많음

 

* 그럼 UNPOOLED로 설정하게 되면?

Connection 객체를 별도로 저장하지 않고, 객체 호출시 매번 생성하여 사용한다.

장점: Connection 연결이 많지 않은 코드를 작성할때 간단하게 구현 가능

단점: 매번 새로운 Conneciton 객체를 생성하므로 속도가 상대적으로 느림

 

 

 

 

XML을 사용하지 않고 Java를 사용하여 SqlSessionFactory 빌드하기

XML 파일과 같은 모든 설정을 제공하는 Configuration 클래스를 사용하면 됨

 

SQL 매핑 어노테이션(@Mapper)을 가진 Mapper클래스를 추가해야한다

 

 

 

 

SqlSessionFactory에서 SqlSession 만들기

SqlSession은 데이터베이스에 대해 SQL 명령어를 실행하기 위해 필요한 모든 메서드를 가지고 있음

SQL 구문의 파라미터와 리턴값을 설명하는 인터페이스를 사용하여

문자열 처리 오류나 타입 캐스팅 오류 없이 타입에 안전하고 깔끔하게 실행 가능

 

 

첫번째 방법

(과거 방법이라 두번째 방법 사용하는게 좋음)

이전 버전

 

두번째 방법

(문자열 처리 오류나 타입 캐스팅 오류 없이 좀더 안전하고 깔끔타입)

 

두번째 코드는 3가지의 장점을 가진다

1. 문자열에 의존하지 않아 좀더 안전하게 만든다.

2. 개발자가 IDE를 사용할 때 매핑된 SQL구문을 사용할때의 수고를 덜어준다.

3. 리턴타입에 대해 타입 캐스팅을 적용하지 않아도 된다 

때문에 BlogMapper 인터페이스는 깔끔하고 타입에 안전하다.

 

 

 

 

 

 

 

그럼 이제 SqlSession 호출 해볼게연~

SqlSession을 호출하는 XML 기반의 매핑 구문

 

한 개의 매퍼 XML 파일에는 많은 수의 매핑 (쿼리 문)구문을 정의할 수 있다.

위에 사진에 org.mybatis.example.BlogMapper 네임스페이스 부분은 경로고 selectBlog라는 매핑 구문을 정의했다

이것은  org.mybatis.example.BlogMapper.selectBlog라는 형태로 실제 명시되게 된다고 한다.

 

namespace 는 인터페이스 바인딩 기능이 있고

쉽게 말해 위치를 찾게해주고 연결해 줌

코드가 깔끔해지고 사용성이 크게 향상되어 필수로 사용해야함!!

 

 

저 구문을 타면 실제로는 다음 아래 구문처럼 실행됨

Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);

 

 

 

🐝꿀팁🐝

간단한 구문에서는 xml 대신 인터페이스 Mapper 위에 애노테이션이 깔끔하다고 한다.

 

 

 

 

그럼 이제 어느 스코프에 작성할지 알아보게연~

 

 

SqlSessionFactoryBuilder

 

SqlSessionFactoryBuilder 인스턴스의 가장 좋은 스코프는 메소드 지역변수이다.

 

이 클래스는 인스턴스화되어 사용되고 던져질 수 있다.

SqlSessionFactory를 생성 후 유지할 필요는 없다.

여러개 SqlSessionFactory 인스턴스를 빌드하기 위해

SqlSessionFactoryBuilder를 재사용할 수 있지만

유지하지 않는 것이 가장 좋다고 한다.

 

 

 

 

SqlSessionFactory

한번 만든 뒤 SqlSessionFactory는 애플리케이션을 실행하는 동안 존재해야만 한다.

그래서 삭제하거나 재생성할 필요가 없다.

애플리케이션이 실행되는 동안 여러 차례 SqlSessionFactory를 다시 빌드하지 않는 것이 좋은 형태이다.

SqlSessionFactory의 가장 좋은 스코프는 애플리케이션 스코프이다.

가장 적절한 방법으로는 static 을 이용한 싱글톤 패턴으로 사용하는 것이 좋다.

의존성 삽입 컨테이너도 선호할 수도 있다 

 

 

 

 

SqlSession

 각각의 쓰레드는 자체적으로 SqlSession 인스턴스를 가져야 한다.

SqlSession인스턴스는 공유되지 않고 쓰레드에 안전하지 않다.

그래서 SqlSession는 메서드 스코프가 가장 적절하다.

SqlSession을 static 필드나 클래스 인스턴스 필드로 지정해서는 안된다.

서블릿 프레임워크인 HttpSession과 같은 관리 스코프에 둬서도 안된다.

SqlSession을 닫는 것은 중요하다. 언제나 finally 블록에서 닫아야한다.

 

 

 

 

Mapper 인스턴스

Mapper는 매핑된 구문을 바인딩 하기 위해 만들어야 할 인터페이스이다.

mapper 인터페이스의 인스턴스는 SqlSession에서 생성한다.

그래서 mapper 인스턴스의 가장 좋은 스코프는 메서드 스코프이다.

사용할 메서드가 호출되면 생성되고 끝난다.명시적으로 닫을 필요는 없다.

 

 

 

기본적인 흐름은 이렇고

추가로 어떤것들이 있는지

확인해보았다.

 

 

 

 

 

Mybatis는  xml 설정 파일에 다양한 기능들을 설정할 수 있다.

1. properties

config.properties파일에 포함된 값으로 driver,url 이 대체 될수 있고, username, password 가 대체 될수 있다.

설정에 사용할 변수 들을 모아 놓을 수 있다  

 

2.settings

런타임시 마이바티스가 일을 처리하는 방식에 

대한 세부 설정을 설정한다.

표를 참고하여 name을 작성해주고 값들을 써주면 된다.

 

 

3.typeAliases

반복적이고 지저분하게 사용하는 코드를

타입 별칭을 이용해 줄일 수 있다. 

 

타입 별칭을 다음과 같이 설정하게 되면

 

원래 이렇게 길게 패키지 위치를 적어야했던것을

 

아래처럼 별칭으로 사용할 수 있다. 

 

 

 

4. typeHandlers

마이바티스가 PreparedStatement에 파라미터를 설정하고

ResultSet에서 값을 가져올때마다

TypeHandler는

적절한 자바 타입의 값을 가져오기 위해 사용된다.

DB에 특정한 종류의 데이터를 어떻게 처리할지 설정할 수 있다.

표를 참고해서 작성하면 되고 없을시 Override해서 커스텀할 수 도 있다 .

 

 

5. objectFactory

객체를 만드는 방법을 정의할 수 있다.

 

 

6. plugins

기능을 확장하고자 할때나,

기존 기능을 특별하게 만들고 싶을때

설정할 수 있다.

 

 

 

7.environments

여러 데이터 베이스 환경에서 사용할 수 있는 설정들을 하는 곳이다.

 

 

8.databaseIdProvider

여러 데이터 베이스를 사용할때 어떤 데이터 베이스를 사용하는지 구별 할 수 있게 해주는 설정

 

 

9.mapper

데이터베이스와 MyBatis가 소통하는 규칙들을 모아두는 곳

어떤 데이터를 가져올지, 어떻게 가져올지 정할 수 있다.

 

 

 

필요에 따라 Config 파일을 커스텀 하면 될 것 같다.

 

 

 

MapperXML 파일

 

select 

- DB에서 데이터를 조회할때 사용함 

구문명 selectPerson , 파라미터 타입 int타입, 결과 데이터는 HashMap 에 저장된다. (외에도 다양한 엘리먼트 존재)

 

 

 

insert

- DB에서 데이터를 추가할때 사용함 

(usegeneratedKeys를 활성화해서 id 칼럼에 자동생성키 적용)

 

 

 update,delete

- DB에서 데이터를 수정,삭제 할때 사용함 

 

 

sql

다른 구문에서 재사용가능한 SQL구문을 정의할 때 사용

로딩시점에 정적으로 파라미터처럼 사용할 수 있다.

다른 프로퍼티값은 포함된 인스턴스에서 달라질 수 있다.

<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
  select
    <include refid="userColumns"><property name="alias" value="t1"/></include>,
    <include refid="userColumns"><property name="alias" value="t2"/></include>
  from some_table t1
    cross join some_table t2
</select>

 

 

 

 

 

 

Result Map

ResultSet에서 데이터를 가져올때 작성되는 JDBC코드를 대부분 줄여주는 역할을 담당

ResultMap은 간단한 구문에서는 매핑이 필요하지 않고 복잡한 구문에서 관계를 서술하기 위해 필요하다.

typeAlias 와 섞어서 쓰면 간단하게 사용할 수 있다.

 

 

만약 칼럼명과 프로퍼티명이 다르다면....

resultMap을 명시적으로 선언해버리고

 

 

이를 참조해버리도록 할 수도 있다.

 

 

 

Dynamic SQL

동적 SQL을 이용하여 생성 기능을 제공하여
프로그램 실행 중에 입력되는 파라미터에 따라 서로 다른 SQL 문을 동적으로 생성해 내는 기능을 제공해 줍니다.

 

if 조건문

- where 절의 일부로 포함

 

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

 

 

 

choose, when, otherwise

switch 문과 유사

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

 

 

 

 

 

 

foreach

collection 에 대해 반복처리 

item, index 두가지 변수 제공

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  <where>
    <foreach item="item" index="index" collection="list"
        open="ID in (" separator="," close=")" nullable="true">
          #{item}
    </foreach>
  </where>
</select>

 

 

 

 

 

Script

애노테이션을 사용한 매퍼 클래스에서

동적 SQL을 사용하는 경우 script 태그를 사용할 수 있다

    @Update({"<script>",
      "update Author",
      "  <set>",
      "    <if test='username != null'>username=#{username},</if>",
      "    <if test='password != null'>password=#{password},</if>",
      "    <if test='email != null'>email=#{email},</if>",
      "    <if test='bio != null'>bio=#{bio}</if>",
      "  </set>",
      "where id=#{id}",
      "</script>"})
    void updateAuthorValues(Author author);

 

 

bind

변수를 만든 뒤 컨텍스트에 바인딩

<select id="selectBlogsLike" resultType="Blog">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM BLOG
  WHERE title LIKE #{pattern}
</select>

 

 

 

 

참고자료

https://mybatis.org/mybatis-3/ko/index.html

'SpringBoot' 카테고리의 다른 글

Model1 -> Model2 -> SpringMVC  (0) 2024.11.22
WAS란?  (0) 2024.09.03
[Spring Boot] JPA를 왜 써야할까??  (0) 2024.06.27
[SpringBoot] Gradle 이란? (Build Tool)  (0) 2024.06.26